diff --git a/.gas-snapshot b/.gas-snapshot index f50e71b..da2a0ca 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,66 +1,66 @@ ClawbackMetadataTest:testDurationAndUnlocksAt() (gas: 500605) -ClawbackMetadataTest:testMetadataPropertiesERC1155((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 157884, ~: 147382) -ClawbackMetadataTest:testMetadataPropertiesERC20((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 160168, ~: 160139) -ClawbackMetadataTest:testMetadataPropertiesERC721((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 258473, ~: 254753) +ClawbackMetadataTest:testMetadataPropertiesERC1155((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 157419, ~: 146784) +ClawbackMetadataTest:testMetadataPropertiesERC20((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 160105, ~: 160041) +ClawbackMetadataTest:testMetadataPropertiesERC721((uint8,uint32,uint56,uint256),(bool,bool,uint56,address)) (runs: 1024, μ: 258274, ~: 254433) ClawbackMetadataTest:testSupportsInterface() (gas: 9612) ClawbackTest:testAddTemplate(address,uint56,bool,bool) (runs: 1024, μ: 46910, ~: 46910) ClawbackTest:testAddTemplateTransferer(address,address) (runs: 1024, μ: 77227, ~: 77227) ClawbackTest:testAddTemplateTransfererInvalidCaller(address,address,bool,address) (runs: 1024, μ: 62924, ~: 75750) -ClawbackTest:testAddToWrap(address,uint8,uint256,uint256,uint56,address,uint64) (runs: 1024, μ: 285089, ~: 260572) +ClawbackTest:testAddToWrap(address,uint8,uint256,uint256,uint56,address,uint64) (runs: 1024, μ: 286286, ~: 260571) ClawbackTest:testAddToWrapInvalidWrappedId(uint256,uint256,address) (runs: 1024, μ: 13962, ~: 13962) -ClawbackTest:testClawback(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 279425, ~: 300635) -ClawbackTest:testClawbackAfterTransfer(uint8,uint256,uint256,uint56,address,address,address) (runs: 1024, μ: 306002, ~: 324613) -ClawbackTest:testClawbackDestructionOnly(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 280166, ~: 305452) -ClawbackTest:testClawbackDestructionOnlyInvalidReceiver(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 269170, ~: 292765) -ClawbackTest:testClawbackInvalidCaller(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 235662, ~: 259070) -ClawbackTest:testClawbackInvalidUnlocked(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 262973, ~: 286568) -ClawbackTest:testEmergencyClawback(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 291479, ~: 317524) -ClawbackTest:testEmergencyClawbackAfterTransfer(uint8,uint256,uint256,uint56,address,address,address) (runs: 1024, μ: 314150, ~: 340998) -ClawbackTest:testEmergencyClawbackDestructionOnly(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 290872, ~: 321459) -ClawbackTest:testEmergencyClawbackDestructionOnlyInvalidReceiver(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 269690, ~: 293285) -ClawbackTest:testEmergencyClawbackInvalidCaller(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 234708, ~: 258116) -ClawbackTest:testEmergencyClawbackInvalidUnlocked(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 263383, ~: 286978) -ClawbackTest:testPreventsOnERC1155Received(uint256,uint256) (runs: 1024, μ: 100436, ~: 100354) -ClawbackTest:testPreventsOnERC1155Received(uint256,uint256,uint256,uint256) (runs: 1024, μ: 159373, ~: 159370) +ClawbackTest:testClawback(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 279155, ~: 300635) +ClawbackTest:testClawbackAfterTransfer(uint8,uint256,uint256,uint56,address,address,address) (runs: 1024, μ: 305713, ~: 324613) +ClawbackTest:testClawbackDestructionOnly(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 280677, ~: 305452) +ClawbackTest:testClawbackDestructionOnlyInvalidReceiver(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 268878, ~: 292765) +ClawbackTest:testClawbackInvalidCaller(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 235304, ~: 259070) +ClawbackTest:testClawbackInvalidUnlocked(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 262681, ~: 286568) +ClawbackTest:testEmergencyClawback(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 291147, ~: 317524) +ClawbackTest:testEmergencyClawbackAfterTransfer(uint8,uint256,uint256,uint56,address,address,address) (runs: 1024, μ: 313729, ~: 340998) +ClawbackTest:testEmergencyClawbackDestructionOnly(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 291491, ~: 321459) +ClawbackTest:testEmergencyClawbackDestructionOnlyInvalidReceiver(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 269398, ~: 293285) +ClawbackTest:testEmergencyClawbackInvalidCaller(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 234350, ~: 258116) +ClawbackTest:testEmergencyClawbackInvalidUnlocked(uint8,uint256,uint256,uint56,address,address) (runs: 1024, μ: 263091, ~: 286978) +ClawbackTest:testPreventsOnERC1155Received(uint256,uint256) (runs: 1024, μ: 100437, ~: 100354) +ClawbackTest:testPreventsOnERC1155Received(uint256,uint256,uint256,uint256) (runs: 1024, μ: 159372, ~: 159370) ClawbackTest:testPreventsOnERC721Received(uint256) (runs: 1024, μ: 118375, ~: 118374) ClawbackTest:testSupportsInterface() (gas: 13448) -ClawbackTest:testTransferByTransfererAsFrom(uint8,uint256,uint256,uint56,uint64,bool,address,bool) (runs: 1024, μ: 276537, ~: 300766) -ClawbackTest:testTransferByTransfererAsOperator(uint8,uint256,uint256,uint56,uint64,bool,address,address,bool) (runs: 1024, μ: 301420, ~: 326941) -ClawbackTest:testTransferByTransfererAsTo(uint8,uint256,uint256,uint56,uint64,bool,address,bool) (runs: 1024, μ: 277110, ~: 301339) -ClawbackTest:testTransferByTransfererNotApproved(uint8,uint256,uint256,uint56,uint64,bool,address,address,bool) (runs: 1024, μ: 268184, ~: 294638) -ClawbackTest:testTransferInvalidOperator(uint8,uint256,uint256,uint56,address,address,bool) (runs: 1024, μ: 269383, ~: 294463) -ClawbackTest:testTransferOpen(uint8,uint256,uint256,uint56,uint64,address) (runs: 1024, μ: 251219, ~: 275183) -ClawbackTest:testUnwrap(address,uint8,uint256,uint256,uint56,uint64) (runs: 1024, μ: 236990, ~: 243165) -ClawbackTest:testUnwrapAfterTransfer(address,uint8,uint256,uint256,uint56,uint64,address) (runs: 1024, μ: 273806, ~: 288749) -ClawbackTest:testUnwrapByInvalidOperator(address,address,uint8,uint256,uint256,uint56) (runs: 1024, μ: 233229, ~: 259415) -ClawbackTest:testUnwrapByOperator(address,address,uint8,uint256,uint256,uint56,uint64) (runs: 1024, μ: 268073, ~: 276271) -ClawbackTest:testUnwrapInvalidAmount(address,uint8,uint256,uint256,uint256,uint56) (runs: 1024, μ: 232938, ~: 258241) -ClawbackTest:testUnwrapInvalidToken(address,uint8,uint256,uint256,uint256,uint56) (runs: 1024, μ: 238423, ~: 263539) -ClawbackTest:testUnwrapTokenLocked(address,uint8,uint256,uint256,uint56) (runs: 1024, μ: 229980, ~: 255596) +ClawbackTest:testTransferByTransfererAsFrom(uint8,uint256,uint256,uint56,uint64,bool,address,bool) (runs: 1024, μ: 277753, ~: 300766) +ClawbackTest:testTransferByTransfererAsOperator(uint8,uint256,uint256,uint56,uint64,bool,address,address,bool) (runs: 1024, μ: 301841, ~: 326941) +ClawbackTest:testTransferByTransfererAsTo(uint8,uint256,uint256,uint56,uint64,bool,address,bool) (runs: 1024, μ: 278326, ~: 301339) +ClawbackTest:testTransferByTransfererNotApproved(uint8,uint256,uint256,uint56,uint64,bool,address,address,bool) (runs: 1024, μ: 268606, ~: 294638) +ClawbackTest:testTransferInvalidOperator(uint8,uint256,uint256,uint56,address,address,bool) (runs: 1024, μ: 268819, ~: 294463) +ClawbackTest:testTransferOpen(uint8,uint256,uint256,uint56,uint64,address) (runs: 1024, μ: 249969, ~: 275183) +ClawbackTest:testUnwrap(address,uint8,uint256,uint256,uint56,uint64) (runs: 1024, μ: 237480, ~: 243165) +ClawbackTest:testUnwrapAfterTransfer(address,uint8,uint256,uint256,uint56,uint64,address) (runs: 1024, μ: 273543, ~: 288742) +ClawbackTest:testUnwrapByInvalidOperator(address,address,uint8,uint256,uint256,uint56) (runs: 1024, μ: 232873, ~: 259415) +ClawbackTest:testUnwrapByOperator(address,address,uint8,uint256,uint256,uint56,uint64) (runs: 1024, μ: 269345, ~: 276271) +ClawbackTest:testUnwrapInvalidAmount(address,uint8,uint256,uint256,uint256,uint56) (runs: 1024, μ: 231156, ~: 258241) +ClawbackTest:testUnwrapInvalidToken(address,uint8,uint256,uint256,uint256,uint56) (runs: 1024, μ: 236708, ~: 263539) +ClawbackTest:testUnwrapTokenLocked(address,uint8,uint256,uint256,uint56) (runs: 1024, μ: 229749, ~: 255596) ClawbackTest:testUpdateTemplateAdmin(address,address,uint56,bool,bool) (runs: 1024, μ: 76260, ~: 76261) ClawbackTest:testUpdateTemplateAdminInvalidAdmin() (gas: 49852) -ClawbackTest:testUpdateTemplateAdminInvalidCaller(address,address,bool) (runs: 1024, μ: 63348, ~: 76224) -ClawbackTest:testUpdateTemplateInvalidCaller(address,address,bool,uint56,bool,bool) (runs: 1024, μ: 62632, ~: 50111) +ClawbackTest:testUpdateTemplateAdminInvalidCaller(address,address,bool) (runs: 1024, μ: 63373, ~: 76224) +ClawbackTest:testUpdateTemplateInvalidCaller(address,address,bool,uint56,bool,bool) (runs: 1024, μ: 62607, ~: 50111) ClawbackTest:testUpdateTemplateInvalidDestructionOnly() (gas: 49760) ClawbackTest:testUpdateTemplateInvalidDuration(address,uint56,bool,bool,uint56) (runs: 1024, μ: 51268, ~: 51268) ClawbackTest:testUpdateTemplateInvalidTransferOpen() (gas: 50208) -ClawbackTest:testUpdateTemplateOperator(address,address,bool) (runs: 1024, μ: 67054, ~: 76985) -ClawbackTest:testUpdateTemplateOperatorInvalidCaller(address,address,bool,address,bool) (runs: 1024, μ: 63108, ~: 50285) -ClawbackTest:testUpdateTemplateValid(address,uint56,bool,bool,uint56,bool,bool) (runs: 1024, μ: 59336, ~: 59458) -ClawbackTest:testWrap(address,uint8,uint256,uint256,address) (runs: 1024, μ: 256053, ~: 281166) -ClawbackTest:testWrapInvalidAmount(address,uint8,uint256,uint256,address) (runs: 1024, μ: 210087, ~: 221861) -ClawbackTest:testWrapInvalidRewrapping(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 227976, ~: 254805) -ClawbackTest:testWrapInvalidTemplate(uint32,uint8,uint256,uint256,address) (runs: 1024, μ: 106233, ~: 115140) -ClawbackTest:testWrapInvalidTokenType(address,uint8,uint256,uint256) (runs: 1024, μ: 145171, ~: 152283) -ERC1155ItemsTest:testBatchMintOwner(uint256[],uint256[]) (runs: 1024, μ: 576935, ~: 587455) -ERC1155ItemsTest:testBatchMintWithRole(address,uint256[],uint256[]) (runs: 1024, μ: 661940, ~: 671839) -ERC1155ItemsTest:testBurnBatchInvalidOwnership(address,uint256[],uint256[]) (runs: 1024, μ: 231087, ~: 233624) -ERC1155ItemsTest:testBurnBatchSuccess(address,uint256[],uint256[],uint256[]) (runs: 1024, μ: 197974, ~: 196202) +ClawbackTest:testUpdateTemplateOperator(address,address,bool) (runs: 1024, μ: 67074, ~: 76985) +ClawbackTest:testUpdateTemplateOperatorInvalidCaller(address,address,bool,address,bool) (runs: 1024, μ: 63134, ~: 50285) +ClawbackTest:testUpdateTemplateValid(address,uint56,bool,bool,uint56,bool,bool) (runs: 1024, μ: 59338, ~: 59459) +ClawbackTest:testWrap(address,uint8,uint256,uint256,address) (runs: 1024, μ: 255421, ~: 281166) +ClawbackTest:testWrapInvalidAmount(address,uint8,uint256,uint256,address) (runs: 1024, μ: 209490, ~: 221861) +ClawbackTest:testWrapInvalidRewrapping(uint8,uint256,uint256,uint56,address) (runs: 1024, μ: 228525, ~: 254805) +ClawbackTest:testWrapInvalidTemplate(uint32,uint8,uint256,uint256,address) (runs: 1024, μ: 106055, ~: 115140) +ClawbackTest:testWrapInvalidTokenType(address,uint8,uint256,uint256) (runs: 1024, μ: 144924, ~: 152283) +ERC1155ItemsTest:testBatchMintOwner(uint256[],uint256[]) (runs: 1024, μ: 576938, ~: 587455) +ERC1155ItemsTest:testBatchMintWithRole(address,uint256[],uint256[]) (runs: 1024, μ: 661955, ~: 671874) +ERC1155ItemsTest:testBurnBatchInvalidOwnership(address,uint256[],uint256[]) (runs: 1024, μ: 230920, ~: 233658) +ERC1155ItemsTest:testBurnBatchSuccess(address,uint256[],uint256[],uint256[]) (runs: 1024, μ: 197915, ~: 196194) ERC1155ItemsTest:testBurnInvalidOwnership(address,uint256,uint256,uint256) (runs: 1024, μ: 109179, ~: 110870) ERC1155ItemsTest:testBurnSuccess(address,uint256,uint256,uint256) (runs: 1024, μ: 125849, ~: 125879) ERC1155ItemsTest:testContractURI() (gas: 83132) ERC1155ItemsTest:testDefaultRoyalty(address,uint96,uint256) (runs: 1024, μ: 41468, ~: 41468) -ERC1155ItemsTest:testFactoryDetermineAddress(address,address,string,string,string,address,uint96) (runs: 1024, μ: 6720772, ~: 6720833) +ERC1155ItemsTest:testFactoryDetermineAddress(address,address,string,string,string,address,uint96) (runs: 1024, μ: 6719774, ~: 6720586) ERC1155ItemsTest:testMetadataInvalid(address) (runs: 1024, μ: 66770, ~: 66770) ERC1155ItemsTest:testMetadataOwner() (gas: 43862) ERC1155ItemsTest:testMetadataWithRole(address) (runs: 1024, μ: 117392, ~: 117392) @@ -70,40 +70,53 @@ ERC1155ItemsTest:testMintWithRole(address,uint256,uint256) (runs: 1024, μ: 1904 ERC1155ItemsTest:testOwnerHasRoles() (gas: 46365) ERC1155ItemsTest:testReinitializeFails() (gas: 27986) ERC1155ItemsTest:testRoyaltyInvalidRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 121054, ~: 121054) -ERC1155ItemsTest:testRoyaltyWithRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 154232, ~: 154355) +ERC1155ItemsTest:testRoyaltyWithRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 154173, ~: 154355) ERC1155ItemsTest:testSelectorCollision() (gas: 82659) ERC1155ItemsTest:testSupportsInterface() (gas: 34330) ERC1155ItemsTest:testTokenRoyalty(uint256,address,uint96,uint256) (runs: 1024, μ: 65161, ~: 65161) -ERC1155SaleTest:testERC20Mint(bool,address,uint256,uint256) (runs: 1024, μ: 3403798, ~: 716548) -ERC1155SaleTest:testERC20MintFailPaidETH(bool,address,uint256,uint256) (runs: 1024, μ: 3318488, ~: 625622) +ERC1155SaleTest:testERC20Mint(bool,address,uint256,uint256) (runs: 1024, μ: 3409205, ~: 716548) +ERC1155SaleTest:testERC20MintFailPaidETH(bool,address,uint256,uint256) (runs: 1024, μ: 3323907, ~: 625622) ERC1155SaleTest:testFactoryDetermineAddress(address,address,address) (runs: 1024, μ: 5465831, ~: 5465831) -ERC1155SaleTest:testFreeGlobalMint(bool,address,uint256,uint256) (runs: 1024, μ: 2849078, ~: 162516) -ERC1155SaleTest:testFreeTokenMint(bool,address,uint256,uint256) (runs: 1024, μ: 2896586, ~: 209295) -ERC1155SaleTest:testMerkleFailBadProof(address[],address,uint256,bool) (runs: 1024, μ: 282253, ~: 280388) -ERC1155SaleTest:testMerkleFailNoProof(address[],address,uint256,bool) (runs: 1024, μ: 278157, ~: 276235) -ERC1155SaleTest:testMerkleReuseFail(address[],uint256,uint256,bool) (runs: 1024, μ: 404732, ~: 405619) -ERC1155SaleTest:testMerkleSuccess(address[],uint256,uint256,bool) (runs: 1024, μ: 396161, ~: 396909) -ERC1155SaleTest:testMerkleSuccessGlobalMultiple(address[],uint256,uint256[]) (runs: 1024, μ: 475005, ~: 484422) +ERC1155SaleTest:testFreeGlobalMint(bool,address,uint256,uint256) (runs: 1024, μ: 2854484, ~: 162516) +ERC1155SaleTest:testFreeTokenMint(bool,address,uint256,uint256) (runs: 1024, μ: 2901994, ~: 209295) +ERC1155SaleTest:testMerkleFailBadProof(address[],address,uint256,bool) (runs: 1024, μ: 282258, ~: 280434) +ERC1155SaleTest:testMerkleFailNoProof(address[],address,uint256,bool) (runs: 1024, μ: 278162, ~: 276267) +ERC1155SaleTest:testMerkleReuseFail(address[],uint256,uint256,bool) (runs: 1024, μ: 404586, ~: 405192) +ERC1155SaleTest:testMerkleSuccess(address[],uint256,uint256,bool) (runs: 1024, μ: 396015, ~: 396482) +ERC1155SaleTest:testMerkleSuccessGlobalMultiple(address[],uint256,uint256[]) (runs: 1024, μ: 474665, ~: 483219) ERC1155SaleTest:testMintExpiredGlobalFail(bool,address,uint256,uint256,uint64,uint64) (runs: 1024, μ: 2795878, ~: 99797) -ERC1155SaleTest:testMintExpiredSingleFail(bool,address,uint256,uint256,uint64,uint64) (runs: 1024, μ: 2796169, ~: 100087) -ERC1155SaleTest:testMintFailMaxTotal(bool,address,uint256,uint256) (runs: 1024, μ: 2882377, ~: 188007) -ERC1155SaleTest:testMintFailWrongPaymentToken(bool,address,uint256,uint256,address) (runs: 1024, μ: 3425257, ~: 6139498) -ERC1155SaleTest:testMintGlobalSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2877929, ~: 191367) -ERC1155SaleTest:testMintGlobalSupplyExceeded(bool,address,uint256,uint256,uint256) (runs: 1024, μ: 2943369, ~: 5669458) -ERC1155SaleTest:testMintGroupSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2983535, ~: 296242) -ERC1155SaleTest:testMintInactiveFail(bool,address,uint256,uint256) (runs: 1024, μ: 2742331, ~: 51398) -ERC1155SaleTest:testMintInactiveInGroupFail(bool,address,uint256,uint256) (runs: 1024, μ: 2799413, ~: 108722) -ERC1155SaleTest:testMintInactiveSingleFail(bool,address,uint256,uint256) (runs: 1024, μ: 2797206, ~: 106515) -ERC1155SaleTest:testMintSingleSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2877985, ~: 191420) -ERC1155SaleTest:testMintTokenSupplyExceeded(bool,address,uint256,uint256,uint256) (runs: 1024, μ: 2943464, ~: 5669556) +ERC1155SaleTest:testMintExpiredSingleFail(bool,address,uint256,uint256,uint64,uint64) (runs: 1024, μ: 2796170, ~: 100087) +ERC1155SaleTest:testMintFailMaxTotal(bool,address,uint256,uint256) (runs: 1024, μ: 2887799, ~: 188007) +ERC1155SaleTest:testMintFailWrongPaymentToken(bool,address,uint256,uint256,address) (runs: 1024, μ: 3430636, ~: 6139498) +ERC1155SaleTest:testMintGlobalSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2883335, ~: 191367) +ERC1155SaleTest:testMintGlobalSupplyExceeded(bool,address,uint256,uint256,uint256) (runs: 1024, μ: 2943368, ~: 5669458) +ERC1155SaleTest:testMintGroupSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2988942, ~: 296242) +ERC1155SaleTest:testMintInactiveFail(bool,address,uint256,uint256) (runs: 1024, μ: 2747745, ~: 51398) +ERC1155SaleTest:testMintInactiveInGroupFail(bool,address,uint256,uint256) (runs: 1024, μ: 2804827, ~: 108722) +ERC1155SaleTest:testMintInactiveSingleFail(bool,address,uint256,uint256) (runs: 1024, μ: 2802620, ~: 106515) +ERC1155SaleTest:testMintSingleSuccess(bool,address,uint256,uint256) (runs: 1024, μ: 2883391, ~: 191420) +ERC1155SaleTest:testMintTokenSupplyExceeded(bool,address,uint256,uint256,uint256) (runs: 1024, μ: 2943463, ~: 5669556) ERC1155SaleTest:testSelectorCollision() (gas: 51971) ERC1155SaleTest:testSupportsInterface() (gas: 10893) -ERC1155SaleTest:testWithdrawERC20(bool,address,uint256) (runs: 1024, μ: 3293262, ~: 6000742) -ERC1155SaleTest:testWithdrawETH(bool,address,uint256) (runs: 1024, μ: 2884989, ~: 5593690) -ERC1155SaleTest:testWithdrawFail(bool,address,uint256) (runs: 1024, μ: 3242654, ~: 5928350) +ERC1155SaleTest:testWithdrawERC20(bool,address,uint256) (runs: 1024, μ: 3282455, ~: 6000763) +ERC1155SaleTest:testWithdrawETH(bool,address,uint256) (runs: 1024, μ: 2900412, ~: 5625346) +ERC1155SaleTest:testWithdrawFail(bool,address,uint256) (runs: 1024, μ: 3242678, ~: 5928374) +ERC1155SoulboundTest:testBatchBurnBlocked(uint256[],uint256[]) (runs: 1024, μ: 47728, ~: 47797) +ERC1155SoulboundTest:testBatchMintAllowed(address,uint256) (runs: 1024, μ: 110185, ~: 110185) +ERC1155SoulboundTest:testBurnBlocked(uint256,uint256) (runs: 1024, μ: 25924, ~: 25924) +ERC1155SoulboundTest:testFactoryDetermineAddress(address,address,string,string,string,string,address,uint96) (runs: 1024, μ: 7042640, ~: 7041442) +ERC1155SoulboundTest:testOwnerHasRoles() (gas: 52087) +ERC1155SoulboundTest:testReinitializeFails() (gas: 34614) +ERC1155SoulboundTest:testSelectorCollision() (gas: 90031) +ERC1155SoulboundTest:testSupportsInterface() (gas: 31363) +ERC1155SoulboundTest:testTransferLocked(address,address,uint256) (runs: 1024, μ: 115056, ~: 115056) +ERC1155SoulboundTest:testTransferLockedOperator(address,address,address,uint256) (runs: 1024, μ: 148322, ~: 148322) +ERC1155SoulboundTest:testTransferUnlocked(address,address,uint256) (runs: 1024, μ: 131318, ~: 131318) +ERC1155SoulboundTest:testTransferUnlockedOperator(address,address,address,uint256) (runs: 1024, μ: 165064, ~: 165064) +ERC1155SoulboundTest:testUnlockInvalidRole(address) (runs: 1024, μ: 51725, ~: 51725) ERC20ItemsTest:testBurnInsufficient(address,uint256,uint256) (runs: 1024, μ: 81152, ~: 82008) ERC20ItemsTest:testBurnSuccess(address,uint256,uint256) (runs: 1024, μ: 90239, ~: 90239) -ERC20ItemsTest:testFactoryDetermineAddress(address,address,string,string,uint8) (runs: 1024, μ: 5252882, ~: 5251395) +ERC20ItemsTest:testFactoryDetermineAddress(address,address,string,string,uint8) (runs: 1024, μ: 5250919, ~: 5251395) ERC20ItemsTest:testInitValues() (gas: 38152) ERC20ItemsTest:testMintInvalidRole(address,uint256) (runs: 1024, μ: 67321, ~: 67321) ERC20ItemsTest:testMintOwner(uint256) (runs: 1024, μ: 80602, ~: 80602) @@ -118,7 +131,7 @@ ERC721ItemsTest:testBurnInvalidOwnership(address) (runs: 1024, μ: 109892, ~: 10 ERC721ItemsTest:testBurnSuccess(address) (runs: 1024, μ: 138130, ~: 138130) ERC721ItemsTest:testContractURI() (gas: 85853) ERC721ItemsTest:testDefaultRoyalty(address,uint96,uint256) (runs: 1024, μ: 41445, ~: 41445) -ERC721ItemsTest:testFactoryDetermineAddress(address,address,string,string,string,string,address,uint96) (runs: 1024, μ: 6794789, ~: 6793149) +ERC721ItemsTest:testFactoryDetermineAddress(address,address,string,string,string,string,address,uint96) (runs: 1024, μ: 6794578, ~: 6793149) ERC721ItemsTest:testMetadataInvalid(address) (runs: 1024, μ: 69188, ~: 69188) ERC721ItemsTest:testMetadataOwner() (gas: 130207) ERC721ItemsTest:testMetadataWithRole(address) (runs: 1024, μ: 117459, ~: 117459) @@ -130,40 +143,65 @@ ERC721ItemsTest:testNameAndSymbol() (gas: 90129) ERC721ItemsTest:testOwnerHasRoles() (gas: 46710) ERC721ItemsTest:testReinitializeFails() (gas: 28551) ERC721ItemsTest:testRoyaltyInvalidRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 125741, ~: 125741) -ERC721ItemsTest:testRoyaltyWithRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 154132, ~: 154333) +ERC721ItemsTest:testRoyaltyWithRole(address,uint256,address,uint96,uint256) (runs: 1024, μ: 154151, ~: 154333) ERC721ItemsTest:testSelectorCollision() (gas: 95069) ERC721ItemsTest:testSupportsInterface() (gas: 37278) ERC721ItemsTest:testTokenMetadata() (gas: 174823) ERC721ItemsTest:testTokenRoyalty(uint256,address,uint96,uint256) (runs: 1024, μ: 65223, ~: 65223) -ERC721SaleTest:testERC20Mint(bool,address,uint256) (runs: 1024, μ: 3160418, ~: 720351) -ERC721SaleTest:testERC20MintFailMaxTotal(bool,address,uint256) (runs: 1024, μ: 3048389, ~: 583551) -ERC721SaleTest:testERC20MintFailPaidETH(bool,address,uint256) (runs: 1024, μ: 3032284, ~: 567446) -ERC721SaleTest:testETHMintFailMaxTotal(bool,address,uint256) (runs: 1024, μ: 2551323, ~: 86420) +ERC721SaleTest:testERC20Mint(bool,address,uint256) (runs: 1024, μ: 3170452, ~: 720351) +ERC721SaleTest:testERC20MintFailMaxTotal(bool,address,uint256) (runs: 1024, μ: 3058429, ~: 583551) +ERC721SaleTest:testERC20MintFailPaidETH(bool,address,uint256) (runs: 1024, μ: 3042324, ~: 567446) +ERC721SaleTest:testETHMintFailMaxTotal(bool,address,uint256) (runs: 1024, μ: 2561364, ~: 86420) ERC721SaleTest:testFactoryDetermineAddress(address,address,address) (runs: 1024, μ: 5065069, ~: 5065069) -ERC721SaleTest:testFreeMint(bool,address,uint256) (runs: 1024, μ: 2607658, ~: 167521) -ERC721SaleTest:testMerkleFailBadProof(address[],address) (runs: 1024, μ: 282442, ~: 278506) -ERC721SaleTest:testMerkleFailNoProof(address[],address) (runs: 1024, μ: 278488, ~: 274565) -ERC721SaleTest:testMerkleReuseFail(address[],uint256) (runs: 1024, μ: 382525, ~: 378954) -ERC721SaleTest:testMerkleSuccess(address[],uint256) (runs: 1024, μ: 375700, ~: 372140) -ERC721SaleTest:testMintExpiredFail(bool,address,uint256,uint64,uint64) (runs: 1024, μ: 2696471, ~: 5233189) -ERC721SaleTest:testMintFailWrongPaymentToken(bool,address,uint256,address) (runs: 1024, μ: 3005394, ~: 540556) -ERC721SaleTest:testMintInactiveFail(bool,address,uint256) (runs: 1024, μ: 2504732, ~: 39587) -ERC721SaleTest:testMintSuccess(bool,address,uint256) (runs: 1024, μ: 2636738, ~: 196601) -ERC721SaleTest:testMintSupplyExceeded(bool,address,uint256,uint256) (runs: 1024, μ: 2616314, ~: 131387) +ERC721SaleTest:testFreeMint(bool,address,uint256) (runs: 1024, μ: 2617693, ~: 167521) +ERC721SaleTest:testMerkleFailBadProof(address[],address) (runs: 1024, μ: 282150, ~: 278510) +ERC721SaleTest:testMerkleFailNoProof(address[],address) (runs: 1024, μ: 278491, ~: 274625) +ERC721SaleTest:testMerkleReuseFail(address[],uint256) (runs: 1024, μ: 382656, ~: 378984) +ERC721SaleTest:testMerkleSuccess(address[],uint256) (runs: 1024, μ: 375831, ~: 372170) +ERC721SaleTest:testMintExpiredFail(bool,address,uint256,uint64,uint64) (runs: 1024, μ: 2681372, ~: 2672822) +ERC721SaleTest:testMintFailWrongPaymentToken(bool,address,uint256,address) (runs: 1024, μ: 3020493, ~: 540556) +ERC721SaleTest:testMintInactiveFail(bool,address,uint256) (runs: 1024, μ: 2514773, ~: 39587) +ERC721SaleTest:testMintSuccess(bool,address,uint256) (runs: 1024, μ: 2646773, ~: 196601) +ERC721SaleTest:testMintSupplyExceeded(bool,address,uint256,uint256) (runs: 1024, μ: 2611293, ~: 131387) ERC721SaleTest:testSelectorCollision() (gas: 44619) ERC721SaleTest:testSupportsInterface() (gas: 10884) -ERC721SaleTest:testWithdrawERC20(bool,address,uint256) (runs: 1024, μ: 3092352, ~: 5599117) -ERC721SaleTest:testWithdrawETH(bool,address,uint256) (runs: 1024, μ: 2684392, ~: 5192376) -ERC721SaleTest:testWithdrawFail(bool,address,uint256) (runs: 1024, μ: 3038633, ~: 5527463) -PaymentCombinerTest:testDetermineAddress(address[],uint256[]) (runs: 1024, μ: 646925, ~: 652205) -PaymentCombinerTest:testListPayeeSplitters(address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1258653, ~: 1269538) -PaymentCombinerTest:testListPayeeSplittersOffsetLimit(address,address[][],uint256[],uint256,uint256) (runs: 1024, μ: 11982755, ~: 4814944) -PaymentCombinerTest:testListReleasableERC20(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1368952, ~: 1380680) -PaymentCombinerTest:testListReleasableNative(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1297385, ~: 1309113) -PaymentCombinerTest:testListReleaseERC20(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1502846, ~: 1514574) -PaymentCombinerTest:testListReleaseNative(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1438278, ~: 1450031) -PaymentCombinerTest:testRepeatedDeploysFail(address[],uint256[]) (runs: 1024, μ: 1040378921, ~: 1040385358) +ERC721SaleTest:testWithdrawERC20(bool,address,uint256) (runs: 1024, μ: 3082327, ~: 5599138) +ERC721SaleTest:testWithdrawETH(bool,address,uint256) (runs: 1024, μ: 2696046, ~: 5224032) +ERC721SaleTest:testWithdrawFail(bool,address,uint256) (runs: 1024, μ: 3038657, ~: 5527487) +ERC721SoulboundTest:testFactoryDetermineAddress(address,address,string,string,string,string,address,uint96) (runs: 1024, μ: 7056525, ~: 7054935) +ERC721SoulboundTest:testOwnerHasRoles() (gas: 52377) +ERC721SoulboundTest:testReinitializeFails() (gas: 35172) +ERC721SoulboundTest:testSelectorCollision() (gas: 102395) +ERC721SoulboundTest:testSupportsInterface() (gas: 40208) +ERC721SoulboundTest:testTransferLocked(address,address) (runs: 1024, μ: 115439, ~: 115439) +ERC721SoulboundTest:testTransferLockedOperator(address,address,address) (runs: 1024, μ: 148251, ~: 148256) +ERC721SoulboundTest:testTransferUnlocked(address,address) (runs: 1024, μ: 151384, ~: 151384) +ERC721SoulboundTest:testTransferUnlockedOperator(address,address,address) (runs: 1024, μ: 184145, ~: 184150) +ERC721SoulboundTest:testUnlockInvalidRole(address) (runs: 1024, μ: 54197, ~: 54197) +PaymentCombinerTest:testDetermineAddress(address[],uint256[]) (runs: 1024, μ: 814888, ~: 822377) +PaymentCombinerTest:testListPayeeSplitters(address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1595183, ~: 1610209) +PaymentCombinerTest:testListPayeeSplittersOffsetLimit(address,address[][],uint256[],uint256,uint256) (runs: 1024, μ: 9966436, ~: 4812798) +PaymentCombinerTest:testListReleasableERC20(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1704648, ~: 1721055) +PaymentCombinerTest:testListReleasableNative(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1633081, ~: 1649487) +PaymentCombinerTest:testListReleaseERC20(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1838542, ~: 1854950) +PaymentCombinerTest:testListReleaseNative(uint256,address[],address[],uint256[],uint256[]) (runs: 1024, μ: 1773950, ~: 1790406) +PaymentCombinerTest:testRepeatedDeploysFail(address[],uint256[]) (runs: 1024, μ: 1040384595, ~: 1040390920) PaymentCombinerTest:testSupportsInterface() (gas: 10684) +PaymentsTest:testMakePaymentExpired(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),uint64,bool) (runs: 1024, μ: 150078, ~: 152514) +PaymentsTest:testMakePaymentFailedChainedCall(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bytes,bool) (runs: 1024, μ: 179235, ~: 180946) +PaymentsTest:testMakePaymentInvalidPayment(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bool) (runs: 1024, μ: 88211, ~: 73578) +PaymentsTest:testMakePaymentInvalidSignature(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bool) (runs: 1024, μ: 40236, ~: 45824) +PaymentsTest:testMakePaymentInvalidTokenSettingsERC20(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),uint256,bool) (runs: 1024, μ: 81223, ~: 66226) +PaymentsTest:testMakePaymentInvalidTokenSettingsERC721(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bool) (runs: 1024, μ: 81311, ~: 65948) +PaymentsTest:testMakePaymentSuccess(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bool) (runs: 1024, μ: 180481, ~: 182624) +PaymentsTest:testMakePaymentSuccessChainedCall(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),bool) (runs: 1024, μ: 254654, ~: 252842) +PaymentsTest:testMakePaymentSuccessMultiplePaymentRecips(address,(uint256,address,uint8,address,uint256,(address,uint256),uint64,string),address,bool) (runs: 1024, μ: 205142, ~: 205897) +PaymentsTest:testPerformChainedCallInvalidCall(bytes,bool) (runs: 1024, μ: 42194, ~: 27302) +PaymentsTest:testPerformChainedCallInvalidSignature(address,uint8,uint256,uint256,address,bool) (runs: 1024, μ: 39924, ~: 45709) +PaymentsTest:testPerformChainedCallSuccess(uint8,uint256,uint256,address,bool) (runs: 1024, μ: 113539, ~: 112428) +PaymentsTest:testSupportsInterface() (gas: 10790) +PaymentsTest:testUpdateSignerInvalidSender(address,address) (runs: 1024, μ: 13790, ~: 13790) +PaymentsTest:testUpdateSignerSuccess(address) (runs: 1024, μ: 19254, ~: 19287) SequenceProxyFactoryTest:testAddressCompute() (gas: 1021270) SequenceProxyFactoryTest:testDeployProxy() (gas: 1021715) SequenceProxyFactoryTest:testDeployProxyAfterUpgrade() (gas: 1033634) diff --git a/scripts/build.ts b/scripts/build.ts index fb8aa76..f62bf5d 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -5,7 +5,7 @@ import util from 'util' import { BUILD_DIR, DEPLOYABLE_CONTRACT_NAMES, - TOKEN_CONTRACT_NAMES, + PROXIED_TOKEN_CONTRACT_NAMES, } from './constants' const exec = util.promisify(execNonPromise) @@ -27,7 +27,7 @@ const main = async () => { // Create the compiler input files for (const solFile of [ ...DEPLOYABLE_CONTRACT_NAMES, - ...TOKEN_CONTRACT_NAMES, + ...PROXIED_TOKEN_CONTRACT_NAMES, 'TransparentUpgradeableBeaconProxy', 'UpgradeableBeacon', ]) { diff --git a/scripts/constants.ts b/scripts/constants.ts index 116756c..a47921c 100644 --- a/scripts/constants.ts +++ b/scripts/constants.ts @@ -8,10 +8,11 @@ export const DEPLOYABLE_CONTRACT_NAMES = [ 'ERC1155SaleFactory', 'ERC1155SoulboundFactory', 'PaymentCombiner', + 'PaymentsFactory', 'Clawback', 'ClawbackMetadata', ] -export const TOKEN_CONTRACT_NAMES = [ +export const PROXIED_TOKEN_CONTRACT_NAMES = [ 'ERC20Items', 'ERC721Items', 'ERC721Sale', @@ -19,4 +20,5 @@ export const TOKEN_CONTRACT_NAMES = [ 'ERC1155Items', 'ERC1155Sale', 'ERC1155Soulbound', + 'Payments', ] diff --git a/scripts/outputSelectors.ts b/scripts/outputSelectors.ts index 6f37953..ddf7f93 100644 --- a/scripts/outputSelectors.ts +++ b/scripts/outputSelectors.ts @@ -1,4 +1,4 @@ -import { TOKEN_CONTRACT_NAMES } from './constants' +import { PROXIED_TOKEN_CONTRACT_NAMES } from './constants' const { spawn } = require('child_process') @@ -31,4 +31,4 @@ const outputSelectors = (contractName: string) => { }) } -TOKEN_CONTRACT_NAMES.forEach(outputSelectors) +PROXIED_TOKEN_CONTRACT_NAMES.forEach(outputSelectors) diff --git a/src/payments/IPayments.sol b/src/payments/IPayments.sol new file mode 100644 index 0000000..54523c5 --- /dev/null +++ b/src/payments/IPayments.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +interface IPaymentsFunctions { + enum TokenType { + ERC20, + ERC721, + ERC1155 + } + + struct PaymentRecipient { + // Payment recipient + address recipient; + // Payment amount + uint256 amount; + } + + struct ChainedCallDetails { + // Address for chained call + address chainedCallAddress; + // Data for chained call + bytes chainedCallData; + } + + struct PaymentDetails { + // Unique ID for this purchase + uint256 purchaseId; + // Recipient of the purchased product + address productRecipient; + // Type of payment token + TokenType tokenType; + // Token address to use for payment + address tokenAddress; + // Token ID to use for payment. Used for ERC-721 and 1155 payments + uint256 tokenId; + // Payment receipients + PaymentRecipient[] paymentRecipients; + // Expiration time of the payment + uint64 expiration; + // ID of the product + string productId; + // Chained call details + ChainedCallDetails chainedCallDetails; + } + + /** + * Returns the hash of the payment details. + * @param paymentDetails The payment details. + * @return paymentHash The hash of the payment details for signing. + */ + function hashPaymentDetails(PaymentDetails calldata paymentDetails) external view returns (bytes32 paymentHash); + + /** + * Check is a payment signature is valid. + * @param paymentDetails The payment details. + * @param signature The signature of the payment. + * @return isValid True if the signature is valid. + */ + function isValidPaymentSignature(PaymentDetails calldata paymentDetails, bytes calldata signature) + external + view + returns (bool isValid); + + /** + * Make a payment for a product. + * @param paymentDetails The payment details. + * @param signature The signature of the payment. + */ + function makePayment(PaymentDetails calldata paymentDetails, bytes calldata signature) external payable; + + /** + * Check if a payment has been accepted. + * @param purchaseId The ID of the purchase. + * @return accepted True if the payment has been accepted. + */ + function paymentAccepted(uint256 purchaseId) external view returns (bool); + + /** + * Returns the hash of the chained call. + * @param chainedCallDetails The chained call details. + * @return callHash The hash of the chained call for signing. + */ + function hashChainedCallDetails(ChainedCallDetails calldata chainedCallDetails) + external + view + returns (bytes32 callHash); + + /** + * Complete a chained call. + * @param chainedCallDetails The chained call details. + * @param signature The signature of the chained call. + * @dev This is called when a payment is accepted off/cross chain. + */ + function performChainedCall(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature) external; + + /** + * Check is a chained call signature is valid. + * @param chainedCallDetails The chained call details. + * @param signature The signature of the chained call. + * @return isValid True if the signature is valid. + */ + function isValidChainedCallSignature(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature) + external + view + returns (bool isValid); + + /** + * Get the signer address. + * @return signer The signer address. + */ + function signer() external view returns (address); +} + +interface IPaymentsSignals { + /// @notice Emitted when contract is already initialized. + error InvalidInitialization(); + + /// @notice Emitted when a payment is already accepted. This prevents double spending. + error PaymentAlreadyAccepted(); + + /// @notice Emitted when a signature is invalid. + error InvalidSignature(); + + /// @notice Emitted when a payment has expired. + error PaymentExpired(); + + /// @notice Emitted when a token transfer is invalid. + error InvalidTokenTransfer(); + + /// @notice Emitted when a chained call fails. + error ChainedCallFailed(); + + /// @notice Emitted when a payment is made. + event PaymentMade( + address indexed spender, address indexed productRecipient, uint256 indexed purchaseId, string productId + ); +} + +interface IPayments is IPaymentsFunctions, IPaymentsSignals {} diff --git a/src/payments/IPaymentsFactory.sol b/src/payments/IPaymentsFactory.sol new file mode 100644 index 0000000..118950a --- /dev/null +++ b/src/payments/IPaymentsFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +interface IPaymentsFactoryFunctions { + /** + * Creates a Payments proxy contract + * @param proxyOwner The owner of the Payments proxy + * @param paymentsOwner The owner of the Payments implementation + * @param paymentsSigner The signer of the Payments implementation + * @return proxyAddr The address of the Payments proxy + */ + function deploy(address proxyOwner, address paymentsOwner, address paymentsSigner) + external + returns (address proxyAddr); + + /** + * Computes the address of a proxy instance. + * @param proxyOwner The owner of the Payments proxy + * @param paymentsOwner The owner of the Payments implementation + * @param paymentsSigner The signer of the Payments implementation + * @return proxyAddr The address of the Payments proxy + */ + function determineAddress(address proxyOwner, address paymentsOwner, address paymentsSigner) + external + returns (address proxyAddr); +} + +interface IPaymentsFactorySignals { + /** + * Event emitted when a new Payments proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event PaymentsDeployed(address proxyAddr); +} + +interface IPaymentsFactory is IPaymentsFactoryFunctions, IPaymentsFactorySignals {} diff --git a/src/payments/Payments.sol b/src/payments/Payments.sol new file mode 100644 index 0000000..48cca6b --- /dev/null +++ b/src/payments/Payments.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {IPayments, IPaymentsFunctions} from "./IPayments.sol"; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; + +import {SignatureValidator} from "../utils/SignatureValidator.sol"; +import {IERC721Transfer} from "../tokens/common/IERC721Transfer.sol"; +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; + +contract Payments is Ownable, IPayments, IERC165 { + using SignatureValidator for bytes32; + + bool private _initialized; + + address public signer; + + // Payment accepted. Works as a nonce. + mapping(uint256 => bool) public paymentAccepted; + + /** + * Initialize the contract. + * @param _owner Owner address + * @param _signer Signer address + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, address _signer) public virtual { + if (_initialized) { + revert InvalidInitialization(); + } + + Ownable._transferOwnership(_owner); + signer = _signer; + + _initialized = true; + } + + /** + * Update the signer address. + */ + function updateSigner(address newSigner) external onlyOwner { + signer = newSigner; + } + + /// @inheritdoc IPaymentsFunctions + function makePayment(PaymentDetails calldata paymentDetails, bytes calldata signature) external payable { + // Check if payment is already accepted + if (paymentAccepted[paymentDetails.purchaseId]) { + revert PaymentAlreadyAccepted(); + } + if (!isValidPaymentSignature(paymentDetails, signature)) { + revert InvalidSignature(); + } + if (block.timestamp > paymentDetails.expiration) { + revert PaymentExpired(); + } + paymentAccepted[paymentDetails.purchaseId] = true; + + address spender = msg.sender; + + for (uint256 i = 0; i < paymentDetails.paymentRecipients.length; i++) { + // We don't check length == 0. Will only be signed if length 0 is a valid input. + PaymentRecipient calldata recipient = paymentDetails.paymentRecipients[i]; + _takePayment( + paymentDetails.tokenType, + paymentDetails.tokenAddress, + spender, + recipient.recipient, + paymentDetails.tokenId, + recipient.amount + ); + } + + // Emit event + emit PaymentMade(spender, paymentDetails.productRecipient, paymentDetails.purchaseId, paymentDetails.productId); + + // Perform chained call + if (paymentDetails.chainedCallDetails.chainedCallAddress != address(0)) { + _performChainedCall(paymentDetails.chainedCallDetails); + } + } + + /// @inheritdoc IPaymentsFunctions + /// @notice A valid signature does not guarantee that the payment will be accepted. + function isValidPaymentSignature(PaymentDetails calldata paymentDetails, bytes calldata signature) + public + view + returns (bool) + { + bytes32 messageHash = hashPaymentDetails(paymentDetails); + address sigSigner = messageHash.recoverSigner(signature); + return sigSigner == signer; + } + + /// @inheritdoc IPaymentsFunctions + /// @dev This hash includes the chain ID. + function hashPaymentDetails(PaymentDetails calldata paymentDetails) public view returns (bytes32) { + return keccak256( + abi.encode( + block.chainid, + paymentDetails.purchaseId, + paymentDetails.productRecipient, + paymentDetails.tokenType, + paymentDetails.tokenAddress, + paymentDetails.tokenId, + paymentDetails.paymentRecipients, + paymentDetails.expiration, + paymentDetails.productId, + paymentDetails.chainedCallDetails + ) + ); + } + + /// @inheritdoc IPaymentsFunctions + /// @dev As the signer can validate any payment (including zero) this function does not increase the security surface. + function performChainedCall(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature) + external + override + { + if (!isValidChainedCallSignature(chainedCallDetails, signature)) { + revert InvalidSignature(); + } + _performChainedCall(chainedCallDetails); + } + + /// @inheritdoc IPaymentsFunctions + function isValidChainedCallSignature(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature) + public + view + returns (bool) + { + bytes32 messageHash = hashChainedCallDetails(chainedCallDetails); + address sigSigner = messageHash.recoverSigner(signature); + return sigSigner == signer; + } + + /// @inheritdoc IPaymentsFunctions + /// @dev This hash includes the chain ID. + function hashChainedCallDetails(ChainedCallDetails calldata chainedCallDetails) public view returns (bytes32) { + return keccak256( + abi.encode(block.chainid, chainedCallDetails.chainedCallAddress, chainedCallDetails.chainedCallData) + ); + } + + /** + * Perform a chained call and revert on error. + */ + function _performChainedCall(ChainedCallDetails calldata chainedCallDetails) internal { + (bool success,) = chainedCallDetails.chainedCallAddress.call{value: 0}(chainedCallDetails.chainedCallData); + if (!success) { + revert ChainedCallFailed(); + } + } + + /** + * Take payment in the given token. + */ + function _takePayment( + TokenType tokenType, + address tokenAddr, + address from, + address to, + uint256 tokenId, + uint256 amount + ) internal { + if (tokenType == TokenType.ERC1155) { + // ERC-1155 + IERC1155(tokenAddr).safeTransferFrom(from, to, tokenId, amount, ""); + } else if (tokenType == TokenType.ERC721) { + // ERC-721 + if (amount > 1) { + revert InvalidTokenTransfer(); + } + IERC721Transfer(tokenAddr).safeTransferFrom(from, to, tokenId); + } else if (tokenType == TokenType.ERC20) { + // ERC-20 + if (tokenId != 0) { + revert InvalidTokenTransfer(); + } + SafeTransferLib.safeTransferFrom(tokenAddr, from, to, amount); + } else { + revert InvalidTokenTransfer(); + } + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceID) public view virtual returns (bool) { + return _interfaceID == type(IPayments).interfaceId || _interfaceID == type(IPaymentsFunctions).interfaceId + || _interfaceID == type(IERC165).interfaceId; + } +} diff --git a/src/payments/PaymentsFactory.sol b/src/payments/PaymentsFactory.sol new file mode 100644 index 0000000..565de35 --- /dev/null +++ b/src/payments/PaymentsFactory.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {Payments} from "@0xsequence/contracts-library/payments/Payments.sol"; +import { + IPaymentsFactory, IPaymentsFactoryFunctions +} from "@0xsequence/contracts-library/payments/IPaymentsFactory.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; + +/** + * Deployer of Payments proxies. + */ +contract PaymentsFactory is IPaymentsFactory, SequenceProxyFactory { + /** + * Creates an Payments Factory. + * @param factoryOwner The owner of the Payments Factory + */ + constructor(address factoryOwner) { + Payments impl = new Payments(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); + } + + /// @inheritdoc IPaymentsFactoryFunctions + function deploy(address proxyOwner, address paymentsOwner, address paymentsSigner) + external + returns (address proxyAddr) + { + bytes32 salt = keccak256(abi.encode(paymentsOwner, paymentsSigner)); + proxyAddr = _createProxy(salt, proxyOwner, ""); + Payments(proxyAddr).initialize(paymentsOwner, paymentsSigner); + emit PaymentsDeployed(proxyAddr); + return proxyAddr; + } + + /// @inheritdoc IPaymentsFactoryFunctions + function determineAddress(address proxyOwner, address paymentsOwner, address paymentsSigner) + external + view + returns (address proxyAddr) + { + bytes32 salt = keccak256(abi.encode(paymentsOwner, paymentsSigner)); + return _computeProxyAddress(salt, proxyOwner, ""); + } +} diff --git a/src/utils/SignatureValidator.sol b/src/utils/SignatureValidator.sol new file mode 100644 index 0000000..94e9e4b --- /dev/null +++ b/src/utils/SignatureValidator.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {ECDSA} from "solady/utils/ECDSA.sol"; +import {IERC1271Wallet} from "@0xsequence/erc-1155/contracts/interfaces/IERC1271Wallet.sol"; + +library SignatureValidator { + using ECDSA for bytes32; + + uint8 private constant SIG_TYPE_ERC712 = 1; + uint8 private constant SIG_TYPE_ERC1271 = 2; + + bytes4 internal constant ERC1271_MAGICVALUE = 0x1626ba7e; + + /** + * Check if a signature is valid. + * @param digest The digest to check. + * @param signature The signature to check. + * @return signer The detected signer address if valid, otherwise address(0). + * @dev An ERC721 signature is formatted `0x01`. + * @dev An ERC1271 signature is formatted `0x02`. + */ + function recoverSigner(bytes32 digest, bytes calldata signature) internal view returns (address signer) { + // Check first byte of signature for signature type + uint8 sigType = uint8(signature[0]); + if (sigType == SIG_TYPE_ERC712) { + // ERC712 + signer = digest.recoverCalldata(signature[1:]); + } else if (sigType == SIG_TYPE_ERC1271 && signature.length >= 21) { + // ERC1271 + assembly { + let word := calldataload(add(1, signature.offset)) + signer := shr(96, word) + } + try IERC1271Wallet(signer).isValidSignature(digest, signature[21:]) returns (bytes4 magicValue) { + if (magicValue != ERC1271_MAGICVALUE) { + signer = address(0); + } + } catch { + signer = address(0); + } + } + } +} diff --git a/test/_mocks/ERC1271Mock.sol b/test/_mocks/ERC1271Mock.sol new file mode 100644 index 0000000..ca9d410 --- /dev/null +++ b/test/_mocks/ERC1271Mock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {IERC1271Wallet} from "@0xsequence/erc-1155/contracts/interfaces/IERC1271Wallet.sol"; + +contract ERC1271Mock is IERC1271Wallet { + mapping(bytes32 => bool) private _validSignatures; + + function setValidSignature(bytes32 signature) public { + _validSignatures[signature] = true; + } + + function isValidSignature(bytes calldata, bytes calldata signature) + external + view + override + returns (bytes4 magicValue) + { + bytes32 sigBytes32 = abi.decode(signature, (bytes32)); + if (_validSignatures[sigBytes32]) { + return 0x20c13b0b; + } else { + return 0x0; + } + } + + function isValidSignature(bytes32, bytes calldata signature) external view override returns (bytes4 magicValue) { + bytes32 sigBytes32 = abi.decode(signature, (bytes32)); + if (_validSignatures[sigBytes32]) { + return 0x1626ba7e; + } else { + return 0x0; + } + } +} diff --git a/test/payments/Payments.t.sol b/test/payments/Payments.t.sol new file mode 100644 index 0000000..f57983f --- /dev/null +++ b/test/payments/Payments.t.sol @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {TestHelper} from "../TestHelper.sol"; + +import {PaymentsFactory} from "src/payments/PaymentsFactory.sol"; +import {Payments, IERC165} from "src/payments/Payments.sol"; +import {IPayments, IPaymentsFunctions, IPaymentsSignals} from "src/payments/IPayments.sol"; + +import {ERC1155Mock} from "test/_mocks/ERC1155Mock.sol"; +import {ERC20Mock} from "test/_mocks/ERC20Mock.sol"; +import {ERC721Mock} from "test/_mocks/ERC721Mock.sol"; +import {IGenericToken} from "test/_mocks/IGenericToken.sol"; +import {ERC1271Mock} from "test/_mocks/ERC1271Mock.sol"; + +contract PaymentsTest is TestHelper, IPaymentsSignals { + Payments public payments; + address public owner; + address public signer; + uint256 public signerPk; + ERC1271Mock public signer1271; + + ERC20Mock public erc20; + ERC721Mock public erc721; + ERC1155Mock public erc1155; + + function setUp() public { + owner = makeAddr("owner"); + (signer, signerPk) = makeAddrAndKey("signer"); + PaymentsFactory factory = new PaymentsFactory(owner); + payments = Payments(factory.deploy(owner, owner, signer)); + + erc20 = new ERC20Mock(address(this)); + erc721 = new ERC721Mock(address(this), "baseURI"); + erc1155 = new ERC1155Mock(address(this), "baseURI"); + + signer1271 = new ERC1271Mock(); + } + + struct DetailsInput { + uint256 purchaseId; + address productRecipient; + uint8 tokenType; + address tokenAddress; + uint256 tokenId; + IPaymentsFunctions.PaymentRecipient paymentRecipient; + uint64 expiration; + string productId; + } + + function _toTokenType(uint8 tokenType) internal pure returns (IPaymentsFunctions.TokenType) { + tokenType = tokenType % 3; + if (tokenType == 0) { + return IPaymentsFunctions.TokenType.ERC20; + } + if (tokenType == 1) { + return IPaymentsFunctions.TokenType.ERC721; + } + return IPaymentsFunctions.TokenType.ERC1155; + } + + function _validTokenParams(IPaymentsFunctions.TokenType tokenType, uint256 tokenId, uint256 amount) + internal + view + returns (address, uint256, uint256) + { + // / 10 to avoid overflow when paying multiple + if (tokenType == IPaymentsFunctions.TokenType.ERC20) { + return (address(erc20), 0, bound(amount, 1, type(uint256).max / 10)); + } + if (tokenType == IPaymentsFunctions.TokenType.ERC721) { + return (address(erc721), bound(tokenId, 1, type(uint256).max / 10), 1); + } + return (address(erc1155), tokenId, bound(amount, 1, type(uint128).max / 10)); + } + + function _signPayment(IPaymentsFunctions.PaymentDetails memory details, bool isERC1271, bool isValid) + internal + returns (bytes memory signature) + { + bytes32 digest = payments.hashPaymentDetails(details); + return _signDigest(digest, isERC1271, isValid); + } + + function _signChainedCall(IPaymentsFunctions.ChainedCallDetails memory details, bool isERC1271, bool isValid) + internal + returns (bytes memory signature) + { + bytes32 digest = payments.hashChainedCallDetails(details); + return _signDigest(digest, isERC1271, isValid); + } + + function _signDigest(bytes32 digest, bool isERC1271, bool isValid) internal returns (bytes memory signature) { + if (isERC1271) { + vm.prank(owner); + payments.updateSigner(address(signer1271)); + + // Pretend digest is the signature + if (isValid) { + signer1271.setValidSignature(digest); + } + return abi.encodePacked(uint8(2), address(signer1271), digest); + } else { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); + if (!isValid) { + v--; // Invalidate sig + } + return abi.encodePacked(uint8(1), r, s, v); + } + } + + /** + * Test all public selectors for collisions against the proxy admin functions. + * @dev yarn ts-node scripts/outputSelectors.ts + */ + function testSelectorCollision() public pure { + checkSelectorCollision(0x0e6fe11f); // hashChainedCallDetails((address,bytes)) + checkSelectorCollision(0x98c3065f); // hashPaymentDetails((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes))) + checkSelectorCollision(0x485cc955); // initialize(address,address) + checkSelectorCollision(0x579a97e6); // isValidChainedCallSignature((address,bytes),bytes) + checkSelectorCollision(0x7b8bdc8e); // isValidPaymentSignature((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes)),bytes) + checkSelectorCollision(0xdecfb3b2); // makePayment((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes)),bytes) + checkSelectorCollision(0x8da5cb5b); // owner() + checkSelectorCollision(0x3a63b803); // paymentAccepted(uint256) + checkSelectorCollision(0xb2238700); // performChainedCall((address,bytes),bytes) + checkSelectorCollision(0x715018a6); // renounceOwnership() + checkSelectorCollision(0x238ac933); // signer() + checkSelectorCollision(0x01ffc9a7); // supportsInterface(bytes4) + checkSelectorCollision(0xf2fde38b); // transferOwnership(address) + checkSelectorCollision(0xa7ecd37e); // updateSigner(address) + } + + function testMakePaymentSuccess(address caller, DetailsInput calldata input, bool isERC1271) + public + safeAddress(caller) + safeAddress(input.paymentRecipient.recipient) + { + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Mint required tokens + IGenericToken(tokenAddr).mint(caller, tokenId, amount); + IGenericToken(tokenAddr).approve(caller, address(payments), tokenId, amount); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectEmit(true, true, true, true, address(payments)); + emit PaymentMade(caller, input.productRecipient, input.purchaseId, input.productId); + vm.prank(caller); + payments.makePayment(details, sig); + + assertEq(IGenericToken(tokenAddr).balanceOf(input.paymentRecipient.recipient, tokenId), amount); + + // Duplicate call fails + vm.expectRevert(PaymentAlreadyAccepted.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentSuccessChainedCall(address caller, DetailsInput calldata input, bool isERC1271) + public + safeAddress(caller) + safeAddress(input.paymentRecipient.recipient) + safeAddress(input.productRecipient) + { + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + // Will mint the next token type + IPaymentsFunctions.TokenType chainedTokenType; + if (tokenType == IPaymentsFunctions.TokenType.ERC20) { + chainedTokenType = IPaymentsFunctions.TokenType.ERC721; + } else if (tokenType == IPaymentsFunctions.TokenType.ERC721) { + chainedTokenType = IPaymentsFunctions.TokenType.ERC1155; + } else { + chainedTokenType = IPaymentsFunctions.TokenType.ERC20; + } + (address chainedTokenAddr, uint256 chainedTokenId, uint256 chainedAmount) = + _validTokenParams(chainedTokenType, input.tokenId, input.paymentRecipient.amount); + bytes memory chainedData = + abi.encodeWithSelector(IGenericToken.mint.selector, input.productRecipient, chainedTokenId, chainedAmount); + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(chainedTokenAddr, chainedData) + ); + + // Mint required tokens + IGenericToken(tokenAddr).mint(caller, tokenId, amount); + IGenericToken(tokenAddr).approve(caller, address(payments), tokenId, amount); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectEmit(true, true, true, true, address(payments)); + emit PaymentMade(caller, input.productRecipient, input.purchaseId, input.productId); + vm.prank(caller); + payments.makePayment(details, sig); + + assertEq(IGenericToken(tokenAddr).balanceOf(input.paymentRecipient.recipient, tokenId), amount); + // Check chaining worked + assertEq(IGenericToken(chainedTokenAddr).balanceOf(input.productRecipient, chainedTokenId), chainedAmount); + } + + function testMakePaymentSuccessMultiplePaymentRecips( + address caller, + DetailsInput calldata input, + address recip2, + bool isERC1271 + ) public safeAddress(caller) safeAddress(input.paymentRecipient.recipient) safeAddress(recip2) { + vm.assume(input.paymentRecipient.recipient != recip2); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + vm.assume(tokenType != IPaymentsFunctions.TokenType.ERC721); // ERC-721 not supported for multi payments + + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](2); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + paymentRecipients[1] = IPaymentsFunctions.PaymentRecipient(recip2, amount); + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Mint required tokens + IGenericToken(tokenAddr).mint(caller, tokenId, amount * 2); + IGenericToken(tokenAddr).approve(caller, address(payments), tokenId, amount * 2); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectEmit(true, true, true, true, address(payments)); + emit PaymentMade(caller, input.productRecipient, input.purchaseId, input.productId); + vm.prank(caller); + payments.makePayment(details, sig); + + assertEq(IGenericToken(tokenAddr).balanceOf(input.paymentRecipient.recipient, tokenId), amount); + assertEq(IGenericToken(tokenAddr).balanceOf(recip2, tokenId), amount); + } + + function testMakePaymentInvalidSignature(address caller, DetailsInput calldata input, bool isERC1271) public { + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Invalid sign it + bytes memory sig = _signPayment(details, isERC1271, false); + + // Send it + vm.expectRevert(InvalidSignature.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentExpired(address caller, DetailsInput calldata input, uint64 blockTimestamp, bool isERC1271) + public + safeAddress(caller) + safeAddress(input.paymentRecipient.recipient) + { + vm.assume(blockTimestamp > input.expiration); + vm.warp(blockTimestamp); + + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + input.expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Mint required tokens + IGenericToken(tokenAddr).mint(caller, tokenId, amount); + IGenericToken(tokenAddr).approve(caller, address(payments), tokenId, amount); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectRevert(PaymentExpired.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentInvalidPayment(address caller, DetailsInput calldata input, bool isERC1271) + public + safeAddress(caller) + safeAddress(input.paymentRecipient.recipient) + { + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Do not mint required tokens + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectRevert(); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentInvalidTokenSettingsERC20( + address caller, + DetailsInput calldata input, + uint256 tokenId, + bool isERC1271 + ) public safeAddress(caller) safeAddress(input.paymentRecipient.recipient) { + tokenId = _bound(tokenId, 1, type(uint256).max); // Non-zero + + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = IPaymentsFunctions.TokenType.ERC20; + (address tokenAddr,, uint256 amount) = _validTokenParams(tokenType, tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectRevert(InvalidTokenTransfer.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentInvalidTokenSettingsERC721(address caller, DetailsInput calldata input, bool isERC1271) + public + safeAddress(caller) + safeAddress(input.paymentRecipient.recipient) + { + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = IPaymentsFunctions.TokenType.ERC721; + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = _bound(amount, 2, type(uint256).max); // Invalid amount + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(0), "") + ); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectRevert(InvalidTokenTransfer.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + function testMakePaymentFailedChainedCall( + address caller, + DetailsInput calldata input, + bytes memory chainedCallData, + bool isERC1271 + ) public safeAddress(caller) safeAddress(input.paymentRecipient.recipient) safeAddress(input.productRecipient) { + // Check the call will fail + (bool success,) = address(payments).call(chainedCallData); + vm.assume(!success); + + uint64 expiration = uint64(_bound(input.expiration, block.timestamp, type(uint64).max)); + IPaymentsFunctions.TokenType tokenType = _toTokenType(input.tokenType); + (address tokenAddr, uint256 tokenId, uint256 amount) = + _validTokenParams(tokenType, input.tokenId, input.paymentRecipient.amount); + IPaymentsFunctions.PaymentRecipient[] memory paymentRecipients = new IPaymentsFunctions.PaymentRecipient[](1); + paymentRecipients[0] = input.paymentRecipient; + paymentRecipients[0].amount = amount; + + IPaymentsFunctions.PaymentDetails memory details = IPaymentsFunctions.PaymentDetails( + input.purchaseId, + input.productRecipient, + tokenType, + tokenAddr, + tokenId, + paymentRecipients, + expiration, + input.productId, + IPaymentsFunctions.ChainedCallDetails(address(payments), chainedCallData) + ); + + // Mint required tokens + IGenericToken(tokenAddr).mint(caller, tokenId, amount); + IGenericToken(tokenAddr).approve(caller, address(payments), tokenId, amount); + + // Sign it + bytes memory sig = _signPayment(details, isERC1271, true); + + // Send it + vm.expectRevert(ChainedCallFailed.selector); + vm.prank(caller); + payments.makePayment(details, sig); + } + + // Chained call + + function testPerformChainedCallSuccess( + uint8 tokenTypeInt, + uint256 tokenId, + uint256 amount, + address recipient, + bool isERC1271 + ) public safeAddress(recipient) { + IPaymentsFunctions.TokenType tokenType = _toTokenType(tokenTypeInt); + address tokenAddr; + (tokenAddr, tokenId, amount) = _validTokenParams(tokenType, tokenId, amount); + + bytes memory callData = abi.encodeWithSelector(IGenericToken.mint.selector, recipient, tokenId, amount); + IPaymentsFunctions.ChainedCallDetails memory chainedCallDetails = + IPaymentsFunctions.ChainedCallDetails(tokenAddr, callData); + + // Sign it + bytes memory sig = _signChainedCall(chainedCallDetails, isERC1271, true); + + // Send it + vm.prank(signer); + payments.performChainedCall(chainedCallDetails, sig); + + assertEq(IGenericToken(tokenAddr).balanceOf(recipient, tokenId), amount); + } + + function testPerformChainedCallInvalidSignature( + address caller, + uint8 tokenTypeInt, + uint256 tokenId, + uint256 amount, + address recipient, + bool isERC1271 + ) public safeAddress(recipient) { + vm.assume(caller != signer); + + IPaymentsFunctions.TokenType tokenType = _toTokenType(tokenTypeInt); + address tokenAddr; + (tokenAddr, tokenId, amount) = _validTokenParams(tokenType, tokenId, amount); + + bytes memory callData = abi.encodeWithSelector(IGenericToken.mint.selector, recipient, tokenId, amount); + IPaymentsFunctions.ChainedCallDetails memory chainedCallDetails = + IPaymentsFunctions.ChainedCallDetails(tokenAddr, callData); + + // Fake sign it + bytes memory sig = _signChainedCall(chainedCallDetails, isERC1271, false); + + // Send it + vm.expectRevert(InvalidSignature.selector); + vm.prank(caller); + payments.performChainedCall(chainedCallDetails, sig); + } + + function testPerformChainedCallInvalidCall(bytes calldata chainedCallData, bool isERC1271) public { + IPaymentsFunctions.ChainedCallDetails memory chainedCallDetails = + IPaymentsFunctions.ChainedCallDetails(address(this), chainedCallData); + // Check the call will fail + (bool success,) = chainedCallDetails.chainedCallAddress.call(chainedCallDetails.chainedCallData); + vm.assume(!success); + + // Sign it + bytes memory sig = _signChainedCall(chainedCallDetails, isERC1271, true); + + vm.expectRevert(ChainedCallFailed.selector); + vm.prank(signer); + payments.performChainedCall(chainedCallDetails, sig); + } + + // Update signer + + function testUpdateSignerSuccess(address newSigner) public { + vm.prank(owner); + payments.updateSigner(newSigner); + assertEq(payments.signer(), newSigner); + } + + function testUpdateSignerInvalidSender(address caller, address newSigner) public { + vm.assume(caller != owner); + + vm.expectRevert(); + vm.prank(caller); + payments.updateSigner(newSigner); + } + + // Supports interface + + function testSupportsInterface() public view { + assertTrue(payments.supportsInterface(type(IPayments).interfaceId)); + assertTrue(payments.supportsInterface(type(IPaymentsFunctions).interfaceId)); + assertTrue(payments.supportsInterface(type(IERC165).interfaceId)); + } + + // Helper + + modifier safeAddress(address addr) { + vm.assume(addr != address(0)); + vm.assume(addr.code.length <= 2); + assumeNotPrecompile(addr); + assumeNotForgeAddress(addr); + _; + } +}