Skip to content

Commit

Permalink
feat: update how permit is handled
Browse files Browse the repository at this point in the history
  • Loading branch information
ashhanai committed Mar 15, 2024
1 parent a30240f commit 0597cdd
Show file tree
Hide file tree
Showing 14 changed files with 717 additions and 615 deletions.
1 change: 0 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[profile.default]
solc_version = '0.8.16'
fs_permissions = [{ access = "read", path = "./deployments.json"}]
via_ir = true


[rpc_endpoints]
Expand Down
2 changes: 2 additions & 0 deletions src/PWNErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ error AccruingInterestAPROutOfBounds(uint256 current, uint256 limit);
error AvailableCreditLimitExceeded(uint256 used, uint256 limit);
error Expired(uint256 current, uint256 expiration);
error CallerNotAllowedAcceptor(address current, address allowed);
error InvalidPermitOwner(address current, address expected);
error InvalidPermitAsset(address current, address expected);

// Input data
error InvalidInputData();
Expand Down
99 changes: 38 additions & 61 deletions src/loan/terms/simple/loan/PWNSimpleLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { PWNSimpleLoanProposal } from "@pwn/loan/terms/simple/proposal/PWNSimple
import { IERC5646 } from "@pwn/loan/token/IERC5646.sol";
import { IPWNLoanMetadataProvider } from "@pwn/loan/token/IPWNLoanMetadataProvider.sol";
import { PWNLOAN } from "@pwn/loan/token/PWNLOAN.sol";
import { PWNVault } from "@pwn/loan/PWNVault.sol";
import { Permit } from "@pwn/loan/vault/Permit.sol";
import { PWNVault } from "@pwn/loan/vault/PWNVault.sol";
import { PWNRevokedNonce } from "@pwn/nonce/PWNRevokedNonce.sol";
import "@pwn/PWNErrors.sol";

Expand Down Expand Up @@ -204,16 +205,14 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* @dev The function assumes a prior token approval to a contract address or signed permits.
* @param proposalHash Hash of a loan offer / request that is signed by a lender / borrower.
* @param loanTerms Loan terms struct.
* @param creditPermit Permit data for a credit asset signed by a lender.
* @param collateralPermit Permit data for a collateral signed by a borrower.
* @param permit Callers credit permit data.
* @param extra Auxiliary data that are emitted in the loan creation event. They are not used in the contract logic.
* @return loanId Id of the created LOAN token.
*/
function createLOAN(
bytes32 proposalHash,
Terms calldata loanTerms,
bytes calldata creditPermit,
bytes calldata collateralPermit,
Permit calldata permit,
bytes calldata extra
) external returns (uint256 loanId) {
// Check that caller is loan proposal contract
Expand All @@ -233,7 +232,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
});

// Transfer collateral to Vault and credit to borrower
_settleNewLoan(loanTerms, creditPermit, collateralPermit);
_settleNewLoan(loanTerms, permit);
}

/**
Expand Down Expand Up @@ -291,21 +290,18 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* @notice Transfer collateral to Vault and credit to borrower.
* @dev The function assumes a prior token approval to a contract address or signed permits.
* @param loanTerms Loan terms struct.
* @param creditPermit Permit data for a credit asset signed by a lender.
* @param collateralPermit Permit data for a collateral signed by a borrower.
* @param permit Callers credit permit data.
*/
function _settleNewLoan(
Terms calldata loanTerms,
bytes calldata creditPermit,
bytes calldata collateralPermit
Permit calldata permit
) private {
// Execute permit for the caller
_tryPermit(permit);

// Transfer collateral to Vault
_permit(loanTerms.collateral, loanTerms.borrower, collateralPermit);
_pull(loanTerms.collateral, loanTerms.borrower);

// Permit credit spending if permit provided
_permit(loanTerms.credit, loanTerms.lender, creditPermit);

MultiToken.Asset memory creditHelper = loanTerms.credit;

// Collect fee if any and update credit asset amount
Expand Down Expand Up @@ -339,17 +335,15 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* The function assumes a prior token approval to a contract address or signed permits.
* @param proposalHash Hash of a loan offer / request that is signed by a lender / borrower. Used to uniquely identify a loan offer / request.
* @param loanTerms Loan terms struct.
* @param lenderCreditPermit Permit data for a credit asset signed by a lender.
* @param borrowerCreditPermit Permit data for a credit asset signed by a borrower.
* @param permit Callers credit permit data.
* @param extra Auxiliary data that are emitted in the loan creation event. They are not used in the contract logic.
* @return refinancedLoanId Id of the refinanced LOAN token.
*/
function refinanceLOAN(
uint256 loanId,
bytes32 proposalHash,
Terms calldata loanTerms,
bytes calldata lenderCreditPermit,
bytes calldata borrowerCreditPermit,
Permit calldata permit,
bytes calldata extra
) external returns (uint256 refinancedLoanId) {
// Check that caller is loan proposal contract
Expand All @@ -374,12 +368,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
});

// Refinance the original loan
_refinanceOriginalLoan(
loanId,
loanTerms,
lenderCreditPermit,
borrowerCreditPermit
);
_refinanceOriginalLoan(loanId, loanTerms, permit);

emit LOANRefinanced({ loanId: loanId, refinancedLoanId: refinancedLoanId });
}
Expand Down Expand Up @@ -426,14 +415,12 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* The function assumes a prior token approval to a contract address or signed permits.
* @param loanId Id of a loan that is being refinanced.
* @param loanTerms Loan terms struct.
* @param lenderCreditPermit Permit data for a credit asset signed by a lender.
* @param borrowerCreditPermit Permit data for a credit asset signed by a borrower.
* @param permit Callers credit permit data.
*/
function _refinanceOriginalLoan(
uint256 loanId,
Terms calldata loanTerms,
bytes calldata lenderCreditPermit,
bytes calldata borrowerCreditPermit
Permit calldata permit
) private {
uint256 repaymentAmount = _loanRepaymentAmount(loanId);

Expand All @@ -446,8 +433,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
loanOwner: loanOwner,
repaymentAmount: repaymentAmount,
loanTerms: loanTerms,
lenderPermit: lenderCreditPermit,
borrowerPermit: borrowerCreditPermit
permit: permit
});
}

Expand All @@ -460,40 +446,31 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* @param loanOwner Address of the current LOAN owner.
* @param repaymentAmount Amount of the original loan to be repaid.
* @param loanTerms Loan terms struct.
* @param lenderPermit Permit data for a credit asset signed by a lender.
* @param borrowerPermit Permit data for a credit asset signed by a borrower.
* @param permit Callers credit permit data.
*/
function _settleLoanRefinance(
bool repayLoanDirectly,
address loanOwner,
uint256 repaymentAmount,
Terms calldata loanTerms,
bytes calldata lenderPermit,
bytes calldata borrowerPermit
Permit calldata permit
) private {
MultiToken.Asset memory creditHelper = loanTerms.credit;

// Compute fee size
(uint256 feeAmount, uint256 newLoanAmount)
= PWNFeeCalculator.calculateFeeAmount(config.fee(), loanTerms.credit.amount);

// Permit lenders credit spending if permit provided
creditHelper.amount -= loanTerms.lender == loanOwner // Permit only the surplus transfer + fee
? Math.min(repaymentAmount, newLoanAmount)
: 0;

if (creditHelper.amount > 0) {
_permit(creditHelper, loanTerms.lender, lenderPermit);
}
// Execute permit for the caller
_tryPermit(permit);

// Collect fees
(uint256 feeAmount, uint256 newLoanAmount)
= PWNFeeCalculator.calculateFeeAmount(config.fee(), loanTerms.credit.amount);
if (feeAmount > 0) {
creditHelper.amount = feeAmount;
_pushFrom(creditHelper, loanTerms.lender, config.feeCollector());
}

// If the new lender is the LOAN token owner, don't execute the transfer at all,
// it would make transfer from the same address to the same address
// New lender repays the original loan
// Note: If the new lender is the LOAN token owner, don't execute the transfer at all,
// it would make transfer from the same address to the same address.
if (loanTerms.lender != loanOwner) {
creditHelper.amount = Math.min(repaymentAmount, newLoanAmount);
_transferLoanRepayment({
Expand All @@ -504,6 +481,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
});
}

// Handle the surplus or the missing amount
if (newLoanAmount >= repaymentAmount) {
// New loan covers the whole original loan, transfer surplus to the borrower if any
uint256 surplus = newLoanAmount - repaymentAmount;
Expand All @@ -512,11 +490,8 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
_pushFrom(creditHelper, loanTerms.lender, loanTerms.borrower);
}
} else {
// Permit borrowers credit spending if permit provided
creditHelper.amount = repaymentAmount - newLoanAmount;
_permit(creditHelper, loanTerms.borrower, borrowerPermit);

// New loan covers only part of the original loan, borrower needs to contribute
creditHelper.amount = repaymentAmount - newLoanAmount;
_transferLoanRepayment({
repayLoanDirectly: repayLoanDirectly || loanTerms.lender == loanOwner,
repaymentCredit: creditHelper,
Expand All @@ -539,11 +514,11 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* a vault, waiting on a LOAN token holder to claim it. The function assumes a prior token approval to a contract address
* or a signed permit.
* @param loanId Id of a loan that is being repaid.
* @param creditPermit Permit data for a credit asset signed by a borrower.
* @param permit Callers credit permit data.
*/
function repayLOAN(
uint256 loanId,
bytes calldata creditPermit
Permit calldata permit
) external {
LOAN storage loan = LOANs[loanId];

Expand All @@ -562,7 +537,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
borrower: borrower,
repaymentCredit: repaymentCredit,
collateral: collateral,
creditPermit: creditPermit
permit: permit
});
}

Expand Down Expand Up @@ -626,7 +601,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* @param borrower Address of the borrower associated with the loan.
* @param repaymentCredit Credit asset to be repaid.
* @param collateral Collateral to be transferred back to the borrower.
* @param creditPermit Permit data for a credit asset signed by a borrower.
* @param permit Callers credit permit data.
*/
function _settleLoanRepayment(
bool repayLoanDirectly,
Expand All @@ -635,10 +610,12 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
address borrower,
MultiToken.Asset memory repaymentCredit,
MultiToken.Asset memory collateral,
bytes calldata creditPermit
Permit calldata permit
) private {
// Execute permit for the caller
_tryPermit(permit);

// Transfer credit to the original lender or to the Vault
_permit(repaymentCredit, repayingAddress, creditPermit);
_transferLoanRepayment(repayLoanDirectly, repaymentCredit, repayingAddress, loanOwner);

// Transfer collateral back to borrower
Expand Down Expand Up @@ -804,12 +781,12 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
* @dev The function assumes a prior token approval to a contract address or a signed permit.
* @param extension Extension proposal struct.
* @param signature Signature of the extension proposal.
* @param compensationPermit Permit data for a fungible compensation asset signed by a borrower.
* @param permit Callers permit.
*/
function extendLOAN(
ExtensionProposal calldata extension,
bytes calldata signature,
bytes calldata compensationPermit
Permit calldata permit
) external {
LOAN storage loan = LOANs[extension.loanId];

Expand Down Expand Up @@ -892,7 +869,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider {
_checkValidAsset(compensation);

// Transfer compensation to the loan owner
_permit(compensation, loan.borrower, compensationPermit);
_tryPermit(permit);
_pushFrom(compensation, loan.borrower, loanOwner);
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/loan/terms/simple/proposal/PWNSimpleLoanProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.16;
import { PWNHub } from "@pwn/hub/PWNHub.sol";
import { PWNHubTags } from "@pwn/hub/PWNHubTags.sol";
import { PWNSignatureChecker } from "@pwn/loan/lib/PWNSignatureChecker.sol";
import { Permit } from "@pwn/loan/vault/Permit.sol";
import { PWNRevokedNonce } from "@pwn/nonce/PWNRevokedNonce.sol";
import { StateFingerprintComputerRegistry, IERC5646 } from "@pwn/state-fingerprint/StateFingerprintComputerRegistry.sol";
import "@pwn/PWNErrors.sol";
Expand Down Expand Up @@ -185,6 +186,23 @@ abstract contract PWNSimpleLoanProposal {
}
}

/**
* @notice Check that permit data have correct owner and asset.
* @param caller Caller address.
* @param creditAddress Address of a credit to be used.
* @param permit Permit to be checked.
*/
function _checkPermit(address caller, address creditAddress, Permit calldata permit) internal pure {
if (permit.asset != address(0)) {
if (permit.owner != caller) {
revert InvalidPermitOwner({ current: permit.owner, expected: caller});
}
if (creditAddress != permit.asset) {
revert InvalidPermitAsset({ current: permit.asset, expected: creditAddress });
}
}
}

/**
* @notice Make an on-chain proposal.
* @dev Function will mark a proposal hash as proposed.
Expand Down
Loading

0 comments on commit 0597cdd

Please sign in to comment.