diff --git a/.gas-snapshot b/.gas-snapshot index 8b931c4e..2ff10bc7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,40 +1,42 @@ -EnforcerTest:testCollateralEnforcer() (gas: 973177) -EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 855101) -EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 810244) -EnforcerTest:testFailRateEnforcerMaxRate() (gas: 810194) -EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 810059) -EnforcerTest:testRateEnforcerBasic() (gas: 930425) -EnforcerTest:testTermEnforcerBasic() (gas: 1002689) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecall() (gas: 988282) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 906210) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 952988) +EnforcerTest:testCollateralEnforcer() (gas: 964781) +EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 846753) +EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 801907) +EnforcerTest:testFailRateEnforcerMaxRate() (gas: 801857) +EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 801722) +EnforcerTest:testRateEnforcerBasic() (gas: 922038) +EnforcerTest:testTermEnforcerBasic() (gas: 994300) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecall() (gas: 983680) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 901571) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 948381) TestCustodian:testCustodySelector() (gas: 7152) -TestCustodian:testGenerateOrderRepay() (gas: 174157) +TestCustodian:testGenerateOrderRepay() (gas: 174179) TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 102146) TestCustodian:testGenerateOrderSettlement() (gas: 125008) TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 100145) TestCustodian:testGetBorrower() (gas: 75840) TestCustodian:testOnlySeaport() (gas: 17931) TestCustodian:testPreviewOrderNoActiveLoan() (gas: 229) -TestCustodian:testPreviewOrderRepay() (gas: 224706) +TestCustodian:testPreviewOrderRepay() (gas: 224750) TestCustodian:testPreviewOrderSettlement() (gas: 152470) -TestCustodian:testRatifyOrder() (gas: 188716) +TestCustodian:testRatifyOrder() (gas: 188760) TestCustodian:testSafeTransferReceive() (gas: 159061) TestCustodian:testSeaportMetadata() (gas: 8519) TestCustodian:testSupportsInterface() (gas: 5791) -TestExoticLoans:testSwap() (gas: 1167541) +TestExoticLoans:testSwap() (gas: 1163700) TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 252) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 521951) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 753642) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 519386) -TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 740159) -TestLoanManager:testGenerateOrder() (gas: 1009691) -TestLoanManager:testGenerateOrderInvalidSender() (gas: 13075) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 517630) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 749358) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 515545) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 735700) +TestLoanManager:testGenerateOrder() (gas: 1008482) +TestLoanManager:testGenerateOrderInvalidSender() (gas: 12986) TestLoanManager:testSupportsInterface() (gas: 7003) -TestNewLoan:testBuyNowPayLater() (gas: 1154993) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 1001549) -TestNewLoan:testNewLoanERC721CollateralDefaultTermsRefinance() (gas: 658024) -TestNewLoan:testSettleLoan() (gas: 1282926) -TestRepayLoan:testRepayLoan() (gas: 736801) +TestNewLoan:testBuyNowPayLater() (gas: 1146513) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 993154) +TestNewLoan:testNewLoanERC721CollateralDefaultTermsRefinance() (gas: 653724) +TestNewLoan:testSettleLoan() (gas: 1274568) +TestRepayLoan:testRepayLoan() (gas: 732514) TestStarLiteUtils:testEncodeReceivedWithRecipient() (gas: 17955) -TestStarLiteUtils:testSpentToReceived() (gas: 17796) \ No newline at end of file +TestStarLiteUtils:testSpentToReceived() (gas: 17708) +TestStarLiteUtils:testValidateSaltOpt(address,bytes32) (runs: 256, μ: 26479, ~: 26479) +TestStarLiteUtils:testValidateSaltRef(address,bytes32) (runs: 256, μ: 26849, ~: 26849) \ No newline at end of file diff --git a/src/LoanManager.sol b/src/LoanManager.sol index 9f7bab92..15d3d44a 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -49,6 +49,7 @@ contract LoanManager is ERC721, ContractOffererInterface, ConduitHelper, Ownable using {StarPortLib.toReceivedItems} for SpentItem[]; using {StarPortLib.getId} for LoanManager.Loan; + using {StarPortLib.validateSalt} for mapping(address => mapping(bytes32 => bool)); ConsiderationInterface public immutable seaport; // ConsiderationInterface public constant seaport = @@ -63,7 +64,7 @@ contract LoanManager is ERC721, ContractOffererInterface, ConduitHelper, Ownable keccak256("IntentOrigination(bytes32 hash,bytes32 salt,uint256 nonce)"); bytes32 constant VERSION = keccak256("0"); - mapping(bytes32 => bool) public usedHashes; + mapping(address => mapping(bytes32 => bool)) public usedSalts; mapping(address => uint256) public borrowerNonce; //needs to be invalidated address public feeTo; @@ -325,11 +326,11 @@ contract LoanManager is ERC721, ContractOffererInterface, ConduitHelper, Ownable exoticFee[exotic] = fee; } - function getExoticFee(SpentItem memory exotic) public returns (Fee memory fee) { + function getExoticFee(SpentItem memory exotic) public view returns (Fee memory fee) { return exoticFee[exotic.token]; } - function _feeRake(SpentItem[] memory debt) internal returns (ReceivedItem[] memory feeConsideration) { + function _feeRake(SpentItem[] memory debt) internal view returns (ReceivedItem[] memory feeConsideration) { uint256 i = 0; feeConsideration = new ReceivedItem[](debt.length); for (; i < debt.length;) { @@ -421,13 +422,14 @@ contract LoanManager is ERC721, ContractOffererInterface, ConduitHelper, Ownable if (enforceCaveats) { bytes32 caveatHash = keccak256( encodeWithSaltAndBorrowerCounter( - obligation.borrower, obligation.salt, keccak256(abi.encode(obligation)) + obligation.borrower, obligation.salt, keccak256(abi.encode(obligation.caveats)) ) ); - //prevent replay on the hash - usedHashes[caveatHash] = true; - uint256 i = 0; - for (; i < obligation.caveats.length;) { + + //prevent replay on the salt + usedSalts.validateSalt(obligation.borrower, obligation.salt); + + for (uint256 i = 0; i < obligation.caveats.length;) { if (!CaveatEnforcer(obligation.caveats[i].enforcer).enforceCaveat(obligation.caveats[i].terms, loan)) { revert InvalidOrigination(); } diff --git a/src/lib/StarPortLib.sol b/src/lib/StarPortLib.sol index c3a912cd..7894c40a 100644 --- a/src/lib/StarPortLib.sol +++ b/src/lib/StarPortLib.sol @@ -5,6 +5,10 @@ import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/Considera import {LoanManager} from "starport-core/LoanManager.sol"; library StarPortLib { + error InvalidSalt(); + + uint256 internal constant _INVALID_SALT = 0x81e69d9b00000000000000000000000000000000000000000000000000000000; + function getId(LoanManager.Loan memory loan) internal pure returns (uint256 loanId) { loanId = uint256(keccak256(abi.encode(loan))); } @@ -88,4 +92,44 @@ library StarPortLib { } } } + + function validateSaltRef( + mapping(address => mapping(bytes32 => bool)) storage usedSalts, + address borrower, + bytes32 salt + ) internal { + if (usedSalts[borrower][salt]) { + revert InvalidSalt(); + } + usedSalts[borrower][salt] = true; + } + + function validateSalt( + mapping(address => mapping(bytes32 => bool)) storage usedSalts, + address borrower, + bytes32 salt + ) internal { + assembly { + mstore(0x0, borrower) + mstore(0x20, usedSalts.slot) + + //usedSalts[borrower] + let loc := keccak256(0x0, 0x40) + + mstore(0x0, salt) + mstore(0x20, loc) + + //usedSalts[borrower][salt] + loc := keccak256(0x0, 0x40) + + //if (usedSalts[borrower][salt] == true) + if iszero(iszero(sload(loc))) { + //revert InvalidSalt() + mstore(0x0, _INVALID_SALT) + revert(0x0, 0x04) + } + + sstore(loc, 1) + } + } } diff --git a/test/StarPortTest.sol b/test/StarPortTest.sol index 9842a80f..5bc854cf 100644 --- a/test/StarPortTest.sol +++ b/test/StarPortTest.sol @@ -395,7 +395,7 @@ contract StarPortTest is BaseOrderTest { //use murky to create a tree that is good bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr)))); + keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); for (uint256 i; i < debt.length;) { @@ -517,7 +517,7 @@ contract StarPortTest is BaseOrderTest { returns (LoanManager.Loan memory loan) { bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr)))); + keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); for (uint256 i; i < debt.length;) { diff --git a/test/TestUtils.sol b/test/TestUtils.sol index b72d0b25..01326f44 100644 --- a/test/TestUtils.sol +++ b/test/TestUtils.sol @@ -16,11 +16,30 @@ import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; contract TestStarLiteUtils is Test { TestContract testContract; + mapping(address => mapping(bytes32 => bool)) usedSalts; function setUp() public { testContract = new TestContract(); } + function testValidateSaltRef(address user, bytes32 salt) public { + StarPortLib.validateSaltRef(usedSalts, user, salt); + + assert(usedSalts[user][salt]); + vm.expectRevert(abi.encodeWithSelector(StarPortLib.InvalidSalt.selector)); + + StarPortLib.validateSaltRef(usedSalts, user, salt); + } + + function testValidateSaltOpt(address user, bytes32 salt) public { + StarPortLib.validateSalt(usedSalts, user, salt); + + assert(usedSalts[user][salt]); + + vm.expectRevert(abi.encodeWithSelector(StarPortLib.InvalidSalt.selector)); + StarPortLib.validateSalt(usedSalts, user, salt); + } + function testSpentToReceived() public { SpentItem[] memory spentItems = new SpentItem[](2); spentItems[0] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4});