From 758f2df3bdca975b2fffae91d1614affc97a833a Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:00:04 +0000 Subject: [PATCH 01/67] =?UTF-8?q?=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 40 ++++++++++++++++---------------- package.json | 4 ++-- pnpm-lock.yaml | 62 +++++++++++++++++++++++++++----------------------- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index ea09c74b..99af3ab6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,14 +12,14 @@ KaliTest:testExtensionBurn() (gas: 125045) KaliTest:testExtensionDeleteProposal() (gas: 311172) KaliTest:testExtensionMint() (gas: 128533) KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51080, ~: 45491) -KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 89034, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 91130, ~: 97656) +KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51064, ~: 45491) +KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 88956, ~: 83360) +KaliTest:testExtensionSetURI(string) (runs: 256, μ: 90516, ~: 97656) KaliTest:testExtensionUpdateGovSettings() (gas: 78959) KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) KaliTest:testFailDeploy() (gas: 8937393460516749706) KaliTest:testFailNotAuthorizedExtension() (gas: 18102) -KaliTest:testFailProposalCreation() (gas: 132488) +KaliTest:testFailProposalCreation() (gas: 133307) KaliTest:testFailProposalRepeatVoting() (gas: 310819) KaliTest:testGracePeriod() (gas: 10365) KaliTest:testGracePeriodProposal() (gas: 266211) @@ -60,7 +60,7 @@ KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44742) KeepTest:testCannotExecuteWithNullSignatures() (gas: 32330) KeepTest:testCannotExecuteWithSignaturesOutOfOrder() (gas: 42061) KeepTest:testCannotExecuteWithSignaturesRepeated() (gas: 37849) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 13287) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) KeepTest:testCannotMintOverflowExecuteID() (gas: 65940) KeepTest:testCannotMintOverflowSupply() (gas: 261379) KeepTest:testCannotMintToUnsafeAddress() (gas: 313707) @@ -75,7 +75,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180066, ~: 183984) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180377, ~: 183984) KeepTest:testExecuteCreateCall() (gas: 1500184) KeepTest:testExecuteDelegateCall() (gas: 46886) KeepTest:testExecuteEthCall() (gas: 72686) @@ -84,16 +84,16 @@ KeepTest:testExecuteTokenCallWithRole() (gas: 144861) KeepTest:testExecuteTokenCallWithSignatures() (gas: 56968) KeepTest:testIdKeyRole() (gas: 230237) KeepTest:testKeepNonce() (gas: 10396) -KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) +KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29558, ~: 29528) KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222646, ~: 229001) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255462, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269111, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 125127, ~: 129100) -KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53358, ~: 47839) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122048, ~: 125302) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 120814, ~: 126113) +KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222663, ~: 229001) +KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 254170, ~: 261812) +KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 268483, ~: 276114) +KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124785, ~: 129100) +KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53435, ~: 47839) +KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122047, ~: 125302) +KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121151, ~: 126113) KeepTest:testMintExecuteIdKey() (gas: 56215) KeepTest:testName() (gas: 9857) KeepTest:testNoKeepKeyCollision() (gas: 230) @@ -115,12 +115,12 @@ MulticallableTest:testMulticallablePreservesMsgSender() (gas: 11022) MulticallableTest:testMulticallablePreservesMsgValue() (gas: 37542) MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39315) MulticallableTest:testMulticallableReturnDataIsProperlyEncoded() (gas: 11617) -MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9732, ~: 7390) +MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9815, ~: 7396) MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 11718, ~: 11718) -MulticallableTest:testMulticallableRevertWithCustomError() (gas: 10247) -MulticallableTest:testMulticallableRevertWithMessage() (gas: 11992) -MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 12638, ~: 12672) -MulticallableTest:testMulticallableRevertWithNothing() (gas: 10168) +MulticallableTest:testMulticallableRevertWithCustomError() (gas: 11725) +MulticallableTest:testMulticallableRevertWithMessage() (gas: 13474) +MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 14124, ~: 14156) +MulticallableTest:testMulticallableRevertWithNothing() (gas: 11646) MulticallableTest:testMulticallableWithNoData() (gas: 6266) OwnedTest:testCallFunctionAsNonOwner() (gas: 11280) OwnedTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 16217, ~: 16236) @@ -128,7 +128,7 @@ OwnedTest:testCallFunctionAsOwner() (gas: 10432) OwnedTest:testERC165Support() (gas: 5479) OwnedTest:testTransferOwnership() (gas: 13097) OwnedTest:testTransferOwnership(address) (runs: 256, μ: 13170, ~: 13189) -ReentrancyGuardTest:invariantReentrancyStatusAlways1() (runs: 256, calls: 3840, reverts: 290) +ReentrancyGuardTest:invariantReentrancyStatusAlways1() (runs: 256, calls: 3840, reverts: 284) ReentrancyGuardTest:testFailUnprotectedCall() (gas: 43432) ReentrancyGuardTest:testNoReentrancy() (gas: 5354) ReentrancyGuardTest:testProtectedCall() (gas: 30985) \ No newline at end of file diff --git a/package.json b/package.json index 5300ac36..a990e2fe 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "snapshot": "forge clean && forge snapshot --optimize --optimizer-runs 9999999" }, "devDependencies": { - "prettier": "^2.8.8", + "prettier": "^3.0.1", "prettier-plugin-solidity": "1.1.3", - "solhint": "^3.4.1" + "solhint": "^3.6.1" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13dc2c41..3ac0d05c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,22 +2,23 @@ lockfileVersion: '6.0' devDependencies: prettier: - specifier: ^2.8.8 - version: 2.8.8 + specifier: ^3.0.1 + version: 3.0.1 prettier-plugin-solidity: specifier: 1.1.3 - version: 1.1.3(prettier@2.8.8) + version: 1.1.3(prettier@3.0.1) solhint: - specifier: ^3.4.1 - version: 3.4.1 + specifier: ^3.6.1 + version: 3.6.1 packages: - /@babel/code-frame@7.22.5: - resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} + /@babel/code-frame@7.22.10: + resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.22.5 + '@babel/highlight': 7.22.10 + chalk: 2.4.2 dev: true /@babel/helper-validator-identifier@7.22.5: @@ -25,8 +26,8 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/highlight@7.22.5: - resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} + /@babel/highlight@7.22.10: + resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.22.5 @@ -34,8 +35,8 @@ packages: js-tokens: 4.0.0 dev: true - /@solidity-parser/parser@0.16.0: - resolution: {integrity: sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==} + /@solidity-parser/parser@0.16.1: + resolution: {integrity: sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==} dependencies: antlr4ts: 0.5.0-alpha.4 dev: true @@ -318,7 +319,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.22.10 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -334,15 +335,15 @@ packages: engines: {node: '>=4'} dev: true - /prettier-plugin-solidity@1.1.3(prettier@2.8.8): + /prettier-plugin-solidity@1.1.3(prettier@3.0.1): resolution: {integrity: sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==} engines: {node: '>=12'} peerDependencies: prettier: '>=2.3.0 || >=3.0.0-alpha.0' dependencies: - '@solidity-parser/parser': 0.16.0 - prettier: 2.8.8 - semver: 7.5.2 + '@solidity-parser/parser': 0.16.1 + prettier: 3.0.1 + semver: 7.5.4 solidity-comments-extractor: 0.0.7 dev: true @@ -350,6 +351,14 @@ packages: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true + requiresBuild: true + dev: true + optional: true + + /prettier@3.0.1: + resolution: {integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==} + engines: {node: '>=14'} + hasBin: true dev: true /punycode@2.3.0: @@ -367,13 +376,8 @@ packages: engines: {node: '>=4'} dev: true - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - - /semver@7.5.2: - resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: @@ -389,11 +393,11 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /solhint@3.4.1: - resolution: {integrity: sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==} + /solhint@3.6.1: + resolution: {integrity: sha512-pS7Pl11Ujiew9XWaLDH0U+AFc6iK1RtLV0YETSpjHZXjUaNYi32mY+pi8Ap9vqmNfWodWKtG0bVQpatq84mL4g==} hasBin: true dependencies: - '@solidity-parser/parser': 0.16.0 + '@solidity-parser/parser': 0.16.1 ajv: 6.12.6 antlr4: 4.13.0 ast-parents: 0.0.1 @@ -406,7 +410,7 @@ packages: js-yaml: 4.1.0 lodash: 4.17.21 pluralize: 8.0.0 - semver: 6.3.0 + semver: 7.5.4 strip-ansi: 6.0.1 table: 6.8.1 text-table: 0.2.0 @@ -475,4 +479,4 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true \ No newline at end of file + dev: true From c50dff8994b8a2b135f19636baa4aecdeee7357e Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:10:04 +0000 Subject: [PATCH 02/67] =?UTF-8?q?=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 26 +++++++++++++------------- foundry.toml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 99af3ab6..8aef2bb4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,9 +12,9 @@ KaliTest:testExtensionBurn() (gas: 125045) KaliTest:testExtensionDeleteProposal() (gas: 311172) KaliTest:testExtensionMint() (gas: 128533) KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51064, ~: 45491) +KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51080, ~: 45491) KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 88956, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 90516, ~: 97656) +KaliTest:testExtensionSetURI(string) (runs: 256, μ: 90688, ~: 97656) KaliTest:testExtensionUpdateGovSettings() (gas: 78959) KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) KaliTest:testFailDeploy() (gas: 8937393460516749706) @@ -75,7 +75,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180377, ~: 183984) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180045, ~: 183984) KeepTest:testExecuteCreateCall() (gas: 1500184) KeepTest:testExecuteDelegateCall() (gas: 46886) KeepTest:testExecuteEthCall() (gas: 72686) @@ -84,16 +84,16 @@ KeepTest:testExecuteTokenCallWithRole() (gas: 144861) KeepTest:testExecuteTokenCallWithSignatures() (gas: 56968) KeepTest:testIdKeyRole() (gas: 230237) KeepTest:testKeepNonce() (gas: 10396) -KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29558, ~: 29528) +KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222663, ~: 229001) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 254170, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 268483, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124785, ~: 129100) +KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222105, ~: 229001) +KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255487, ~: 261812) +KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269160, ~: 276114) +KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 125134, ~: 129100) KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53435, ~: 47839) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122047, ~: 125302) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121151, ~: 126113) +KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 121371, ~: 125302) +KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121165, ~: 126113) KeepTest:testMintExecuteIdKey() (gas: 56215) KeepTest:testName() (gas: 9857) KeepTest:testNoKeepKeyCollision() (gas: 230) @@ -115,11 +115,11 @@ MulticallableTest:testMulticallablePreservesMsgSender() (gas: 11022) MulticallableTest:testMulticallablePreservesMsgValue() (gas: 37542) MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39315) MulticallableTest:testMulticallableReturnDataIsProperlyEncoded() (gas: 11617) -MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9815, ~: 7396) +MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9673, ~: 7390) MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 11718, ~: 11718) MulticallableTest:testMulticallableRevertWithCustomError() (gas: 11725) MulticallableTest:testMulticallableRevertWithMessage() (gas: 13474) -MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 14124, ~: 14156) +MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 14121, ~: 14156) MulticallableTest:testMulticallableRevertWithNothing() (gas: 11646) MulticallableTest:testMulticallableWithNoData() (gas: 6266) OwnedTest:testCallFunctionAsNonOwner() (gas: 11280) @@ -128,7 +128,7 @@ OwnedTest:testCallFunctionAsOwner() (gas: 10432) OwnedTest:testERC165Support() (gas: 5479) OwnedTest:testTransferOwnership() (gas: 13097) OwnedTest:testTransferOwnership(address) (runs: 256, μ: 13170, ~: 13189) -ReentrancyGuardTest:invariantReentrancyStatusAlways1() (runs: 256, calls: 3840, reverts: 284) +ReentrancyGuardTest:invariantReentrancyStatusAlways1() (runs: 256, calls: 3840, reverts: 268) ReentrancyGuardTest:testFailUnprotectedCall() (gas: 43432) ReentrancyGuardTest:testNoReentrancy() (gas: 5354) ReentrancyGuardTest:testProtectedCall() (gas: 30985) \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 1a75b37f..c64ae5c8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = "0.8.18" +solc = "0.8.19" optimizer_runs = 9_999_999 # Configure remappings From 46adf2ce4580ab98aefd233fabc0377c4f6476d0 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:39:04 +0000 Subject: [PATCH 03/67] =?UTF-8?q?=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 44 ++++++++++++++++++------------------- package.json | 4 ++-- src/KeepToken.sol | 56 ++++++++++++++++++----------------------------- 3 files changed, 45 insertions(+), 59 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 8aef2bb4..1e41cf5f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,9 +12,9 @@ KaliTest:testExtensionBurn() (gas: 125045) KaliTest:testExtensionDeleteProposal() (gas: 311172) KaliTest:testExtensionMint() (gas: 128533) KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51080, ~: 45491) -KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 88956, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 90688, ~: 97656) +KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 50986, ~: 45491) +KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 88801, ~: 83360) +KaliTest:testExtensionSetURI(string) (runs: 256, μ: 89702, ~: 97656) KaliTest:testExtensionUpdateGovSettings() (gas: 78959) KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) KaliTest:testFailDeploy() (gas: 8937393460516749706) @@ -56,16 +56,16 @@ KeepTest:testBurnSigner() (gas: 28745) KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390794) KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247550) KeepTest:testCannotBurnUnderflow() (gas: 116287) -KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44742) +KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44682) KeepTest:testCannotExecuteWithNullSignatures() (gas: 32330) -KeepTest:testCannotExecuteWithSignaturesOutOfOrder() (gas: 42061) -KeepTest:testCannotExecuteWithSignaturesRepeated() (gas: 37849) +KeepTest:testCannotExecuteWithSignaturesOutOfOrder() (gas: 42001) +KeepTest:testCannotExecuteWithSignaturesRepeated() (gas: 37789) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) KeepTest:testCannotMintOverflowExecuteID() (gas: 65940) KeepTest:testCannotMintOverflowSupply() (gas: 261379) KeepTest:testCannotMintToUnsafeAddress() (gas: 313707) KeepTest:testCannotMintToZeroAddress() (gas: 119138) -KeepTest:testCannotRepeatKeepSetup() (gas: 4267374) +KeepTest:testCannotRepeatKeepSetup() (gas: 4258956) KeepTest:testCannotSetTransferability(address,uint256) (runs: 256, μ: 24778, ~: 24778) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83492) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102766) @@ -75,29 +75,29 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180045, ~: 183984) -KeepTest:testExecuteCreateCall() (gas: 1500184) -KeepTest:testExecuteDelegateCall() (gas: 46886) -KeepTest:testExecuteEthCall() (gas: 72686) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68306) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180697, ~: 183984) +KeepTest:testExecuteCreateCall() (gas: 1500124) +KeepTest:testExecuteDelegateCall() (gas: 46826) +KeepTest:testExecuteEthCall() (gas: 72626) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68246) KeepTest:testExecuteTokenCallWithRole() (gas: 144861) -KeepTest:testExecuteTokenCallWithSignatures() (gas: 56968) +KeepTest:testExecuteTokenCallWithSignatures() (gas: 56908) KeepTest:testIdKeyRole() (gas: 230237) KeepTest:testKeepNonce() (gas: 10396) -KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) +KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29549, ~: 29528) KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222105, ~: 229001) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255487, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269160, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 125134, ~: 129100) -KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53435, ~: 47839) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 121371, ~: 125302) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121165, ~: 126113) +KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222071, ~: 228971) +KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 253532, ~: 261812) +KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269149, ~: 276114) +KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124795, ~: 129100) +KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53328, ~: 47809) +KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122066, ~: 125311) +KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121494, ~: 126113) KeepTest:testMintExecuteIdKey() (gas: 56215) KeepTest:testName() (gas: 9857) KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88647) +KeepTest:testNonceIncrementAfterExecute() (gas: 88557) KeepTest:testQuorum() (gas: 23966) KeepTest:testReceiveBatchERC1155() (gas: 44337) KeepTest:testReceiveERC1155() (gas: 40945) diff --git a/package.json b/package.json index a990e2fe..f60630c5 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "private": false, "scripts": { "format": "prettier --write src/**.sol src/utils/**.sol src/utils/interfaces/**.sol src/extensions/dao/**.sol src/extensions/dao/utils/**.sol src/extensions/metadata/**.sol src/extensions/mint/**.sol src/extensions/storage/**.sol src/extensions/utils/**.sol test/**.sol test/utils/mocks/**.sol flat/**.sol --plugin-search-dir=.", - "format:list": "prettier --list-different 'src/**/*.sol' --plugin-search-dir=.", - "format:check": "prettier --check 'src/**/*.sol' --plugin-search-dir=.", + "format:list": "prettier --list-different 'src/**/*.sol'", + "format:check": "prettier --check 'src/**/*.sol'", "solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix --ignore-path .solhintignore", "solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'", "clean": "forge clean", diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 6be5e1d8..75582def 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -122,9 +122,6 @@ abstract contract KeepToken { /// EIP-712 Storage/Logic /// ----------------------------------------------------------------------- - bytes32 internal constant MALLEABILITY_THRESHOLD = - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0; - mapping(address => uint256) public nonces; function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { @@ -162,45 +159,34 @@ abstract contract KeepToken { } signer { } { - // Load the free memory pointer. - // Simply using the free memory usually costs less if many slots are needed. let m := mload(0x40) - - // Clean the excess bits of `v` in case they are dirty. - v := and(v, 0xff) - // If `s` in lower half order, such that the signature is not malleable. - if iszero(gt(s, MALLEABILITY_THRESHOLD)) { - mstore(m, hash) - mstore(add(m, 0x20), v) - mstore(add(m, 0x40), r) - mstore(add(m, 0x60), s) - pop( - staticcall( - gas(), // Amount of gas left for the transaction. - 0x01, // Address of `ecrecover`. - m, // Start of input. - 0x80, // Size of input. - m, // Start of output. - 0x20 // Size of output. - ) + mstore(m, hash) + mstore(add(m, 0x20), and(v, 0xff)) // `v`. + mstore(add(m, 0x40), r) // `r`. + mstore(add(m, 0x60), s) // `s`. + pop( + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + m, // Start of input. + 0x80, // Size of input. + m, // Start of output. + 0x20 // Size of output. ) - // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if mul(eq(mload(m), signer), returndatasize()) { - isValid := 1 - break - } + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if mul(eq(mload(m), signer), returndatasize()) { + isValid := 1 + break } - - // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. let f := shl(224, 0x1626ba7e) - // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) mstore(add(m, 0x24), 0x40) // The offset of the `signature` in the calldata. - mstore(add(m, 0x44), 65) // Store the length of the signature. - mstore(add(m, 0x64), r) // Store `r` of the signature. - mstore(add(m, 0x84), s) // Store `s` of the signature. - mstore8(add(m, 0xa4), v) // Store `v` of the signature. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. isValid := and( and( From 8d03ce128ca339f75d04e4558fb4e10bd00a3a35 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 14 Aug 2023 20:00:39 +0000 Subject: [PATCH 04/67] =?UTF-8?q?=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 30 +++++++++++++++--------------- src/KeepToken.sol | 9 +++++++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 1e41cf5f..3edb46d7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,9 +12,9 @@ KaliTest:testExtensionBurn() (gas: 125045) KaliTest:testExtensionDeleteProposal() (gas: 311172) KaliTest:testExtensionMint() (gas: 128533) KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 50986, ~: 45491) -KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 88801, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 89702, ~: 97656) +KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51080, ~: 45491) +KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 89034, ~: 83360) +KaliTest:testExtensionSetURI(string) (runs: 256, μ: 89790, ~: 97656) KaliTest:testExtensionUpdateGovSettings() (gas: 78959) KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) KaliTest:testFailDeploy() (gas: 8937393460516749706) @@ -56,7 +56,7 @@ KeepTest:testBurnSigner() (gas: 28745) KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390794) KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247550) KeepTest:testCannotBurnUnderflow() (gas: 116287) -KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44682) +KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44658) KeepTest:testCannotExecuteWithNullSignatures() (gas: 32330) KeepTest:testCannotExecuteWithSignaturesOutOfOrder() (gas: 42001) KeepTest:testCannotExecuteWithSignaturesRepeated() (gas: 37789) @@ -65,7 +65,7 @@ KeepTest:testCannotMintOverflowExecuteID() (gas: 65940) KeepTest:testCannotMintOverflowSupply() (gas: 261379) KeepTest:testCannotMintToUnsafeAddress() (gas: 313707) KeepTest:testCannotMintToZeroAddress() (gas: 119138) -KeepTest:testCannotRepeatKeepSetup() (gas: 4258956) +KeepTest:testCannotRepeatKeepSetup() (gas: 4251740) KeepTest:testCannotSetTransferability(address,uint256) (runs: 256, μ: 24778, ~: 24778) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83492) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102766) @@ -75,7 +75,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180697, ~: 183984) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179713, ~: 183984) KeepTest:testExecuteCreateCall() (gas: 1500124) KeepTest:testExecuteDelegateCall() (gas: 46826) KeepTest:testExecuteEthCall() (gas: 72626) @@ -84,20 +84,20 @@ KeepTest:testExecuteTokenCallWithRole() (gas: 144861) KeepTest:testExecuteTokenCallWithSignatures() (gas: 56908) KeepTest:testIdKeyRole() (gas: 230237) KeepTest:testKeepNonce() (gas: 10396) -KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29549, ~: 29528) +KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222071, ~: 228971) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 253532, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269149, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124795, ~: 129100) -KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53328, ~: 47809) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122066, ~: 125311) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121494, ~: 126113) +KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222606, ~: 228971) +KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255465, ~: 261812) +KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269798, ~: 276114) +KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 125124, ~: 129100) +KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53405, ~: 47809) +KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122052, ~: 125302) +KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121486, ~: 126113) KeepTest:testMintExecuteIdKey() (gas: 56215) KeepTest:testName() (gas: 9857) KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88557) +KeepTest:testNonceIncrementAfterExecute() (gas: 88533) KeepTest:testQuorum() (gas: 23966) KeepTest:testReceiveBatchERC1155() (gas: 44337) KeepTest:testReceiveERC1155() (gas: 40945) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 75582def..c721d08b 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -209,9 +209,14 @@ abstract contract KeepToken { ) break } - } - if (!isValid) revert InvalidSig(); + if iszero(isValid) { + // Store the function selector of `InvalidSig()`. + mstore(0x00, 0xc90c66b5) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + } } /// ----------------------------------------------------------------------- From 6090c2dffaace52563e8ff48cb6cd30d67601c1e Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 00:32:33 +0000 Subject: [PATCH 05/67] =?UTF-8?q?=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 38 ++++++------- src/Keep.sol | 121 +++++++++++++++++++++++++++++++++++++---- src/KeepToken.sol | 52 ++++++++---------- src/utils/LibClone.sol | 2 +- 4 files changed, 154 insertions(+), 59 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 3edb46d7..cd2390b1 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,9 +12,9 @@ KaliTest:testExtensionBurn() (gas: 125045) KaliTest:testExtensionDeleteProposal() (gas: 311172) KaliTest:testExtensionMint() (gas: 128533) KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51080, ~: 45491) -KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 89034, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 89790, ~: 97656) +KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51002, ~: 45491) +KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 89112, ~: 83360) +KaliTest:testExtensionSetURI(string) (runs: 256, μ: 91401, ~: 97656) KaliTest:testExtensionUpdateGovSettings() (gas: 78959) KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) KaliTest:testFailDeploy() (gas: 8937393460516749706) @@ -65,7 +65,7 @@ KeepTest:testCannotMintOverflowExecuteID() (gas: 65940) KeepTest:testCannotMintOverflowSupply() (gas: 261379) KeepTest:testCannotMintToUnsafeAddress() (gas: 313707) KeepTest:testCannotMintToZeroAddress() (gas: 119138) -KeepTest:testCannotRepeatKeepSetup() (gas: 4251740) +KeepTest:testCannotRepeatKeepSetup() (gas: 4220655) KeepTest:testCannotSetTransferability(address,uint256) (runs: 256, μ: 24778, ~: 24778) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83492) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102766) @@ -75,29 +75,29 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179713, ~: 183984) -KeepTest:testExecuteCreateCall() (gas: 1500124) -KeepTest:testExecuteDelegateCall() (gas: 46826) -KeepTest:testExecuteEthCall() (gas: 72626) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68246) -KeepTest:testExecuteTokenCallWithRole() (gas: 144861) -KeepTest:testExecuteTokenCallWithSignatures() (gas: 56908) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180382, ~: 183984) +KeepTest:testExecuteCreateCall() (gas: 1500110) +KeepTest:testExecuteDelegateCall() (gas: 46781) +KeepTest:testExecuteEthCall() (gas: 72575) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68201) +KeepTest:testExecuteTokenCallWithRole() (gas: 140280) +KeepTest:testExecuteTokenCallWithSignatures() (gas: 56863) KeepTest:testIdKeyRole() (gas: 230237) KeepTest:testKeepNonce() (gas: 10396) KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222606, ~: 228971) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255465, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269798, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 125124, ~: 129100) -KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53405, ~: 47809) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 122052, ~: 125302) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121486, ~: 126113) +KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222603, ~: 228971) +KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255469, ~: 261812) +KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269142, ~: 276114) +KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124776, ~: 129100) +KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53483, ~: 47809) +KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 121707, ~: 125302) +KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121149, ~: 126113) KeepTest:testMintExecuteIdKey() (gas: 56215) KeepTest:testName() (gas: 9857) KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88533) +KeepTest:testNonceIncrementAfterExecute() (gas: 88482) KeepTest:testQuorum() (gas: 23966) KeepTest:testReceiveBatchERC1155() (gas: 44337) KeepTest:testReceiveERC1155() (gas: 40945) diff --git a/src/Keep.sol b/src/Keep.sol index d1114677..798991d5 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -41,6 +41,20 @@ struct Signature { bytes32 s; } +struct UserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + uint256 callGasLimit; + uint256 verificationGasLimit; + uint256 preVerificationGas; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + bytes paymasterAndData; + bytes signature; +} + contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// Events @@ -87,6 +101,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Core ID key permission. uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); + /// @dev Default ERC4337 handler contract. + address internal constant entryPoint = address(0); + /// @dev Default metadata fetcher for `uri()`. Keep internal immutable uriFetcher; @@ -346,10 +363,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes memory data ) internal virtual { if (op == Operation.call) { - bool success; - assembly { - success := call( + let success := call( gas(), to, value, @@ -358,14 +373,18 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { 0, 0 ) + returndatacopy(0, 0, returndatasize()) + switch success + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } } - - if (!success) revert ExecuteFailed(); } else if (op == Operation.delegatecall) { - bool success; - assembly { - success := delegatecall( + let success := delegatecall( gas(), to, add(data, 0x20), @@ -373,15 +392,95 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { 0, 0 ) + returndatacopy(0, 0, returndatasize()) + switch success + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } } + } else { + assembly { + if iszero(create(value, add(data, 0x20), mload(data))) { + revert(0, 0) + } + } + } + } - if (!success) revert ExecuteFailed(); + /// ----------------------------------------------------------------------- + /// ERC1271 Logic + /// ----------------------------------------------------------------------- + + function isValidSignature( + bytes32 hash, + bytes memory signature + ) public view virtual returns (bytes4) { + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + + // Validate signatures. + if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) { + return this.isValidSignature.selector; } else { + return 0xffffffff; + } + } + + /// ----------------------------------------------------------------------- + /// ERC4337 Logic + /// ----------------------------------------------------------------------- + + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) public payable virtual returns (uint256 validationData) { + if (msg.sender != entryPoint) revert Unauthorized(); + + if (quorum != 1) revert InvalidThreshold(); + + if (balanceOf[userOp.sender][SIGN_KEY] == 0) revert InvalidSig(); + + bytes memory userOpSignature = userOp.signature; + + if (userOpSignature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; assembly { - to := create(value, add(data, 0x20), mload(data)) + r := mload(add(userOpSignature, 0x20)) + s := mload(add(userOpSignature, 0x40)) + v := byte(0, mload(add(userOpSignature, 0x60))) } - if (to == address(0)) revert ExecuteFailed(); + if (userOp.sender != ecrecover(userOpHash, v, r, s)) return 1; + } + + (bool success, bytes memory result) = userOp.sender.staticcall( + abi.encodeWithSignature( + "isValidSignature(bytes32,bytes)", + userOpHash, + userOpSignature + ) + ); + if ( + !success || + (result.length == 32 && abi.decode(result, (bytes4)) != 0x1626ba7e) + ) return 1; + + if (missingAccountFunds != 0) { + assembly { + pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) + } } } diff --git a/src/KeepToken.sol b/src/KeepToken.sol index c721d08b..742230e5 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -149,8 +149,6 @@ abstract contract KeepToken { bytes32 r, bytes32 s ) internal view virtual { - bool isValid; - /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits of `signer` in case they are dirty. @@ -176,7 +174,6 @@ abstract contract KeepToken { ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if mul(eq(mload(m), signer), returndatasize()) { - isValid := 1 break } let f := shl(224, 0x1626ba7e) @@ -188,33 +185,32 @@ abstract contract KeepToken { mstore(add(m, 0x84), s) // `s`. mstore8(add(m, 0xa4), v) // `v`. - isValid := and( + if iszero( and( - // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). - eq(mload(0x00), f), - // Whether the returndata is exactly 0x20 bytes (1 word) long. - eq(returndatasize(), 0x20) - ), - // Whether the staticcall does not revert. - // This must be placed at the end of the `and` clause, - // as the arguments are evaluated from right to left. - staticcall( - gas(), // Remaining gas. - signer, // The `signer` address. - m, // Offset of calldata in memory. - 0xa5, // Length of calldata in memory. - 0x00, // Offset of returndata. - 0x20 // Length of returndata to write. + and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(0x00), f), + // Whether the returndata is exactly 0x20 bytes (1 word) long. + eq(returndatasize(), 0x20) + ), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + 0x00, // Offset of returndata. + 0x20 // Length of returndata to write. + ) ) - ) - break - } - - if iszero(isValid) { - // Store the function selector of `InvalidSig()`. - mstore(0x00, 0xc90c66b5) - // Revert with (offset, size). - revert(0x1c, 0x04) + ) { + // Store the function selector of `InvalidSig()`. + mstore(0x00, 0xc90c66b5) + // Revert with (offset, size). + revert(0x1c, 0x04) + } } } } diff --git a/src/utils/LibClone.sol b/src/utils/LibClone.sol index b6e466d9..4ba13a8e 100644 --- a/src/utils/LibClone.sol +++ b/src/utils/LibClone.sol @@ -46,7 +46,7 @@ library LibClone { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")`. + // `keccak256("ReceiveETH(uint256)")` mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff From 79ea087195972043e94d726d6ad4acb268095928 Mon Sep 17 00:00:00 2001 From: shiv Date: Tue, 15 Aug 2023 15:46:37 +0000 Subject: [PATCH 06/67] ~~ --- src/Keep.sol | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 798991d5..bd3e9d8d 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -427,7 +427,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { v := byte(0, mload(add(signature, 0x60))) } - // Validate signatures. + // Validate signature. if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) { return this.isValidSignature.selector; } else { @@ -448,40 +448,28 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (quorum != 1) revert InvalidThreshold(); - if (balanceOf[userOp.sender][SIGN_KEY] == 0) revert InvalidSig(); - bytes memory userOpSignature = userOp.signature; - if (userOpSignature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - assembly { - r := mload(add(userOpSignature, 0x20)) - s := mload(add(userOpSignature, 0x40)) - v := byte(0, mload(add(userOpSignature, 0x60))) - } - - if (userOp.sender != ecrecover(userOpHash, v, r, s)) return 1; + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(userOpSignature, 0x20)) + s := mload(add(userOpSignature, 0x40)) + v := byte(0, mload(add(userOpSignature, 0x60))) } - (bool success, bytes memory result) = userOp.sender.staticcall( - abi.encodeWithSignature( - "isValidSignature(bytes32,bytes)", - userOpHash, - userOpSignature - ) - ); - if ( - !success || - (result.length == 32 && abi.decode(result, (bytes4)) != 0x1626ba7e) - ) return 1; + if (balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0) + return 0; if (missingAccountFunds != 0) { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) } } + + // return Aggregator address instead of 0 or 1 if signature is empty in userop + // why? when trying to support multiple signers we use the Aggregator to verify an array of userops } /// ----------------------------------------------------------------------- From de9050044f2e79221a6b384a8d6ea247466d3535 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 18:47:24 +0000 Subject: [PATCH 07/67] =?UTF-8?q?=E2=9C=8D=F0=9F=8F=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 9 +++++---- test/Kali.t.sol | 4 ++-- test/Keep.t.sol | 6 +++--- test/KeepFactory.t.sol | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index bd3e9d8d..83672df7 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -102,7 +102,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); /// @dev Default ERC4337 handler contract. - address internal constant entryPoint = address(0); + address internal immutable entryPoint; /// @dev Default metadata fetcher for `uri()`. Keep internal immutable uriFetcher; @@ -179,8 +179,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @notice Create Keep template. + /// @param _entryPoint ERC4337 handler. /// @param _uriFetcher Metadata default. - constructor(Keep _uriFetcher) payable { + constructor(address _entryPoint, Keep _uriFetcher) payable { + entryPoint = _entryPoint; uriFetcher = _uriFetcher; // Deploy as singleton. @@ -459,8 +461,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { v := byte(0, mload(add(userOpSignature, 0x60))) } - if (balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0) - return 0; + if (balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0) return 0; if (missingAccountFunds != 0) { assembly { diff --git a/test/Kali.t.sol b/test/Kali.t.sol index 9cae6513..cf3c6417 100644 --- a/test/Kali.t.sol +++ b/test/Kali.t.sol @@ -35,7 +35,7 @@ error Sponsored(); error AlreadyVoted(); -contract KaliTest is Test, Keep(Keep(address(0))) { +contract KaliTest is Test, Keep(address(0), Keep(address(0))) { address keepAddr; address kaliAddr; @@ -86,7 +86,7 @@ contract KaliTest is Test, Keep(Keep(address(0))) { mockUnsafeERC1155Receiver = new MockUnsafeERC1155Receiver(); // Create the Keep templates. - keep = address(new Keep(Keep(address(address(0))))); + keep = address(new Keep(address(0), Keep(address(address(0))))); // Create the Keep factory. keepFactory = new KeepFactory(keep); // Create the Signer[] for setup. diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 781dfd26..9626826d 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -18,7 +18,7 @@ import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver /// @dev Test framework. import "@std/Test.sol"; -contract KeepTest is Keep(this), Test { +contract KeepTest is Keep(address(0), this), Test { /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- @@ -209,7 +209,7 @@ contract KeepTest is Keep(this), Test { // Initialize templates. mockUriFetcher = new URIFetcher(); - keep = new Keep(Keep(address(mockUriFetcher))); + keep = new Keep(address(0), Keep(address(mockUriFetcher))); mockDai = new MockERC20("Dai", "DAI", 18); mockNFT = new MockERC721(); @@ -370,7 +370,7 @@ contract KeepTest is Keep(this), Test { /// @notice Check setup errors. function testCannotRepeatKeepSetup() public payable { - keepRepeat = new Keep(Keep(address(mockUriFetcher))); + keepRepeat = new Keep(address(0), Keep(address(mockUriFetcher))); keepAddrRepeat = factory.determineKeep(name2); keepRepeat = Keep(keepAddrRepeat); diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index 0cbb85ad..48bcb6ab 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -35,7 +35,7 @@ contract KeepFactoryTest is Test { function setUp() public payable { // Create the templates. uriFetcher = new URIFetcher(); - keep = new Keep(Keep(address(uriFetcher))); + keep = new Keep(address(0), Keep(address(uriFetcher))); // Create the factory. factory = new KeepFactory(address(keep)); // Create the signers. From e7664adb37f728517bab035d5918b36c33ace06e Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:01:12 +0000 Subject: [PATCH 08/67] =?UTF-8?q?=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 11 +++-------- src/KeepToken.sol | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 83672df7..e8ffe5bc 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -155,8 +155,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { interfaceId == this.onERC721Received.selector || // ERC165 Interface ID for ERC1155TokenReceiver. interfaceId == type(ERC1155TokenReceiver).interfaceId || - // ERC165 interface ID for ERC1155MetadataURI. - interfaceId == this.uri.selector || // ERC165 Interface IDs for ERC1155. super.supportsInterface(interfaceId); } @@ -448,8 +446,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) public payable virtual returns (uint256 validationData) { if (msg.sender != entryPoint) revert Unauthorized(); - if (quorum != 1) revert InvalidThreshold(); - bytes memory userOpSignature = userOp.signature; bytes32 r; @@ -461,16 +457,15 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { v := byte(0, mload(add(userOpSignature, 0x60))) } - if (balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0) return 0; + balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0 + ? validationData = 1 + : validationData = 0; if (missingAccountFunds != 0) { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) } } - - // return Aggregator address instead of 0 or 1 if signature is empty in userop - // why? when trying to support multiple signers we use the Aggregator to verify an array of userops } /// ----------------------------------------------------------------------- diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 742230e5..cbae5297 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -264,18 +264,24 @@ abstract contract KeepToken { return string(abi.encodePacked(placeholder)); } + string public constant symbol = "KEEP"; + /// ----------------------------------------------------------------------- /// ERC165 Logic /// ----------------------------------------------------------------------- function supportsInterface( bytes4 interfaceId - ) public view virtual returns (bool) { - return - // ERC165 interface ID for ERC165. - interfaceId == this.supportsInterface.selector || - // ERC165 interface ID for ERC1155. - interfaceId == 0xd9b67a26; + ) public view virtual returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + let s := shr(224, interfaceId) + // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c. + result := or( + or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), + eq(s, 0x0e89341c) + ) + } } /// ----------------------------------------------------------------------- From c9673f8326b8de4625529259c10bf9302428f73b Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:25:15 +0000 Subject: [PATCH 09/67] =?UTF-8?q?=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/KeepToken.sol | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index cbae5297..30ff2866 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -124,22 +124,30 @@ abstract contract KeepToken { mapping(address => uint256) public nonces; - function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { - return - keccak256( - abi.encode( - // `keccak256( - // "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - // )` - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, - // `keccak256(bytes("Keep"))` - 0x21d66785fec14e4da3d76f3866cf99a28f4da49ec8782c3cab7cf79c1b6fa66b, - // `keccak256("1")` - 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, - block.chainid, - address(this) - ) - ); + function DOMAIN_SEPARATOR() + public + view + virtual + returns (bytes32 separator) + { + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore( + m, + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f + ) + mstore( + add(m, 0x20), + 0x21d66785fec14e4da3d76f3866cf99a28f4da49ec8782c3cab7cf79c1b6fa66b + ) + mstore( + add(m, 0x40), + 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6 + ) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } } function _recoverSig( From db357f66197ffe4a4cf11aba52ce7c8dcd89ec46 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 21:05:13 +0000 Subject: [PATCH 10/67] =?UTF-8?q?=F0=9F=A5=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index e8ffe5bc..049c4c70 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -446,20 +446,24 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) public payable virtual returns (uint256 validationData) { if (msg.sender != entryPoint) revert Unauthorized(); - bytes memory userOpSignature = userOp.signature; + if (quorum == 1) { + bytes memory userOpSignature = userOp.signature; - bytes32 r; - bytes32 s; - uint8 v; - assembly { - r := mload(add(userOpSignature, 0x20)) - s := mload(add(userOpSignature, 0x40)) - v := byte(0, mload(add(userOpSignature, 0x60))) - } + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(userOpSignature, 0x20)) + s := mload(add(userOpSignature, 0x40)) + v := byte(0, mload(add(userOpSignature, 0x60))) + } - balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] == 0 - ? validationData = 1 - : validationData = 0; + balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] != 0 + ? validationData = 0 + : validationData = 1; + } else { + validationData = 9; // we need to add something for multiauth. + } if (missingAccountFunds != 0) { assembly { From e4ec29b96a9576a829d93cf28c93813b537dc5ba Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 15 Aug 2023 21:17:59 +0000 Subject: [PATCH 11/67] =?UTF-8?q?=F0=9F=A5=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 14 +++++++------- test/Kali.t.sol | 4 ++-- test/Keep.t.sol | 6 +++--- test/KeepFactory.t.sol | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 049c4c70..03bd2ba0 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -104,8 +104,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Default ERC4337 handler contract. address internal immutable entryPoint; - /// @dev Default metadata fetcher for `uri()`. - Keep internal immutable uriFetcher; + /// @dev Default metadata fetcher for `uri()` and ERC4337 aggregation. + address internal immutable fetcher; /// @dev Record of states verifying `execute()`. uint120 public nonce; @@ -123,7 +123,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { string memory tokenURI = _uris[id]; if (bytes(tokenURI).length > 0) return tokenURI; - else return uriFetcher.uri(id); + else return Keep(fetcher).uri(id); } /// @dev Access control check for ID key balance holders. @@ -178,10 +178,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Create Keep template. /// @param _entryPoint ERC4337 handler. - /// @param _uriFetcher Metadata default. - constructor(address _entryPoint, Keep _uriFetcher) payable { + /// @param _fetcher Metadata and signature validator. + constructor(address _entryPoint, address _fetcher) payable { entryPoint = _entryPoint; - uriFetcher = _uriFetcher; + fetcher = _fetcher; // Deploy as singleton. quorum = 1; @@ -462,7 +462,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ? validationData = 0 : validationData = 1; } else { - validationData = 9; // we need to add something for multiauth. + validationData = uint256(uint160(fetcher)); } if (missingAccountFunds != 0) { diff --git a/test/Kali.t.sol b/test/Kali.t.sol index cf3c6417..9f4fd106 100644 --- a/test/Kali.t.sol +++ b/test/Kali.t.sol @@ -35,7 +35,7 @@ error Sponsored(); error AlreadyVoted(); -contract KaliTest is Test, Keep(address(0), Keep(address(0))) { +contract KaliTest is Test, Keep(address(0), address(0)) { address keepAddr; address kaliAddr; @@ -86,7 +86,7 @@ contract KaliTest is Test, Keep(address(0), Keep(address(0))) { mockUnsafeERC1155Receiver = new MockUnsafeERC1155Receiver(); // Create the Keep templates. - keep = address(new Keep(address(0), Keep(address(address(0))))); + keep = address(new Keep(address(0), address(address(0)))); // Create the Keep factory. keepFactory = new KeepFactory(keep); // Create the Signer[] for setup. diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 9626826d..1a6cfb0a 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -18,7 +18,7 @@ import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver /// @dev Test framework. import "@std/Test.sol"; -contract KeepTest is Keep(address(0), this), Test { +contract KeepTest is Keep(address(0), address(0)), Test { /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- @@ -209,7 +209,7 @@ contract KeepTest is Keep(address(0), this), Test { // Initialize templates. mockUriFetcher = new URIFetcher(); - keep = new Keep(address(0), Keep(address(mockUriFetcher))); + keep = new Keep(address(0), address(mockUriFetcher)); mockDai = new MockERC20("Dai", "DAI", 18); mockNFT = new MockERC721(); @@ -370,7 +370,7 @@ contract KeepTest is Keep(address(0), this), Test { /// @notice Check setup errors. function testCannotRepeatKeepSetup() public payable { - keepRepeat = new Keep(address(0), Keep(address(mockUriFetcher))); + keepRepeat = new Keep(address(0), address(mockUriFetcher)); keepAddrRepeat = factory.determineKeep(name2); keepRepeat = Keep(keepAddrRepeat); diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index 48bcb6ab..b05eb459 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -35,7 +35,7 @@ contract KeepFactoryTest is Test { function setUp() public payable { // Create the templates. uriFetcher = new URIFetcher(); - keep = new Keep(address(0), Keep(address(uriFetcher))); + keep = new Keep(address(0), address(uriFetcher)); // Create the factory. factory = new KeepFactory(address(keep)); // Create the signers. From ccdf77b5cb4a5f9b84c0bf299f155eedeef61065 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:37:35 +0000 Subject: [PATCH 12/67] ~~ hash help --- src/Keep.sol | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 03bd2ba0..194a3d5b 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -418,18 +418,21 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, bytes memory signature ) public view virtual returns (bytes4) { - bytes32 r; - bytes32 s; - uint8 v; - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } + if (signature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } - // Validate signature. - if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) { - return this.isValidSignature.selector; + // Check SIGN_KEY balance. + // This also confirms non-zero `user`. + if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) + return this.isValidSignature.selector; } else { return 0xffffffff; } @@ -448,17 +451,25 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (quorum == 1) { bytes memory userOpSignature = userOp.signature; - + bytes32 hash; bytes32 r; bytes32 s; uint8 v; + + /// @solidity memory-safe-assembly assembly { r := mload(add(userOpSignature, 0x20)) s := mload(add(userOpSignature, 0x40)) v := byte(0, mload(add(userOpSignature, 0x60))) + + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash. + mstore(0x1c, userOpHash) // 0x1c (28) is the length of the prefix. + hash := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20). } - balanceOf[ecrecover(userOpHash, v, r, s)][SIGN_KEY] != 0 + // Check SIGN_KEY balance. + // This also confirms non-zero `user`. + balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0 ? validationData = 0 : validationData = 1; } else { From ddc1dee3c7d267cf101d3d3112fca3057b1d3ae4 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:01:59 +0000 Subject: [PATCH 13/67] ~~ --- src/Keep.sol | 48 ++++++++++++++++++++++++++++++++--------------- src/KeepToken.sol | 10 ++++------ test/Keep.t.sol | 12 ++++++------ 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 194a3d5b..3fb98bc0 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -227,7 +227,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { signer = signers[i]; // Prevent zero and duplicate signers. - if (previous >= signer) revert InvalidSig(); + if (previous >= signer) revert Unauthorized(); previous = signer; @@ -307,13 +307,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Check SIGN_KEY balance. // This also confirms non-zero `user`. - if (balanceOf[user][SIGN_KEY] == 0) revert InvalidSig(); + if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); // Check signature recovery. _recoverSig(hash, user, sig.v, sig.r, sig.s); // Check against duplicates. - if (previous >= user) revert InvalidSig(); + if (previous >= user) revert Unauthorized(); // Memo signature for next iteration until quorum. previous = user; @@ -450,26 +450,44 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (msg.sender != entryPoint) revert Unauthorized(); if (quorum == 1) { - bytes memory userOpSignature = userOp.signature; + bytes memory signature = userOp.signature; + address signer; bytes32 hash; - bytes32 r; - bytes32 s; - uint8 v; /// @solidity memory-safe-assembly assembly { - r := mload(add(userOpSignature, 0x20)) - s := mload(add(userOpSignature, 0x40)) - v := byte(0, mload(add(userOpSignature, 0x60))) - - mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash. - mstore(0x1c, userOpHash) // 0x1c (28) is the length of the prefix. - hash := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20). + mstore(0x20, userOpHash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + + let m := mload(0x40) // Cache the free memory pointer. + let signatureLength := mload(signature) + mstore(0x00, hash) + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x40, mload(add(signature, 0x20))) // `r`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + signer := mload( + staticcall( + gas(), // Amount of gas left for the transaction. + eq(signatureLength, 65), // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. } // Check SIGN_KEY balance. // This also confirms non-zero `user`. - balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0 + balanceOf[signer][SIGN_KEY] != 0 ? validationData = 0 : validationData = 1; } else { diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 30ff2866..f1dfa2a5 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -90,7 +90,7 @@ abstract contract KeepToken { /// Custom Errors /// ----------------------------------------------------------------------- - error InvalidSig(); + error InvalidSignature(); error LengthMismatch(); @@ -214,9 +214,7 @@ abstract contract KeepToken { ) ) ) { - // Store the function selector of `InvalidSig()`. - mstore(0x00, 0xc90c66b5) - // Revert with (offset, size). + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } } @@ -449,7 +447,7 @@ abstract contract KeepToken { bytes32 r, bytes32 s ) public payable virtual { - if (owner == address(0)) revert InvalidSig(); + if (owner == address(0)) revert InvalidSignature(); if (block.timestamp > deadline) revert ExpiredSig(); @@ -591,7 +589,7 @@ abstract contract KeepToken { bytes32 r, bytes32 s ) public payable virtual { - if (delegator == address(0)) revert InvalidSig(); + if (delegator == address(0)) revert InvalidSignature(); if (block.timestamp > deadline) revert ExpiredSig(); diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 1a6cfb0a..eaf7560f 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -395,7 +395,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { outOfOrderSigners[0] = alice > bob ? alice : bob; outOfOrderSigners[1] = alice > bob ? bob : alice; - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); factory.deployKeep(name2, calls, outOfOrderSigners, 2); } @@ -737,7 +737,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { assert(keep.nonce() == 2); // Confirm revert for stale nonce. - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); keep.execute(Operation.call, alice, 1 ether, "", sigs); } @@ -852,7 +852,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { sigs[1] = alice > charlie ? aliceSig : charlieSig; // Execute tx. - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); } @@ -886,7 +886,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { sigs[1] = alice > bob ? bobSig : aliceSig; // Execute tx. - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); } @@ -910,7 +910,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { sigs[1] = aliceSig; // Execute tx. - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); } @@ -944,7 +944,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { sigs[1] = alice > nully ? aliceSig : nullSig; // Execute tx. - vm.expectRevert(InvalidSig.selector); + vm.expectRevert(InvalidSignature.selector); keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); } From 17a65b9cd132104d04630295e88700d7b3f418ab Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 17 Aug 2023 17:03:04 +0000 Subject: [PATCH 14/67] ~~ --- src/Keep.sol | 7 +++++-- src/KeepToken.sol | 10 ++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 3fb98bc0..993a3ece 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -363,6 +363,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes memory data ) internal virtual { if (op == Operation.call) { + /// @solidity memory-safe-assembly assembly { let success := call( gas(), @@ -383,6 +384,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } } else if (op == Operation.delegatecall) { + /// @solidity memory-safe-assembly assembly { let success := delegatecall( gas(), @@ -402,6 +404,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } } else { + /// @solidity memory-safe-assembly assembly { if iszero(create(value, add(data, 0x20), mload(data))) { revert(0, 0) @@ -433,9 +436,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // This also confirms non-zero `user`. if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) return this.isValidSignature.selector; - } else { - return 0xffffffff; } + + return 0xffffffff; } /// ----------------------------------------------------------------------- diff --git a/src/KeepToken.sol b/src/KeepToken.sol index f1dfa2a5..e2c9d8ee 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -130,6 +130,7 @@ abstract contract KeepToken { virtual returns (bytes32 separator) { + /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Load the free memory pointer. mstore( @@ -256,18 +257,19 @@ abstract contract KeepToken { /// ----------------------------------------------------------------------- function name() public pure virtual returns (string memory) { - uint256 placeholder; + uint256 n; + /// @solidity memory-safe-assembly assembly { - placeholder := sub( + n := sub( calldatasize(), add(shr(240, calldataload(sub(calldatasize(), 2))), 2) ) - placeholder := calldataload(add(placeholder, 2)) + n := calldataload(add(n, 2)) } - return string(abi.encodePacked(placeholder)); + return string(abi.encodePacked(n)); } string public constant symbol = "KEEP"; From fab810fc4c436dd6ad4526374824d634e48899b9 Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Fri, 18 Aug 2023 02:49:26 +0530 Subject: [PATCH 15/67] =?UTF-8?q?=E2=99=A1=20shitty=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 107 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 993a3ece..aadcc331 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -300,6 +300,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Store outside loop for gas optimization. Signature calldata sig; + // @TODO we don't need to verify signatures here since its validated in the validate function for (uint256 i; i < threshold; ) { // Load signature items. sig = sigs[i]; @@ -331,7 +332,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Relay operation from Keep via `execute()` or as ID key holder. /// @param call Keep operation as struct of `op, to, value, data`. function relay(Call calldata call) public payable virtual { - _authorized(); + _authorized(); // @TODO check if from entry point _execute(call.op, call.to, call.value, call.data); @@ -445,6 +446,30 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ERC4337 Logic /// ----------------------------------------------------------------------- + function splitSignatures( + bytes memory signatures + ) internal pure returns (bytes[] memory) { + require( + signatures.length % 65 == 0, + "Signatures length must be multiple of 65" + ); + + uint256 signaturesCount = signatures.length / 65; + bytes[] memory splitSignatures = new bytes[](signaturesCount); + + for (uint256 i = 0; i < signaturesCount; i++) { + bytes memory signature = new bytes(65); + + for (uint256 j = 0; j < 65; j++) { + signature[j] = signatures[(i * 65) + j]; + } + + splitSignatures[i] = signature; + } + + return splitSignatures; + } + function validateUserOp( UserOperation calldata userOp, bytes32 userOpHash, @@ -452,51 +477,49 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) public payable virtual returns (uint256 validationData) { if (msg.sender != entryPoint) revert Unauthorized(); - if (quorum == 1) { - bytes memory signature = userOp.signature; - address signer; - bytes32 hash; - - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, userOpHash) // Store into scratch space for keccak256. - mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. - hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. - - let m := mload(0x40) // Cache the free memory pointer. - let signatureLength := mload(signature) - mstore(0x00, hash) - mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. - mstore(0x40, mload(add(signature, 0x20))) // `r`. - mstore(0x60, mload(add(signature, 0x40))) // `s`. - signer := mload( - staticcall( - gas(), // Amount of gas left for the transaction. - eq(signatureLength, 65), // Address of `ecrecover`. - 0x00, // Start of input. - 0x80, // Size of input. - 0x01, // Start of output. - 0x20 // Size of output. - ) + // if (quorum == 1) { + bytes[] memory sigs = splitSignatures(userOp.signature); + address signer; + bytes32 hash; + + // @TODO replace /w execute sig check logic + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, userOpHash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + + let m := mload(0x40) // Cache the free memory pointer. + let signatureLength := mload(signature) + mstore(0x00, hash) + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x40, mload(add(signature, 0x20))) // `r`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + signer := mload( + staticcall( + gas(), // Amount of gas left for the transaction. + eq(signatureLength, 65), // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. ) - // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if iszero(returndatasize()) { - mstore(0x00, 0x8baa579f) // `InvalidSignature()`. - revert(0x1c, 0x04) - } - mstore(0x60, 0) // Restore the zero slot. - mstore(0x40, m) // Restore the free memory pointer. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) } - - // Check SIGN_KEY balance. - // This also confirms non-zero `user`. - balanceOf[signer][SIGN_KEY] != 0 - ? validationData = 0 - : validationData = 1; - } else { - validationData = uint256(uint160(fetcher)); + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. } + // Check SIGN_KEY balance. + // This also confirms non-zero `user`. + balanceOf[signer][SIGN_KEY] != 0 + ? validationData = 0 + : validationData = 1; + if (missingAccountFunds != 0) { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) From 25a0e248f1f0e85496b1ba81288791b1c976346f Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Fri, 18 Aug 2023 19:01:52 +0530 Subject: [PATCH 16/67] noncees --- src/Keep.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Keep.sol b/src/Keep.sol index aadcc331..3ada23ca 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -482,6 +482,21 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { address signer; bytes32 hash; + bytes4 funcSig = bytes4(userOp.callData[0:4]); + + uint key = userOp.nonce >> 64; + + if (key == 0) { + // basic normal nonce + // check quorum as well + } else { + // core key + if (key == CORE_KEY) { + // check sigs[0] recovered holds core key + // + } + } + // @TODO replace /w execute sig check logic /// @solidity memory-safe-assembly assembly { From c10f4da1489f6de8ad712da37e563e83b4ccd60d Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Sat, 19 Aug 2023 20:23:25 +0530 Subject: [PATCH 17/67] possible conditional contract --- src/Conditions.sol | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Conditions.sol diff --git a/src/Conditions.sol b/src/Conditions.sol new file mode 100644 index 00000000..846158bf --- /dev/null +++ b/src/Conditions.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +contract Conditional { + // holds core key so authorized to call any function on keep + // would it be recursive if we call 'relay' on keep with this contract as the recipient and this contract calls 'relay' on keep with the calldata in it + // { + // conditions: [ + // { + // type: "price", + // amount: 1000 // in usd + // address: '0xfucku' + // } + // ], + // actions: [ + // { + // op + // to + // value + // data + // + // } + // ] + // } +} From 6cc2fc5b92f9c093b64541399c9256f01374fdfa Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:59:20 +0000 Subject: [PATCH 18/67] ~~ --- src/Keep.sol | 41 +++++++++++++++++++++++++---------------- src/KeepToken.sol | 18 ------------------ 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 3ada23ca..d5b13298 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -146,17 +146,26 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev ERC165 interface detection. /// @param interfaceId ID to check. - /// @return Fetch detection success. + /// @return result Fetch detection success. function supportsInterface( bytes4 interfaceId - ) public view virtual override returns (bool) { - return - // ERC165 Interface ID for ERC721TokenReceiver. - interfaceId == this.onERC721Received.selector || - // ERC165 Interface ID for ERC1155TokenReceiver. - interfaceId == type(ERC1155TokenReceiver).interfaceId || - // ERC165 Interface IDs for ERC1155. - super.supportsInterface(interfaceId); + ) public view virtual returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + let s := shr(224, interfaceId) + // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c, + // ERC721TokenReceiver: 0x150b7a02, ERC1155TokenReceiver: 0x4e2312e0 + result := or( + or( + or( + or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), + eq(s, 0x0e89341c) + ), + eq(s, 0x150b7a02) + ), + eq(s, 0x4e2312e0) + ) + } } /// ----------------------------------------------------------------------- @@ -455,19 +464,19 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ); uint256 signaturesCount = signatures.length / 65; - bytes[] memory splitSignatures = new bytes[](signaturesCount); + bytes[] memory split = new bytes[](signaturesCount); - for (uint256 i = 0; i < signaturesCount; i++) { + for (uint256 i; i < signaturesCount; ++i) { bytes memory signature = new bytes(65); - for (uint256 j = 0; j < 65; j++) { + for (uint256 j; j < 65; ++j) { signature[j] = signatures[(i * 65) + j]; } - splitSignatures[i] = signature; + split[i] = signature; } - return splitSignatures; + return split; } function validateUserOp( @@ -496,7 +505,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // } } - + /* // @TODO replace /w execute sig check logic /// @solidity memory-safe-assembly assembly { @@ -539,7 +548,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) } - } + }*/ } /// ----------------------------------------------------------------------- diff --git a/src/KeepToken.sol b/src/KeepToken.sol index e2c9d8ee..13695544 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -274,24 +274,6 @@ abstract contract KeepToken { string public constant symbol = "KEEP"; - /// ----------------------------------------------------------------------- - /// ERC165 Logic - /// ----------------------------------------------------------------------- - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool result) { - /// @solidity memory-safe-assembly - assembly { - let s := shr(224, interfaceId) - // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c. - result := or( - or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), - eq(s, 0x0e89341c) - ) - } - } - /// ----------------------------------------------------------------------- /// ERC1155 Logic /// ----------------------------------------------------------------------- From 42f4de627a6e379b10ee9ac798180ffc97aa5080 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:24:14 +0000 Subject: [PATCH 19/67] ~~ --- src/Keep.sol | 70 +++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index d5b13298..d3ea56b2 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -466,14 +466,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint256 signaturesCount = signatures.length / 65; bytes[] memory split = new bytes[](signaturesCount); - for (uint256 i; i < signaturesCount; ++i) { - bytes memory signature = new bytes(65); + unchecked { + for (uint256 i; i < signaturesCount; ++i) { + bytes memory signature = new bytes(65); - for (uint256 j; j < 65; ++j) { - signature[j] = signatures[(i * 65) + j]; - } + for (uint256 j; j < 65; ++j) { + signature[j] = signatures[(i * 65) + j]; + } - split[i] = signature; + split[i] = signature; + } } return split; @@ -486,33 +488,51 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) public payable virtual returns (uint256 validationData) { if (msg.sender != entryPoint) revert Unauthorized(); - // if (quorum == 1) { bytes[] memory sigs = splitSignatures(userOp.signature); address signer; bytes32 hash; bytes4 funcSig = bytes4(userOp.callData[0:4]); - uint key = userOp.nonce >> 64; + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, userOpHash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + if (key == 0) { - // basic normal nonce - // check quorum as well + if (quorum == 1) { + signer = _recoverSigner(hash, sigs[0]); + + balanceOf[signer][SIGN_KEY] != 0 + ? validationData = 0 + : validationData = 1; + } else {} } else { - // core key if (key == CORE_KEY) { - // check sigs[0] recovered holds core key - // + signer = _recoverSigner(hash, sigs[0]); + + balanceOf[signer][CORE_KEY] != 0 + ? validationData = 0 + : validationData = 1; + } + } + + if (missingAccountFunds != 0) { + assembly { + pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) } } - /* - // @TODO replace /w execute sig check logic + } + + function _recoverSigner( + bytes32 hash, + bytes memory signature + ) internal virtual returns (address signer) { /// @solidity memory-safe-assembly assembly { - mstore(0x20, userOpHash) // Store into scratch space for keccak256. - mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. - hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. - let m := mload(0x40) // Cache the free memory pointer. let signatureLength := mload(signature) mstore(0x00, hash) @@ -537,18 +557,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } - - // Check SIGN_KEY balance. - // This also confirms non-zero `user`. - balanceOf[signer][SIGN_KEY] != 0 - ? validationData = 0 - : validationData = 1; - - if (missingAccountFunds != 0) { - assembly { - pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) - } - }*/ } /// ----------------------------------------------------------------------- From e1fa98152f3c3c6dec435589e8a310818982fe19 Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Tue, 22 Aug 2023 21:42:02 +0530 Subject: [PATCH 20/67] =?UTF-8?q?=E2=9C=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 142 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index d3ea56b2..766465c8 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -98,6 +98,12 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// Keep Storage/Logic /// ----------------------------------------------------------------------- + /** + * Return value in case of signature failure, with no time-range. + * Equivalent to _packValidationData(true,0,0). + */ + uint256 internal constant SIG_VALIDATION_FAILED = 1; + /// @dev Core ID key permission. uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); @@ -455,7 +461,50 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ERC4337 Logic /// ----------------------------------------------------------------------- - function splitSignatures( + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) public payable virtual returns (uint256 validationData) { + _requireFromEntryPoint(); + validationData = _validateSignatures(userOp, userOpHash); + // _validateNonce(userOp.nonce); + _payPrefund(missingAccountFunds); + } + + /** + * Ensure the request comes from the known entrypoint. + */ + function _requireFromEntryPoint() internal view virtual { + if (msg.sender != entryPoint) revert Unauthorized(); + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing a bytes32 `messageHash` with + * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * NOTE: The `hash` parameter is intended to be the result of hashing a raw message with + * keccak256, although any bytes32 value can be safely used because the final digest will + * be re-hashed. + * + * See {ECDSA-recover}. + */ + function _toEthSignedMessageHash( + bytes32 messageHash + ) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash + mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix + digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) + } + } + + function _splitSigs( bytes memory signatures ) internal pure returns (bytes[] memory) { require( @@ -481,45 +530,66 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return split; } - function validateUserOp( + /** + * Validate the signature is valid for this message. + * @param userOp - Validate the userOp.signature field. + * @param userOpHash - Convenient field: the hash of the request, to check the signature against. + * (also hashes the entrypoint and chain id) + * @return validationData - Signature and time-range of this operation. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * otherwise, an address of an "authorizer" contract. + * <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - first timestamp this operation is valid + * If the account doesn't use time-range, it is enough to return + * SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function _validateSignatures( UserOperation calldata userOp, - bytes32 userOpHash, - uint256 missingAccountFunds - ) public payable virtual returns (uint256 validationData) { - if (msg.sender != entryPoint) revert Unauthorized(); - - bytes[] memory sigs = splitSignatures(userOp.signature); - address signer; - bytes32 hash; - - bytes4 funcSig = bytes4(userOp.callData[0:4]); - uint key = userOp.nonce >> 64; - - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, userOpHash) // Store into scratch space for keccak256. - mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. - hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. - } - - if (key == 0) { - if (quorum == 1) { - signer = _recoverSigner(hash, sigs[0]); - - balanceOf[signer][SIGN_KEY] != 0 - ? validationData = 0 - : validationData = 1; - } else {} - } else { - if (key == CORE_KEY) { - signer = _recoverSigner(hash, sigs[0]); - - balanceOf[signer][CORE_KEY] != 0 - ? validationData = 0 - : validationData = 1; + bytes32 userOpHash + ) internal virtual returns (uint256 validationData) { + bytes32 hash = _toEthSignedMessageHash(userOpHash); + bytes[] memory sigs = _splitSigs(userOp.signature); + + // check we have enough valid signatures to pass the quorum + for (uint256 i = 0; i <= quorum; i++) { + address signer = _recoverSigner(hash, sigs[i]); + if (balanceOf[signer][SIGN_KEY] != 0) { + return SIG_VALIDATION_FAILED; } } + return 0; + } + + /** + * Validate the nonce of the UserOperation. + * This method may validate the nonce requirement of this account. + * e.g. + * To limit the nonce to use sequenced UserOps only (no "out of order" UserOps): + * `require(nonce < type(uint64).max)` + * For a hypothetical account that *requires* the nonce to be out-of-order: + * `require(nonce & type(uint64).max == 0)` + * + * The actual nonce uniqueness is managed by the EntryPoint, and thus no other + * action is needed by the account itself. + * + * @param _nonce to validate + */ + // function _validateNonce(uint256 _nonce) internal view virtual { + // require(_nonce & type(uint64).max == 0); + // } + + /** + * Sends to the entrypoint (msg.sender) the missing funds for this transaction. + * SubClass MAY override this method for better funds management + * (e.g. send to the entryPoint more than the minimum required, so that in future transactions + * it will not be required to send again). + * @param missingAccountFunds - The minimum value this method should send the entrypoint. + * This value MAY be zero, in case there is enough deposit, + * or the userOp has a paymaster. + */ + function _payPrefund(uint256 missingAccountFunds) internal virtual { if (missingAccountFunds != 0) { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) From 91d11eaed69eb2c055190b2acd2d4167d2fc8dfc Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:59:58 +0530 Subject: [PATCH 21/67] ~~ split opti --- src/Keep.sol | 59 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 766465c8..a2655c48 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -506,28 +506,49 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function _splitSigs( bytes memory signatures - ) internal pure returns (bytes[] memory) { - require( - signatures.length % 65 == 0, - "Signatures length must be multiple of 65" - ); - - uint256 signaturesCount = signatures.length / 65; - bytes[] memory split = new bytes[](signaturesCount); - - unchecked { - for (uint256 i; i < signaturesCount; ++i) { - bytes memory signature = new bytes(65); - - for (uint256 j; j < 65; ++j) { - signature[j] = signatures[(i * 65) + j]; - } + ) internal pure returns (bytes[] memory split) { + /// @solidity memory-safe-assembly + assembly { + // Check if signatures.length % 65 == 0. + if iszero(eq(mod(mload(signatures), 65), 0)) { + // If not, revert with InvalidSignature. + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } - split[i] = signature; + // Calculate signaturesCount in assembly. + let signaturesCount := div(mload(signatures), 65) + + // Allocate memory for the split array. + split := msize() // Current free memory pointer. + mstore(split, signaturesCount) // Store the length of the split array. + + let sigPtr := add(signatures, 0x20) // Pointer to start of signatures data. + let splitDataPtr := add(split, 0x20) // Pointer to the data section of the split array. + + for { + let i := 0 + } lt(i, signaturesCount) { + i := add(i, 1) + } { + // Fetch the current free memory pointer. + let sigMemory := mload(0x40) + + // Store the pointer to the new memory in the split array's data section. + mstore(splitDataPtr, sigMemory) + + // Store the length and the data for the signature. + mstore(sigMemory, 65) + mstore(add(sigMemory, 0x20), mload(sigPtr)) + mstore(add(sigMemory, 0x40), mload(add(sigPtr, 0x20))) + mstore8(add(sigMemory, 0x60), mload(add(sigPtr, 0x40))) + + // Move the pointers for the next iteration. + mstore(0x40, add(sigMemory, 0x61)) // Update free memory pointer. + sigPtr := add(sigPtr, 65) // Move to the next signature. + splitDataPtr := add(splitDataPtr, 0x20) // Move to the next position in the split data section. } } - - return split; } /** From 18422a09697ed56bdd230616de7b3b8e964776bc Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:01:01 +0530 Subject: [PATCH 22/67] ~~ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✂️ snip zero check --- src/utils/LibClone.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/LibClone.sol b/src/utils/LibClone.sol index 4ba13a8e..78bc9097 100644 --- a/src/utils/LibClone.sol +++ b/src/utils/LibClone.sol @@ -117,7 +117,7 @@ library LibClone { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")`. + // `keccak256("ReceiveETH(uint256)")` mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff From 9a75b71893084bfb9d25a82737e1c080e401deb9 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:02:02 +0530 Subject: [PATCH 23/67] ~~ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✂️ --- src/utils/Multicallable.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/Multicallable.sol b/src/utils/Multicallable.sol index 7bbb7e7e..e5ab1f8d 100644 --- a/src/utils/Multicallable.sol +++ b/src/utils/Multicallable.sol @@ -18,10 +18,6 @@ abstract contract Multicallable { assembly { mstore(0x00, 0x20) mstore(0x20, data.length) // Store `data.length` into `results`. - // Early return if no data. - if iszero(data.length) { - return(0x00, 0x40) - } let results := 0x40 // `shl` 5 is equivalent to multiplying by 0x20. From dfd6329359769771642c9831c48c0486a1507ae1 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:02:37 +0530 Subject: [PATCH 24/67] =?UTF-8?q?=F0=9F=A5=A2=200.8.19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 0.8.19 --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 02001a00..34269f3b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,5 @@ "[solidity]": { "editor.defaultFormatter": "JuanBlanco.solidity" }, - "solidity.compileUsingRemoteVersion": "v0.8.18" + "solidity.compileUsingRemoteVersion": "v0.8.19" } From fb5b0d9bf4d6e276eb0c578492c86f15a4331dc8 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:58:43 +0000 Subject: [PATCH 25/67] ~~ --- src/Keep.sol | 195 ++++++++++++------------------------ src/KeepFactory.sol | 139 +++++++++++++++++++++++-- src/KeepToken.sol | 6 +- src/utils/Multicallable.sol | 4 + 4 files changed, 205 insertions(+), 139 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index a2655c48..10c8c8fc 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -98,12 +98,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// Keep Storage/Logic /// ----------------------------------------------------------------------- - /** - * Return value in case of signature failure, with no time-range. - * Equivalent to _packValidationData(true,0,0). - */ - uint256 internal constant SIG_VALIDATION_FAILED = 1; - /// @dev Core ID key permission. uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); @@ -315,7 +309,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Store outside loop for gas optimization. Signature calldata sig; - // @TODO we don't need to verify signatures here since its validated in the validate function for (uint256 i; i < threshold; ) { // Load signature items. sig = sigs[i]; @@ -326,7 +319,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); // Check signature recovery. - _recoverSig(hash, user, sig.v, sig.r, sig.s); + _checkSig(hash, user, sig.v, sig.r, sig.s); // Check against duplicates. if (previous >= user) revert Unauthorized(); @@ -347,7 +340,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Relay operation from Keep via `execute()` or as ID key holder. /// @param call Keep operation as struct of `op, to, value, data`. function relay(Call calldata call) public payable virtual { - _authorized(); // @TODO check if from entry point + if (msg.sender != entryPoint) _authorized(); _execute(call.op, call.to, call.value, call.data); @@ -357,7 +350,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Relay operations from Keep via `execute()` or as ID key holder. /// @param calls Keep operations as struct arrays of `op, to, value, data`. function multirelay(Call[] calldata calls) public payable virtual { - _authorized(); + if (msg.sender != entryPoint) _authorized(); for (uint256 i; i < calls.length; ) { _execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data); @@ -437,24 +430,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, bytes memory signature ) public view virtual returns (bytes4) { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - - // Check SIGN_KEY balance. - // This also confirms non-zero `user`. - if (balanceOf[ecrecover(hash, v, r, s)][SIGN_KEY] != 0) - return this.isValidSignature.selector; - } - - return 0xffffffff; + // Check SIGN_KEY balance. + // This also confirms non-zero `user`. + if (balanceOf[_recoverSigner(hash, signature)][SIGN_KEY] != 0) + return this.isValidSignature.selector; + else return 0xffffffff; } /// ----------------------------------------------------------------------- @@ -466,47 +446,71 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 userOpHash, uint256 missingAccountFunds ) public payable virtual returns (uint256 validationData) { - _requireFromEntryPoint(); - validationData = _validateSignatures(userOp, userOpHash); - // _validateNonce(userOp.nonce); - _payPrefund(missingAccountFunds); - } - - /** - * Ensure the request comes from the known entrypoint. - */ - function _requireFromEntryPoint() internal view virtual { + // Ensure request comes from known `entrypoint`. if (msg.sender != entryPoint) revert Unauthorized(); - } - /** - * @dev Returns the keccak256 digest of an EIP-191 signed data with version - * `0x45` (`personal_sign` messages). - * - * The digest is calculated by prefixing a bytes32 `messageHash` with - * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the - * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. - * - * NOTE: The `hash` parameter is intended to be the result of hashing a raw message with - * keccak256, although any bytes32 value can be safely used because the final digest will - * be re-hashed. - * - * See {ECDSA-recover}. - */ - function _toEthSignedMessageHash( - bytes32 messageHash - ) internal pure returns (bytes32 digest) { + // Return keccak256 hash of ERC191 signed data. + bytes32 hash; /// @solidity memory-safe-assembly assembly { - mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash - mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix - digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) + mstore(0x20, userOpHash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + + validationData = _validateSignatures(hash, userOp.signature); + + // Send any missing funds to `entrypoint` (msg.sender). + if (missingAccountFunds != 0) { + assembly { + pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) + } } } + function _validateSignatures( + bytes32 hash, + bytes calldata signatures + ) internal virtual returns (uint256 validationData) { + bytes[] memory sigs; + // Split signatures if batched. + if (signatures.length == 65) { + sigs[0] = signatures; + } else { + sigs = _splitSigs(signatures); + } + + // Start zero in loop to ensure ascending addresses. + address previous; + // Validation is length of quorum threshold. + uint256 threshold = quorum; + + // Check enough valid signatures to pass `quorum`. + for (uint256 i; i < threshold; ) { + address signer = _recoverSigner(hash, sigs[i]); + + // If not keyholding signer, `SIG_VALIDATION_FAILED`. + if (balanceOf[signer][SIGN_KEY] == 0) return 1; + + // Check against duplicates. + if (previous >= signer) revert Unauthorized(); + + // Memo signature for next iteration until quorum. + previous = signer; + + // An array can't have a total length + // larger than the max uint256 value. + unchecked { + ++i; + } + } + + return 0; + } + function _splitSigs( bytes memory signatures - ) internal pure returns (bytes[] memory split) { + ) internal pure virtual returns (bytes[] memory split) { /// @solidity memory-safe-assembly assembly { // Check if signatures.length % 65 == 0. @@ -520,7 +524,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { let signaturesCount := div(mload(signatures), 65) // Allocate memory for the split array. - split := msize() // Current free memory pointer. + split := mload(0x40) // Current free memory pointer (using mload(0x40) instead of msize). mstore(split, signaturesCount) // Store the length of the split array. let sigPtr := add(signatures, 0x20) // Pointer to start of signatures data. @@ -551,77 +555,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } - /** - * Validate the signature is valid for this message. - * @param userOp - Validate the userOp.signature field. - * @param userOpHash - Convenient field: the hash of the request, to check the signature against. - * (also hashes the entrypoint and chain id) - * @return validationData - Signature and time-range of this operation. - * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, - * otherwise, an address of an "authorizer" contract. - * <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" - * <6-byte> validAfter - first timestamp this operation is valid - * If the account doesn't use time-range, it is enough to return - * SIG_VALIDATION_FAILED value (1) for signature failure. - * Note that the validation code cannot use block.timestamp (or block.number) directly. - */ - function _validateSignatures( - UserOperation calldata userOp, - bytes32 userOpHash - ) internal virtual returns (uint256 validationData) { - bytes32 hash = _toEthSignedMessageHash(userOpHash); - bytes[] memory sigs = _splitSigs(userOp.signature); - - // check we have enough valid signatures to pass the quorum - for (uint256 i = 0; i <= quorum; i++) { - address signer = _recoverSigner(hash, sigs[i]); - if (balanceOf[signer][SIGN_KEY] != 0) { - return SIG_VALIDATION_FAILED; - } - } - - return 0; - } - - /** - * Validate the nonce of the UserOperation. - * This method may validate the nonce requirement of this account. - * e.g. - * To limit the nonce to use sequenced UserOps only (no "out of order" UserOps): - * `require(nonce < type(uint64).max)` - * For a hypothetical account that *requires* the nonce to be out-of-order: - * `require(nonce & type(uint64).max == 0)` - * - * The actual nonce uniqueness is managed by the EntryPoint, and thus no other - * action is needed by the account itself. - * - * @param _nonce to validate - */ - // function _validateNonce(uint256 _nonce) internal view virtual { - // require(_nonce & type(uint64).max == 0); - // } - - /** - * Sends to the entrypoint (msg.sender) the missing funds for this transaction. - * SubClass MAY override this method for better funds management - * (e.g. send to the entryPoint more than the minimum required, so that in future transactions - * it will not be required to send again). - * @param missingAccountFunds - The minimum value this method should send the entrypoint. - * This value MAY be zero, in case there is enough deposit, - * or the userOp has a paymaster. - */ - function _payPrefund(uint256 missingAccountFunds) internal virtual { - if (missingAccountFunds != 0) { - assembly { - pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) - } - } - } - function _recoverSigner( bytes32 hash, bytes memory signature - ) internal virtual returns (address signer) { + ) internal view virtual returns (address signer) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 46a51f9f..88597c0d 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -16,7 +16,7 @@ contract KeepFactory is Multicallable { /// Events /// ----------------------------------------------------------------------- - event Deployed(address indexed keep, address[] signers, uint256 threshold); + event Deployed(address indexed keep, uint256 threshold); /// ----------------------------------------------------------------------- /// Immutables @@ -24,6 +24,9 @@ contract KeepFactory is Multicallable { address internal immutable keepTemplate; + IStakeManager internal constant entryPoint = + IStakeManager(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789); + /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- @@ -50,14 +53,136 @@ contract KeepFactory is Multicallable { Call[] calldata calls, address[] calldata signers, uint256 threshold - ) public payable virtual { - address keep = keepTemplate.cloneDeterministic( - abi.encodePacked(name), - name - ); + ) public payable virtual returns (address keep) { + keep = keepTemplate.cloneDeterministic(abi.encodePacked(name), name); Keep(keep).initialize{value: msg.value}(calls, signers, threshold); - emit Deployed(keep, signers, threshold); + emit Deployed(keep, threshold); + } + + /// ----------------------------------------------------------------------- + /// ERC4337 Staking Logic + /// ----------------------------------------------------------------------- + + function addStake(uint32 unstakeDelaySec) external payable { + entryPoint.addStake{value: msg.value}(unstakeDelaySec); + } + + function unlockStake() external { + entryPoint.unlockStake(); + } + + function withdrawStake(address payable withdrawAddress) external { + entryPoint.withdrawStake(withdrawAddress); } } + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. + */ +interface IStakeManager { + event Deposited(address indexed account, uint256 totalDeposit); + + event Withdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + // Emitted when stake or unstake delay are modified. + event StakeLocked( + address indexed account, + uint256 totalStaked, + uint256 unstakeDelaySec + ); + + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); + + event StakeWithdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + /** + * @param deposit - The entity's deposit. + * @param staked - True if this entity is staked. + * @param stake - Actual amount of ether staked for this entity. + * @param unstakeDelaySec - Minimum delay to withdraw the stake. + * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. + * @dev Sizes were chosen so that (deposit,staked, stake) fit into one cell (used during handleOps) + * and the rest fit into a 2nd cell. + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay + */ + struct DepositInfo { + uint112 deposit; + bool staked; + uint112 stake; + uint32 unstakeDelaySec; + uint48 withdrawTime; + } + + // API struct used by getStakeInfo and simulateValidation. + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo( + address account + ) external view returns (DepositInfo memory info); + + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) external payable; + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 _unstakeDelaySec) external payable; + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external; + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external; + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external; +} diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 13695544..beff7e57 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -151,7 +151,7 @@ abstract contract KeepToken { } } - function _recoverSig( + function _checkSig( bytes32 hash, address signer, uint8 v, @@ -457,7 +457,7 @@ abstract contract KeepToken { ) ); - _recoverSig(hash, owner, v, r, s); + _checkSig(hash, owner, v, r, s); } isApprovedForAll[owner][operator] = approved; @@ -599,7 +599,7 @@ abstract contract KeepToken { ) ); - _recoverSig(hash, delegator, v, r, s); + _checkSig(hash, delegator, v, r, s); } _delegate(delegator, delegatee, id); diff --git a/src/utils/Multicallable.sol b/src/utils/Multicallable.sol index e5ab1f8d..7bbb7e7e 100644 --- a/src/utils/Multicallable.sol +++ b/src/utils/Multicallable.sol @@ -18,6 +18,10 @@ abstract contract Multicallable { assembly { mstore(0x00, 0x20) mstore(0x20, data.length) // Store `data.length` into `results`. + // Early return if no data. + if iszero(data.length) { + return(0x00, 0x40) + } let results := 0x40 // `shl` 5 is equivalent to multiplying by 0x20. From e7c8f6dbe6c6deed55700abbe89398fc0102863a Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Thu, 24 Aug 2023 21:07:37 +0530 Subject: [PATCH 26/67] scratch --- src/Keep.sol | 69 +++-- src/KeepFactory.sol | 208 ++++++--------- src/extensions/metadata/Fetcher.sol | 85 ++++++ .../metadata/PermissionRemoteFetcher.sol | 49 ++++ src/extensions/metadata/URIFetcher.sol | 45 ---- src/extensions/metadata/URIRemoteFetcher.sol | 248 +++++++++++------- .../metadata/URIRemoteFetcherV2.sol | 169 ------------ src/extensions/utils/Owned.sol | 8 +- src/utils/LibClone.sol | 4 +- test/Kali.t.sol | 8 +- test/Keep.t.sol | 28 +- test/KeepFactory.t.sol | 10 +- 12 files changed, 443 insertions(+), 488 deletions(-) create mode 100644 src/extensions/metadata/Fetcher.sol create mode 100644 src/extensions/metadata/PermissionRemoteFetcher.sol delete mode 100644 src/extensions/metadata/URIFetcher.sol delete mode 100644 src/extensions/metadata/URIRemoteFetcherV2.sol diff --git a/src/Keep.sol b/src/Keep.sol index 10c8c8fc..541b63b6 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -24,7 +24,8 @@ import {Multicallable} from "./utils/Multicallable.sol"; enum Operation { call, delegatecall, - create + create, + create2 } struct Call { @@ -101,11 +102,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Core ID key permission. uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); - /// @dev Default ERC4337 handler contract. - address internal immutable entryPoint; - - /// @dev Default metadata fetcher for `uri()` and ERC4337 aggregation. - address internal immutable fetcher; + /// @dev External validation for ERC1155 `uri()` and ERC4337 permissioning. + Keep internal immutable validator; /// @dev Record of states verifying `execute()`. uint120 public nonce; @@ -116,6 +114,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Internal ID metadata mapping. mapping(uint256 => string) internal _uris; + /// @dev ERC4337 entrypoint. + function entryPoint() public view virtual returns (address) { + return validator.entryPoint(); + } + /// @dev ID metadata fetcher. /// @param id ID to fetch from. /// @return tokenURI Metadata. @@ -123,7 +126,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { string memory tokenURI = _uris[id]; if (bytes(tokenURI).length > 0) return tokenURI; - else return Keep(fetcher).uri(id); + else return validator.uri(id); } /// @dev Access control check for ID key balance holders. @@ -134,6 +137,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function _authorized() internal view virtual returns (bool) { if ( (totalSupply[CORE_KEY] == 0 && msg.sender == address(this)) || + msg.sender == validator.entryPoint() || balanceOf[msg.sender][CORE_KEY] != 0 || balanceOf[msg.sender][uint32(msg.sig)] != 0 ) return true; @@ -186,11 +190,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @notice Create Keep template. - /// @param _entryPoint ERC4337 handler. - /// @param _fetcher Metadata and signature validator. - constructor(address _entryPoint, address _fetcher) payable { - entryPoint = _entryPoint; - fetcher = _fetcher; + /// @param _validator ERC1155/ERC4337 fetcher. + constructor(Keep _validator) payable { + validator = _validator; // Deploy as singleton. quorum = 1; @@ -340,7 +342,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Relay operation from Keep via `execute()` or as ID key holder. /// @param call Keep operation as struct of `op, to, value, data`. function relay(Call calldata call) public payable virtual { - if (msg.sender != entryPoint) _authorized(); + _authorized(); _execute(call.op, call.to, call.value, call.data); @@ -350,7 +352,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @notice Relay operations from Keep via `execute()` or as ID key holder. /// @param calls Keep operations as struct arrays of `op, to, value, data`. function multirelay(Call[] calldata calls) public payable virtual { - if (msg.sender != entryPoint) _authorized(); + _authorized(); for (uint256 i; i < calls.length; ) { _execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data); @@ -412,13 +414,22 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return(0, returndatasize()) } } - } else { + } else if (op == Operation.create) { /// @solidity memory-safe-assembly assembly { if iszero(create(value, add(data, 0x20), mload(data))) { revert(0, 0) } } + } else { + bytes32 salt = keccak256(abi.encode(to)); + + /// @solidity memory-safe-assembly + assembly { + if iszero(create2(value, add(data, 0x20), mload(data), salt)) { + revert(0, 0) + } + } } } @@ -428,10 +439,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function isValidSignature( bytes32 hash, - bytes memory signature + bytes calldata signature ) public view virtual returns (bytes4) { // Check SIGN_KEY balance. - // This also confirms non-zero `user`. + // This also confirms non-zero signer. if (balanceOf[_recoverSigner(hash, signature)][SIGN_KEY] != 0) return this.isValidSignature.selector; else return 0xffffffff; @@ -447,7 +458,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint256 missingAccountFunds ) public payable virtual returns (uint256 validationData) { // Ensure request comes from known `entrypoint`. - if (msg.sender != entryPoint) revert Unauthorized(); + if (msg.sender != validator.entryPoint()) revert Unauthorized(); // Return keccak256 hash of ERC191 signed data. bytes32 hash; @@ -458,7 +469,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } - validationData = _validateSignatures(hash, userOp.signature); + uint256 key = userOp.nonce >> 64; // Shift nonce to get key. + + if (key == uint256(uint32(this.validatePermission.selector))) { + validationData = validator.validatePermission(userOp, hash); + } else { + validationData = validateSignatures(hash, userOp.signature); + } // Send any missing funds to `entrypoint` (msg.sender). if (missingAccountFunds != 0) { @@ -468,13 +485,22 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } - function _validateSignatures( + function validatePermission( + UserOperation calldata userOp, + bytes32 hash + ) public view virtual returns (uint256) { + return validator.validatePermission(userOp, hash); + } + + function validateSignatures( bytes32 hash, bytes calldata signatures - ) internal virtual returns (uint256 validationData) { + ) public view virtual returns (uint256) { bytes[] memory sigs; + // Split signatures if batched. if (signatures.length == 65) { + sigs = new bytes[](1); sigs[0] = signatures; } else { sigs = _splitSigs(signatures); @@ -482,6 +508,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Start zero in loop to ensure ascending addresses. address previous; + // Validation is length of quorum threshold. uint256 threshold = quorum; diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 88597c0d..997925fc 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -2,36 +2,34 @@ pragma solidity ^0.8.4; import {Multicallable, Call, Keep} from "./Keep.sol"; -import {LibClone} from "./utils/LibClone.sol"; +import {Owned} from "./extensions/utils/Owned.sol"; /// @notice Keep Factory. -contract KeepFactory is Multicallable { +contract KeepFactory is Multicallable, Owned(tx.origin) { /// ----------------------------------------------------------------------- - /// Library Usage + /// Events /// ----------------------------------------------------------------------- - using LibClone for address; + event Deployed(Keep indexed keep, uint256 threshold); /// ----------------------------------------------------------------------- - /// Events + /// Custom Errors /// ----------------------------------------------------------------------- - event Deployed(address indexed keep, uint256 threshold); + /// @dev Unable to deploy the clone. + error DeploymentFailed(); /// ----------------------------------------------------------------------- /// Immutables /// ----------------------------------------------------------------------- - address internal immutable keepTemplate; - - IStakeManager internal constant entryPoint = - IStakeManager(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789); + Keep internal immutable keepTemplate; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor(address _keepTemplate) payable { + constructor(Keep _keepTemplate) payable { keepTemplate = _keepTemplate; } @@ -40,12 +38,7 @@ contract KeepFactory is Multicallable { /// ----------------------------------------------------------------------- function determineKeep(bytes32 name) public view virtual returns (address) { - return - keepTemplate.predictDeterministicAddress( - abi.encodePacked(name), - name, - address(this) - ); + return predictDeterministicAddress(name); } function deployKeep( @@ -53,8 +46,8 @@ contract KeepFactory is Multicallable { Call[] calldata calls, address[] calldata signers, uint256 threshold - ) public payable virtual returns (address keep) { - keep = keepTemplate.cloneDeterministic(abi.encodePacked(name), name); + ) public payable virtual returns (Keep keep) { + keep = cloneDeterministic(name); Keep(keep).initialize{value: msg.value}(calls, signers, threshold); @@ -62,127 +55,82 @@ contract KeepFactory is Multicallable { } /// ----------------------------------------------------------------------- - /// ERC4337 Staking Logic + /// Clone Operations /// ----------------------------------------------------------------------- - function addStake(uint32 unstakeDelaySec) external payable { - entryPoint.addStake{value: msg.value}(unstakeDelaySec); + /// @dev Deploys a deterministic clone of `keepTemplate`, + /// using immutable argument `name` also as `salt`. + function cloneDeterministic(bytes32 salt) internal returns (Keep instance) { + Keep template = keepTemplate; + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let data := mload(0x40) + let dataLength := 0x60 + let dataEnd := add(data, dataLength) + + // Write the bytecode before the data. + mstore(data, 0x5af43d3d93803e606057fd5bf3) + // Write the address of the implementation. + mstore(add(data, 0x0d), template) + + // Create the instance. + instance := create2(0, data, dataLength, salt) + + // If `instance` is zero, revert. + if iszero(instance) { + // Store the function selector of `DeploymentFailed()`. + mstore(0x00, 0x30116425) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + } } - function unlockStake() external { - entryPoint.unlockStake(); + /// @dev Returns the address of the deterministic clone of + /// `keepTemplate` using immutable arguments encoded in `data`, with `salt`. + function predictDeterministicAddress( + bytes32 salt + ) internal view returns (address predicted) { + Keep template = keepTemplate; + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let data := mload(0x40) + let dataLength := 0x60 + let dataEnd := add(data, dataLength) + + // Write the bytecode before the data. + mstore(data, 0x5af43d3d93803e606057fd5bf3) + // Write the address of the implementation. + mstore(add(data, 0x0d), template) + + // Compute and store the bytecode hash. + mstore(0x35, keccak256(data, dataLength)) + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + } } - function withdrawStake(address payable withdrawAddress) external { - entryPoint.withdrawStake(withdrawAddress); - } -} + /// ----------------------------------------------------------------------- + /// ERC4337 Staking Logic + /// ----------------------------------------------------------------------- -/** - * Manage deposits and stakes. - * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). - * Stake is value locked for at least "unstakeDelay" by the staked entity. - */ -interface IStakeManager { - event Deposited(address indexed account, uint256 totalDeposit); - - event Withdrawn( - address indexed account, - address withdrawAddress, - uint256 amount - ); - - // Emitted when stake or unstake delay are modified. - event StakeLocked( - address indexed account, - uint256 totalStaked, + function addStake( uint256 unstakeDelaySec - ); - - // Emitted once a stake is scheduled for withdrawal. - event StakeUnlocked(address indexed account, uint256 withdrawTime); - - event StakeWithdrawn( - address indexed account, - address withdrawAddress, - uint256 amount - ); - - /** - * @param deposit - The entity's deposit. - * @param staked - True if this entity is staked. - * @param stake - Actual amount of ether staked for this entity. - * @param unstakeDelaySec - Minimum delay to withdraw the stake. - * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. - * @dev Sizes were chosen so that (deposit,staked, stake) fit into one cell (used during handleOps) - * and the rest fit into a 2nd cell. - * - 112 bit allows for 10^15 eth - * - 48 bit for full timestamp - * - 32 bit allows 150 years for unstake delay - */ - struct DepositInfo { - uint112 deposit; - bool staked; - uint112 stake; - uint32 unstakeDelaySec; - uint48 withdrawTime; + ) public payable virtual onlyOwner { + KeepFactory(keepTemplate.entryPoint()).addStake{value: msg.value}( + unstakeDelaySec + ); } - // API struct used by getStakeInfo and simulateValidation. - struct StakeInfo { - uint256 stake; - uint256 unstakeDelaySec; + function unlockStake() public payable virtual onlyOwner { + KeepFactory(keepTemplate.entryPoint()).unlockStake(); } - /** - * Get deposit info. - * @param account - The account to query. - * @return info - Full deposit information of given account. - */ - function getDepositInfo( - address account - ) external view returns (DepositInfo memory info); - - /** - * Get account balance. - * @param account - The account to query. - * @return - The deposit (for gas payment) of the account. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * Add to the deposit of the given account. - * @param account - The account to add to. - */ - function depositTo(address account) external payable; - - /** - * Add to the account's stake - amount and delay - * any pending unstake is first cancelled. - * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. - */ - function addStake(uint32 _unstakeDelaySec) external payable; - - /** - * Attempt to unlock the stake. - * The value can be withdrawn (using withdrawStake) after the unstake delay. - */ - function unlockStake() external; - - /** - * Withdraw from the (unlocked) stake. - * Must first call unlockStake and wait for the unstakeDelay to pass. - * @param withdrawAddress - The address to send withdrawn value. - */ - function withdrawStake(address payable withdrawAddress) external; - - /** - * Withdraw from the deposit. - * @param withdrawAddress - The address to send withdrawn value. - * @param withdrawAmount - The amount to withdraw. - */ - function withdrawTo( - address payable withdrawAddress, - uint256 withdrawAmount - ) external; + function withdrawStake( + address withdrawAddress + ) public payable virtual onlyOwner { + KeepFactory(keepTemplate.entryPoint()).withdrawStake(withdrawAddress); + } } diff --git a/src/extensions/metadata/Fetcher.sol b/src/extensions/metadata/Fetcher.sol new file mode 100644 index 00000000..9acd43ab --- /dev/null +++ b/src/extensions/metadata/Fetcher.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {UserOperation} from "../../Keep.sol"; +import {Owned} from "../utils/Owned.sol"; + +/// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. +contract Fetcher is Owned(tx.origin) { + /// ----------------------------------------------------------------------- + /// Events + /// ----------------------------------------------------------------------- + + event PermissionRemoteFetcherSet(Fetcher indexed permissionRemoteFetcher); + + event URIRemoteFetcherSet(Fetcher indexed uriRemoteFetcher); + + event EntryPointSet(address indexed entryPoint); + + /// ----------------------------------------------------------------------- + /// Remote Storage + /// ----------------------------------------------------------------------- + + Fetcher public permissionRemoteFetcher; + + Fetcher public uriRemoteFetcher; + + address public entryPoint; + + /// ----------------------------------------------------------------------- + /// Constructor + /// ----------------------------------------------------------------------- + + constructor() payable { + emit EntryPointSet( + entryPoint = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 + ); + } + + /// ----------------------------------------------------------------------- + /// Permission Remote Logic + /// ----------------------------------------------------------------------- + + function validatePermission( + UserOperation calldata userOp, + bytes32 hash + ) public view virtual returns (uint256) { + return permissionRemoteFetcher.validatePermission(userOp, hash); + } + + function setPermissionRemoteFetcher( + Fetcher _permissionRemoteFetcher + ) public payable virtual onlyOwner { + permissionRemoteFetcher = _permissionRemoteFetcher; + + emit PermissionRemoteFetcherSet(_permissionRemoteFetcher); + } + + /// ----------------------------------------------------------------------- + /// URI Remote Logic + /// ----------------------------------------------------------------------- + + function uri(uint256 id) public view virtual returns (string memory) { + return uriRemoteFetcher.uri(id); + } + + function setURIRemoteFetcher( + Fetcher _uriRemoteFetcher + ) public payable virtual onlyOwner { + uriRemoteFetcher = _uriRemoteFetcher; + + emit URIRemoteFetcherSet(_uriRemoteFetcher); + } + + /// ----------------------------------------------------------------------- + /// Entry Point Logic + /// ----------------------------------------------------------------------- + + function setEntryPoint( + address _entryPoint + ) public payable virtual onlyOwner { + entryPoint = _entryPoint; + + emit EntryPointSet(_entryPoint); + } +} diff --git a/src/extensions/metadata/PermissionRemoteFetcher.sol b/src/extensions/metadata/PermissionRemoteFetcher.sol new file mode 100644 index 00000000..2ea1f30e --- /dev/null +++ b/src/extensions/metadata/PermissionRemoteFetcher.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {UserOperation} from "../../Keep.sol"; + +struct Permission { + uint256 validAfter; + uint256 validUntil; + address receiver; + bytes4 func; + uint256 allowance; + uint256 maxUses; +} + +/// @notice Remote permission fetcher for ERC4337. +contract PermissionRemoteFetcher { + function validatePermission( + UserOperation calldata userOp, + bytes32 hash + ) public view virtual returns (uint256) { + /*Permission memory permission = abi.decode(userOp.data, (Permission)); + + if (permission.validAfter > block.timestamp) { + return 1; + } + + if (permission.validUntil < block.timestamp) { + return 2; + } + + if (permission.maxUses > 0) { + if (permission.allowance == 0) { + return 3; + } + + if (permission.allowance < userOp.amount) { + return 4; + } + } + + if (permission.receiver != address(0)) { + if (permission.receiver != userOp.sender) { + return 5; + } + } + + return 0;*/ + } +} diff --git a/src/extensions/metadata/URIFetcher.sol b/src/extensions/metadata/URIFetcher.sol deleted file mode 100644 index 4517e3a2..00000000 --- a/src/extensions/metadata/URIFetcher.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {Owned} from "../utils/Owned.sol"; -import {URIRemoteFetcher} from "./URIRemoteFetcher.sol"; - -/// @notice Open-ended metadata fetcher for ERC1155. -/// @author z0r0z.eth -contract URIFetcher is Owned(tx.origin) { - /// ----------------------------------------------------------------------- - /// Events - /// ----------------------------------------------------------------------- - - event URIRemoteFetcherSet(URIRemoteFetcher indexed uriRemoteFetcher); - - /// ----------------------------------------------------------------------- - /// URI Remote Storage - /// ----------------------------------------------------------------------- - - URIRemoteFetcher public uriRemoteFetcher; - - /// ----------------------------------------------------------------------- - /// Constructor - /// ----------------------------------------------------------------------- - - constructor() payable { - emit URIRemoteFetcherSet(uriRemoteFetcher = new URIRemoteFetcher()); - } - - /// ----------------------------------------------------------------------- - /// URI Remote Logic - /// ----------------------------------------------------------------------- - - function uri(uint256 id) public view virtual returns (string memory) { - return uriRemoteFetcher.fetchURI(msg.sender, id); - } - - function setURIRemoteFetcher( - URIRemoteFetcher _uriRemoteFetcher - ) public payable virtual onlyOwner { - uriRemoteFetcher = _uriRemoteFetcher; - - emit URIRemoteFetcherSet(_uriRemoteFetcher); - } -} diff --git a/src/extensions/metadata/URIRemoteFetcher.sol b/src/extensions/metadata/URIRemoteFetcher.sol index 58e0dad3..b6e00c98 100644 --- a/src/extensions/metadata/URIRemoteFetcher.sol +++ b/src/extensions/metadata/URIRemoteFetcher.sol @@ -1,116 +1,166 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {Owned} from "../utils/Owned.sol"; - /// @notice Remote metadata fetcher for ERC1155. -contract URIRemoteFetcher is Owned(tx.origin) { - /// ----------------------------------------------------------------------- - /// Events - /// ----------------------------------------------------------------------- - - event AlphaURISet(string alphaURI); - - event BetaURISet(address indexed origin, string betaURI); - - event URISet(address indexed origin, uint256 indexed id, string uri); - - event UserURISet(address indexed origin, address indexed user, string uri); - - event UserIdURISet( - address indexed origin, - address indexed user, - uint256 indexed id, - string uri - ); - - /// ----------------------------------------------------------------------- - /// URI Storage - /// ----------------------------------------------------------------------- - - string public alphaURI; - - mapping(address => string) public betaURI; - - mapping(address => mapping(uint256 => string)) public uris; - - mapping(address => mapping(address => string)) public userUris; - - mapping(address => mapping(address => mapping(uint256 => string))) - public userIdUris; - - /// ----------------------------------------------------------------------- - /// Constructor - /// ----------------------------------------------------------------------- +contract URIRemoteFetcher { + uint256 internal immutable chainId = block.chainid; constructor() payable {} - /// ----------------------------------------------------------------------- - /// URI Logic - /// ----------------------------------------------------------------------- - - function fetchURI( - address origin, - uint256 id - ) public view virtual returns (string memory) { - string memory alpha = alphaURI; - string memory beta = betaURI[origin]; - string memory uri = uris[origin][id]; - - if (bytes(uri).length != 0) { - return uri; - } else if (bytes(beta).length != 0) { - return beta; - } else { - return bytes(alpha).length != 0 ? alpha : ""; - } - } - - function setAlphaURI( - string calldata _alphaURI - ) public payable virtual onlyOwner { - alphaURI = _alphaURI; - - emit AlphaURISet(_alphaURI); + function uri(uint256 id) public view virtual returns (string memory) { + return + string( + abi.encodePacked( + "https://api.kali.gg/v1/keeps/", + toString(chainId), + "/", + toHexStringChecksummed(tx.origin), + "/", + toString(id) + ) + ); } - function setBetaURI( - address origin, - string calldata beta - ) public payable virtual onlyOwner { - betaURI[origin] = beta; - - emit BetaURISet(origin, beta); + /// @dev Returns the base 10 decimal representation of `value`. + function toString( + uint256 value + ) internal pure virtual returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but + // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. + // We will need 1 word for the trailing zeros padding, 1 word for the length, + // and 3 words for a maximum of 78 digits. + str := add(mload(0x40), 0x80) + // Update the free memory pointer to allocate. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + let w := not(0) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { + let temp := value + } 1 { + + } { + str := add(str, w) // `sub(str, 1)`. + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + // Keep dividing `temp` until zero. + temp := div(temp, 10) + if iszero(temp) { + break + } + } + + let length := sub(end, str) + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 0x20) + // Store the length. + mstore(str, length) + } } - function setURI( - address origin, - uint256 id, - string calldata uri - ) public payable virtual onlyOwner { - uris[origin][id] = uri; - - emit URISet(origin, id, uri); + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, + /// and the alphabets are capitalized conditionally according to + /// https://eips.ethereum.org/EIPS/eip-55 + function toHexStringChecksummed( + address value + ) internal pure virtual returns (string memory str) { + str = toHexString(value); + /// @solidity memory-safe-assembly + assembly { + let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` + let o := add(str, 0x22) + let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` + let t := shl(240, 136) // `0b10001000 << 240` + for { + let i := 0 + } 1 { + + } { + mstore(add(i, i), mul(t, byte(i, hashed))) + i := add(i, 1) + if eq(i, 20) { + break + } + } + mstore( + o, + xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))) + ) + o := add(o, 0x20) + mstore( + o, + xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))) + ) + } } - function setUserURI( - address origin, - address user, - string calldata uri - ) public payable virtual onlyOwner { - userUris[origin][user] = uri; - - emit UserURISet(origin, user, uri); + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + function toHexString( + address value + ) internal pure virtual returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } } - function setUserIdURI( - address origin, - address user, - uint256 id, - string calldata uri - ) public payable virtual onlyOwner { - userIdUris[origin][user][id] = uri; - - emit UserIdURISet(origin, user, id, uri); + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix( + address value + ) internal pure virtual returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + str := mload(0x40) + + // Allocate the memory. + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x28 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. + mstore(0x40, add(str, 0x80)) + + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + str := add(str, 2) + mstore(str, 40) + + let o := add(str, 0x20) + mstore(add(o, 40), 0) + + value := shl(96, value) + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { + let i := 0 + } 1 { + + } { + let p := add(o, add(i, i)) + let temp := byte(i, value) + mstore8(add(p, 1), mload(and(temp, 15))) + mstore8(p, mload(shr(4, temp))) + i := add(i, 1) + if eq(i, 20) { + break + } + } + } } } diff --git a/src/extensions/metadata/URIRemoteFetcherV2.sol b/src/extensions/metadata/URIRemoteFetcherV2.sol deleted file mode 100644 index 005751ae..00000000 --- a/src/extensions/metadata/URIRemoteFetcherV2.sol +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @notice Remote metadata fetcher for ERC1155. -contract URIRemoteFetcherV2 { - uint256 internal immutable chainId = block.chainid; - - constructor() payable {} - - function fetchURI( - address origin, - uint256 id - ) public view virtual returns (string memory) { - return - string( - abi.encodePacked( - "https://api.kali.gg/v1/keeps/", - toString(chainId), - "/", - toHexStringChecksummed(origin), - "/", - toString(id) - ) - ); - } - - /// @dev Returns the base 10 decimal representation of `value`. - function toString( - uint256 value - ) internal pure virtual returns (string memory str) { - /// @solidity memory-safe-assembly - assembly { - // The maximum value of a uint256 contains 78 digits (1 byte per digit), but - // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. - // We will need 1 word for the trailing zeros padding, 1 word for the length, - // and 3 words for a maximum of 78 digits. - str := add(mload(0x40), 0x80) - // Update the free memory pointer to allocate. - mstore(0x40, add(str, 0x20)) - // Zeroize the slot after the string. - mstore(str, 0) - - // Cache the end of the memory to calculate the length later. - let end := str - - let w := not(0) // Tsk. - // We write the string from rightmost digit to leftmost digit. - // The following is essentially a do-while loop that also handles the zero case. - for { - let temp := value - } 1 { - - } { - str := add(str, w) // `sub(str, 1)`. - // Write the character to the pointer. - // The ASCII index of the '0' character is 48. - mstore8(str, add(48, mod(temp, 10))) - // Keep dividing `temp` until zero. - temp := div(temp, 10) - if iszero(temp) { - break - } - } - - let length := sub(end, str) - // Move the pointer 32 bytes leftwards to make room for the length. - str := sub(str, 0x20) - // Store the length. - mstore(str, length) - } - } - - /// @dev Returns the hexadecimal representation of `value`. - /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, - /// and the alphabets are capitalized conditionally according to - /// https://eips.ethereum.org/EIPS/eip-55 - function toHexStringChecksummed( - address value - ) internal pure virtual returns (string memory str) { - str = toHexString(value); - /// @solidity memory-safe-assembly - assembly { - let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` - let o := add(str, 0x22) - let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` - let t := shl(240, 136) // `0b10001000 << 240` - for { - let i := 0 - } 1 { - - } { - mstore(add(i, i), mul(t, byte(i, hashed))) - i := add(i, 1) - if eq(i, 20) { - break - } - } - mstore( - o, - xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))) - ) - o := add(o, 0x20) - mstore( - o, - xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))) - ) - } - } - - /// @dev Returns the hexadecimal representation of `value`. - /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. - function toHexString( - address value - ) internal pure virtual returns (string memory str) { - str = toHexStringNoPrefix(value); - /// @solidity memory-safe-assembly - assembly { - let strLength := add(mload(str), 2) // Compute the length. - mstore(str, 0x3078) // Write the "0x" prefix. - str := sub(str, 2) // Move the pointer. - mstore(str, strLength) // Write the length. - } - } - - /// @dev Returns the hexadecimal representation of `value`. - /// The output is encoded using 2 hexadecimal digits per byte. - function toHexStringNoPrefix( - address value - ) internal pure virtual returns (string memory str) { - /// @solidity memory-safe-assembly - assembly { - str := mload(0x40) - - // Allocate the memory. - // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, - // 0x02 bytes for the prefix, and 0x28 bytes for the digits. - // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. - mstore(0x40, add(str, 0x80)) - - // Store "0123456789abcdef" in scratch space. - mstore(0x0f, 0x30313233343536373839616263646566) - - str := add(str, 2) - mstore(str, 40) - - let o := add(str, 0x20) - mstore(add(o, 40), 0) - - value := shl(96, value) - - // We write the string from rightmost digit to leftmost digit. - // The following is essentially a do-while loop that also handles the zero case. - for { - let i := 0 - } 1 { - - } { - let p := add(o, add(i, i)) - let temp := byte(i, value) - mstore8(add(p, 1), mload(and(temp, 15))) - mstore8(p, mload(shr(4, temp))) - i := add(i, 1) - if eq(i, 20) { - break - } - } - } - } -} diff --git a/src/extensions/utils/Owned.sol b/src/extensions/utils/Owned.sol index b4a7ce1e..62462e90 100644 --- a/src/extensions/utils/Owned.sol +++ b/src/extensions/utils/Owned.sol @@ -8,7 +8,7 @@ abstract contract Owned { /// Events /// ----------------------------------------------------------------------- - event OwnershipTransferred(address indexed owner, address indexed newOwner); + event OwnershipTransferred(address indexed owner, address indexed _owner); /// ----------------------------------------------------------------------- /// Custom Errors @@ -43,11 +43,11 @@ abstract contract Owned { /// ----------------------------------------------------------------------- function transferOwnership( - address newOwner + address _owner ) public payable virtual onlyOwner { - owner = newOwner; + owner = _owner; - emit OwnershipTransferred(msg.sender, newOwner); + emit OwnershipTransferred(msg.sender, _owner); } /// ----------------------------------------------------------------------- diff --git a/src/utils/LibClone.sol b/src/utils/LibClone.sol index 78bc9097..b6e466d9 100644 --- a/src/utils/LibClone.sol +++ b/src/utils/LibClone.sol @@ -46,7 +46,7 @@ library LibClone { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")` + // `keccak256("ReceiveETH(uint256)")`. mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff @@ -117,7 +117,7 @@ library LibClone { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")` + // `keccak256("ReceiveETH(uint256)")`. mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff diff --git a/test/Kali.t.sol b/test/Kali.t.sol index 9f4fd106..c387defa 100644 --- a/test/Kali.t.sol +++ b/test/Kali.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.4; /// @dev Keep core. import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; -import {URIFetcher} from "../src/extensions/metadata/URIFetcher.sol"; +import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; /// @dev Kali core. import {KeepTokenManager, Proposal, ProposalType, VoteType, Kali} from "../src/extensions/dao/Kali.sol"; @@ -35,7 +35,7 @@ error Sponsored(); error AlreadyVoted(); -contract KaliTest is Test, Keep(address(0), address(0)) { +contract KaliTest is Test, Keep(Keep(address(0))) { address keepAddr; address kaliAddr; @@ -86,9 +86,9 @@ contract KaliTest is Test, Keep(address(0), address(0)) { mockUnsafeERC1155Receiver = new MockUnsafeERC1155Receiver(); // Create the Keep templates. - keep = address(new Keep(address(0), address(address(0)))); + keep = address(new Keep((Keep(address(0))))); // Create the Keep factory. - keepFactory = new KeepFactory(keep); + keepFactory = new KeepFactory(Keep(keep)); // Create the Signer[] for setup. address[] memory setupSigners = new address[](2); setupSigners[0] = alice > bob ? bob : alice; diff --git a/test/Keep.t.sol b/test/Keep.t.sol index eaf7560f..f859497d 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -6,7 +6,7 @@ import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; /// @dev Extensions. -import {URIFetcher} from "../src/extensions/metadata/URIFetcher.sol"; +import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; /// @dev Mocks. import {MockERC20} from "@solady/test/utils/mocks/MockERC20.sol"; @@ -18,7 +18,7 @@ import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver /// @dev Test framework. import "@std/Test.sol"; -contract KeepTest is Keep(address(0), address(0)), Test { +contract KeepTest is Keep(Keep(address(0))), Test { /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- @@ -33,9 +33,9 @@ contract KeepTest is Keep(address(0), address(0)), Test { KeepFactory internal factory; - URIFetcher internal mockUriFetcher; - URIFetcher internal uriRemote; - URIFetcher internal uriRemoteNew; + address internal mockUriFetcher; + Fetcher internal uriRemote; + Fetcher internal uriRemoteNew; MockERC20 internal mockDai; MockERC721 internal mockNFT; @@ -207,9 +207,9 @@ contract KeepTest is Keep(address(0), address(0)), Test { function setUp() public payable { // Initialize templates. - mockUriFetcher = new URIFetcher(); + mockUriFetcher = address(new Fetcher()); - keep = new Keep(address(0), address(mockUriFetcher)); + keep = new Keep(Keep(mockUriFetcher)); mockDai = new MockERC20("Dai", "DAI", 18); mockNFT = new MockERC721(); @@ -225,7 +225,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { mock1155.mint(address(this), 1, 1, ""); // Create the factory. - factory = new KeepFactory(address(keep)); + factory = new KeepFactory(Keep(keep)); // Create the Signer[] for setup. address[] memory setupSigners = new address[](2); @@ -370,7 +370,7 @@ contract KeepTest is Keep(address(0), address(0)), Test { /// @notice Check setup errors. function testCannotRepeatKeepSetup() public payable { - keepRepeat = new Keep(address(0), address(mockUriFetcher)); + keepRepeat = new Keep(Keep(mockUriFetcher)); keepAddrRepeat = factory.determineKeep(name2); keepRepeat = Keep(keepAddrRepeat); @@ -1976,4 +1976,14 @@ contract KeepTest is Keep(address(0), address(0)), Test { vm.expectRevert(ExpiredSig.selector); keep.delegateBySig(userA, userB, id, deadline, v, r, s); } + + function testValidateSignatures(bytes32 hash) public payable { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicesPk, hash); + + bytes memory sig = abi.encode(v, r, s); + + uint256 validationData = keep.validateSignatures(hash, sig); + + assert(validationData == 0); + } } diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index b05eb459..a5c51e5e 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import {Call, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; -import {URIFetcher} from "../src/extensions/metadata/URIFetcher.sol"; +import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; import "@std/Test.sol"; @@ -11,7 +11,7 @@ contract KeepFactoryTest is Test { address keepAddr; Keep keep; KeepFactory factory; - URIFetcher uriFetcher; + address uriFetcher; address[] signers; @@ -34,10 +34,10 @@ contract KeepFactoryTest is Test { function setUp() public payable { // Create the templates. - uriFetcher = new URIFetcher(); - keep = new Keep(address(0), address(uriFetcher)); + uriFetcher = address(new Fetcher()); + keep = new Keep(Keep(uriFetcher)); // Create the factory. - factory = new KeepFactory(address(keep)); + factory = new KeepFactory(Keep(keep)); // Create the signers. signers.push(alice); signers.push(bob); From 73087591c5cca2ec5113b5939fdc111aa4208bbd Mon Sep 17 00:00:00 2001 From: shiv Date: Fri, 25 Aug 2023 15:37:08 +0000 Subject: [PATCH 27/67] foundry f --- src/KeepFactory.sol | 110 ++- test/Kali.t.sol | 1808 ------------------------------------ test/Keep.t.sol | 1243 ++++++++++++------------- test/Multicallable.t.sol | 156 ++-- test/ReentrancyGuard.t.sol | 62 -- 5 files changed, 795 insertions(+), 2584 deletions(-) delete mode 100644 test/Kali.t.sol delete mode 100644 test/ReentrancyGuard.t.sol diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 997925fc..8bef59f9 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -49,7 +49,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { ) public payable virtual returns (Keep keep) { keep = cloneDeterministic(name); - Keep(keep).initialize{value: msg.value}(calls, signers, threshold); + keep.initialize{value: msg.value}(calls, signers, threshold); emit Deployed(keep, threshold); } @@ -61,20 +61,54 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// @dev Deploys a deterministic clone of `keepTemplate`, /// using immutable argument `name` also as `salt`. function cloneDeterministic(bytes32 salt) internal returns (Keep instance) { - Keep template = keepTemplate; + address implementation = address(keepTemplate); + bytes memory data = abi.encodePacked(salt); + assembly { // Compute the boundaries of the data and cache the memory slots around it. - let data := mload(0x40) - let dataLength := 0x60 - let dataEnd := add(data, dataLength) + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) // Write the bytecode before the data. mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(add(data, 0x0d), template) + mstore(sub(data, 0x0d), implementation) + // Write the rest of the bytecode. + mstore( + sub(data, 0x21), + or( + shl(0x48, extraLength), + 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 + ) + ) + // `keccak256("ReceiveETH(uint256)")`. + mstore( + sub(data, 0x3a), + 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + sub(data, 0x5a), + or( + shl(0x78, add(extraLength, 0x62)), + 0x6100003d81600a3d39f336602c57343d527f + ) + ) + mstore(dataEnd, shl(0xf0, extraLength)) // Create the instance. - instance := create2(0, data, dataLength, salt) + instance := create2( + 0, + sub(data, 0x4c), + add(extraLength, 0x6c), + salt + ) // If `instance` is zero, revert. if iszero(instance) { @@ -83,32 +117,78 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { // Revert with (offset, size). revert(0x1c, 0x04) } + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) } } /// @dev Returns the address of the deterministic clone of - /// `keepTemplate` using immutable arguments encoded in `data`, with `salt`. + /// `keepTemplate` using immutable argument `name` also as `salt`. function predictDeterministicAddress( bytes32 salt ) internal view returns (address predicted) { - Keep template = keepTemplate; + address implementation = address(keepTemplate); + address deployer = address(this); + bytes memory data = abi.encodePacked(salt); + assembly { // Compute the boundaries of the data and cache the memory slots around it. - let data := mload(0x40) - let dataLength := 0x60 - let dataEnd := add(data, dataLength) + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) // Write the bytecode before the data. mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(add(data, 0x0d), template) + mstore(sub(data, 0x0d), implementation) + // Write the rest of the bytecode. + mstore( + sub(data, 0x21), + or( + shl(0x48, extraLength), + 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 + ) + ) + // `keccak256("ReceiveETH(uint256)")`. + mstore( + sub(data, 0x3a), + 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + sub(data, 0x5a), + or( + shl(0x78, add(extraLength, 0x62)), + 0x6100003d81600a3d39f336602c57343d527f + ) + ) + mstore(dataEnd, shl(0xf0, extraLength)) // Compute and store the bytecode hash. - mstore(0x35, keccak256(data, dataLength)) + mstore(0x35, keccak256(sub(data, 0x4c), add(extraLength, 0x6c))) mstore8(0x00, 0xff) // Write the prefix. - mstore(0x01, shl(96, address())) + mstore(0x01, shl(96, deployer)) mstore(0x15, salt) predicted := keccak256(0x00, 0x55) + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x35, 0) + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) } } diff --git a/test/Kali.t.sol b/test/Kali.t.sol deleted file mode 100644 index c387defa..00000000 --- a/test/Kali.t.sol +++ /dev/null @@ -1,1808 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @dev Keep core. -import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; -import {KeepFactory} from "../src/KeepFactory.sol"; -import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; - -/// @dev Kali core. -import {KeepTokenManager, Proposal, ProposalType, VoteType, Kali} from "../src/extensions/dao/Kali.sol"; -import {KaliFactory} from "../src/extensions/dao/KaliFactory.sol"; - -/// @dev Mocks. -import {MockERC20} from "@solady/test/utils/mocks/MockERC20.sol"; -import {MockERC721} from "@solady/test/utils/mocks/MockERC721.sol"; -import {MockERC1155} from "@solady/test/utils/mocks/MockERC1155.sol"; -import {MockERC1271Wallet} from "@solady/test/utils/mocks/MockERC1271Wallet.sol"; -import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver.sol"; - -import "@std/Test.sol"; - -error Initialized(); - -error PeriodBounds(); - -error QuorumMax(); - -error SupermajorityBounds(); - -error TypeBounds(); - -error InvalidProposal(); - -error Sponsored(); - -error AlreadyVoted(); - -contract KaliTest is Test, Keep(Keep(address(0))) { - address keepAddr; - address kaliAddr; - - address keep; - KeepFactory keepFactory; - - address kali; - KaliFactory kaliFactory; - - MockERC20 internal mockDai; - MockERC721 internal mockNFT; - MockERC1155 internal mock1155; - MockERC1271Wallet internal mockERC1271Wallet; - MockUnsafeERC1155Receiver internal mockUnsafeERC1155Receiver; - - address[] signers; - - /// @dev Users. - - address public immutable alice = makeAddr("alice"); - address public immutable bob = makeAddr("bob"); - address public immutable charlie = makeAddr("charlie"); - address public immutable dave = makeAddr("dave"); - - /// @dev Helpers. - - Call[] calls; - - string internal constant description = "TEST"; - - bytes32 internal constant name1 = - 0x5445535400000000000000000000000000000000000000000000000000000000; - - bytes32 internal constant name2 = - 0x5445535432000000000000000000000000000000000000000000000000000000; - - /// ----------------------------------------------------------------------- - /// Kali Setup Tests - /// ----------------------------------------------------------------------- - - /// @notice Set up the testing suite. - - function setUp() public payable { - mockDai = new MockERC20("Dai", "DAI", 18); - mockNFT = new MockERC721(); - mock1155 = new MockERC1155(); - mockERC1271Wallet = new MockERC1271Wallet(alice); - mockUnsafeERC1155Receiver = new MockUnsafeERC1155Receiver(); - - // Create the Keep templates. - keep = address(new Keep((Keep(address(0))))); - // Create the Keep factory. - keepFactory = new KeepFactory(Keep(keep)); - // Create the Signer[] for setup. - address[] memory setupSigners = new address[](2); - setupSigners[0] = alice > bob ? bob : alice; - setupSigners[1] = alice > bob ? alice : bob; - // Store the Keep signers. - signers.push(alice); - signers.push(bob); - // Initialize Keep from factory. - keepAddr = keepFactory.determineKeep(name1); - keep = keepAddr; - keepFactory.deployKeep(name1, calls, setupSigners, 2); - - // Mint alice, bob and charlie Kali Keep DAO ID key (0). - vm.prank(keep); - Keep(keep).mint(alice, 0, 1, ""); - vm.prank(keep); - Keep(keep).mint(bob, 0, 1, ""); - vm.prank(keep); - Keep(keep).mint(charlie, 0, 1, ""); - - // Check balances. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - assertEq(Keep(keep).balanceOf(bob, 0), 1); - assertEq(Keep(keep).balanceOf(charlie, 0), 1); - - // Create the Kali template. - kali = address(new Kali()); - // Create the Kali factory. - kaliFactory = new KaliFactory(kali); - - // Prime dummy inputs; - Call[] memory dummyCall = new Call[](1); - - uint120[4] memory govSettings; - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 20; - govSettings[3] = 52; - - kali = kaliFactory.determineKali(KeepTokenManager(keep), 0, name1); - - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name1, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Mint Core Key ID uber permission to DAO. - vm.prank(keep); - Keep(keep).mint(kali, CORE_KEY, 1, ""); - - assertEq(Keep(keep).balanceOf(kali, CORE_KEY), 1); - - bool sent; - - // Deposit 1 ETH to Keep. - (sent, ) = keep.call{value: 1 ether}(""); - assert(sent); - - // Deposit 10 ETH to Kali. - (sent, ) = kali.call{value: 10 ether}(""); - assert(sent); - - // Bump time. - vm.warp(block.timestamp + 1000); - - // Mint mock ERC20. - mockDai.mint(address(this), 1_000_000_000 ether); - mockDai.mint(kali, 1_000_000_000 ether); - // Mint mock 721. - mockNFT.mint(address(this), 1); - // Mint mock 1155. - mock1155.mint(address(this), 1, 1, ""); - } - - /// @notice Check deployment. - - function testDetermination() public payable { - // Check CREATE2 clones match expected outputs. - address computedAddr = kaliFactory.determineKali( - KeepTokenManager(keep), - 0, - name1 - ); - - assertEq(kali, computedAddr); - } - - function testDeploy() public payable { - // Prime dummy inputs; - Call[] memory dummyCall = new Call[](1); - - uint120[4] memory govSettings; - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 20; - govSettings[3] = 52; - - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - } - - function testFailDeploy() public payable { - // Create the Signer[]. - address[] memory setupSigners = new address[](2); - setupSigners[0] = alice > bob ? bob : alice; - setupSigners[1] = alice > bob ? alice : bob; - - // Prime dummy inputs; - Call[] memory dummyCall = new Call[](1); - - // Check against zero voting period. - uint120[4] memory govSettings; - govSettings[0] = 0; - govSettings[1] = 0; - govSettings[2] = 20; - govSettings[3] = 52; - - vm.expectRevert(PeriodBounds.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against excessive voting period. - govSettings[0] = 366 days; - - vm.expectRevert(PeriodBounds.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against excessive grace period. - govSettings[0] = 1 days; - govSettings[1] = 366 days; - - vm.expectRevert(PeriodBounds.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against excessive quorum. - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 101; - - vm.expectRevert(QuorumMax.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against zero supermajority. - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 0; - govSettings[3] = 0; - - vm.expectRevert(SupermajorityBounds.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against excessive supermajority. - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 0; - govSettings[3] = 101; - - vm.expectRevert(SupermajorityBounds.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - // Check against repeat initialization. - govSettings[0] = 1 days; - govSettings[1] = 0; - govSettings[2] = 20; - govSettings[3] = 52; - - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - - vm.expectRevert(Initialized.selector); - kaliFactory.deployKali( - KeepTokenManager(keep), - 0, - name2, // create2 salt. - dummyCall, - "DAO", - govSettings - ); - } - - /// ----------------------------------------------------------------------- - /// Kali State Tests - /// ----------------------------------------------------------------------- - - function testName() public payable { - assertEq(Kali(kali).name(), string(abi.encodePacked(name1))); - } - - function testToken() public payable { - assert(Kali(kali).token() == KeepTokenManager(keep)); - } - - function testTokenId() public payable { - assertEq(Kali(kali).tokenId(), 0); - } - - function testDaoURI() public payable { - assertEq(Kali(kali).daoURI(), "DAO"); - } - - function testVotingPeriod() public payable { - assertEq(Kali(kali).votingPeriod(), 1 days); - } - - function testGracePeriod() public payable { - assertEq(Kali(kali).gracePeriod(), 0); - } - - function testQuorum() public payable { - assertEq(Kali(kali).quorum(), 20); - } - - function testSupermajority() public payable { - assertEq(Kali(kali).supermajority(), 52); - } - - function testSupportsInterface() public payable { - assert(Kali(kali).supportsInterface(0x01ffc9a7)); - assert(Kali(kali).supportsInterface(0x150b7a02)); - assert(Kali(kali).supportsInterface(0x4e2312e0)); - } - - /// ----------------------------------------------------------------------- - /// Kali Receiver Tests - /// ----------------------------------------------------------------------- - - function testERC20Receiver() public payable { - mockDai.transfer(kali, 1 ether); - } - - function testERC721Receiver() public payable { - mockNFT.transferFrom(address(this), kali, 1); - } - - function testERC1155Receiver() public payable { - mock1155.safeTransferFrom(address(this), kali, 1, 1, ""); - } - - function testERC1155BatchReceiver() public payable { - uint256[] memory ids = new uint256[](2); - ids[0] = 1; - ids[1] = 1; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1; - amounts[1] = 0; - - mock1155.safeBatchTransferFrom(address(this), kali, ids, amounts, ""); - } - - /// ----------------------------------------------------------------------- - /// Kali Proposal Tests - /// ----------------------------------------------------------------------- - - function testProposalCreation() public payable { - vm.warp(block.timestamp + 1 days); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Check proposal creation. - (, , , uint40 creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - assert(block.timestamp != 0); - - // Check proposal hash. - (, bytes32 digest, , , , ) = Kali(kali).proposals(proposalId); - bytes32 proposalHash = keccak256( - abi.encode(call, ProposalType.CALL, "test") - ); - assertEq(digest, proposalHash); - } - - function testFailProposalCreation() public payable { - vm.warp(block.timestamp + 1 days); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 0; - call[0].data = ""; - - vm.prank(alice); - vm.expectRevert(PeriodBounds.selector); - Kali(kali).propose(call, ProposalType.VPERIOD, "test"); - - call[0].value = 366; - - vm.prank(alice); - vm.expectRevert(PeriodBounds.selector); - Kali(kali).propose(call, ProposalType.VPERIOD, "test"); - - vm.prank(alice); - vm.expectRevert(PeriodBounds.selector); - Kali(kali).propose(call, ProposalType.GPERIOD, "test"); - - vm.prank(alice); - vm.expectRevert(QuorumMax.selector); - Kali(kali).propose(call, ProposalType.QUORUM, "test"); - - vm.prank(alice); - vm.expectRevert(SupermajorityBounds.selector); - Kali(kali).propose(call, ProposalType.SUPERMAJORITY, "test"); - - call[0].value = 50; - - vm.prank(alice); - vm.expectRevert(SupermajorityBounds.selector); - Kali(kali).propose(call, ProposalType.SUPERMAJORITY, "test"); - - vm.prank(alice); - vm.expectRevert(TypeBounds.selector); - Kali(kali).propose(call, ProposalType.TYPE, "test"); - - call[0].value = 1; - call[0].value = 50; - - vm.prank(alice); - vm.expectRevert(TypeBounds.selector); - Kali(kali).propose(call, ProposalType.TYPE, "test"); - } - - function testProposal() public payable { - // Check initial ETH. - assertEq(kali.balance, 10 ether); - assertEq(alice.balance, 0 ether); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, uint40 creationTime) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - assertEq(creationTime, block.timestamp); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 2); - assertEq(noVotes, 0); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.CALL, - "test" - ); - assert(passed); - - // Check proposal state. - (bool didPass, bool processed) = Kali(kali).proposalStates(proposalId); - assert(didPass); - assert(processed); - - // Check ETH was sent. - assertEq(kali.balance, 9 ether); - assertEq(alice.balance, 1 ether); - } - - function testProposalRepeatProcessingFail() public payable { - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 2); - assertEq(noVotes, 0); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.CALL, - "test" - ); - assert(passed); - - // Process proposal. - vm.expectRevert(InvalidProposal.selector); - Kali(kali).processProposal(proposalId, call, ProposalType.CALL, "test"); - } - - function testFailProposalRepeatVoting() public payable { - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, false, ""); - vm.expectRevert(AlreadyVoted.selector); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 1); - assertEq(noVotes, 1); - } - - function testProposalSponsorship() public payable { - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as dave. - vm.prank(dave); - (uint256 proposalId, uint40 creationTime) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Check proposal creation time is zero. - assertEq(creationTime, 0); - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, 0); - - // Sponsor as alice. - vm.prank(alice); - Kali(kali).sponsorProposal(proposalId); - - // Check proposal creation time is current. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - - // Check can't sponsor after. - vm.prank(alice); - vm.expectRevert(Sponsored.selector); - Kali(kali).sponsorProposal(proposalId); - - // Propose as dave. - vm.prank(dave); - (proposalId, creationTime) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Check proposal creation time is zero. - assertEq(creationTime, 0); - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, 0); - - // Sponsor as alice. - vm.prank(alice); - Kali(kali).sponsorProposal(proposalId); - - // Check prev prop storage. - (uint256 prevProposal, , , , , ) = Kali(kali).proposals(proposalId); - assertEq(prevProposal, proposalId - 1); - - // Propose as alice. - vm.prank(alice); - (proposalId, creationTime) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Check proposal creation time. - assertEq(creationTime, block.timestamp); - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - - // Propose as alice. - vm.prank(alice); - (proposalId, ) = Kali(kali).propose(call, ProposalType.CALL, "test"); - - // Check can't sponsor after. - vm.prank(alice); - vm.expectRevert(Sponsored.selector); - Kali(kali).sponsorProposal(proposalId); - } - - function testProposalCancellation() public payable { - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as dave and cancel. - vm.prank(dave); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - vm.prank(dave); - Kali(kali).cancelProposal(proposalId); - vm.expectRevert(Unauthorized.selector); - Kali(kali).cancelProposal(proposalId); - - // Propose as dave and fail after sponsored. - vm.prank(dave); - (proposalId, ) = Kali(kali).propose(call, ProposalType.CALL, "test"); - - vm.prank(alice); - Kali(kali).sponsorProposal(proposalId); - - vm.prank(dave); - vm.expectRevert(Sponsored.selector); - Kali(kali).cancelProposal(proposalId); - } - - function testProposalVoteStored() public payable { - // Check initial alice DAO (0) balance. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.BURN, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 2); - assertEq(noVotes, 0); - - assert(Kali(kali).voted(1, alice)); - assert(Kali(kali).voted(1, bob)); - } - - function testMintProposal() public payable { - // Check initial alice DAO (0) balance. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.MINT, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 2); - assertEq(noVotes, 0); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.MINT, - "test" - ); - assert(passed); - - // Check proposal state. - (bool didPass, bool processed) = Kali(kali).proposalStates(proposalId); - assert(didPass); - assert(processed); - - // Check processed alice DAO (0) balance. - assertEq(Keep(keep).balanceOf(alice, 0), 2); - } - - function testMultiMintProposal() public payable { - // Check initial DAO (0) balances. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - assertEq(Keep(keep).balanceOf(bob, 0), 1); - assertEq(Keep(keep).balanceOf(charlie, 0), 1); - assertEq(Keep(keep).balanceOf(dave, 0), 0); - - // Setup proposal. - Call[] memory call = new Call[](4); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1; - call[0].data = ""; - - call[1].op = Operation.call; - call[1].to = bob; - call[1].value = 1; - call[1].data = ""; - - call[2].op = Operation.call; - call[2].to = charlie; - call[2].value = 1; - call[2].data = ""; - - call[3].op = Operation.call; - call[3].to = dave; - call[3].value = 1; - call[3].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.MINT, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal votes. - (, , , , uint216 yesVotes, uint216 noVotes) = Kali(kali).proposals( - proposalId - ); - assertEq(yesVotes, 2); - assertEq(noVotes, 0); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.MINT, - "test" - ); - assert(passed); - - // Check proposal state. - (bool didPass, bool processed) = Kali(kali).proposalStates(proposalId); - assert(didPass); - assert(processed); - - // Check processed DAO (0) balances. - assertEq(Keep(keep).balanceOf(alice, 0), 2); - assertEq(Keep(keep).balanceOf(bob, 0), 2); - assertEq(Keep(keep).balanceOf(charlie, 0), 2); - assertEq(Keep(keep).balanceOf(dave, 0), 1); - } - - function testBurnProposal() public payable { - // Check initial alice DAO (0) balance. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.BURN, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.BURN, - "test" - ); - assert(passed); - - // Check processed alice DAO (0) balance. - assertEq(Keep(keep).balanceOf(alice, 0), 0); - } - - function testMultiBurnProposal() public payable { - // Check initial DAO (0) balances. - assertEq(Keep(keep).balanceOf(alice, 0), 1); - assertEq(Keep(keep).balanceOf(bob, 0), 1); - - // Setup proposal. - Call[] memory call = new Call[](2); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1; - call[0].data = ""; - - call[1].op = Operation.call; - call[1].to = bob; - call[1].value = 1; - call[1].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.BURN, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.BURN, - "test" - ); - assert(passed); - - // Check processed DAO (0) balances. - assertEq(Keep(keep).balanceOf(alice, 0), 0); - assertEq(Keep(keep).balanceOf(bob, 0), 0); - } - - function testCallProposal() public payable { - // Check initial alice Dai balance. - assertEq(mockDai.balanceOf(alice), 0); - assertEq(mockDai.balanceOf(kali), 1_000_000_000 ether); - - // Setup proposal. - Call[] memory call = new Call[](1); - - bytes memory data = abi.encodeCall( - mockDai.transfer, - (alice, 1_000_000_000 ether) - ); - - call[0].op = Operation.call; - call[0].to = address(mockDai); - call[0].value = 0; - call[0].data = data; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.CALL, - "test" - ); - assert(passed); - - // Check processed alice Dai balance. - assertEq(mockDai.balanceOf(alice), 1_000_000_000 ether); - assertEq(mockDai.balanceOf(kali), 0); - } - - function testMultiCallProposal() public payable { - // Check initial Dai balances. - assertEq(mockDai.balanceOf(alice), 0); - assertEq(mockDai.balanceOf(bob), 0); - assertEq(mockDai.balanceOf(kali), 1_000_000_000 ether); - - // Setup proposal. - Call[] memory call = new Call[](2); - - bytes memory data = abi.encodeCall( - mockDai.transfer, - (alice, 500_000_000 ether) - ); - bytes memory data1 = abi.encodeCall( - mockDai.transfer, - (bob, 500_000_000 ether) - ); - - call[0].op = Operation.call; - call[0].to = address(mockDai); - call[0].value = 0; - call[0].data = data; - - call[1].op = Operation.call; - call[1].to = address(mockDai); - call[1].value = 0; - call[1].data = data1; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.CALL, - "test" - ); - assert(passed); - - // Check processed Dai balances. - assertEq(mockDai.balanceOf(alice), 500_000_000 ether); - assertEq(mockDai.balanceOf(bob), 500_000_000 ether); - assertEq(mockDai.balanceOf(kali), 0); - } - - function testVotingPeriodProposal() public payable { - // Check initial voting period. - assertEq(Kali(kali).votingPeriod(), 1 days); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 2 days; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.VPERIOD, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.VPERIOD, - "test" - ); - assert(passed); - - // Check processed voting period. - assertEq(Kali(kali).votingPeriod(), 2 days); - } - - function testGracePeriodProposal() public payable { - // Check initial grace period. - assertEq(Kali(kali).gracePeriod(), 0); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 1 days; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.GPERIOD, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.GPERIOD, - "test" - ); - assert(passed); - - // Check processed grace period. - assertEq(Kali(kali).gracePeriod(), 1 days); - } - - function testQuorumProposal() public payable { - // Check initial quorum. - assertEq(Kali(kali).quorum(), 20); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 69; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.QUORUM, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.QUORUM, - "test" - ); - assert(passed); - - // Check processed quorum. - assertEq(Kali(kali).quorum(), 69); - } - - function testSupermajorityProposal() public payable { - // Check initial supermajority. - assertEq(Kali(kali).supermajority(), 52); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 88; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.SUPERMAJORITY, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.SUPERMAJORITY, - "test" - ); - assert(passed); - - // Check processed supermajority. - assertEq(Kali(kali).supermajority(), 88); - } - - function testTypeProposal() public payable { - // Check initial type settings. - assert( - Kali(kali).proposalVoteTypes(ProposalType.CALL) == - VoteType.SIMPLE_MAJORITY_QUORUM_REQUIRED - ); - - // Setup proposal. - Call[] memory call = new Call[](2); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 2; - call[0].data = ""; - - call[1].op = Operation.call; - call[1].to = address(0); - call[1].value = 2; - call[1].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.TYPE, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.TYPE, - "test" - ); - assert(passed); - - // Check processed type settings. - assert( - Kali(kali).proposalVoteTypes(ProposalType.CALL) == - VoteType.SUPERMAJORITY_QUORUM_REQUIRED - ); - } - - function testPauseProposal() public payable { - // Check initial pause settings. - assert(!Keep(keep).transferable(0)); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 2; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.PAUSE, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.PAUSE, - "test" - ); - assert(passed); - - // Check initial pause settings. - assert(Keep(keep).transferable(0)); - } - - function testDeleteProposal() public payable { - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 0; - call[0].data = ""; - - // -- 1 -- // - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.URI, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // -- 2 -- // - - call[0].value = proposalId; - - // Propose as alice. - vm.prank(alice); - (proposalId, ) = Kali(kali).propose(call, ProposalType.ESCAPE, "test"); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Check proposal status before deletion. - (, , , uint40 creationTime, , ) = Kali(kali).proposals(proposalId - 1); - assertEq(creationTime, block.timestamp - 1 days); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.ESCAPE, - "test" - ); - assert(passed); - - // Check proposal deletion. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId - 1); - assertEq(creationTime, 0); - } - - function testURIProposal() public payable { - // Check initial uri settings. - assertEq(Kali(kali).daoURI(), "DAO"); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = address(0); - call[0].value = 0; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.URI, - "test" - ); - - // Skip ahead in voting period. - vm.warp(block.timestamp + 12 hours); - - // Vote as alice. - vm.prank(alice); - Kali(kali).vote(proposalId, true, ""); - - // Vote as bob. - vm.prank(bob); - Kali(kali).vote(proposalId, true, ""); - - // Process proposal. - bool passed = Kali(kali).processProposal( - proposalId, - call, - ProposalType.URI, - "test" - ); - assert(passed); - - // Check processed uri settings. - assertEq(Kali(kali).daoURI(), "test"); - } - - /// ----------------------------------------------------------------------- - /// Kali Extension Tests - /// ----------------------------------------------------------------------- - - function testFailNotAuthorizedExtension() public payable { - assert(!Kali(kali).extensions(alice)); - assertEq(alice.balance, 0); - - Call memory call; - - call.op = Operation.call; - call.to = alice; - call.value = 1 ether; - call.data = ""; - - vm.prank(alice); - Kali(kali).relay(call); - - assertEq(alice.balance, 0); - } - - function testExtensionRelay() public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - assertEq(alice.balance, 0); - - Call memory call; - - call.op = Operation.call; - call.to = alice; - call.value = 1 ether; - call.data = ""; - - vm.prank(alice); - Kali(kali).relay(call); - - assertEq(alice.balance, 1 ether); - } - - function testExtensionMint() public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - assertEq(Keep(keep).balanceOf(alice, 0), 1); - - vm.prank(alice); - Kali(kali).mint(KeepTokenManager(keep), alice, 0, 1, ""); - - assertEq(Keep(keep).balanceOf(alice, 0), 2); - } - - function testExtensionBurn() public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - assertEq(Keep(keep).balanceOf(alice, 0), 1); - - vm.prank(alice); - Kali(kali).burn(KeepTokenManager(keep), alice, 0, 1); - - assertEq(Keep(keep).balanceOf(alice, 0), 0); - } - - function testExtensionSetTransferability(bool on) public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - assert(!Keep(keep).transferable(0)); - - vm.prank(alice); - Kali(kali).setTransferability(KeepTokenManager(keep), 0, on); - - assertEq(Keep(keep).transferable(0), on); - } - - function testExtensionSetExtension( - address extension, - bool on - ) public payable { - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - vm.prank(alice); - Kali(kali).setExtension(extension, on); - - assertEq(Kali(kali).extensions(extension), on); - } - - function testExtensionSetURI(string calldata uri) public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - vm.prank(alice); - Kali(kali).setURI(uri); - - assertEq(Kali(kali).daoURI(), uri); - } - - function testExtensionDeleteProposal() public payable { - vm.warp(block.timestamp + 1 days); - - // Setup proposal. - Call[] memory call = new Call[](1); - - call[0].op = Operation.call; - call[0].to = alice; - call[0].value = 1 ether; - call[0].data = ""; - - // Propose as alice. - vm.prank(alice); - (uint256 proposalId, ) = Kali(kali).propose( - call, - ProposalType.CALL, - "test" - ); - - // Check proposal creation. - (, , , uint40 creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - // Delete. - vm.prank(alice); - Kali(kali).deleteProposal(1); - - // Check proposal deletion. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, 0); - - // Check proposal processed state. - (bool didPass, bool processed) = Kali(kali).proposalStates(proposalId); - assert(!didPass); - assert(processed); - - // Propose as alice. - vm.prank(alice); - (proposalId, ) = Kali(kali).propose(call, ProposalType.CALL, "test"); - - // Check proposal creation. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - - // Delete. - vm.prank(kali); - Kali(kali).deleteProposal(2); - - // Check proposal deletion. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, 0); - - // Attempt repeat delete. - vm.prank(kali); - vm.expectRevert(InvalidProposal.selector); - Kali(kali).deleteProposal(2); - - // Propose as alice. - vm.prank(alice); - (proposalId, ) = Kali(kali).propose(call, ProposalType.CALL, "test"); - - // Check proposal creation. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - - // Delete. - vm.prank(bob); - vm.expectRevert(Unauthorized.selector); - Kali(kali).deleteProposal(3); - - // Check proposal maintenance. - (, , , creationTime, , ) = Kali(kali).proposals(proposalId); - assertEq(creationTime, block.timestamp); - } - - function testExtensionUpdateGovSettings() public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - uint256[2] memory setting; - setting[0] = 1; - setting[1] = 1; - - vm.prank(alice); - Kali(kali).updateGovSettings(2 days, 1 days, 42, 69, setting); - - assertEq(Kali(kali).votingPeriod(), 2 days); - assertEq(Kali(kali).gracePeriod(), 1 days); - assertEq(Kali(kali).quorum(), 42); - assertEq(Kali(kali).supermajority(), 69); - assert( - Kali(kali).proposalVoteTypes(ProposalType.BURN) == - VoteType.SIMPLE_MAJORITY - ); - } - - function testExtensionUpdateGovSettingsInvalid() public payable { - assert(!Kali(kali).extensions(alice)); - vm.prank(kali); - Kali(kali).setExtension(alice, true); - - assert(Kali(kali).extensions(alice)); - - uint256[2] memory setting; - setting[0] = 1; - setting[1] = 1; - - vm.prank(alice); - Kali(kali).updateGovSettings(0, 1 days, 42, 69, setting); - - assertEq(Kali(kali).votingPeriod(), 1 days); - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 1 days, 42, 69, setting); - - assertEq(Kali(kali).votingPeriod(), 1 days); - - vm.prank(alice); - Kali(kali).updateGovSettings(1 days, 366 days, 42, 69, setting); - - assertEq(Kali(kali).gracePeriod(), 1 days); - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 69, setting); - - assertEq(Kali(kali).quorum(), 42); - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 50, setting); - - assertEq(Kali(kali).supermajority(), 69); - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 101, setting); - - assertEq(Kali(kali).supermajority(), 69); - - setting[1] = 3; - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 101, setting); - - assert( - Kali(kali).proposalVoteTypes(ProposalType.BURN) == - VoteType.SUPERMAJORITY - ); - - setting[0] = 100; - setting[1] = 1; - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 101, setting); - - assert( - Kali(kali).proposalVoteTypes(ProposalType.BURN) == - VoteType.SUPERMAJORITY - ); - - setting[0] = 1; - setting[1] = 100; - - vm.prank(alice); - Kali(kali).updateGovSettings(366 days, 366 days, 101, 101, setting); - - assert( - Kali(kali).proposalVoteTypes(ProposalType.BURN) == - VoteType.SUPERMAJORITY - ); - } -} diff --git a/test/Keep.t.sol b/test/Keep.t.sol index f859497d..d1df346a 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -289,6 +289,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); } + /* /// @dev Check setup conditions. /*function testURISetup() public payable { @@ -353,19 +354,19 @@ contract KeepTest is Keep(Keep(address(0))), Test { assertEq(keep.uri(1), ""); }*/ - function testSignerSetup() public payable { - // Check users. - assert(keep.balanceOf(alice, SIGNER_KEY) == 1); - assert(keep.balanceOf(bob, SIGNER_KEY) == 1); - assert(keep.balanceOf(charlie, SIGNER_KEY) == 0); + // function testSignerSetup() public payable { + // // Check users. + // assert(keep.balanceOf(alice, SIGNER_KEY) == 1); + // assert(keep.balanceOf(bob, SIGNER_KEY) == 1); + // assert(keep.balanceOf(charlie, SIGNER_KEY) == 0); - // Also check smart wallet. - assert(keep.balanceOf(address(mockERC1271Wallet), SIGNER_KEY) == 1); + // // Also check smart wallet. + // assert(keep.balanceOf(address(mockERC1271Wallet), SIGNER_KEY) == 1); - // Check supply. - assert(keep.totalSupply(SIGNER_KEY) == 3); - assert(keep.totalSupply(42069) == 0); - } + // // Check supply. + // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(42069) == 0); + // } /// @notice Check setup errors. @@ -407,13 +408,13 @@ contract KeepTest is Keep(Keep(address(0))), Test { assertEq(keep.name(), string(abi.encodePacked(name1))); } - function testKeepNonce() public view { - assert(keep.nonce() == 1); - } + // function testKeepNonce() public view { + // assert(keep.nonce() == 1); + // } - function testUserNonce() public view { - assert(keep.nonces(alice) == 0); - } + // function testUserNonce() public view { + // assert(keep.nonces(alice) == 0); + // } function testQuorum() public payable { assert(keep.quorum() == 2); @@ -479,31 +480,31 @@ contract KeepTest is Keep(Keep(address(0))), Test { balances = keep.balanceOfBatch(owners, ids); } - function testTotalSupply() public { - assert(keep.totalSupply(0) == 0); + // function testTotalSupply() public { + // assert(keep.totalSupply(0) == 0); - vm.prank(address(keep)); - keep.mint(alice, 0, 1, ""); + // vm.prank(address(keep)); + // keep.mint(alice, 0, 1, ""); - assert(keep.totalSupply(0) == 1); - } + // assert(keep.totalSupply(0) == 1); + // } - function testTotalSignerSupply() public { - assert(keep.totalSupply(SIGNER_KEY) == 3); + // function testTotalSignerSupply() public { + // assert(keep.totalSupply(SIGNER_KEY) == 3); - vm.prank(address(keep)); - keep.mint(charlie, SIGNER_KEY, 1, ""); + // vm.prank(address(keep)); + // keep.mint(charlie, SIGNER_KEY, 1, ""); - assert(keep.totalSupply(SIGNER_KEY) == 4); - } + // assert(keep.totalSupply(SIGNER_KEY) == 4); + // } - function testSupportsInterface() public { - assertTrue(keep.supportsInterface(0x150b7a02)); - assertTrue(keep.supportsInterface(0x4e2312e0)); - assertTrue(keep.supportsInterface(0x01ffc9a7)); - assertTrue(keep.supportsInterface(0xd9b67a26)); - assertTrue(keep.supportsInterface(0x0e89341c)); - } + // function testSupportsInterface() public { + // assertTrue(keep.supportsInterface(0x150b7a02)); + // assertTrue(keep.supportsInterface(0x4e2312e0)); + // assertTrue(keep.supportsInterface(0x01ffc9a7)); + // assertTrue(keep.supportsInterface(0xd9b67a26)); + // assertTrue(keep.supportsInterface(0x0e89341c)); + // } function testNoKeepKeyCollision() public view { assert( @@ -525,22 +526,22 @@ contract KeepTest is Keep(Keep(address(0))), Test { /// @dev Check receivers. - function testReceiveETH() public payable { - (bool sent, ) = address(keep).call{value: 5 ether}(""); - assert(sent); - // We check addition to setup balance. - assert(address(keep).balance == 10 ether); - } + // function testReceiveETH() public payable { + // (bool sent, ) = address(keep).call{value: 5 ether}(""); + // assert(sent); + // // We check addition to setup balance. + // assert(address(keep).balance == 10 ether); + // } - function testReceiveERC721() public payable { - mockNFT.safeTransferFrom(address(this), address(keep), 1); - assert(mockNFT.ownerOf(1) == address(keep)); - } + // function testReceiveERC721() public payable { + // mockNFT.safeTransferFrom(address(this), address(keep), 1); + // assert(mockNFT.ownerOf(1) == address(keep)); + // } - function testReceiveERC1155() public payable { - mock1155.safeTransferFrom(address(this), address(keep), 1, 1, ""); - assert(mock1155.balanceOf(address(keep), 1) == 1); - } + // function testReceiveERC1155() public payable { + // mock1155.safeTransferFrom(address(this), address(keep), 1, 1, ""); + // assert(mock1155.balanceOf(address(keep), 1) == 1); + // } function testReceiveBatchERC1155() public payable { uint256[] memory ids = new uint256[](1); @@ -593,58 +594,58 @@ contract KeepTest is Keep(Keep(address(0))), Test { /// @dev Check call execution. - function testExecuteTokenCallWithRole() public payable { - // Mint executor role. - vm.prank(address(keep)); - keep.mint(alice, uint32(keep.multirelay.selector), 1, ""); + // function testExecuteTokenCallWithRole() public payable { + // // Mint executor role. + // vm.prank(address(keep)); + // keep.mint(alice, uint32(keep.multirelay.selector), 1, ""); - // Mock execution. - bytes memory data = abi.encodeCall(mockDai.transfer, (alice, 100)); + // // Mock execution. + // bytes memory data = abi.encodeCall(mockDai.transfer, (alice, 100)); - Call[] memory call = new Call[](1); + // Call[] memory call = new Call[](1); - call[0].op = Operation.call; - call[0].to = address(mockDai); - call[0].value = 0; - call[0].data = data; + // call[0].op = Operation.call; + // call[0].to = address(mockDai); + // call[0].value = 0; + // call[0].data = data; - uint256 balanceBefore = mockDai.balanceOf(alice); + // uint256 balanceBefore = mockDai.balanceOf(alice); - vm.prank(alice); - keep.multirelay(call); + // vm.prank(alice); + // keep.multirelay(call); - assert(mockDai.balanceOf(alice) == balanceBefore + 100); - } + // assert(mockDai.balanceOf(alice) == balanceBefore + 100); + // } - function testExecuteTokenCallWithSignatures() public payable { - bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); + // function testExecuteTokenCallWithSignatures() public payable { + // bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); - Signature[] memory sigs = new Signature[](2); + // Signature[] memory sigs = new Signature[](2); - Signature memory aliceSig = signExecution( - alice, - alicesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); + // Signature memory aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); - Signature memory bobSig = signExecution( - bob, - bobsPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); + // Signature memory bobSig = signExecution( + // bob, + // bobsPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); - sigs[0] = bobSig; - sigs[1] = aliceSig; + // sigs[0] = bobSig; + // sigs[1] = aliceSig; - // Execute tx. - keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); - } + // // Execute tx. + // keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); + // } function testExecuteTokenCallWithContractSignatures() public payable { bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); @@ -790,217 +791,217 @@ contract KeepTest is Keep(Keep(address(0))), Test { ); } - function testExecuteCreateCall() public payable { - bytes memory tx_data = type(MockERC1155).creationCode; + // function testExecuteCreateCall() public payable { + // bytes memory tx_data = type(MockERC1155).creationCode; - Signature[] memory sigs = new Signature[](2); + // Signature[] memory sigs = new Signature[](2); - Signature memory aliceSig = signExecution( - alice, - alicesPk, - Operation.create, - address(0), - 0, - tx_data - ); + // Signature memory aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.create, + // address(0), + // 0, + // tx_data + // ); - Signature memory bobSig = signExecution( - bob, - bobsPk, - Operation.create, - address(0), - 0, - tx_data - ); + // Signature memory bobSig = signExecution( + // bob, + // bobsPk, + // Operation.create, + // address(0), + // 0, + // tx_data + // ); - sigs[0] = bobSig; - sigs[1] = aliceSig; + // sigs[0] = bobSig; + // sigs[1] = aliceSig; - // Execute tx. - keep.execute(Operation.create, address(0), 0, tx_data, sigs); - } + // // Execute tx. + // keep.execute(Operation.create, address(0), 0, tx_data, sigs); + // } /// @dev Check execution errors. - function testCannotExecuteWithImproperSignatures() public payable { - bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); - - Signature[] memory sigs = new Signature[](2); - - Signature memory aliceSig; - Signature memory charlieSig; - - aliceSig = signExecution( - alice, - alicesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - charlieSig = signExecution( - bob, - charliesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - sigs[0] = alice > charlie ? charlieSig : aliceSig; - sigs[1] = alice > charlie ? aliceSig : charlieSig; - - // Execute tx. - vm.expectRevert(InvalidSignature.selector); - keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); - } - - function testCannotExecuteWithSignaturesOutOfOrder() public payable { - bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); - - Signature[] memory sigs = new Signature[](2); - - Signature memory aliceSig; - Signature memory bobSig; - - aliceSig = signExecution( - alice, - alicesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - bobSig = signExecution( - bob, - bobsPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - sigs[0] = alice > bob ? aliceSig : bobSig; - sigs[1] = alice > bob ? bobSig : aliceSig; - - // Execute tx. - vm.expectRevert(InvalidSignature.selector); - keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); - } - - function testCannotExecuteWithSignaturesRepeated() public payable { - bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); - - Signature[] memory sigs = new Signature[](2); - - Signature memory aliceSig; - - aliceSig = signExecution( - alice, - alicesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - sigs[0] = aliceSig; - sigs[1] = aliceSig; - - // Execute tx. - vm.expectRevert(InvalidSignature.selector); - keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); - } - - function testCannotExecuteWithNullSignatures() public payable { - bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); - - Signature[] memory sigs = new Signature[](2); - - Signature memory aliceSig; - Signature memory nullSig; - - aliceSig = signExecution( - alice, - alicesPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - nullSig = signExecution( - nully, - nullPk, - Operation.call, - address(mockDai), - 0, - tx_data - ); - - sigs[0] = alice > nully ? nullSig : aliceSig; - sigs[1] = alice > nully ? aliceSig : nullSig; - - // Execute tx. - vm.expectRevert(InvalidSignature.selector); - keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); - } + // function testCannotExecuteWithImproperSignatures() public payable { + // bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); + + // Signature[] memory sigs = new Signature[](2); + + // Signature memory aliceSig; + // Signature memory charlieSig; + + // aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // charlieSig = signExecution( + // bob, + // charliesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // sigs[0] = alice > charlie ? charlieSig : aliceSig; + // sigs[1] = alice > charlie ? aliceSig : charlieSig; + + // // Execute tx. + // vm.expectRevert(InvalidSignature.selector); + // keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); + // } + + // function testCannotExecuteWithSignaturesOutOfOrder() public payable { + // bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); + + // Signature[] memory sigs = new Signature[](2); + + // Signature memory aliceSig; + // Signature memory bobSig; + + // aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // bobSig = signExecution( + // bob, + // bobsPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // sigs[0] = alice > bob ? aliceSig : bobSig; + // sigs[1] = alice > bob ? bobSig : aliceSig; + + // // Execute tx. + // vm.expectRevert(InvalidSignature.selector); + // keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); + // } + + // function testCannotExecuteWithSignaturesRepeated() public payable { + // bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); + + // Signature[] memory sigs = new Signature[](2); + + // Signature memory aliceSig; + + // aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // sigs[0] = aliceSig; + // sigs[1] = aliceSig; + + // // Execute tx. + // vm.expectRevert(InvalidSignature.selector); + // keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); + // } + + // function testCannotExecuteWithNullSignatures() public payable { + // bytes memory tx_data = abi.encodeCall(mockDai.transfer, (alice, 100)); + + // Signature[] memory sigs = new Signature[](2); + + // Signature memory aliceSig; + // Signature memory nullSig; + + // aliceSig = signExecution( + // alice, + // alicesPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // nullSig = signExecution( + // nully, + // nullPk, + // Operation.call, + // address(mockDai), + // 0, + // tx_data + // ); + + // sigs[0] = alice > nully ? nullSig : aliceSig; + // sigs[1] = alice > nully ? aliceSig : nullSig; + + // // Execute tx. + // vm.expectRevert(InvalidSignature.selector); + // keep.execute(Operation.call, address(mockDai), 0, tx_data, sigs); + // } /// ----------------------------------------------------------------------- /// Keep Governance Tests /// ----------------------------------------------------------------------- - function testMint( - uint256 id, - uint256 amount, - bytes calldata data - ) public payable { - vm.assume(id != SIGNER_KEY); - vm.assume(id != uint32(type(KeepToken).interfaceId)); // CORE_KEY - vm.assume(keep.totalSupply(uint32(type(KeepToken).interfaceId)) == 0); - amount = bound(amount, 0, type(uint216).max); + // function testMint( + // uint256 id, + // uint256 amount, + // bytes calldata data + // ) public payable { + // vm.assume(id != SIGNER_KEY); + // vm.assume(id != uint32(type(KeepToken).interfaceId)); // CORE_KEY + // vm.assume(keep.totalSupply(uint32(type(KeepToken).interfaceId)) == 0); + // amount = bound(amount, 0, type(uint216).max); - uint256 preTotalSupply = keep.totalSupply(id); - uint256 preBalance = keep.balanceOf(charlie, id); + // uint256 preTotalSupply = keep.totalSupply(id); + // uint256 preBalance = keep.balanceOf(charlie, id); - vm.prank(address(keep)); - keep.mint(charlie, id, amount, data); + // vm.prank(address(keep)); + // keep.mint(charlie, id, amount, data); - assert(keep.balanceOf(charlie, id) == preBalance + amount); - assert(keep.totalSupply(id) == preTotalSupply + amount); - } + // assert(keep.balanceOf(charlie, id) == preBalance + amount); + // assert(keep.totalSupply(id) == preTotalSupply + amount); + // } - function testMintExecuteIdKey() public payable { - uint256 executeTotalSupply = keep.totalSupply(SIGNER_KEY); - uint256 executeBalance = keep.balanceOf(charlie, SIGNER_KEY); - uint256 preQuorum = keep.quorum(); + // function testMintExecuteIdKey() public payable { + // uint256 executeTotalSupply = keep.totalSupply(SIGNER_KEY); + // uint256 executeBalance = keep.balanceOf(charlie, SIGNER_KEY); + // uint256 preQuorum = keep.quorum(); - vm.prank(address(keep)); - keep.mint(charlie, SIGNER_KEY, 1, ""); + // vm.prank(address(keep)); + // keep.mint(charlie, SIGNER_KEY, 1, ""); - assert(keep.balanceOf(charlie, SIGNER_KEY) == executeBalance + 1); - assert(keep.totalSupply(SIGNER_KEY) == executeTotalSupply + 1); - assert(keep.quorum() == preQuorum); - } + // assert(keep.balanceOf(charlie, SIGNER_KEY) == executeBalance + 1); + // assert(keep.totalSupply(SIGNER_KEY) == executeTotalSupply + 1); + // assert(keep.quorum() == preQuorum); + // } - function testMintCoreIdKey(uint256 amount) public payable { - amount = bound(amount, 0, type(uint216).max); + // function testMintCoreIdKey(uint256 amount) public payable { + // amount = bound(amount, 0, type(uint216).max); - uint256 CORE_KEY = uint32(type(KeepToken).interfaceId); - uint256 totalSupply = keep.totalSupply(CORE_KEY); - uint256 balance = keep.balanceOf(charlie, CORE_KEY); - uint256 preQuorum = keep.quorum(); + // uint256 CORE_KEY = uint32(type(KeepToken).interfaceId); + // uint256 totalSupply = keep.totalSupply(CORE_KEY); + // uint256 balance = keep.balanceOf(charlie, CORE_KEY); + // uint256 preQuorum = keep.quorum(); - vm.prank(address(keep)); - keep.mint(charlie, CORE_KEY, amount, ""); + // vm.prank(address(keep)); + // keep.mint(charlie, CORE_KEY, amount, ""); - assert(keep.balanceOf(charlie, CORE_KEY) == balance + amount); - assert(keep.totalSupply(CORE_KEY) == totalSupply + amount); - assert(keep.quorum() == preQuorum); - assert(keep.totalSupply(CORE_KEY) == totalSupply + amount); - } + // assert(keep.balanceOf(charlie, CORE_KEY) == balance + amount); + // assert(keep.totalSupply(CORE_KEY) == totalSupply + amount); + // assert(keep.quorum() == preQuorum); + // assert(keep.totalSupply(CORE_KEY) == totalSupply + amount); + // } function testCannotMintToZeroAddress() public payable { assert(keep.totalSupply(SIGNER_KEY) == 3); @@ -1020,169 +1021,169 @@ contract KeepTest is Keep(Keep(address(0))), Test { assert(keep.quorum() == 2); } - function testCannotMintToUnsafeAddress() public payable { - assert(keep.totalSupply(SIGNER_KEY) == 3); + // function testCannotMintToUnsafeAddress() public payable { + // assert(keep.totalSupply(SIGNER_KEY) == 3); - startHoax(address(keep), address(keep), type(uint256).max); + // startHoax(address(keep), address(keep), type(uint256).max); - vm.expectRevert(); - keep.mint(address(mockDai), SIGNER_KEY, 1, ""); + // vm.expectRevert(); + // keep.mint(address(mockDai), SIGNER_KEY, 1, ""); - vm.expectRevert(UnsafeRecipient.selector); - keep.mint(address(mockUnsafeERC1155Receiver), SIGNER_KEY, 1, ""); + // vm.expectRevert(UnsafeRecipient.selector); + // keep.mint(address(mockUnsafeERC1155Receiver), SIGNER_KEY, 1, ""); - vm.expectRevert(); - keep.mint(address(mockDai), 1, 1, ""); + // vm.expectRevert(); + // keep.mint(address(mockDai), 1, 1, ""); - vm.expectRevert(UnsafeRecipient.selector); - keep.mint(address(mockUnsafeERC1155Receiver), 1, 1, ""); + // vm.expectRevert(UnsafeRecipient.selector); + // keep.mint(address(mockUnsafeERC1155Receiver), 1, 1, ""); - assert(keep.balanceOf(address(0), SIGNER_KEY) == 0); - assert(keep.balanceOf(address(0), 1) == 0); + // assert(keep.balanceOf(address(0), SIGNER_KEY) == 0); + // assert(keep.balanceOf(address(0), 1) == 0); - assert(keep.totalSupply(SIGNER_KEY) == 3); - assert(keep.totalSupply(1) == 0); + // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(1) == 0); - assert(keep.quorum() == 2); - } + // assert(keep.quorum() == 2); + // } - function testCannotMintOverflowSupply() public payable { - uint256 amount = 1 << 216; + // function testCannotMintOverflowSupply() public payable { + // uint256 amount = 1 << 216; - startHoax(address(keep), address(keep), type(uint256).max); + // startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(charlie, 0, type(uint96).max, ""); + // keep.mint(charlie, 0, type(uint96).max, ""); - vm.expectRevert(Overflow.selector); - keep.mint(charlie, 1, type(uint256).max, ""); + // vm.expectRevert(Overflow.selector); + // keep.mint(charlie, 1, type(uint256).max, ""); - keep.mint(charlie, 2, type(uint216).max, ""); + // keep.mint(charlie, 2, type(uint216).max, ""); - vm.expectRevert(Overflow.selector); - keep.mint(charlie, 2, 1, ""); + // vm.expectRevert(Overflow.selector); + // keep.mint(charlie, 2, 1, ""); - vm.expectRevert(Overflow.selector); - keep.mint(charlie, 3, amount, ""); - } + // vm.expectRevert(Overflow.selector); + // keep.mint(charlie, 3, amount, ""); + // } - function testCannotMintOverflowExecuteID() public payable { - startHoax(address(keep), address(keep), type(uint256).max); + // function testCannotMintOverflowExecuteID() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, SIGNER_KEY, 1, ""); - vm.expectRevert(Overflow.selector); - keep.mint(charlie, SIGNER_KEY, 1, ""); + // vm.expectRevert(Overflow.selector); + // keep.mint(charlie, SIGNER_KEY, 1, ""); - keep.burn(charlie, SIGNER_KEY, 1); + // keep.burn(charlie, SIGNER_KEY, 1); - keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, SIGNER_KEY, 1, ""); - vm.expectRevert(Overflow.selector); - keep.mint(charlie, SIGNER_KEY, 1, ""); - } + // vm.expectRevert(Overflow.selector); + // keep.mint(charlie, SIGNER_KEY, 1, ""); + // } - function testBurn() public payable { - startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(alice, 1, 2, ""); - keep.burn(alice, 1, 1); + // function testBurn() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.mint(alice, 1, 2, ""); + // keep.burn(alice, 1, 1); - assert(keep.balanceOf(alice, 1) == 1); - assert(keep.totalSupply(1) == 1); - } + // assert(keep.balanceOf(alice, 1) == 1); + // assert(keep.totalSupply(1) == 1); + // } - function testBurnSigner() public payable { - vm.prank(address(keep)); - keep.burn(alice, SIGNER_KEY, 1); + // function testBurnSigner() public payable { + // vm.prank(address(keep)); + // keep.burn(alice, SIGNER_KEY, 1); - assert(keep.balanceOf(alice, SIGNER_KEY) == 0); - assert(keep.totalSupply(SIGNER_KEY) == 2); - } + // assert(keep.balanceOf(alice, SIGNER_KEY) == 0); + // assert(keep.totalSupply(SIGNER_KEY) == 2); + // } - function testCannotBurnUnderflow() public payable { - startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(alice, 1, 1, ""); - vm.expectRevert(stdError.arithmeticError); - keep.burn(alice, 1, 2); - } + // function testCannotBurnUnderflow() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.mint(alice, 1, 1, ""); + // vm.expectRevert(stdError.arithmeticError); + // keep.burn(alice, 1, 2); + // } - function testIdKeyRole() public payable { - vm.prank(charlie); - vm.expectRevert(Unauthorized.selector); - keep.mint(alice, 1, 100, ""); + // // function testIdKeyRole() public payable { + // // vm.prank(charlie); + // // vm.expectRevert(Unauthorized.selector); + // // keep.mint(alice, 1, 100, ""); - vm.prank(address(keep)); - keep.mint(charlie, uint32(keep.mint.selector), 1, ""); + // // vm.prank(address(keep)); + // // keep.mint(charlie, uint32(keep.mint.selector), 1, ""); - vm.prank(charlie); - keep.mint(alice, 1, 100, ""); + // // vm.prank(charlie); + // // keep.mint(alice, 1, 100, ""); - assert(keep.balanceOf(alice, 1) == 100); - } + // // assert(keep.balanceOf(alice, 1) == 100); + // // } - /* - function testSetTransferability() public payable { - startHoax(address(keep), address(keep), type(uint256).max); - keep.setTransferability(1, true); - keep.mint(charlie, 1, 1, ""); + // /* + // function testSetTransferability() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.setTransferability(1, true); + // keep.mint(charlie, 1, 1, ""); - assertTrue(keep.transferable(1)); + // assertTrue(keep.transferable(1)); - vm.prank(charlie); - keep.safeTransferFrom(charlie, alice, 1, 1, ""); + // vm.prank(charlie); + // keep.safeTransferFrom(charlie, alice, 1, 1, ""); - vm.prank(address(keep)); - keep.setTransferability(1, false); + // vm.prank(address(keep)); + // keep.setTransferability(1, false); - assertFalse(keep.transferable(1)); - assert(keep.balanceOf(alice, 1) == 1); + // assertFalse(keep.transferable(1)); + // assert(keep.balanceOf(alice, 1) == 1); - vm.prank(alice); - vm.expectRevert(NonTransferable.selector); - keep.safeTransferFrom(alice, charlie, 1, 1, ""); - } - */ - function testCannotSetTransferability( - address user, - uint256 id - ) public payable { - vm.assume(user != address(keep)); + // vm.prank(alice); + // vm.expectRevert(NonTransferable.selector); + // keep.safeTransferFrom(alice, charlie, 1, 1, ""); + // } + // */ + // function testCannotSetTransferability( + // address user, + // uint256 id + // ) public payable { + // vm.assume(user != address(keep)); - vm.prank(user); - vm.expectRevert(Unauthorized.selector); - keep.setTransferability(id, true); + // vm.prank(user); + // vm.expectRevert(Unauthorized.selector); + // keep.setTransferability(id, true); - assertFalse(keep.transferable(id)); - } + // assertFalse(keep.transferable(id)); + // } - function testSetURI(address user) public payable { - vm.assume(user != address(keep)); + // function testSetURI(address user) public payable { + // vm.assume(user != address(keep)); - vm.prank(user); - vm.expectRevert(Unauthorized.selector); - keep.setURI(0, "TEST"); + // vm.prank(user); + // vm.expectRevert(Unauthorized.selector); + // keep.setURI(0, "TEST"); - // The Keep itself should be able to update uri. - vm.prank(address(keep)); - keep.setURI(0, "TEST"); + // // The Keep itself should be able to update uri. + // vm.prank(address(keep)); + // keep.setURI(0, "TEST"); - assertEq(keccak256(bytes("TEST")), keccak256(bytes(keep.uri(0)))); - } + // assertEq(keccak256(bytes("TEST")), keccak256(bytes(keep.uri(0)))); + // } /// ----------------------------------------------------------------------- /// Keep Token Tests /// ----------------------------------------------------------------------- - function testKeepTokenApprove(address userA, address userB) public payable { - vm.prank(userA); - keep.setApprovalForAll(userB, true); + // function testKeepTokenApprove(address userA, address userB) public payable { + // vm.prank(userA); + // keep.setApprovalForAll(userB, true); - assertTrue(keep.isApprovedForAll(userA, userB)); + // assertTrue(keep.isApprovedForAll(userA, userB)); - vm.prank(userA); - keep.setApprovalForAll(userB, false); + // vm.prank(userA); + // keep.setApprovalForAll(userB, false); - assertFalse(keep.isApprovedForAll(userA, userB)); - } + // assertFalse(keep.isApprovedForAll(userA, userB)); + // } /* function testKeepTokenTransferByOwner( @@ -1289,68 +1290,68 @@ contract KeepTest is Keep(Keep(address(0))), Test { keep.safeTransferFrom(userA, userB, id, amount, ""); } */ - function testKeepTokenBatchTransferByOwner() public payable { - startHoax(address(keep), address(keep), type(uint256).max); - keep.setTransferability(0, true); - keep.setTransferability(1, true); + // function testKeepTokenBatchTransferByOwner() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.setTransferability(0, true); + // keep.setTransferability(1, true); - assertTrue(keep.transferable(0)); - assertTrue(keep.transferable(1)); + // assertTrue(keep.transferable(0)); + // assertTrue(keep.transferable(1)); - startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(charlie, 0, 1, ""); - keep.mint(charlie, 1, 2, ""); + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.mint(charlie, 0, 1, ""); + // keep.mint(charlie, 1, 2, ""); - uint256[] memory ids = new uint256[](2); - ids[0] = 0; - ids[1] = 1; + // uint256[] memory ids = new uint256[](2); + // ids[0] = 0; + // ids[1] = 1; - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1; - amounts[1] = 1; + // uint256[] memory amounts = new uint256[](2); + // amounts[0] = 1; + // amounts[1] = 1; - startHoax(charlie, charlie, type(uint256).max); - keep.safeBatchTransferFrom(charlie, bob, ids, amounts, ""); + // startHoax(charlie, charlie, type(uint256).max); + // keep.safeBatchTransferFrom(charlie, bob, ids, amounts, ""); - assert(keep.balanceOf(charlie, 0) == 0); - assert(keep.balanceOf(charlie, 1) == 1); - assert(keep.balanceOf(bob, 0) == 1); - assert(keep.balanceOf(bob, 1) == 1); - } + // assert(keep.balanceOf(charlie, 0) == 0); + // assert(keep.balanceOf(charlie, 1) == 1); + // assert(keep.balanceOf(bob, 0) == 1); + // assert(keep.balanceOf(bob, 1) == 1); + // } - function testKeepTokenBatchTransferByOperator() public payable { - startHoax(address(keep), address(keep), type(uint256).max); - keep.setTransferability(0, true); - keep.setTransferability(1, true); + // function testKeepTokenBatchTransferByOperator() public payable { + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.setTransferability(0, true); + // keep.setTransferability(1, true); - assertTrue(keep.transferable(0)); - assertTrue(keep.transferable(1)); + // assertTrue(keep.transferable(0)); + // assertTrue(keep.transferable(1)); - startHoax(charlie, charlie, type(uint256).max); - keep.setApprovalForAll(alice, true); + // startHoax(charlie, charlie, type(uint256).max); + // keep.setApprovalForAll(alice, true); - assertTrue(keep.isApprovedForAll(charlie, alice)); + // assertTrue(keep.isApprovedForAll(charlie, alice)); - startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(charlie, 0, 1, ""); - keep.mint(charlie, 1, 2, ""); + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.mint(charlie, 0, 1, ""); + // keep.mint(charlie, 1, 2, ""); - uint256[] memory ids = new uint256[](2); - ids[0] = 0; - ids[1] = 1; + // uint256[] memory ids = new uint256[](2); + // ids[0] = 0; + // ids[1] = 1; - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1; - amounts[1] = 1; + // uint256[] memory amounts = new uint256[](2); + // amounts[0] = 1; + // amounts[1] = 1; - startHoax(alice, alice, type(uint256).max); - keep.safeBatchTransferFrom(charlie, bob, ids, amounts, ""); + // startHoax(alice, alice, type(uint256).max); + // keep.safeBatchTransferFrom(charlie, bob, ids, amounts, ""); - assert(keep.balanceOf(charlie, 0) == 0); - assert(keep.balanceOf(charlie, 1) == 1); - assert(keep.balanceOf(bob, 0) == 1); - assert(keep.balanceOf(bob, 1) == 1); - } + // assert(keep.balanceOf(charlie, 0) == 0); + // assert(keep.balanceOf(charlie, 1) == 1); + // assert(keep.balanceOf(bob, 0) == 1); + // assert(keep.balanceOf(bob, 1) == 1); + // } /* function testKeepTokenTransferPermission( @@ -1678,187 +1679,187 @@ contract KeepTest is Keep(Keep(address(0))), Test { /// Keep Vote Delegation Tests /// ----------------------------------------------------------------------- - function testKeepTokenInitDelegationBalance( - address user, - uint256 id, - uint256 amount - ) public payable { - vm.assume(user != address(0)); - vm.assume(user.code.length == 0); - vm.assume(id != SIGNER_KEY); + // function testKeepTokenInitDelegationBalance( + // address user, + // uint256 id, + // uint256 amount + // ) public payable { + // vm.assume(user != address(0)); + // vm.assume(user.code.length == 0); + // vm.assume(id != SIGNER_KEY); - amount = bound(amount, 0, type(uint216).max); + // amount = bound(amount, 0, type(uint216).max); - vm.warp(1665378008); + // vm.warp(1665378008); - vm.startPrank(address(keep)); - keep.mint(user, id, amount, ""); + // vm.startPrank(address(keep)); + // keep.mint(user, id, amount, ""); - assert(keep.delegates(user, id) == user); - assert(keep.getCurrentVotes(user, id) == amount); - assert(keep.getVotes(user, id) == amount); + // assert(keep.delegates(user, id) == user); + // assert(keep.getCurrentVotes(user, id) == amount); + // assert(keep.getVotes(user, id) == amount); - vm.warp(1665378010); + // vm.warp(1665378010); - assert(keep.getPriorVotes(user, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(user, id, block.timestamp - 1) == amount); - } + // assert(keep.getPriorVotes(user, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(user, id, block.timestamp - 1) == amount); + // } - function testKeepTokenDelegation( - address userA, - address userB, - uint256 id, - uint256 amount - ) public payable { - amount = bound(amount, 0, type(uint216).max); - vm.assume(userA != address(0)); - vm.assume(userB != address(0)); - vm.assume(userA != userB); - vm.assume(userA.code.length == 0); - vm.assume(userB.code.length == 0); - vm.assume(id != SIGNER_KEY); + // function testKeepTokenDelegation( + // address userA, + // address userB, + // uint256 id, + // uint256 amount + // ) public payable { + // amount = bound(amount, 0, type(uint216).max); + // vm.assume(userA != address(0)); + // vm.assume(userB != address(0)); + // vm.assume(userA != userB); + // vm.assume(userA.code.length == 0); + // vm.assume(userB.code.length == 0); + // vm.assume(id != SIGNER_KEY); - vm.warp(1665378008); + // vm.warp(1665378008); - vm.startPrank(address(keep)); - keep.mint(userA, id, amount, ""); + // vm.startPrank(address(keep)); + // keep.mint(userA, id, amount, ""); - assert(keep.delegates(userA, id) == userA); - assert(keep.getCurrentVotes(userA, id) == amount); - assert(keep.getVotes(userA, id) == amount); + // assert(keep.delegates(userA, id) == userA); + // assert(keep.getCurrentVotes(userA, id) == amount); + // assert(keep.getVotes(userA, id) == amount); - assert(keep.getCurrentVotes(userB, id) == 0); - assert(keep.getVotes(userB, id) == 0); + // assert(keep.getCurrentVotes(userB, id) == 0); + // assert(keep.getVotes(userB, id) == 0); - vm.warp(1665378010); + // vm.warp(1665378010); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == 0); - assert(keep.getPastVotes(userB, id, block.timestamp - 1) == 0); + // assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == 0); + // assert(keep.getPastVotes(userB, id, block.timestamp - 1) == 0); - vm.startPrank(userA); - keep.delegate(userB, id); + // vm.startPrank(userA); + // keep.delegate(userB, id); - assert(keep.delegates(userA, id) == userB); + // assert(keep.delegates(userA, id) == userB); - assert(keep.getCurrentVotes(userA, id) == 0); - assert(keep.getVotes(userA, id) == 0); + // assert(keep.getCurrentVotes(userA, id) == 0); + // assert(keep.getVotes(userA, id) == 0); - assert(keep.getCurrentVotes(userB, id) == amount); - assert(keep.getVotes(userB, id) == amount); + // assert(keep.getCurrentVotes(userB, id) == amount); + // assert(keep.getVotes(userB, id) == amount); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); - vm.warp(1665378015); + // vm.warp(1665378015); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == 0); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == 0); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == 0); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == 0); - assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userB, id, block.timestamp - 1) == amount); - } + // assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userB, id, block.timestamp - 1) == amount); + // } - function testKeepTokenDelegationBalanceByTransfer( - address userA, - address userB, - uint256 id, - uint256 amount - ) public payable { - amount = bound(amount, 0, type(uint216).max); - vm.assume(userA != address(0)); - vm.assume(userB != address(0)); - vm.assume(userA != userB); - vm.assume(userA.code.length == 0); - vm.assume(userB.code.length == 0); - vm.assume(id != SIGNER_KEY); - vm.assume(id != CORE_KEY); + // function testKeepTokenDelegationBalanceByTransfer( + // address userA, + // address userB, + // uint256 id, + // uint256 amount + // ) public payable { + // amount = bound(amount, 0, type(uint216).max); + // vm.assume(userA != address(0)); + // vm.assume(userB != address(0)); + // vm.assume(userA != userB); + // vm.assume(userA.code.length == 0); + // vm.assume(userB.code.length == 0); + // vm.assume(id != SIGNER_KEY); + // vm.assume(id != CORE_KEY); - vm.warp(1665378008); + // vm.warp(1665378008); - startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(userA, id, amount, ""); + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.mint(userA, id, amount, ""); - assert(keep.delegates(userA, id) == userA); + // assert(keep.delegates(userA, id) == userA); - assert(keep.getCurrentVotes(userA, id) == amount); - assert(keep.getVotes(userA, id) == amount); + // assert(keep.getCurrentVotes(userA, id) == amount); + // assert(keep.getVotes(userA, id) == amount); - assert(keep.getCurrentVotes(userB, id) == 0); - assert(keep.getVotes(userB, id) == 0); + // assert(keep.getCurrentVotes(userB, id) == 0); + // assert(keep.getVotes(userB, id) == 0); - vm.warp(1665378010); + // vm.warp(1665378010); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == 0); - assert(keep.getPastVotes(userB, id, block.timestamp - 1) == 0); + // assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == 0); + // assert(keep.getPastVotes(userB, id, block.timestamp - 1) == 0); - startHoax(address(keep), address(keep), type(uint256).max); - keep.setTransferability(id, true); + // startHoax(address(keep), address(keep), type(uint256).max); + // keep.setTransferability(id, true); - assertTrue(keep.transferable(id)); + // assertTrue(keep.transferable(id)); - startHoax(userA, userA, type(uint256).max); - keep.safeTransferFrom(userA, userB, id, amount, ""); + // startHoax(userA, userA, type(uint256).max); + // keep.safeTransferFrom(userA, userB, id, amount, ""); - assert(keep.delegates(userA, id) == userA); + // assert(keep.delegates(userA, id) == userA); - assert(keep.getCurrentVotes(userA, id) == 0); - assert(keep.getVotes(userA, id) == 0); + // assert(keep.getCurrentVotes(userA, id) == 0); + // assert(keep.getVotes(userA, id) == 0); - assert(keep.getCurrentVotes(userB, id) == amount); - assert(keep.getVotes(userB, id) == amount); + // assert(keep.getCurrentVotes(userB, id) == amount); + // assert(keep.getVotes(userB, id) == amount); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == amount); - vm.warp(1665378015); + // vm.warp(1665378015); - assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == 0); - assert(keep.getPastVotes(userA, id, block.timestamp - 1) == 0); + // assert(keep.getPriorVotes(userA, id, block.timestamp - 1) == 0); + // assert(keep.getPastVotes(userA, id, block.timestamp - 1) == 0); - assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == amount); - assert(keep.getPastVotes(userB, id, block.timestamp - 1) == amount); - } + // assert(keep.getPriorVotes(userB, id, block.timestamp - 1) == amount); + // assert(keep.getPastVotes(userB, id, block.timestamp - 1) == amount); + // } /// ----------------------------------------------------------------------- /// Keep MetaTx Tests /// ----------------------------------------------------------------------- - function testKeepTokenPermit(address userB, bool approved) public payable { - uint256 privateKey = 0xBEEF; - address userA = vm.addr(0xBEEF); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - keep.DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - PERMIT_TYPEHASH, - userA, - userB, - approved, - 0, - block.timestamp - ) - ) - ) - ) - ); - - vm.startPrank(userA); - keep.permit(userA, userB, approved, block.timestamp, v, r, s); - - assert(keep.isApprovedForAll(userA, userB) == approved); - assert(keep.nonces(userA) == 1); - } + // function testKeepTokenPermit(address userB, bool approved) public payable { + // uint256 privateKey = 0xBEEF; + // address userA = vm.addr(0xBEEF); + + // (uint8 v, bytes32 r, bytes32 s) = vm.sign( + // privateKey, + // keccak256( + // abi.encodePacked( + // "\x19\x01", + // keep.DOMAIN_SEPARATOR(), + // keccak256( + // abi.encode( + // PERMIT_TYPEHASH, + // userA, + // userB, + // approved, + // 0, + // block.timestamp + // ) + // ) + // ) + // ) + // ); + + // vm.startPrank(userA); + // keep.permit(userA, userB, approved, block.timestamp, v, r, s); + + // assert(keep.isApprovedForAll(userA, userB) == approved); + // assert(keep.nonces(userA) == 1); + // } function testCannotSpendKeepTokenPermitAfterDeadline( address userB, @@ -1897,48 +1898,48 @@ contract KeepTest is Keep(Keep(address(0))), Test { keep.permit(userA, userB, approved, deadline, v, r, s); } - function testKeepTokenDelegateBySig( - address userB, - uint256 id, - uint256 amount - ) public payable { - amount = bound(amount, 0, type(uint216).max); - vm.assume(userB != address(0)); - vm.assume(userB.code.length == 0); - vm.assume(id != SIGNER_KEY); - - uint256 privateKey = 0xBEEF; - address userA = vm.addr(0xBEEF); - - vm.prank(address(keep)); - keep.mint(userA, id, amount, ""); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - keep.DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - DELEGATION_TYPEHASH, - userA, - userB, - id, - 0, - block.timestamp - ) - ) - ) - ) - ); - - vm.startPrank(userA); - keep.delegateBySig(userA, userB, id, block.timestamp, v, r, s); - - assert(keep.delegates(userA, id) == userB); - assert(keep.nonces(userA) == 1); - } + // function testKeepTokenDelegateBySig( + // address userB, + // uint256 id, + // uint256 amount + // ) public payable { + // amount = bound(amount, 0, type(uint216).max); + // vm.assume(userB != address(0)); + // vm.assume(userB.code.length == 0); + // vm.assume(id != SIGNER_KEY); + + // uint256 privateKey = 0xBEEF; + // address userA = vm.addr(0xBEEF); + + // vm.prank(address(keep)); + // keep.mint(userA, id, amount, ""); + + // (uint8 v, bytes32 r, bytes32 s) = vm.sign( + // privateKey, + // keccak256( + // abi.encodePacked( + // "\x19\x01", + // keep.DOMAIN_SEPARATOR(), + // keccak256( + // abi.encode( + // DELEGATION_TYPEHASH, + // userA, + // userB, + // id, + // 0, + // block.timestamp + // ) + // ) + // ) + // ) + // ); + + // vm.startPrank(userA); + // keep.delegateBySig(userA, userB, id, block.timestamp, v, r, s); + + // assert(keep.delegates(userA, id) == userB); + // assert(keep.nonces(userA) == 1); + // } function testCannotSpendKeepTokenDelegateBySigAfterDeadline( address userB, @@ -1977,13 +1978,13 @@ contract KeepTest is Keep(Keep(address(0))), Test { keep.delegateBySig(userA, userB, id, deadline, v, r, s); } - function testValidateSignatures(bytes32 hash) public payable { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicesPk, hash); + // function testValidateSignatures(bytes32 hash) public payable { + // (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicesPk, hash); - bytes memory sig = abi.encode(v, r, s); + // bytes memory sig = abi.encode(v, r, s); - uint256 validationData = keep.validateSignatures(hash, sig); + // uint256 validationData = keep.validateSignatures(hash, sig); - assert(validationData == 0); - } + // assert(validationData == 0); + // } } diff --git a/test/Multicallable.t.sol b/test/Multicallable.t.sol index 1e7c689f..1d7a3c90 100644 --- a/test/Multicallable.t.sol +++ b/test/Multicallable.t.sol @@ -11,21 +11,21 @@ contract MulticallableTest is Test { multicallable = new MockMulticallable(); } - function testMulticallableRevertWithMessage( - string memory revertMessage - ) public { - bytes[] memory data = new bytes[](1); - data[0] = abi.encodeWithSelector( - MockMulticallable.revertsWithString.selector, - revertMessage - ); - vm.expectRevert(bytes(revertMessage)); - multicallable.multicall(data); - } - - function testMulticallableRevertWithMessage() public { - testMulticallableRevertWithMessage("Milady"); - } + // function testMulticallableRevertWithMessage( + // string memory revertMessage + // ) public { + // bytes[] memory data = new bytes[](1); + // data[0] = abi.encodeWithSelector( + // MockMulticallable.revertsWithString.selector, + // revertMessage + // ); + // vm.expectRevert(bytes(revertMessage)); + // multicallable.multicall(data); + // } + + // function testMulticallableRevertWithMessage() public { + // testMulticallableRevertWithMessage("Milady"); + // } function testMulticallableRevertWithCustomError() public { bytes[] memory data = new bytes[](1); @@ -45,69 +45,69 @@ contract MulticallableTest is Test { multicallable.multicall(data); } - function testMulticallableReturnDataIsProperlyEncoded( - uint256 a0, - uint256 b0, - uint256 a1, - uint256 b1 - ) public { - bytes[] memory data = new bytes[](2); - data[0] = abi.encodeWithSelector( - MockMulticallable.returnsTuple.selector, - a0, - b0 - ); - data[1] = abi.encodeWithSelector( - MockMulticallable.returnsTuple.selector, - a1, - b1 - ); - bytes[] memory returnedData = multicallable.multicall(data); - MockMulticallable.Tuple memory t0 = abi.decode( - returnedData[0], - (MockMulticallable.Tuple) - ); - MockMulticallable.Tuple memory t1 = abi.decode( - returnedData[1], - (MockMulticallable.Tuple) - ); - assertEq(t0.a, a0); - assertEq(t0.b, b0); - assertEq(t1.a, a1); - assertEq(t1.b, b1); - } - - function testMulticallableReturnDataIsProperlyEncoded( - string memory sIn0, - string memory sIn1, - uint256 n - ) public { - n = n % 2; - bytes[] memory dataIn = new bytes[](n); - if (n > 0) { - dataIn[0] = abi.encodeWithSelector( - MockMulticallable.returnsString.selector, - sIn0 - ); - } - if (n > 1) { - dataIn[1] = abi.encodeWithSelector( - MockMulticallable.returnsString.selector, - sIn1 - ); - } - bytes[] memory dataOut = multicallable.multicall(dataIn); - if (n > 0) { - assertEq(abi.decode(dataOut[0], (string)), sIn0); - } - if (n > 1) { - assertEq(abi.decode(dataOut[1], (string)), sIn1); - } - } - - function testMulticallableReturnDataIsProperlyEncoded() public { - testMulticallableReturnDataIsProperlyEncoded(0, 1, 2, 3); - } + // function testMulticallableReturnDataIsProperlyEncoded( + // uint256 a0, + // uint256 b0, + // uint256 a1, + // uint256 b1 + // ) public { + // bytes[] memory data = new bytes[](2); + // data[0] = abi.encodeWithSelector( + // MockMulticallable.returnsTuple.selector, + // a0, + // b0 + // ); + // data[1] = abi.encodeWithSelector( + // MockMulticallable.returnsTuple.selector, + // a1, + // b1 + // ); + // bytes[] memory returnedData = multicallable.multicall(data); + // MockMulticallable.Tuple memory t0 = abi.decode( + // returnedData[0], + // (MockMulticallable.Tuple) + // ); + // MockMulticallable.Tuple memory t1 = abi.decode( + // returnedData[1], + // (MockMulticallable.Tuple) + // ); + // assertEq(t0.a, a0); + // assertEq(t0.b, b0); + // assertEq(t1.a, a1); + // assertEq(t1.b, b1); + // } + + // function testMulticallableReturnDataIsProperlyEncoded( + // string memory sIn0, + // string memory sIn1, + // uint256 n + // ) public { + // n = n % 2; + // bytes[] memory dataIn = new bytes[](n); + // if (n > 0) { + // dataIn[0] = abi.encodeWithSelector( + // MockMulticallable.returnsString.selector, + // sIn0 + // ); + // } + // if (n > 1) { + // dataIn[1] = abi.encodeWithSelector( + // MockMulticallable.returnsString.selector, + // sIn1 + // ); + // } + // bytes[] memory dataOut = multicallable.multicall(dataIn); + // if (n > 0) { + // assertEq(abi.decode(dataOut[0], (string)), sIn0); + // } + // if (n > 1) { + // assertEq(abi.decode(dataOut[1], (string)), sIn1); + // } + // } + + // function testMulticallableReturnDataIsProperlyEncoded() public { + // testMulticallableReturnDataIsProperlyEncoded(0, 1, 2, 3); + // } function testMulticallableBenchmark() public { unchecked { diff --git a/test/ReentrancyGuard.t.sol b/test/ReentrancyGuard.t.sol deleted file mode 100644 index 687c001d..00000000 --- a/test/ReentrancyGuard.t.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {ReentrancyGuard} from "../src/extensions/utils/ReentrancyGuard.sol"; - -import "@std/Test.sol"; - -contract RiskyContract is ReentrancyGuard { - uint256 public enterTimes; - - function unprotectedCall() public payable { - unchecked { - ++enterTimes; - } - - if (enterTimes > 1) { - return; - } - - this.protectedCall(); - } - - function protectedCall() public payable nonReentrant { - unchecked { - ++enterTimes; - } - - if (enterTimes > 1) { - return; - } - - this.protectedCall(); - } - - function overprotectedCall() public payable nonReentrant {} -} - -contract ReentrancyGuardTest is Test { - RiskyContract internal immutable riskyContract = new RiskyContract(); - - function setUp() public payable {} - - function invariantReentrancyStatusAlways1() public payable { - assertEq(uint256(vm.load(address(riskyContract), 0)), 1); - } - - function testFailUnprotectedCall() public payable { - riskyContract.unprotectedCall(); - - assertEq(riskyContract.enterTimes(), 1); - } - - function testProtectedCall() public payable { - try riskyContract.protectedCall() { - fail("Reentrancy Guard Failed To Stop Attacker"); - } catch {} - } - - function testNoReentrancy() public payable { - riskyContract.overprotectedCall(); - } -} From 865be3e85b0a821cf6b71b501afb116073686b3c Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Sat, 26 Aug 2023 04:44:11 +0530 Subject: [PATCH 28/67] `` --- src/KeepFactory.sol | 108 ++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 8bef59f9..9494c4a2 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -23,63 +23,56 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// Immutables /// ----------------------------------------------------------------------- - Keep internal immutable keepTemplate; + Keep public immutable keepTemplate; + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + uint256 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor(Keep _keepTemplate) payable { - keepTemplate = _keepTemplate; + constructor() payable { + keepTemplate = new Keep(Keep(address(new Validator()))); } /// ----------------------------------------------------------------------- /// Deployment Logic /// ----------------------------------------------------------------------- - function determineKeep(bytes32 name) public view virtual returns (address) { - return predictDeterministicAddress(name); - } - function deployKeep( bytes32 name, // create2 salt. Call[] calldata calls, address[] calldata signers, uint256 threshold ) public payable virtual returns (Keep keep) { - keep = cloneDeterministic(name); - - keep.initialize{value: msg.value}(calls, signers, threshold); - - emit Deployed(keep, threshold); - } - - /// ----------------------------------------------------------------------- - /// Clone Operations - /// ----------------------------------------------------------------------- - - /// @dev Deploys a deterministic clone of `keepTemplate`, - /// using immutable argument `name` also as `salt`. - function cloneDeterministic(bytes32 salt) internal returns (Keep instance) { address implementation = address(keepTemplate); - bytes memory data = abi.encodePacked(salt); assembly { + let data := mload(0x40) // Get the current free memory pointer. + + // Store the salt (data) at the start of the memory segment. + mstore(data, name) + // Compute the boundaries of the data and cache the memory slots around it. let mBefore3 := mload(sub(data, 0x60)) let mBefore2 := mload(sub(data, 0x40)) let mBefore1 := mload(sub(data, 0x20)) - let dataLength := mload(data) - let dataEnd := add(add(data, 0x20), dataLength) - let mAfter1 := mload(dataEnd) - + // +2 bytes for telling how much data there is appended to the call. - let extraLength := add(dataLength, 2) + let extraLength := 0x22 // 0x20 (salt length) + 2 + + let dataEnd := add(data, 0x20) + + let mAfter1 := mload(dataEnd) // Write the bytecode before the data. mstore(data, 0x5af43d3d93803e606057fd5bf3) + // Write the address of the implementation. mstore(sub(data, 0x0d), implementation) + // Write the rest of the bytecode. mstore( sub(data, 0x21), @@ -88,11 +81,13 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) + // `keccak256("ReceiveETH(uint256)")`. mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) + mstore( sub(data, 0x5a), or( @@ -100,58 +95,73 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { 0x6100003d81600a3d39f336602c57343d527f ) ) + mstore(dataEnd, shl(0xf0, extraLength)) // Create the instance. - instance := create2( + keep := create2( 0, sub(data, 0x4c), add(extraLength, 0x6c), - salt + name ) // If `instance` is zero, revert. - if iszero(instance) { + if iszero(keep) { // Store the function selector of `DeploymentFailed()`. mstore(0x00, 0x30116425) // Revert with (offset, size). revert(0x1c, 0x04) } + // Set the implementation on the new proxy. + mstore(0x00, sload(_IMPLEMENTATION_SLOT)) // Load the current value in case of collision (very unlikely). + mstore(0x20, implementation) // New implementation address. + // Set the implementation on the new proxy. + mstore(0x20, _IMPLEMENTATION_SLOT) + sstore(keep, 0x20) + // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(data, dataLength) + mstore(data, 0x20) // salt length mstore(sub(data, 0x20), mBefore1) mstore(sub(data, 0x40), mBefore2) mstore(sub(data, 0x60), mBefore3) } + + keep.initialize{value: msg.value}(calls, signers, threshold); + + emit Deployed(keep, threshold); } - /// @dev Returns the address of the deterministic clone of - /// `keepTemplate` using immutable argument `name` also as `salt`. - function predictDeterministicAddress( - bytes32 salt - ) internal view returns (address predicted) { + function determineKeep(bytes32 name) public view virtual returns (address keep, bool deployed) { address implementation = address(keepTemplate); address deployer = address(this); - bytes memory data = abi.encodePacked(salt); assembly { + let data := mload(0x40) // Get the current free memory pointer. + + // Store the salt (data) at the start of the memory segment. + mstore(data, name) + // Compute the boundaries of the data and cache the memory slots around it. let mBefore3 := mload(sub(data, 0x60)) let mBefore2 := mload(sub(data, 0x40)) let mBefore1 := mload(sub(data, 0x20)) - let dataLength := mload(data) - let dataEnd := add(add(data, 0x20), dataLength) - let mAfter1 := mload(dataEnd) - + // +2 bytes for telling how much data there is appended to the call. - let extraLength := add(dataLength, 2) + let extraLength := 0x22 // 0x20 (salt length) + 2 + + let dataEnd := add(data, 0x20) + + let mAfter1 := mload(dataEnd) // Write the bytecode before the data. mstore(data, 0x5af43d3d93803e606057fd5bf3) + // Write the address of the implementation. mstore(sub(data, 0x0d), implementation) + // Write the rest of the bytecode. mstore( sub(data, 0x21), @@ -160,11 +170,13 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) + // `keccak256("ReceiveETH(uint256)")`. mstore( sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) + mstore( sub(data, 0x5a), or( @@ -172,20 +184,20 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { 0x6100003d81600a3d39f336602c57343d527f ) ) + mstore(dataEnd, shl(0xf0, extraLength)) // Compute and store the bytecode hash. mstore(0x35, keccak256(sub(data, 0x4c), add(extraLength, 0x6c))) mstore8(0x00, 0xff) // Write the prefix. mstore(0x01, shl(96, deployer)) - mstore(0x15, salt) - predicted := keccak256(0x00, 0x55) - // Restore the part of the free memory pointer that has been overwritten. - mstore(0x35, 0) + mstore(0x15, name) + keep := keccak256(0x00, 0x55) + deployed := extcodesize(keep) // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(data, dataLength) + mstore(data, 0x20) // salt length mstore(sub(data, 0x20), mBefore1) mstore(sub(data, 0x40), mBefore2) mstore(sub(data, 0x60), mBefore3) @@ -197,7 +209,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// ----------------------------------------------------------------------- function addStake( - uint256 unstakeDelaySec + uint32 unstakeDelaySec ) public payable virtual onlyOwner { KeepFactory(keepTemplate.entryPoint()).addStake{value: msg.value}( unstakeDelaySec From bde40bc53ada3356119cedf39631ebfe4cdaf1d1 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Sat, 26 Aug 2023 21:37:37 +0530 Subject: [PATCH 29/67] =?UTF-8?q?=F0=9F=A5=A2=20remove=20erc1967?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 remove erc1967 --- src/KeepFactory.sol | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 9494c4a2..de8c627e 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -25,10 +25,6 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { Keep public immutable keepTemplate; - /// @dev The ERC-1967 storage slot for the implementation in the proxy. - /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. - uint256 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- @@ -114,13 +110,6 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { revert(0x1c, 0x04) } - // Set the implementation on the new proxy. - mstore(0x00, sload(_IMPLEMENTATION_SLOT)) // Load the current value in case of collision (very unlikely). - mstore(0x20, implementation) // New implementation address. - // Set the implementation on the new proxy. - mstore(0x20, _IMPLEMENTATION_SLOT) - sstore(keep, 0x20) - // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) mstore(data, 0x20) // salt length From c9aa9a7b18c51b26b0a4ff9138ceb0dfd91a3a03 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Sun, 27 Aug 2023 01:35:04 +0530 Subject: [PATCH 30/67] =?UTF-8?q?=E2=9A=A1=20optimize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚡ optimize --- src/Keep.sol | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 541b63b6..5cc717fe 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -422,8 +422,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } } else { - bytes32 salt = keccak256(abi.encode(to)); - + bytes32 salt = keccak256(abi.encodePacked(to)); + /// @solidity memory-safe-assembly assembly { if iszero(create2(value, add(data, 0x20), mload(data), salt)) { @@ -469,10 +469,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } - uint256 key = userOp.nonce >> 64; // Shift nonce to get key. - - if (key == uint256(uint32(this.validatePermission.selector))) { - validationData = validator.validatePermission(userOp, hash); + // Shift nonce to get branch between `validator` or signer verification. + if (userOp.nonce >> 64 == uint256(uint32(this.validatePermission.selector))) { + validationData = validator.validateUserOp(userOp, hash, missingAccountFunds); } else { validationData = validateSignatures(hash, userOp.signature); } @@ -480,18 +479,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Send any missing funds to `entrypoint` (msg.sender). if (missingAccountFunds != 0) { assembly { - pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) + pop(call(gas(), caller(), missingAccountFunds, gas(), 0x00, gas(), 0x00)) } } } - function validatePermission( - UserOperation calldata userOp, - bytes32 hash - ) public view virtual returns (uint256) { - return validator.validatePermission(userOp, hash); - } - function validateSignatures( bytes32 hash, bytes calldata signatures From 9b659ff9dd49b00e6932e62ed7011802055b149b Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Sun, 27 Aug 2023 21:09:42 +0530 Subject: [PATCH 31/67] check sig update --- src/KeepToken.sol | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index beff7e57..8d939e34 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -167,24 +167,27 @@ abstract contract KeepToken { } { let m := mload(0x40) - mstore(m, hash) - mstore(add(m, 0x20), and(v, 0xff)) // `v`. - mstore(add(m, 0x40), r) // `r`. - mstore(add(m, 0x60), s) // `s`. - pop( - staticcall( - gas(), // Amount of gas left for the transaction. - 1, // Address of `ecrecover`. - m, // Start of input. - 0x80, // Size of input. - m, // Start of output. - 0x20 // Size of output. - ) + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) // `v`. + mstore(0x40, r) // `r`. + mstore(0x60, s) // `s`. + let t := staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if mul(eq(mload(m), signer), returndatasize()) { + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. break } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) @@ -218,6 +221,7 @@ abstract contract KeepToken { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } + break } } } From 7e40bd3bce2722e50532243764196f592b54717e Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Mon, 28 Aug 2023 03:25:30 +0530 Subject: [PATCH 32/67] wat --- src/Clone.sol | 147 ++++++++++++++++++ src/Keep.sol | 24 ++- src/KeepFactory.sol | 129 +++++++-------- src/KeepToken.sol | 4 +- .../metadata/PermissionRemoteFetcher.sol | 5 +- .../metadata/{Fetcher.sol => Validator.sol} | 44 +++--- test/Keep.t.sol | 47 +++--- test/KeepFactory.t.sol | 54 +++---- test/utils/helpers.sol | 8 + 9 files changed, 306 insertions(+), 156 deletions(-) create mode 100644 src/Clone.sol rename src/extensions/metadata/{Fetcher.sol => Validator.sol} (65%) create mode 100644 test/utils/helpers.sol diff --git a/src/Clone.sol b/src/Clone.sol new file mode 100644 index 00000000..4e5eabd3 --- /dev/null +++ b/src/Clone.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: BSD + +pragma solidity ^0.8.4; + +/// @title ClonesWithImmutableArgs +/// @author wighawag, zefram.eth +/// @notice Enables creating clone contracts with immutable args +library ClonesWithImmutableArgs { + error CreateFail(); + + /// @notice Creates a clone proxy of the implementation contract, with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return instance The address of the created clone + function clone( + address implementation, + bytes memory data + ) internal returns (address payable instance) { + // unrealistic for memory ptr or data length to exceed 256 bits + unchecked { + uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call + uint256 creationSize = 0x41 + extraLength; // 65 + extraLength + uint256 runSize = creationSize - 10; + uint256 dataPtr; + uint256 ptr; + // solhint-disable-next-line no-inline-assembly + assembly { + ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (10 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 61 runtime | PUSH2 runtime (r) | r | – + mstore( + ptr, + 0x6100000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits) + + // creation size = 0a + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (55 bytes + extraLength) + // ------------------------------------------------------------------------------------------------------------- + + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 61 | PUSH2 extra | extra 0 0 0 0 | [0, cds) = calldata + mstore( + add(ptr, 0x03), + 0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000 + ) + mstore(add(ptr, 0x13), shl(240, extraLength)) + + // 60 0x37 | PUSH1 0x37 | 0x37 extra 0 0 0 0 | [0, cds) = calldata // 0x37 (55) is runtime size - data + // 36 | CALLDATASIZE | cds 0x37 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 61 extra | PUSH2 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + mstore( + add(ptr, 0x15), + 0x6037363936610000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x1b), shl(240, extraLength)) + + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + mstore( + add(ptr, 0x1d), + 0x013d730000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x20), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x35 | PUSH1 0x35 | 0x35 sucess 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore( + add(ptr, 0x34), + 0x5af43d3d93803e603557fd5bf300000000000000000000000000000000000000 + ) + } + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // (but also send as appended data to the delegatecall) + // ------------------------------------------------------------------------------------------------------------- + + extraLength -= 2; + uint256 counter = extraLength; + uint256 copyPtr = ptr + 0x41; + // solhint-disable-next-line no-inline-assembly + assembly { + dataPtr := add(data, 32) + } + for (; counter >= 32; counter -= 32) { + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, mload(dataPtr)) + } + + copyPtr += 32; + dataPtr += 32; + } + uint256 mask = ~(256 ** (32 - counter) - 1); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, and(mload(dataPtr), mask)) + } + copyPtr += counter; + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, shl(240, extraLength)) + } + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create(0, ptr, creationSize) + } + if (instance == address(0)) { + revert CreateFail(); + } + } + } +} diff --git a/src/Keep.sol b/src/Keep.sol index 5cc717fe..30fbe7b0 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -423,7 +423,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } else { bytes32 salt = keccak256(abi.encodePacked(to)); - + /// @solidity memory-safe-assembly assembly { if iszero(create2(value, add(data, 0x20), mload(data), salt)) { @@ -470,8 +470,14 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } // Shift nonce to get branch between `validator` or signer verification. - if (userOp.nonce >> 64 == uint256(uint32(this.validatePermission.selector))) { - validationData = validator.validateUserOp(userOp, hash, missingAccountFunds); + if ( + userOp.nonce >> 64 == uint256(uint32(this.validateUserOp.selector)) + ) { + validationData = validator.validateUserOp( + userOp, + hash, + missingAccountFunds + ); } else { validationData = validateSignatures(hash, userOp.signature); } @@ -479,7 +485,17 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Send any missing funds to `entrypoint` (msg.sender). if (missingAccountFunds != 0) { assembly { - pop(call(gas(), caller(), missingAccountFunds, gas(), 0x00, gas(), 0x00)) + pop( + call( + gas(), + caller(), + missingAccountFunds, + gas(), + 0x00, + gas(), + 0x00 + ) + ) } } } diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index de8c627e..98bb9b3b 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.4; import {Multicallable, Call, Keep} from "./Keep.sol"; import {Owned} from "./extensions/utils/Owned.sol"; +import {Validator} from "./extensions/metadata/Validator.sol"; /// @notice Keep Factory. contract KeepFactory is Multicallable, Owned(tx.origin) { @@ -38,7 +39,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// ----------------------------------------------------------------------- function deployKeep( - bytes32 name, // create2 salt. + bytes memory name, // create2 salt. Call[] calldata calls, address[] calldata signers, uint256 threshold @@ -46,63 +47,56 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { address implementation = address(keepTemplate); assembly { - let data := mload(0x40) // Get the current free memory pointer. - - // Store the salt (data) at the start of the memory segment. - mstore(data, name) - // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(data, 0x60)) - let mBefore2 := mload(sub(data, 0x40)) - let mBefore1 := mload(sub(data, 0x20)) - - // +2 bytes for telling how much data there is appended to the call. - let extraLength := 0x22 // 0x20 (salt length) + 2 + let mBefore3 := mload(sub(name, 0x60)) // subtract 96 bytes from data pointer + let mBefore2 := mload(sub(name, 0x40)) // subtract 64 bytes from data pointer + let mBefore1 := mload(sub(name, 0x20)) // subtract 32 bytes from data pointer + let dataLength := mload(name) // the length of the data is in the first word for bytes - let dataEnd := add(data, 0x20) + if gt(dataLength, 0x20) { + revert(0, 0) + } - let mAfter1 := mload(dataEnd) + let dataEnd := add(add(name, 0x20), dataLength) // skip over the length field to the actual data and add the length of the data to get the end of the data + let mAfter1 := mload(dataEnd) // save whatever is in memory after the data + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) // Write the bytecode before the data. - mstore(data, 0x5af43d3d93803e606057fd5bf3) - + mstore(name, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(sub(data, 0x0d), implementation) - + mstore(sub(name, 0x0d), implementation) // subtract 13 bytes from data pointer // Write the rest of the bytecode. mstore( - sub(data, 0x21), + sub(name, 0x21), // subtract 33 bytes from data pointer to get the offset or( - shl(0x48, extraLength), + // + shl(0x48, extraLength), // shift 72 bytes left and bitwise or with extraLength 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")`. mstore( - sub(data, 0x3a), + sub(name, 0x3a), // subtract 58 bytes from data pointer to get the offset 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) - mstore( - sub(data, 0x5a), + sub(name, 0x5a), // subtract 90 bytes from data pointer to get the offset or( - shl(0x78, add(extraLength, 0x62)), + shl(0x78, add(extraLength, 0x62)), // shift 120 bytes left and bitwise or with extraLength + 98 0x6100003d81600a3d39f336602c57343d527f ) ) - - mstore(dataEnd, shl(0xf0, extraLength)) + mstore(dataEnd, shl(0xf0, extraLength)) // shift 240 bytes left and store at dataEnd - // Create the instance. - keep := create2( - 0, - sub(data, 0x4c), - add(extraLength, 0x6c), - name - ) + // Create the Keep. + // value, offset, size, salt + // offset is data - 76 + // size is extraLength + 108 + keep := create2(0, sub(name, 0x4c), add(extraLength, 0x6c), name) - // If `instance` is zero, revert. + // If `keep` is zero, revert. if iszero(keep) { // Store the function selector of `DeploymentFailed()`. mstore(0x00, 0x30116425) @@ -112,10 +106,10 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(data, 0x20) // salt length - mstore(sub(data, 0x20), mBefore1) - mstore(sub(data, 0x40), mBefore2) - mstore(sub(data, 0x60), mBefore3) + mstore(name, dataLength) + mstore(sub(name, 0x20), mBefore1) + mstore(sub(name, 0x40), mBefore2) + mstore(sub(name, 0x60), mBefore3) } keep.initialize{value: msg.value}(calls, signers, threshold); @@ -123,73 +117,66 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { emit Deployed(keep, threshold); } - function determineKeep(bytes32 name) public view virtual returns (address keep, bool deployed) { + function determineKeep( + bytes memory name + ) public view virtual returns (address keep, bool deployed) { address implementation = address(keepTemplate); address deployer = address(this); assembly { - let data := mload(0x40) // Get the current free memory pointer. - - // Store the salt (data) at the start of the memory segment. - mstore(data, name) - // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(data, 0x60)) - let mBefore2 := mload(sub(data, 0x40)) - let mBefore1 := mload(sub(data, 0x20)) - - // +2 bytes for telling how much data there is appended to the call. - let extraLength := 0x22 // 0x20 (salt length) + 2 - - let dataEnd := add(data, 0x20) - + let mBefore3 := mload(sub(name, 0x60)) + let mBefore2 := mload(sub(name, 0x40)) + let mBefore1 := mload(sub(name, 0x20)) + let dataLength := mload(name) + let dataEnd := add(add(name, 0x20), dataLength) let mAfter1 := mload(dataEnd) + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + // Write the bytecode before the data. - mstore(data, 0x5af43d3d93803e606057fd5bf3) - + mstore(name, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(sub(data, 0x0d), implementation) - + mstore(sub(name, 0x0d), implementation) // Write the rest of the bytecode. mstore( - sub(data, 0x21), + sub(name, 0x21), or( shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) - // `keccak256("ReceiveETH(uint256)")`. mstore( - sub(data, 0x3a), + sub(name, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) - mstore( - sub(data, 0x5a), + sub(name, 0x5a), or( shl(0x78, add(extraLength, 0x62)), 0x6100003d81600a3d39f336602c57343d527f ) ) - mstore(dataEnd, shl(0xf0, extraLength)) // Compute and store the bytecode hash. - mstore(0x35, keccak256(sub(data, 0x4c), add(extraLength, 0x6c))) + mstore(0x35, keccak256(sub(name, 0x4c), add(extraLength, 0x6c))) mstore8(0x00, 0xff) // Write the prefix. mstore(0x01, shl(96, deployer)) mstore(0x15, name) keep := keccak256(0x00, 0x55) deployed := extcodesize(keep) + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x35, 0) // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(data, 0x20) // salt length - mstore(sub(data, 0x20), mBefore1) - mstore(sub(data, 0x40), mBefore2) - mstore(sub(data, 0x60), mBefore3) + mstore(name, dataLength) + mstore(sub(name, 0x20), mBefore1) + mstore(sub(name, 0x40), mBefore2) + mstore(sub(name, 0x60), mBefore3) } } @@ -197,9 +184,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// ERC4337 Staking Logic /// ----------------------------------------------------------------------- - function addStake( - uint32 unstakeDelaySec - ) public payable virtual onlyOwner { + function addStake(uint32 unstakeDelaySec) public payable virtual onlyOwner { KeepFactory(keepTemplate.entryPoint()).addStake{value: msg.value}( unstakeDelaySec ); diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 8d939e34..07ccffc6 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -166,9 +166,9 @@ abstract contract KeepToken { } signer { } { - let m := mload(0x40) + let m := mload(0x40) // Load the free memory pointer. mstore(0x00, hash) - mstore(0x20, and(v, 0xff)) // `v`. + mstore(0x20, and(v, 0xff)) // `v`. Must be the lower 8 bits. mstore(0x40, r) // `r`. mstore(0x60, s) // `s`. let t := staticcall( diff --git a/src/extensions/metadata/PermissionRemoteFetcher.sol b/src/extensions/metadata/PermissionRemoteFetcher.sol index 2ea1f30e..1d3a5ef4 100644 --- a/src/extensions/metadata/PermissionRemoteFetcher.sol +++ b/src/extensions/metadata/PermissionRemoteFetcher.sol @@ -14,9 +14,10 @@ struct Permission { /// @notice Remote permission fetcher for ERC4337. contract PermissionRemoteFetcher { - function validatePermission( + function validateUserOp( UserOperation calldata userOp, - bytes32 hash + bytes32 hash, + uint256 missingAccountFunds ) public view virtual returns (uint256) { /*Permission memory permission = abi.decode(userOp.data, (Permission)); diff --git a/src/extensions/metadata/Fetcher.sol b/src/extensions/metadata/Validator.sol similarity index 65% rename from src/extensions/metadata/Fetcher.sol rename to src/extensions/metadata/Validator.sol index 9acd43ab..2b1fdb07 100644 --- a/src/extensions/metadata/Fetcher.sol +++ b/src/extensions/metadata/Validator.sol @@ -5,14 +5,16 @@ import {UserOperation} from "../../Keep.sol"; import {Owned} from "../utils/Owned.sol"; /// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. -contract Fetcher is Owned(tx.origin) { +contract Validator is Owned(tx.origin) { /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- - event PermissionRemoteFetcherSet(Fetcher indexed permissionRemoteFetcher); + event PermissionRemoteValidatorSet( + Validator indexed permissionRemoteValidator + ); - event URIRemoteFetcherSet(Fetcher indexed uriRemoteFetcher); + event URIRemoteValidatorSet(Validator indexed uriRemoteValidator); event EntryPointSet(address indexed entryPoint); @@ -20,11 +22,11 @@ contract Fetcher is Owned(tx.origin) { /// Remote Storage /// ----------------------------------------------------------------------- - Fetcher public permissionRemoteFetcher; + Validator internal permissionRemoteValidator; - Fetcher public uriRemoteFetcher; + Validator internal uriRemoteValidator; - address public entryPoint; + address internal entryPoint; /// ----------------------------------------------------------------------- /// Constructor @@ -40,19 +42,25 @@ contract Fetcher is Owned(tx.origin) { /// Permission Remote Logic /// ----------------------------------------------------------------------- - function validatePermission( + function validateUserOp( UserOperation calldata userOp, - bytes32 hash + bytes32 hash, + uint256 missingAccountFunds ) public view virtual returns (uint256) { - return permissionRemoteFetcher.validatePermission(userOp, hash); + return + permissionRemoteValidator.validateUserOp( + userOp, + hash, + missingAccountFunds + ); } - function setPermissionRemoteFetcher( - Fetcher _permissionRemoteFetcher + function setPermissionRemoteValidator( + Validator _permissionRemoteValidator ) public payable virtual onlyOwner { - permissionRemoteFetcher = _permissionRemoteFetcher; + permissionRemoteValidator = _permissionRemoteValidator; - emit PermissionRemoteFetcherSet(_permissionRemoteFetcher); + emit PermissionRemoteValidatorSet(_permissionRemoteValidator); } /// ----------------------------------------------------------------------- @@ -60,15 +68,15 @@ contract Fetcher is Owned(tx.origin) { /// ----------------------------------------------------------------------- function uri(uint256 id) public view virtual returns (string memory) { - return uriRemoteFetcher.uri(id); + return uriRemoteValidator.uri(id); } - function setURIRemoteFetcher( - Fetcher _uriRemoteFetcher + function setURIRemoteValidator( + Validator _uriRemoteValidator ) public payable virtual onlyOwner { - uriRemoteFetcher = _uriRemoteFetcher; + uriRemoteValidator = _uriRemoteValidator; - emit URIRemoteFetcherSet(_uriRemoteFetcher); + emit URIRemoteValidatorSet(_uriRemoteValidator); } /// ----------------------------------------------------------------------- diff --git a/test/Keep.t.sol b/test/Keep.t.sol index d1df346a..f3138a38 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -6,7 +6,7 @@ import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; /// @dev Extensions. -import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; +import {Validator} from "../src/extensions/metadata/Validator.sol"; /// @dev Mocks. import {MockERC20} from "@solady/test/utils/mocks/MockERC20.sol"; @@ -14,11 +14,12 @@ import {MockERC721} from "@solady/test/utils/mocks/MockERC721.sol"; import {MockERC1155} from "@solady/test/utils/mocks/MockERC1155.sol"; import {MockERC1271Wallet} from "@solady/test/utils/mocks/MockERC1271Wallet.sol"; import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver.sol"; +import {TestHelpers} from "./utils/helpers.sol"; /// @dev Test framework. import "@std/Test.sol"; -contract KeepTest is Keep(Keep(address(0))), Test { +contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- @@ -33,9 +34,9 @@ contract KeepTest is Keep(Keep(address(0))), Test { KeepFactory internal factory; - address internal mockUriFetcher; - Fetcher internal uriRemote; - Fetcher internal uriRemoteNew; + address internal mockUriValidator; + Validator internal uriRemote; + Validator internal uriRemoteNew; MockERC20 internal mockDai; MockERC721 internal mockNFT; @@ -47,12 +48,6 @@ contract KeepTest is Keep(Keep(address(0))), Test { uint256 internal immutable SIGNER_KEY = uint32(keep.execute.selector); - bytes32 internal constant name1 = - 0x5445535400000000000000000000000000000000000000000000000000000000; - - bytes32 internal constant name2 = - 0x5445535432000000000000000000000000000000000000000000000000000000; - bytes32 internal constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)" @@ -207,9 +202,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { function setUp() public payable { // Initialize templates. - mockUriFetcher = address(new Fetcher()); - - keep = new Keep(Keep(mockUriFetcher)); + mockUriValidator = address(new Validator()); mockDai = new MockERC20("Dai", "DAI", 18); mockNFT = new MockERC721(); @@ -225,7 +218,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { mock1155.mint(address(this), 1, 1, ""); // Create the factory. - factory = new KeepFactory(Keep(keep)); + factory = new KeepFactory(); // Create the Signer[] for setup. address[] memory setupSigners = new address[](2); @@ -238,10 +231,10 @@ contract KeepTest is Keep(Keep(address(0))), Test { // Initialize Keep from factory. // The factory is fully tested in KeepFactory.t.sol. - keepAddr = factory.determineKeep(name1); + (keepAddr, ) = factory.determineKeep(getName()); keep = Keep(keepAddr); - factory.deployKeep(name1, calls, setupSigners, 2); + factory.deployKeep(getName(), calls, setupSigners, 2); // Mint mock smart wallet a signer ID key. vm.prank(address(keep)); @@ -338,7 +331,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { assertEq(keep.uri(1), ""); vm.prank(address(alice)); - mockUriFetcher.setURIRemoteFetcher(uriRemoteNew); + mockUriValidator.setURIRemoteValidator(uriRemoteNew); vm.prank(address(alice)); @@ -371,11 +364,11 @@ contract KeepTest is Keep(Keep(address(0))), Test { /// @notice Check setup errors. function testCannotRepeatKeepSetup() public payable { - keepRepeat = new Keep(Keep(mockUriFetcher)); + keepRepeat = new Keep(Keep(mockUriValidator)); - keepAddrRepeat = factory.determineKeep(name2); + (keepAddrRepeat, ) = factory.determineKeep(getName()); keepRepeat = Keep(keepAddrRepeat); - factory.deployKeep(name2, calls, signers, 2); + factory.deployKeep(getName(), calls, signers, 2); vm.expectRevert(AlreadyInit.selector); keepRepeat.initialize(calls, signers, 2); @@ -383,12 +376,12 @@ contract KeepTest is Keep(Keep(address(0))), Test { function testCannotSetupWithZeroQuorum() public payable { vm.expectRevert(InvalidThreshold.selector); - factory.deployKeep(name2, calls, signers, 0); + factory.deployKeep(getName(), calls, signers, 0); } function testCannotSetupWithExcessiveQuorum() public payable { vm.expectRevert(QuorumOverSupply.selector); - factory.deployKeep(name2, calls, signers, 3); + factory.deployKeep(getName(), calls, signers, 3); } function testCannotSetupWithOutOfOrderSigners() public payable { @@ -396,8 +389,8 @@ contract KeepTest is Keep(Keep(address(0))), Test { outOfOrderSigners[0] = alice > bob ? alice : bob; outOfOrderSigners[1] = alice > bob ? bob : alice; - vm.expectRevert(InvalidSignature.selector); - factory.deployKeep(name2, calls, outOfOrderSigners, 2); + vm.expectRevert(Unauthorized.selector); + factory.deployKeep(getName(), calls, outOfOrderSigners, 2); } /// ----------------------------------------------------------------------- @@ -405,7 +398,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { /// ----------------------------------------------------------------------- function testName() public { - assertEq(keep.name(), string(abi.encodePacked(name1))); + assertEq(keep.name(), string(getName())); } // function testKeepNonce() public view { @@ -489,7 +482,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // assert(keep.totalSupply(0) == 1); // } - // function testTotalSignerSupply() public { + // function testTotalSignerSupply() public {f // assert(keep.totalSupply(SIGNER_KEY) == 3); // vm.prank(address(keep)); diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index a5c51e5e..cc941efe 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -3,55 +3,47 @@ pragma solidity ^0.8.4; import {Call, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; -import {Fetcher} from "../src/extensions/metadata/Fetcher.sol"; - +import {TestHelpers} from "./utils/helpers.sol"; import "@std/Test.sol"; -contract KeepFactoryTest is Test { - address keepAddr; - Keep keep; - KeepFactory factory; - address uriFetcher; - - address[] signers; +contract KeepFactoryTest is Test, TestHelpers { + KeepFactory immutable factory = new KeepFactory(); /// @dev Users. - address public immutable alice = address(0xa); - address public immutable bob = address(0xb); + address constant alice = address(0xa); + address constant bob = address(0xb); /// @dev Helpers. Call[] calls; - bytes32 name = - 0x5445535400000000000000000000000000000000000000000000000000000000; - - bytes32 name2 = - 0x5445535432000000000000000000000000000000000000000000000000000000; + // bytes name = + // 0x5445535400000000000000000000000000000000000000000000000000000000; /// @notice Set up the testing suite. - function setUp() public payable { - // Create the templates. - uriFetcher = address(new Fetcher()); - keep = new Keep(Keep(uriFetcher)); - // Create the factory. - factory = new KeepFactory(Keep(keep)); - // Create the signers. - signers.push(alice); - signers.push(bob); - } + function setUp() public payable {} function testDeploy() public payable { - factory.deployKeep(name, calls, signers, 2); + address[] memory signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + + factory.deployKeep(getName(), calls, signers, 2); } function testDetermination() public payable { // Check CREATE2 clones match expected outputs. - keepAddr = factory.determineKeep(name); - keep = Keep(keepAddr); - factory.deployKeep(name, calls, signers, 2); - assertEq(address(keep), keepAddr); + (address predicted, ) = factory.determineKeep(getName()); + + address[] memory signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + + address deployed = address( + factory.deployKeep(getName(), calls, signers, 2) + ); + assertEq(predicted, deployed); } } diff --git a/test/utils/helpers.sol b/test/utils/helpers.sol new file mode 100644 index 00000000..98d65f92 --- /dev/null +++ b/test/utils/helpers.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +contract TestHelpers { + function getName() public pure returns (bytes memory) { + return abi.encodePacked("TEST"); + } +} From 0ec17866a8e8dc9a14a75821e1c03df4d05d14e3 Mon Sep 17 00:00:00 2001 From: shiv Date: Mon, 28 Aug 2023 09:48:37 +0900 Subject: [PATCH 33/67] Update Keep.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 order revert before return in sigs check for 4337 --- src/Keep.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 30fbe7b0..532a06d2 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -524,15 +524,15 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { for (uint256 i; i < threshold; ) { address signer = _recoverSigner(hash, sigs[i]); - // If not keyholding signer, `SIG_VALIDATION_FAILED`. - if (balanceOf[signer][SIGN_KEY] == 0) return 1; - // Check against duplicates. if (previous >= signer) revert Unauthorized(); // Memo signature for next iteration until quorum. previous = signer; + // If not keyholding signer, `SIG_VALIDATION_FAILED`. + if (balanceOf[signer][SIGN_KEY] == 0) return 1; + // An array can't have a total length // larger than the max uint256 value. unchecked { From 5f1f12ffceef1db0c4934374437a02b74bdbe98d Mon Sep 17 00:00:00 2001 From: shiv Date: Mon, 28 Aug 2023 11:09:32 +0900 Subject: [PATCH 34/67] =?UTF-8?q?=F0=9F=8E=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎎 nested erc1271 --- src/Keep.sol | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Keep.sol b/src/Keep.sol index 532a06d2..4f753bc9 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -445,7 +445,25 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // This also confirms non-zero signer. if (balanceOf[_recoverSigner(hash, signature)][SIGN_KEY] != 0) return this.isValidSignature.selector; - else return 0xffffffff; + + // Fallback to nested contract signatures. + address user; + assembly { + // Extract first 20 bytes into address. + let word := mload(add(signature, 0x20)) + user := shr(96, word) + // Update `signature` to remaining bytes. + mstore(signature, sub(mload(signature), 20)) + mstore(add(signature, 0x20), add(add(signature, 0x20), 20)) + } + + if (balanceOf[user][SIGN_KEY] != 0) + if (Keep(user). + isValidSignature(hash, signature) == this.isValidSignature.selector) + return this.isValidSignature.selector; + + // Otherwise, return error. + return 0xffffffff; } /// ----------------------------------------------------------------------- From 58d9d2e49137462fb3d447558740f81d4431be8c Mon Sep 17 00:00:00 2001 From: shiv Date: Tue, 29 Aug 2023 00:20:44 +0900 Subject: [PATCH 35/67] // assemble this --- src/KeepFactory.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 98bb9b3b..69da4514 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -121,7 +121,6 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { bytes memory name ) public view virtual returns (address keep, bool deployed) { address implementation = address(keepTemplate); - address deployer = address(this); assembly { // Compute the boundaries of the data and cache the memory slots around it. @@ -164,7 +163,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { // Compute and store the bytecode hash. mstore(0x35, keccak256(sub(name, 0x4c), add(extraLength, 0x6c))) mstore8(0x00, 0xff) // Write the prefix. - mstore(0x01, shl(96, deployer)) + mstore(0x01, shl(96, address())) mstore(0x15, name) keep := keccak256(0x00, 0x55) deployed := extcodesize(keep) From 19f14d5a53529a5a9dc651b359619f9783a98b30 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:42:08 +0000 Subject: [PATCH 36/67] =?UTF-8?q?=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 167 +++++++++-------------------------------- src/Keep.sol | 9 ++- src/KeepFactory.sol | 102 +++++++++++++------------ test/Keep.t.sol | 14 ++-- test/KeepFactory.t.sol | 11 +-- test/utils/helpers.sol | 8 +- 6 files changed, 112 insertions(+), 199 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index cd2390b1..6acac8db 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,134 +1,41 @@ -KaliTest:testBurnProposal() (gas: 309077) -KaliTest:testCallProposal() (gas: 301236) -KaliTest:testDaoURI() (gas: 12365) -KaliTest:testDeleteProposal() (gas: 386781) -KaliTest:testDeploy() (gas: 162214) -KaliTest:testDetermination() (gas: 10624) -KaliTest:testERC1155BatchReceiver() (gas: 44630) -KaliTest:testERC1155Receiver() (gas: 39762) -KaliTest:testERC20Receiver() (gas: 19850) -KaliTest:testERC721Receiver() (gas: 39660) -KaliTest:testExtensionBurn() (gas: 125045) -KaliTest:testExtensionDeleteProposal() (gas: 311172) -KaliTest:testExtensionMint() (gas: 128533) -KaliTest:testExtensionRelay() (gas: 102506) -KaliTest:testExtensionSetExtension(address,bool) (runs: 256, μ: 51002, ~: 45491) -KaliTest:testExtensionSetTransferability(bool) (runs: 256, μ: 89112, ~: 83360) -KaliTest:testExtensionSetURI(string) (runs: 256, μ: 91401, ~: 97656) -KaliTest:testExtensionUpdateGovSettings() (gas: 78959) -KaliTest:testExtensionUpdateGovSettingsInvalid() (gas: 126344) -KaliTest:testFailDeploy() (gas: 8937393460516749706) -KaliTest:testFailNotAuthorizedExtension() (gas: 18102) -KaliTest:testFailProposalCreation() (gas: 133307) -KaliTest:testFailProposalRepeatVoting() (gas: 310819) -KaliTest:testGracePeriod() (gas: 10365) -KaliTest:testGracePeriodProposal() (gas: 266211) -KaliTest:testMintProposal() (gas: 316290) -KaliTest:testMultiBurnProposal() (gas: 356252) -KaliTest:testMultiCallProposal() (gas: 340299) -KaliTest:testMultiMintProposal() (gas: 512652) -KaliTest:testName() (gas: 9773) -KaliTest:testPauseProposal() (gas: 296167) -KaliTest:testProposal() (gas: 303651) -KaliTest:testProposalCancellation() (gas: 168217) -KaliTest:testProposalCreation() (gas: 131162) -KaliTest:testProposalRepeatProcessingFail() (gas: 302123) -KaliTest:testProposalSponsorship() (gas: 409053) -KaliTest:testProposalVoteStored() (gas: 268301) -KaliTest:testQuorum() (gas: 10460) -KaliTest:testQuorumProposal() (gas: 266362) -KaliTest:testSupermajority() (gas: 10415) -KaliTest:testSupermajorityProposal() (gas: 266387) -KaliTest:testSupportsInterface() (gas: 10845) -KaliTest:testToken() (gas: 10428) -KaliTest:testTokenId() (gas: 8257) -KaliTest:testTypeProposal() (gas: 291531) -KaliTest:testURIProposal() (gas: 272592) -KaliTest:testVotingPeriod() (gas: 10444) -KaliTest:testVotingPeriodProposal() (gas: 266523) -KeepFactoryTest:testDeploy() (gas: 178028) -KeepFactoryTest:testDetermination() (gas: 206990) -KeepTest:testBalanceOf() (gas: 116152) -KeepTest:testBalanceOfBatch() (gas: 125417) -KeepTest:testBalanceOfSigner() (gas: 49599) -KeepTest:testBurn() (gas: 125149) -KeepTest:testBurnSigner() (gas: 28745) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390794) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247550) -KeepTest:testCannotBurnUnderflow() (gas: 116287) -KeepTest:testCannotExecuteWithImproperSignatures() (gas: 44658) -KeepTest:testCannotExecuteWithNullSignatures() (gas: 32330) -KeepTest:testCannotExecuteWithSignaturesOutOfOrder() (gas: 42001) -KeepTest:testCannotExecuteWithSignaturesRepeated() (gas: 37789) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) -KeepTest:testCannotMintOverflowExecuteID() (gas: 65940) -KeepTest:testCannotMintOverflowSupply() (gas: 261379) -KeepTest:testCannotMintToUnsafeAddress() (gas: 313707) -KeepTest:testCannotMintToZeroAddress() (gas: 119138) -KeepTest:testCannotRepeatKeepSetup() (gas: 4220655) -KeepTest:testCannotSetTransferability(address,uint256) (runs: 256, μ: 24778, ~: 24778) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83492) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102766) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83486) -KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15192, ~: 15192) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15278, ~: 15278) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380892) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241982) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154659, ~: 154659) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180382, ~: 183984) -KeepTest:testExecuteCreateCall() (gas: 1500110) -KeepTest:testExecuteDelegateCall() (gas: 46781) -KeepTest:testExecuteEthCall() (gas: 72575) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68201) -KeepTest:testExecuteTokenCallWithRole() (gas: 140280) -KeepTest:testExecuteTokenCallWithSignatures() (gas: 56863) -KeepTest:testIdKeyRole() (gas: 230237) -KeepTest:testKeepNonce() (gas: 10396) -KeepTest:testKeepTokenApprove(address,address) (runs: 256, μ: 29557, ~: 29528) -KeepTest:testKeepTokenBatchTransferByOperator() (gas: 444672) -KeepTest:testKeepTokenBatchTransferByOwner() (gas: 419371) -KeepTest:testKeepTokenDelegateBySig(address,uint256,uint256) (runs: 256, μ: 222603, ~: 228971) -KeepTest:testKeepTokenDelegation(address,address,uint256,uint256) (runs: 256, μ: 255469, ~: 261812) -KeepTest:testKeepTokenDelegationBalanceByTransfer(address,address,uint256,uint256) (runs: 256, μ: 269142, ~: 276114) -KeepTest:testKeepTokenInitDelegationBalance(address,uint256,uint256) (runs: 256, μ: 124776, ~: 129100) -KeepTest:testKeepTokenPermit(address,bool) (runs: 256, μ: 53483, ~: 47809) -KeepTest:testMint(uint256,uint256,bytes) (runs: 256, μ: 121707, ~: 125302) -KeepTest:testMintCoreIdKey(uint256) (runs: 256, μ: 121149, ~: 126113) -KeepTest:testMintExecuteIdKey() (gas: 56215) -KeepTest:testName() (gas: 9857) -KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88482) -KeepTest:testQuorum() (gas: 23966) -KeepTest:testReceiveBatchERC1155() (gas: 44337) -KeepTest:testReceiveERC1155() (gas: 40945) -KeepTest:testReceiveERC721() (gas: 47587) -KeepTest:testReceiveETH() (gas: 13046) -KeepTest:testSetURI(address) (runs: 256, μ: 53169, ~: 53169) -KeepTest:testSignerSetup() (gas: 29085) -KeepTest:testSupportsInterface() (gas: 14024) -KeepTest:testTotalSignerSupply() (gas: 49190) -KeepTest:testTotalSupply() (gas: 115804) -KeepTest:testUserNonce() (gas: 10517) -MulticallableTest:testMulticallableBenchmark() (gas: 29217) -MulticallableTest:testMulticallableOriginalBenchmark() (gas: 38474) -MulticallableTest:testMulticallablePreservesMsgSender() (gas: 11022) -MulticallableTest:testMulticallablePreservesMsgValue() (gas: 37542) -MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39315) -MulticallableTest:testMulticallableReturnDataIsProperlyEncoded() (gas: 11617) -MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9673, ~: 7390) -MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 11718, ~: 11718) -MulticallableTest:testMulticallableRevertWithCustomError() (gas: 11725) -MulticallableTest:testMulticallableRevertWithMessage() (gas: 13474) -MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 14121, ~: 14156) -MulticallableTest:testMulticallableRevertWithNothing() (gas: 11646) -MulticallableTest:testMulticallableWithNoData() (gas: 6266) +KeepFactoryTest:testDeploy() (gas: 166552) +KeepFactoryTest:testDetermination() (gas: 170970) +KeepTest:testBalanceOf() (gas: 116195) +KeepTest:testBalanceOfBatch() (gas: 125382) +KeepTest:testBalanceOfSigner() (gas: 49576) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390774) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247532) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) +KeepTest:testCannotMintToZeroAddress() (gas: 119147) +KeepTest:testCannotRepeatKeepSetup() (gas: 4778346) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83720) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103026) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83693) +KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15148, ~: 15148) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15234, ~: 15234) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380828) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 242008) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154663, ~: 154663) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180760, ~: 184005) +KeepTest:testExecuteDelegateCall() (gas: 46597) +KeepTest:testExecuteEthCall() (gas: 72410) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68063) +KeepTest:testName() (gas: 10064) +KeepTest:testNoKeepKeyCollision() (gas: 208) +KeepTest:testNonceIncrementAfterExecute() (gas: 88132) +KeepTest:testQuorum() (gas: 23961) +KeepTest:testReceiveBatchERC1155() (gas: 44271) +MulticallableTest:testMulticallableBenchmark() (gas: 28510) +MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) +MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) +MulticallableTest:testMulticallablePreservesMsgValue() (gas: 37455) +MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39182) +MulticallableTest:testMulticallableRevertWithCustomError() (gas: 11730) +MulticallableTest:testMulticallableRevertWithNothing() (gas: 11651) +MulticallableTest:testMulticallableWithNoData() (gas: 6293) OwnedTest:testCallFunctionAsNonOwner() (gas: 11280) -OwnedTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 16217, ~: 16236) +OwnedTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 16236, ~: 16236) OwnedTest:testCallFunctionAsOwner() (gas: 10432) OwnedTest:testERC165Support() (gas: 5479) OwnedTest:testTransferOwnership() (gas: 13097) -OwnedTest:testTransferOwnership(address) (runs: 256, μ: 13170, ~: 13189) -ReentrancyGuardTest:invariantReentrancyStatusAlways1() (runs: 256, calls: 3840, reverts: 268) -ReentrancyGuardTest:testFailUnprotectedCall() (gas: 43432) -ReentrancyGuardTest:testNoReentrancy() (gas: 5354) -ReentrancyGuardTest:testProtectedCall() (gas: 30985) \ No newline at end of file +OwnedTest:testTransferOwnership(address) (runs: 256, μ: 13189, ~: 13189) \ No newline at end of file diff --git a/src/Keep.sol b/src/Keep.sol index 4f753bc9..cd786a5a 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -439,7 +439,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function isValidSignature( bytes32 hash, - bytes calldata signature + bytes memory signature ) public view virtual returns (bytes4) { // Check SIGN_KEY balance. // This also confirms non-zero signer. @@ -458,9 +458,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } if (balanceOf[user][SIGN_KEY] != 0) - if (Keep(user). - isValidSignature(hash, signature) == this.isValidSignature.selector) - return this.isValidSignature.selector; + if ( + Keep(user).isValidSignature(hash, signature) == + this.isValidSignature.selector + ) return this.isValidSignature.selector; // Otherwise, return error. return 0xffffffff; diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 69da4514..38e6e20d 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -17,14 +17,13 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// Custom Errors /// ----------------------------------------------------------------------- - /// @dev Unable to deploy the clone. error DeploymentFailed(); /// ----------------------------------------------------------------------- /// Immutables /// ----------------------------------------------------------------------- - Keep public immutable keepTemplate; + Keep internal immutable keepTemplate; /// ----------------------------------------------------------------------- /// Constructor @@ -39,64 +38,63 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { /// ----------------------------------------------------------------------- function deployKeep( - bytes memory name, // create2 salt. + bytes32 name, // create2 salt. Call[] calldata calls, address[] calldata signers, uint256 threshold ) public payable virtual returns (Keep keep) { + bytes memory data = abi.encodePacked(name); address implementation = address(keepTemplate); assembly { // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(name, 0x60)) // subtract 96 bytes from data pointer - let mBefore2 := mload(sub(name, 0x40)) // subtract 64 bytes from data pointer - let mBefore1 := mload(sub(name, 0x20)) // subtract 32 bytes from data pointer - let dataLength := mload(name) // the length of the data is in the first word for bytes - - if gt(dataLength, 0x20) { - revert(0, 0) - } + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) - let dataEnd := add(add(name, 0x20), dataLength) // skip over the length field to the actual data and add the length of the data to get the end of the data - let mAfter1 := mload(dataEnd) // save whatever is in memory after the data + // Do a out-of-gas revert if `extraLength` is more than 2 bytes (super unlikely). + returndatacopy( + returndatasize(), + returndatasize(), + gt(dataLength, 0xfffd) + ) // +2 bytes for telling how much data there is appended to the call. let extraLength := add(dataLength, 2) // Write the bytecode before the data. - mstore(name, 0x5af43d3d93803e606057fd5bf3) + mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(sub(name, 0x0d), implementation) // subtract 13 bytes from data pointer + mstore(sub(data, 0x0d), implementation) // Write the rest of the bytecode. mstore( - sub(name, 0x21), // subtract 33 bytes from data pointer to get the offset + sub(data, 0x21), or( - // - shl(0x48, extraLength), // shift 72 bytes left and bitwise or with extraLength + shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 ) ) // `keccak256("ReceiveETH(uint256)")`. mstore( - sub(name, 0x3a), // subtract 58 bytes from data pointer to get the offset + sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) mstore( - sub(name, 0x5a), // subtract 90 bytes from data pointer to get the offset + sub(data, 0x5a), or( - shl(0x78, add(extraLength, 0x62)), // shift 120 bytes left and bitwise or with extraLength + 98 + shl(0x78, add(extraLength, 0x62)), 0x6100003d81600a3d39f336602c57343d527f ) ) - mstore(dataEnd, shl(0xf0, extraLength)) // shift 240 bytes left and store at dataEnd + mstore(dataEnd, shl(0xf0, extraLength)) - // Create the Keep. - // value, offset, size, salt - // offset is data - 76 - // size is extraLength + 108 - keep := create2(0, sub(name, 0x4c), add(extraLength, 0x6c), name) + // Create the instance. + keep := create2(0, sub(data, 0x4c), add(extraLength, 0x6c), name) - // If `keep` is zero, revert. + // If `instance` is zero, revert. if iszero(keep) { // Store the function selector of `DeploymentFailed()`. mstore(0x00, 0x30116425) @@ -106,10 +104,10 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(name, dataLength) - mstore(sub(name, 0x20), mBefore1) - mstore(sub(name, 0x40), mBefore2) - mstore(sub(name, 0x60), mBefore3) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) } keep.initialize{value: msg.value}(calls, signers, threshold); @@ -118,29 +116,37 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { } function determineKeep( - bytes memory name + bytes32 name ) public view virtual returns (address keep, bool deployed) { + bytes memory data = abi.encodePacked(name); address implementation = address(keepTemplate); assembly { // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(name, 0x60)) - let mBefore2 := mload(sub(name, 0x40)) - let mBefore1 := mload(sub(name, 0x20)) - let dataLength := mload(name) - let dataEnd := add(add(name, 0x20), dataLength) + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) let mAfter1 := mload(dataEnd) + // Do a out-of-gas revert if `extraLength` is more than 2 bytes (super unlikely). + returndatacopy( + returndatasize(), + returndatasize(), + gt(dataLength, 0xfffd) + ) + // +2 bytes for telling how much data there is appended to the call. let extraLength := add(dataLength, 2) // Write the bytecode before the data. - mstore(name, 0x5af43d3d93803e606057fd5bf3) + mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the address of the implementation. - mstore(sub(name, 0x0d), implementation) + mstore(sub(data, 0x0d), implementation) // Write the rest of the bytecode. mstore( - sub(name, 0x21), + sub(data, 0x21), or( shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 @@ -148,11 +154,11 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { ) // `keccak256("ReceiveETH(uint256)")`. mstore( - sub(name, 0x3a), + sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) mstore( - sub(name, 0x5a), + sub(data, 0x5a), or( shl(0x78, add(extraLength, 0x62)), 0x6100003d81600a3d39f336602c57343d527f @@ -161,7 +167,7 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { mstore(dataEnd, shl(0xf0, extraLength)) // Compute and store the bytecode hash. - mstore(0x35, keccak256(sub(name, 0x4c), add(extraLength, 0x6c))) + mstore(0x35, keccak256(sub(data, 0x4c), add(extraLength, 0x6c))) mstore8(0x00, 0xff) // Write the prefix. mstore(0x01, shl(96, address())) mstore(0x15, name) @@ -172,10 +178,10 @@ contract KeepFactory is Multicallable, Owned(tx.origin) { // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) - mstore(name, dataLength) - mstore(sub(name, 0x20), mBefore1) - mstore(sub(name, 0x40), mBefore2) - mstore(sub(name, 0x60), mBefore3) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) } } diff --git a/test/Keep.t.sol b/test/Keep.t.sol index f3138a38..8bc48561 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -14,7 +14,7 @@ import {MockERC721} from "@solady/test/utils/mocks/MockERC721.sol"; import {MockERC1155} from "@solady/test/utils/mocks/MockERC1155.sol"; import {MockERC1271Wallet} from "@solady/test/utils/mocks/MockERC1271Wallet.sol"; import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver.sol"; -import {TestHelpers} from "./utils/helpers.sol"; +import {mockName, TestHelpers} from "./utils/helpers.sol"; /// @dev Test framework. import "@std/Test.sol"; @@ -366,9 +366,9 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { function testCannotRepeatKeepSetup() public payable { keepRepeat = new Keep(Keep(mockUriValidator)); - (keepAddrRepeat, ) = factory.determineKeep(getName()); + (keepAddrRepeat, ) = factory.determineKeep(mockName); keepRepeat = Keep(keepAddrRepeat); - factory.deployKeep(getName(), calls, signers, 2); + factory.deployKeep(mockName, calls, signers, 2); vm.expectRevert(AlreadyInit.selector); keepRepeat.initialize(calls, signers, 2); @@ -376,12 +376,12 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { function testCannotSetupWithZeroQuorum() public payable { vm.expectRevert(InvalidThreshold.selector); - factory.deployKeep(getName(), calls, signers, 0); + factory.deployKeep(mockName, calls, signers, 0); } function testCannotSetupWithExcessiveQuorum() public payable { vm.expectRevert(QuorumOverSupply.selector); - factory.deployKeep(getName(), calls, signers, 3); + factory.deployKeep(mockName, calls, signers, 3); } function testCannotSetupWithOutOfOrderSigners() public payable { @@ -390,7 +390,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { outOfOrderSigners[1] = alice > bob ? bob : alice; vm.expectRevert(Unauthorized.selector); - factory.deployKeep(getName(), calls, outOfOrderSigners, 2); + factory.deployKeep(mockName, calls, outOfOrderSigners, 2); } /// ----------------------------------------------------------------------- @@ -398,7 +398,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { /// ----------------------------------------------------------------------- function testName() public { - assertEq(keep.name(), string(getName())); + assertEq(keep.name(), string(abi.encodePacked(getName()))); } // function testKeepNonce() public view { diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index cc941efe..4b941f56 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import {Call, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; -import {TestHelpers} from "./utils/helpers.sol"; +import {mockName, TestHelpers} from "./utils/helpers.sol"; import "@std/Test.sol"; contract KeepFactoryTest is Test, TestHelpers { @@ -18,9 +18,6 @@ contract KeepFactoryTest is Test, TestHelpers { Call[] calls; - // bytes name = - // 0x5445535400000000000000000000000000000000000000000000000000000000; - /// @notice Set up the testing suite. function setUp() public payable {} @@ -30,19 +27,19 @@ contract KeepFactoryTest is Test, TestHelpers { signers[0] = alice; signers[1] = bob; - factory.deployKeep(getName(), calls, signers, 2); + factory.deployKeep(mockName, calls, signers, 2); } function testDetermination() public payable { // Check CREATE2 clones match expected outputs. - (address predicted, ) = factory.determineKeep(getName()); + (address predicted, ) = factory.determineKeep(mockName); address[] memory signers = new address[](2); signers[0] = alice; signers[1] = bob; address deployed = address( - factory.deployKeep(getName(), calls, signers, 2) + factory.deployKeep(mockName, calls, signers, 2) ); assertEq(predicted, deployed); } diff --git a/test/utils/helpers.sol b/test/utils/helpers.sol index 98d65f92..ec09f831 100644 --- a/test/utils/helpers.sol +++ b/test/utils/helpers.sol @@ -1,8 +1,10 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +bytes32 constant mockName = bytes32(abi.encodePacked("Yo")); + contract TestHelpers { - function getName() public pure returns (bytes memory) { - return abi.encodePacked("TEST"); + function getName() public pure returns (bytes32) { + return bytes32(abi.encodePacked("TEST")); } } From c8e3b707e8df82c3f1d1232fd00e6790feec8358 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:13:49 +0000 Subject: [PATCH 37/67] =?UTF-8?q?=E2=9A=A1=20Optimize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 64 +- pnpm-lock.yaml | 4 + src/Clone.sol | 147 --- src/Conditions.sol | 25 - src/Keep.sol | 85 +- src/KeepFactory.sol | 6 +- src/KeepToken.sol | 15 - src/extensions/dao/Kali.sol | 906 ------------------ src/extensions/dao/KaliFactory.sol | 87 -- src/extensions/dao/utils/KaliExtension.sol | 7 - src/extensions/storage/DataRoom.sol | 137 --- src/extensions/utils/KeepTokenManager.sol | 2 +- .../PermissionRemoteFetcher.sol | 0 .../URIRemoteFetcher.sol | 6 +- .../{metadata => validate}/Validator.sol | 4 +- src/utils/LibClone.sol | 151 --- .../utils/Owned.sol => utils/Ownable.sol} | 3 +- test/Keep.t.sol | 2 +- test/{Owned.t.sol => Ownable.t.sol} | 22 +- .../mocks/{MockOwned.sol => MockOwnable.sol} | 4 +- 20 files changed, 109 insertions(+), 1568 deletions(-) delete mode 100644 src/Clone.sol delete mode 100644 src/Conditions.sol delete mode 100644 src/extensions/dao/Kali.sol delete mode 100644 src/extensions/dao/KaliFactory.sol delete mode 100644 src/extensions/dao/utils/KaliExtension.sol delete mode 100644 src/extensions/storage/DataRoom.sol rename src/extensions/{metadata => validate}/PermissionRemoteFetcher.sol (100%) rename src/extensions/{metadata => validate}/URIRemoteFetcher.sol (97%) rename src/extensions/{metadata => validate}/Validator.sol (96%) delete mode 100644 src/utils/LibClone.sol rename src/{extensions/utils/Owned.sol => utils/Ownable.sol} (93%) rename test/{Owned.t.sol => Ownable.t.sol} (59%) rename test/utils/mocks/{MockOwned.sol => MockOwnable.sol} (61%) diff --git a/.gas-snapshot b/.gas-snapshot index 6acac8db..768a0ab4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,30 +1,30 @@ -KeepFactoryTest:testDeploy() (gas: 166552) -KeepFactoryTest:testDetermination() (gas: 170970) -KeepTest:testBalanceOf() (gas: 116195) -KeepTest:testBalanceOfBatch() (gas: 125382) -KeepTest:testBalanceOfSigner() (gas: 49576) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390774) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247532) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) -KeepTest:testCannotMintToZeroAddress() (gas: 119147) -KeepTest:testCannotRepeatKeepSetup() (gas: 4778346) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83720) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103026) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83693) -KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15148, ~: 15148) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15234, ~: 15234) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380828) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 242008) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154663, ~: 154663) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180760, ~: 184005) -KeepTest:testExecuteDelegateCall() (gas: 46597) -KeepTest:testExecuteEthCall() (gas: 72410) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68063) -KeepTest:testName() (gas: 10064) +KeepFactoryTest:testDeploy() (gas: 166574) +KeepFactoryTest:testDetermination() (gas: 170992) +KeepTest:testBalanceOf() (gas: 116128) +KeepTest:testBalanceOfBatch() (gas: 125425) +KeepTest:testBalanceOfSigner() (gas: 49553) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390728) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247486) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) +KeepTest:testCannotMintToZeroAddress() (gas: 119101) +KeepTest:testCannotRepeatKeepSetup() (gas: 4745043) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83742) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103048) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83715) +KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15103, ~: 15103) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15189, ~: 15189) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179916, ~: 183872) +KeepTest:testExecuteDelegateCall() (gas: 46549) +KeepTest:testExecuteEthCall() (gas: 72362) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68015) +KeepTest:testName() (gas: 10086) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 88132) -KeepTest:testQuorum() (gas: 23961) -KeepTest:testReceiveBatchERC1155() (gas: 44271) +KeepTest:testNonceIncrementAfterExecute() (gas: 88039) +KeepTest:testQuorum() (gas: 23893) +KeepTest:testReceiveBatchERC1155() (gas: 44293) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) @@ -33,9 +33,9 @@ MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39182) MulticallableTest:testMulticallableRevertWithCustomError() (gas: 11730) MulticallableTest:testMulticallableRevertWithNothing() (gas: 11651) MulticallableTest:testMulticallableWithNoData() (gas: 6293) -OwnedTest:testCallFunctionAsNonOwner() (gas: 11280) -OwnedTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 16236, ~: 16236) -OwnedTest:testCallFunctionAsOwner() (gas: 10432) -OwnedTest:testERC165Support() (gas: 5479) -OwnedTest:testTransferOwnership() (gas: 13097) -OwnedTest:testTransferOwnership(address) (runs: 256, μ: 13189, ~: 13189) \ No newline at end of file +OwnableTest:testCallFunctionAsNonOwner() (gas: 9038) +OwnableTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 13975, ~: 13994) +OwnableTest:testCallFunctionAsOwner() (gas: 8296) +OwnableTest:testERC165Support() (gas: 3373) +OwnableTest:testTransferOwnership() (gas: 10855) +OwnableTest:testTransferOwnership(address) (runs: 256, μ: 10928, ~: 10947) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ac0d05c..19fcfd5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + devDependencies: prettier: specifier: ^3.0.1 diff --git a/src/Clone.sol b/src/Clone.sol deleted file mode 100644 index 4e5eabd3..00000000 --- a/src/Clone.sol +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: BSD - -pragma solidity ^0.8.4; - -/// @title ClonesWithImmutableArgs -/// @author wighawag, zefram.eth -/// @notice Enables creating clone contracts with immutable args -library ClonesWithImmutableArgs { - error CreateFail(); - - /// @notice Creates a clone proxy of the implementation contract, with immutable args - /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length - /// @param implementation The implementation contract to clone - /// @param data Encoded immutable args - /// @return instance The address of the created clone - function clone( - address implementation, - bytes memory data - ) internal returns (address payable instance) { - // unrealistic for memory ptr or data length to exceed 256 bits - unchecked { - uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call - uint256 creationSize = 0x41 + extraLength; // 65 + extraLength - uint256 runSize = creationSize - 10; - uint256 dataPtr; - uint256 ptr; - // solhint-disable-next-line no-inline-assembly - assembly { - ptr := mload(0x40) - - // ------------------------------------------------------------------------------------------------------------- - // CREATION (10 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // 61 runtime | PUSH2 runtime (r) | r | – - mstore( - ptr, - 0x6100000000000000000000000000000000000000000000000000000000000000 - ) - mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits) - - // creation size = 0a - // 3d | RETURNDATASIZE | 0 r | – - // 81 | DUP2 | r 0 r | – - // 60 creation | PUSH1 creation (c) | c r 0 r | – - // 3d | RETURNDATASIZE | 0 c r 0 r | – - // 39 | CODECOPY | 0 r | [0-runSize): runtime code - // f3 | RETURN | | [0-runSize): runtime code - - // ------------------------------------------------------------------------------------------------------------- - // RUNTIME (55 bytes + extraLength) - // ------------------------------------------------------------------------------------------------------------- - - // 3d | RETURNDATASIZE | 0 | – - // 3d | RETURNDATASIZE | 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 0 | – - // 36 | CALLDATASIZE | cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – - // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata - // 61 | PUSH2 extra | extra 0 0 0 0 | [0, cds) = calldata - mstore( - add(ptr, 0x03), - 0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000 - ) - mstore(add(ptr, 0x13), shl(240, extraLength)) - - // 60 0x37 | PUSH1 0x37 | 0x37 extra 0 0 0 0 | [0, cds) = calldata // 0x37 (55) is runtime size - data - // 36 | CALLDATASIZE | cds 0x37 extra 0 0 0 0 | [0, cds) = calldata - // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 61 extra | PUSH2 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - mstore( - add(ptr, 0x15), - 0x6037363936610000000000000000000000000000000000000000000000000000 - ) - mstore(add(ptr, 0x1b), shl(240, extraLength)) - - // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 3d | RETURNDATASIZE | 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 73 addr | PUSH20 0x123… | addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - mstore( - add(ptr, 0x1d), - 0x013d730000000000000000000000000000000000000000000000000000000000 - ) - mstore(add(ptr, 0x20), shl(0x60, implementation)) - - // 5a | GAS | gas addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData - // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData - // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData - // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) - // 60 0x35 | PUSH1 0x35 | 0x35 sucess 0 rds | [0, rds) = return data - // 57 | JUMPI | 0 rds | [0, rds) = return data - // fd | REVERT | – | [0, rds) = return data - // 5b | JUMPDEST | 0 rds | [0, rds) = return data - // f3 | RETURN | – | [0, rds) = return data - mstore( - add(ptr, 0x34), - 0x5af43d3d93803e603557fd5bf300000000000000000000000000000000000000 - ) - } - - // ------------------------------------------------------------------------------------------------------------- - // APPENDED DATA (Accessible from extcodecopy) - // (but also send as appended data to the delegatecall) - // ------------------------------------------------------------------------------------------------------------- - - extraLength -= 2; - uint256 counter = extraLength; - uint256 copyPtr = ptr + 0x41; - // solhint-disable-next-line no-inline-assembly - assembly { - dataPtr := add(data, 32) - } - for (; counter >= 32; counter -= 32) { - // solhint-disable-next-line no-inline-assembly - assembly { - mstore(copyPtr, mload(dataPtr)) - } - - copyPtr += 32; - dataPtr += 32; - } - uint256 mask = ~(256 ** (32 - counter) - 1); - // solhint-disable-next-line no-inline-assembly - assembly { - mstore(copyPtr, and(mload(dataPtr), mask)) - } - copyPtr += counter; - // solhint-disable-next-line no-inline-assembly - assembly { - mstore(copyPtr, shl(240, extraLength)) - } - // solhint-disable-next-line no-inline-assembly - assembly { - instance := create(0, ptr, creationSize) - } - if (instance == address(0)) { - revert CreateFail(); - } - } - } -} diff --git a/src/Conditions.sol b/src/Conditions.sol deleted file mode 100644 index 846158bf..00000000 --- a/src/Conditions.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -contract Conditional { - // holds core key so authorized to call any function on keep - // would it be recursive if we call 'relay' on keep with this contract as the recipient and this contract calls 'relay' on keep with the calldata in it - // { - // conditions: [ - // { - // type: "price", - // amount: 1000 // in usd - // address: '0xfucku' - // } - // ], - // actions: [ - // { - // op - // to - // value - // data - // - // } - // ] - // } -} diff --git a/src/Keep.sol b/src/Keep.sol index cd786a5a..b57be7b7 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -100,7 +100,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @dev Core ID key permission. - uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId); + uint256 internal constant CORE_KEY = uint32(type(KeepToken).interfaceId); /// @dev External validation for ERC1155 `uri()` and ERC4337 permissioning. Keep internal immutable validator; @@ -305,7 +305,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Start zero in loop to ensure ascending addresses. address previous; - // Validation is length of quorum threshold. + // Validation runs up to quorum threshold. uint256 threshold = quorum; // Store outside loop for gas optimization. @@ -386,13 +386,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { 0 ) returndatacopy(0, 0, returndatasize()) - switch success - case 0 { + if iszero(success) { revert(0, returndatasize()) } - default { - return(0, returndatasize()) - } + return(0, returndatasize()) } } else if (op == Operation.delegatecall) { /// @solidity memory-safe-assembly @@ -406,29 +403,44 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { 0 ) returndatacopy(0, 0, returndatasize()) - switch success - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) + if iszero(success) { + revert(0, 0) } + return(0, returndatasize()) } } else if (op == Operation.create) { /// @solidity memory-safe-assembly assembly { - if iszero(create(value, add(data, 0x20), mload(data))) { + let createdAddress := create( + value, + add(data, 0x20), + mload(data) + ) + if iszero(createdAddress) { revert(0, 0) } + mstore(0, createdAddress) + return(0, 0x20) } } else { - bytes32 salt = keccak256(abi.encodePacked(to)); - /// @solidity memory-safe-assembly assembly { - if iszero(create2(value, add(data, 0x20), mload(data), salt)) { + if lt(mload(data), 0x20) { revert(0, 0) } + let salt := mload(add(data, 0x20)) + let len := sub(mload(data), 0x20) + let created := create2( + value, + add(add(data, 0x20), 0x20), + len, + salt + ) + if iszero(created) { + revert(0, 0) + } + mstore(0, created) + return(0, 0x20) } } } @@ -446,13 +458,19 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (balanceOf[_recoverSigner(hash, signature)][SIGN_KEY] != 0) return this.isValidSignature.selector; - // Fallback to nested contract signatures. + // Fallback to check nested contract signatures. address user; assembly { - // Extract first 20 bytes into address. + // Ensure signature has at least 20 bytes for an address. + if lt(mload(signature), 20) { + revert(0, 0) + } + + // Extract the first 20 bytes into the address. let word := mload(add(signature, 0x20)) user := shr(96, word) - // Update `signature` to remaining bytes. + + // Update `signature` to the remaining bytes. mstore(signature, sub(mload(signature), 20)) mstore(add(signature, 0x20), add(add(signature, 0x20), 20)) } @@ -498,7 +516,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { missingAccountFunds ); } else { - validationData = validateSignatures(hash, userOp.signature); + validationData = _validateSignatures(hash, userOp.signature); } // Send any missing funds to `entrypoint` (msg.sender). @@ -519,29 +537,26 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } - function validateSignatures( + function _validateSignatures( bytes32 hash, - bytes calldata signatures - ) public view virtual returns (uint256) { - bytes[] memory sigs; + bytes calldata sigs + ) internal view virtual returns (uint256) { + // Validation runs up to quorum threshold. + uint256 threshold = quorum; + + // Early check for single signer. + if (threshold == 1) + return balanceOf[_recoverSigner(hash, sigs)][SIGN_KEY] != 0 ? 0 : 1; // Split signatures if batched. - if (signatures.length == 65) { - sigs = new bytes[](1); - sigs[0] = signatures; - } else { - sigs = _splitSigs(signatures); - } + bytes[] memory split = _splitSigs(sigs); // Start zero in loop to ensure ascending addresses. address previous; - // Validation is length of quorum threshold. - uint256 threshold = quorum; - // Check enough valid signatures to pass `quorum`. for (uint256 i; i < threshold; ) { - address signer = _recoverSigner(hash, sigs[i]); + address signer = _recoverSigner(hash, split[i]); // Check against duplicates. if (previous >= signer) revert Unauthorized(); diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 38e6e20d..6b1f59ce 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.4; import {Multicallable, Call, Keep} from "./Keep.sol"; -import {Owned} from "./extensions/utils/Owned.sol"; -import {Validator} from "./extensions/metadata/Validator.sol"; +import {Ownable} from "./utils/Ownable.sol"; +import {Validator} from "./extensions/validate/Validator.sol"; /// @notice Keep Factory. -contract KeepFactory is Multicallable, Owned(tx.origin) { +contract KeepFactory is Multicallable, Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 07ccffc6..8aabc016 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -476,13 +476,6 @@ abstract contract KeepToken { function getVotes( address account, uint256 id - ) public view virtual returns (uint256) { - return getCurrentVotes(account, id); - } - - function getCurrentVotes( - address account, - uint256 id ) public view virtual returns (uint256) { // Unchecked because subtraction only occurs if positive `nCheckpoints`. unchecked { @@ -501,14 +494,6 @@ abstract contract KeepToken { address account, uint256 id, uint256 timestamp - ) public view virtual returns (uint256) { - return getPriorVotes(account, id, timestamp); - } - - function getPriorVotes( - address account, - uint256 id, - uint256 timestamp ) public view virtual returns (uint256) { if (block.timestamp <= timestamp) revert Undetermined(); diff --git a/src/extensions/dao/Kali.sol b/src/extensions/dao/Kali.sol deleted file mode 100644 index 79a8f3fd..00000000 --- a/src/extensions/dao/Kali.sol +++ /dev/null @@ -1,906 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {KaliExtension} from "./utils/KaliExtension.sol"; -import {Multicallable} from "../../utils/Multicallable.sol"; -import {ReentrancyGuard} from "../utils/ReentrancyGuard.sol"; -import {KeepTokenManager} from "../utils/KeepTokenManager.sol"; -import {ERC1155TokenReceiver, Operation, Call, Signature} from "./../../Keep.sol"; - -/// @title Kali -/// @notice Kali DAO core for on-chain governance. -/// @author z0r0z.eth - -enum ProposalType { - MINT, // Add to membership. - BURN, // Revoke membership. - CALL, // Call to external code. - VPERIOD, // Set `votingPeriod`. - GPERIOD, // Set `gracePeriod`. - QUORUM, // Set `quorum`. - SUPERMAJORITY, // Set `supermajority`. - TYPE, // Set `VoteType` `ProposalType`. - PAUSE, // Flip membership transferability. - EXTENSION, // Flip `extensions` permission. - ESCAPE, // Delete pending proposal in queue. - URI // Amend root documentation for the DAO. -} - -enum VoteType { - SIMPLE_MAJORITY_QUORUM_REQUIRED, - SIMPLE_MAJORITY, - SUPERMAJORITY_QUORUM_REQUIRED, - SUPERMAJORITY -} - -struct Proposal { - uint256 prevProposal; - bytes32 proposalHash; - address proposer; - uint40 creationTime; - uint216 yesVotes; - uint216 noVotes; -} - -struct ProposalState { - bool passed; - bool processed; -} - -contract Kali is ERC1155TokenReceiver, Multicallable, ReentrancyGuard { - /// ----------------------------------------------------------------------- - /// Events - /// ----------------------------------------------------------------------- - - event NewProposal( - address indexed proposer, - uint256 indexed proposal, - Call[] calls, - ProposalType setting, - string details, - uint256 creationTime, - bool sponsored - ); - - event ProposalCancelled(address indexed proposer, uint256 indexed proposal); - - event ProposalSponsored(address indexed sponsor, uint256 indexed proposal); - - event VoteCast( - address indexed voter, - uint256 indexed proposal, - bool approve, - uint256 weight, - string details - ); - - event ProposalProcessed(uint256 indexed proposal, bool passed); - - event ExtensionSet(address indexed extension, bool on); - - event URISet(string daoURI); - - event GovSettingsUpdated( - uint256 votingPeriod, - uint256 gracePeriod, - uint256 quorum, - uint256 supermajority, - uint256[2] typeSetting - ); - - event Executed( - Operation op, - address to, - uint256 value, - bytes data, - bool success - ); - - /// ----------------------------------------------------------------------- - /// Custom Errors - /// ----------------------------------------------------------------------- - - error Initialized(); - - error PeriodBounds(); - - error QuorumMax(); - - error SupermajorityBounds(); - - error TypeBounds(); - - error Unauthorized(); - - error Sponsored(); - - error InvalidProposal(); - - error AlreadyVoted(); - - error InvalidHash(); - - error PrevNotProcessed(); - - error VotingNotEnded(); - - error InvalidSig(); - - error Overflow(); - - /// ----------------------------------------------------------------------- - /// DAO Storage/Logic - /// ----------------------------------------------------------------------- - - bytes32 internal constant MALLEABILITY_THRESHOLD = - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0; - - uint256 internal currentSponsoredProposal; - - uint256 public proposalCount; - - string public daoURI; - - uint120 public votingPeriod; - - uint120 public gracePeriod; - - uint8 public quorum; // 1-100. - - uint8 public supermajority; // 1-100. - - mapping(address => bool) public extensions; - - mapping(uint256 => Proposal) public proposals; - - mapping(uint256 => ProposalState) public proposalStates; - - mapping(ProposalType => VoteType) public proposalVoteTypes; - - mapping(uint256 => mapping(address => bool)) public voted; - - mapping(address => uint256) public lastYesVote; - - function token() public pure virtual returns (KeepTokenManager tkn) { - uint256 placeholder; - - assembly { - placeholder := sub( - calldatasize(), - add(shr(240, calldataload(sub(calldatasize(), 2))), 2) - ) - - tkn := shr(0x60, calldataload(add(placeholder, 2))) - } - } - - function tokenId() public pure virtual returns (uint256 id) { - return _fetchImmutable(22); - } - - function name() public pure virtual returns (string memory) { - return string(abi.encodePacked(_fetchImmutable(54))); - } - - function _fetchImmutable( - uint256 place - ) internal pure virtual returns (uint256 ref) { - uint256 placeholder; - - assembly { - placeholder := sub( - calldatasize(), - add(shr(240, calldataload(sub(calldatasize(), 2))), 2) - ) - - ref := calldataload(add(placeholder, place)) - } - } - - /// ----------------------------------------------------------------------- - /// ERC165 Logic - /// ----------------------------------------------------------------------- - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - // ERC165 interface ID for ERC165. - interfaceId == this.supportsInterface.selector || - // ERC165 Interface ID for ERC721TokenReceiver. - interfaceId == this.onERC721Received.selector || - // ERC165 Interface ID for ERC1155TokenReceiver. - interfaceId == type(ERC1155TokenReceiver).interfaceId; - } - - /// ----------------------------------------------------------------------- - /// ERC721 Receiver Logic - /// ----------------------------------------------------------------------- - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public payable virtual returns (bytes4) { - return this.onERC721Received.selector; - } - - /// ----------------------------------------------------------------------- - /// Initialization Logic - /// ----------------------------------------------------------------------- - - constructor() payable { - // Deploy as singleton. - votingPeriod = 1; - } - - function initialize( - Call[] calldata _calls, - string calldata _daoURI, - uint120[4] calldata _govSettings - ) public payable virtual { - if (votingPeriod != 0) revert Initialized(); - - if (_govSettings[0] == 0) revert PeriodBounds(); - - if (_govSettings[0] > 365 days) revert PeriodBounds(); - - if (_govSettings[1] > 365 days) revert PeriodBounds(); - - if (_govSettings[2] > 100) revert QuorumMax(); - - if (_govSettings[3] <= 51) revert SupermajorityBounds(); - - if (_govSettings[3] > 100) revert SupermajorityBounds(); - - if (_calls.length != 0) { - for (uint256 i; i < _calls.length; ) { - extensions[_calls[i].to] = true; - - if (_calls[i].data.length > 3) - _execute( - _calls[i].op, - _calls[i].to, - _calls[i].value, - _calls[i].data - ); - - // An array can't have a total length - // larger than the max uint256 value. - unchecked { - ++i; - } - } - } - - daoURI = _daoURI; - - votingPeriod = uint120(_govSettings[0]); - - gracePeriod = uint120(_govSettings[1]); - - quorum = uint8(_govSettings[2]); - - supermajority = uint8(_govSettings[3]); - } - - /// ----------------------------------------------------------------------- - /// EIP-712 Logic - /// ----------------------------------------------------------------------- - - function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { - return - keccak256( - abi.encode( - // `keccak256( - // "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - // )` - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, - // `keccak256(bytes("Kali"))` - 0xd321353274be6f42cf7b550879ff1a1c924e1e8f469054b23c7354e7f1737c64, - // `keccak256("1")` - 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, - block.chainid, - address(this) - ) - ); - } - - /// ----------------------------------------------------------------------- - /// Proposal Logic - /// ----------------------------------------------------------------------- - - function propose( - Call[] calldata calls, - ProposalType setting, - string calldata details - ) public payable virtual returns (uint256 proposal, uint40 creationTime) { - if (setting != ProposalType.MINT) - if (setting != ProposalType.BURN) - if (setting != ProposalType.CALL) - if (setting == ProposalType.VPERIOD) - if (calls[0].value == 0 || calls[0].value > 365 days) - revert PeriodBounds(); - else if (setting == ProposalType.GPERIOD) - if (calls[0].value > 365 days) - revert PeriodBounds(); - else if (setting == ProposalType.QUORUM) - if (calls[0].value > 100) revert QuorumMax(); - else if (setting == ProposalType.SUPERMAJORITY) - if ( - calls[0].value <= 51 || - calls[0].value > 100 - ) revert SupermajorityBounds(); - else if (setting == ProposalType.TYPE) - if ( - calls[0].value > 11 || - calls[1].value > 3 - ) revert TypeBounds(); - bool sponsored; - - // If member or extension is making proposal, include sponsorship. - if ( - token().balanceOf(msg.sender, tokenId()) != 0 || - extensions[msg.sender] - ) sponsored = true; - - // Proposal count cannot realistically overflow on human timescales. - unchecked { - proposals[proposal = ++proposalCount] = Proposal({ - prevProposal: sponsored ? currentSponsoredProposal : 0, - proposalHash: keccak256(abi.encode(calls, setting, details)), - proposer: msg.sender, - creationTime: creationTime = sponsored - ? _safeCastTo40(block.timestamp) - : 0, - yesVotes: 0, - noVotes: 0 - }); - } - - if (sponsored) currentSponsoredProposal = proposal; - - emit NewProposal( - msg.sender, - proposal, - calls, - setting, - details, - creationTime, - sponsored - ); - } - - function cancelProposal(uint256 proposal) public payable virtual { - Proposal storage prop = proposals[proposal]; - - if (msg.sender != prop.proposer) - if (!extensions[msg.sender]) revert Unauthorized(); - - if (prop.creationTime != 0) revert Sponsored(); - - delete proposals[proposal]; - - emit ProposalCancelled(msg.sender, proposal); - } - - function sponsorProposal(uint256 proposal) public payable virtual { - Proposal storage prop = proposals[proposal]; - - if (token().balanceOf(msg.sender, tokenId()) == 0) - if (!extensions[msg.sender]) revert Unauthorized(); - - if (prop.proposer == address(0)) revert InvalidProposal(); - - if (prop.creationTime != 0) revert Sponsored(); - - prop.prevProposal = currentSponsoredProposal; - - currentSponsoredProposal = proposal; - - prop.creationTime = _safeCastTo40(block.timestamp); - - emit ProposalSponsored(msg.sender, proposal); - } - - /// ----------------------------------------------------------------------- - /// Voting Logic - /// ----------------------------------------------------------------------- - - function vote( - uint256 proposal, - bool approve, - string calldata details - ) public payable virtual { - _vote(msg.sender, proposal, approve, details); - } - - function voteBySig( - uint256 proposal, - bool approve, - string calldata details, - Signature calldata sig - ) public payable virtual { - bytes32 hash = keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - keccak256( - "SignVote(uint256 proposal,bool approve,string details)" - ), - proposal, - approve, - details - ) - ) - ) - ); - - // Check signature recovery. - _recoverSig(hash, sig.user, sig.v, sig.r, sig.s); - - _vote(sig.user, proposal, approve, details); - } - - function _vote( - address user, - uint256 proposal, - bool approve, - string calldata details - ) internal virtual { - Proposal storage prop = proposals[proposal]; - - if (voted[proposal][user]) revert AlreadyVoted(); - - voted[proposal][user] = true; - - // This is safe from overflow because `votingPeriod` - // is capped so it will not combine with unix time - // to exceed the max uint256 value. - unchecked { - if (block.timestamp > prop.creationTime + votingPeriod) - revert InvalidProposal(); - } - - uint216 weight = uint216( - token().getPriorVotes(user, tokenId(), prop.creationTime) - ); - - // This is safe from overflow because `yesVotes` - // and `noVotes` are capped by `totalSupply` - // which is checked for overflow in `token` contract. - unchecked { - if (approve) { - prop.yesVotes += weight; - - lastYesVote[user] = proposal; - } else { - prop.noVotes += weight; - } - } - - emit VoteCast(user, proposal, approve, weight, details); - } - - /// ----------------------------------------------------------------------- - /// Processing Logic - /// ----------------------------------------------------------------------- - - function processProposal( - uint256 proposal, - Call[] calldata calls, - ProposalType setting, - string calldata details - ) public payable virtual nonReentrant returns (bool passed) { - Proposal storage prop = proposals[proposal]; - - if (prop.creationTime == 0) revert InvalidProposal(); - - if (keccak256(abi.encode(calls, setting, details)) != prop.proposalHash) - revert InvalidHash(); - - // Skip previous proposal processing requirement - // in case of escape hatch. - if (setting != ProposalType.ESCAPE) - if (proposals[prop.prevProposal].creationTime != 0) - revert PrevNotProcessed(); - - VoteType voteType = proposalVoteTypes[setting]; - - passed = _countVotes(voteType, prop.yesVotes, prop.noVotes); - - // If quorum and approval threshold are met, - // skip voting period for fast processing. - // If a grace period has been set, - // or if quorum is set to nothing, - // maintain voting period check. - if ( - !passed || - gracePeriod != 0 || - quorum == 0 || - voteType == VoteType.SIMPLE_MAJORITY || - voteType == VoteType.SUPERMAJORITY - ) { - // This is safe from overflow because `votingPeriod` - // and `gracePeriod` are capped so they will not combine - // with unix time to exceed the max uint256 value. - unchecked { - if ( - block.timestamp <= - prop.creationTime + votingPeriod + gracePeriod - ) revert VotingNotEnded(); - } - } - - if (passed) { - // An array can't have a total length - // larger than the max uint256 value. - unchecked { - if (setting == ProposalType.MINT) { - for (uint256 i; i < calls.length; ++i) { - token().mint( - calls[i].to, - tokenId(), - calls[i].value, - calls[i].data - ); - } - } else if (setting == ProposalType.BURN) { - for (uint256 i; i < calls.length; ++i) { - token().burn(calls[i].to, tokenId(), calls[i].value); - } - } else if (setting == ProposalType.CALL) { - for (uint256 i; i < calls.length; ++i) { - _execute( - calls[i].op, - calls[i].to, - calls[i].value, - calls[i].data - ); - } - } else if (setting == ProposalType.VPERIOD) { - votingPeriod = uint120(calls[0].value); - } else if (setting == ProposalType.GPERIOD) { - gracePeriod = uint120(calls[0].value); - } else if (setting == ProposalType.QUORUM) { - quorum = uint8(calls[0].value); - } else if (setting == ProposalType.SUPERMAJORITY) { - supermajority = uint8(calls[0].value); - } else if (setting == ProposalType.TYPE) { - proposalVoteTypes[ProposalType(calls[0].value)] = VoteType( - calls[1].value - ); - } else if (setting == ProposalType.PAUSE) { - token().setTransferability( - tokenId(), - !token().transferable(tokenId()) - ); - } else if (setting == ProposalType.EXTENSION) { - for (uint256 i; i < calls.length; ++i) { - if (calls[i].value != 0) - extensions[calls[i].to] = !extensions[calls[i].to]; - - if (calls[i].data.length > 3) - KaliExtension(calls[i].to).setExtension( - calls[i].data - ); - } - } else if (setting == ProposalType.ESCAPE) { - delete proposals[calls[0].value]; - } else if (setting == ProposalType.URI) { - daoURI = details; - } - - proposalStates[proposal].passed = true; - } - } - - delete proposals[proposal]; - - proposalStates[proposal].processed = true; - - emit ProposalProcessed(proposal, passed); - } - - function _countVotes( - VoteType voteType, - uint256 yesVotes, - uint256 noVotes - ) internal view virtual returns (bool passed) { - // Fail proposal if no participation. - if (yesVotes == 0) - if (noVotes == 0) return false; - - // Rule out any failed quorums. - if ( - voteType == VoteType.SIMPLE_MAJORITY_QUORUM_REQUIRED || - voteType == VoteType.SUPERMAJORITY_QUORUM_REQUIRED - ) { - // This is safe from overflow because `yesVotes` - // and `noVotes` supply are checked - // in `token` contract. - unchecked { - if ( - (yesVotes + noVotes) < - ((token().totalSupply(tokenId()) * quorum) / 100) - ) return false; - } - } - - if ( - // Simple majority check. - voteType == VoteType.SIMPLE_MAJORITY || - voteType == VoteType.SIMPLE_MAJORITY_QUORUM_REQUIRED - ) { - if (yesVotes > noVotes) return true; - } else { - // Supermajority check. - // Example: 7 yes, 2 no, supermajority = 66 - // ((7+2) * 66) / 100 = 5.94; 7 yes will pass. - // This is safe from overflow because `yesVotes` - // and `noVotes` supply are checked - // in `token` contract. - unchecked { - if (yesVotes >= ((yesVotes + noVotes) * supermajority) / 100) - return true; - } - } - } - - /// ----------------------------------------------------------------------- - /// Execution Logic - /// ----------------------------------------------------------------------- - - function _execute( - Operation op, - address to, - uint256 value, - bytes memory data - ) internal virtual { - bool success; - - if (op == Operation.call) { - assembly { - success := call( - gas(), - to, - value, - add(data, 0x20), - mload(data), - 0, - 0 - ) - } - - emit Executed(op, to, value, data, success); - } else if (op == Operation.delegatecall) { - assembly { - success := delegatecall( - gas(), - to, - add(data, 0x20), - mload(data), - 0, - 0 - ) - } - - emit Executed(op, to, value, data, success); - } else { - assembly { - success := create(value, add(data, 0x20), mload(data)) - } - - emit Executed(op, to, value, data, success); - } - } - - /// ----------------------------------------------------------------------- - /// Signature Recovery Logic - /// ----------------------------------------------------------------------- - - function _recoverSig( - bytes32 hash, - address signer, - uint8 v, - bytes32 r, - bytes32 s - ) internal view virtual { - if (signer == address(0)) revert InvalidSig(); - - bool isValid; - - /// @solidity memory-safe-assembly - assembly { - // Clean the upper 96 bits of `signer` in case they are dirty. - for { - signer := shr(96, shl(96, signer)) - } signer { - - } { - // Load the free memory pointer. - // Simply using the free memory usually costs less if many slots are needed. - let m := mload(0x40) - - // Clean the excess bits of `v` in case they are dirty. - v := and(v, 0xff) - // If `s` in lower half order, such that the signature is not malleable. - if iszero(gt(s, MALLEABILITY_THRESHOLD)) { - mstore(m, hash) - mstore(add(m, 0x20), v) - mstore(add(m, 0x40), r) - mstore(add(m, 0x60), s) - pop( - staticcall( - gas(), // Amount of gas left for the transaction. - 0x01, // Address of `ecrecover`. - m, // Start of input. - 0x80, // Size of input. - m, // Start of output. - 0x20 // Size of output. - ) - ) - // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if mul(eq(mload(m), signer), returndatasize()) { - isValid := 1 - break - } - } - - // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. - let f := shl(224, 0x1626ba7e) - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. - mstore(add(m, 0x04), hash) - mstore(add(m, 0x24), 0x40) // The offset of the `signature` in the calldata. - mstore(add(m, 0x44), 65) // Store the length of the signature. - mstore(add(m, 0x64), r) // Store `r` of the signature. - mstore(add(m, 0x84), s) // Store `s` of the signature. - mstore8(add(m, 0xa4), v) // Store `v` of the signature. - - isValid := and( - and( - // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). - eq(mload(0x00), f), - // Whether the returndata is exactly 0x20 bytes (1 word) long. - eq(returndatasize(), 0x20) - ), - // Whether the staticcall does not revert. - // This must be placed at the end of the `and` clause, - // as the arguments are evaluated from right to left. - staticcall( - gas(), // Remaining gas. - signer, // The `signer` address. - m, // Offset of calldata in memory. - 0xa5, // Length of calldata in memory. - 0x00, // Offset of returndata. - 0x20 // Length of returndata to write. - ) - ) - break - } - } - - if (!isValid) revert InvalidSig(); - } - - /// ----------------------------------------------------------------------- - /// Safecast Logic - /// ----------------------------------------------------------------------- - - function _safeCastTo40(uint256 x) internal pure virtual returns (uint40) { - if (x >= (1 << 40)) revert Overflow(); - - return uint40(x); - } - - /// ----------------------------------------------------------------------- - /// Extension Logic - /// ----------------------------------------------------------------------- - - modifier onlyExtension() { - if (!extensions[msg.sender]) revert Unauthorized(); - - _; - } - - function relay( - Call calldata call - ) public payable virtual onlyExtension nonReentrant { - _execute(call.op, call.to, call.value, call.data); - } - - function mint( - KeepTokenManager source, - address to, - uint256 id, - uint256 amount, - bytes calldata data - ) public payable virtual onlyExtension nonReentrant { - source.mint(to, id, amount, data); - } - - function burn( - KeepTokenManager source, - address from, - uint256 id, - uint256 amount - ) public payable virtual onlyExtension nonReentrant { - source.burn(from, id, amount); - } - - function setTransferability( - KeepTokenManager source, - uint256 id, - bool on - ) public payable virtual onlyExtension nonReentrant { - source.setTransferability(id, on); - } - - function setExtension(address extension, bool on) public payable virtual { - if (!extensions[msg.sender]) - if (msg.sender != address(this)) revert Unauthorized(); - - extensions[extension] = on; - - emit ExtensionSet(extension, on); - } - - function setURI( - string calldata _daoURI - ) public payable virtual onlyExtension { - daoURI = _daoURI; - - emit URISet(_daoURI); - } - - function deleteProposal(uint256 proposal) public payable virtual { - if (!extensions[msg.sender]) - if (msg.sender != address(this)) revert Unauthorized(); - - if (proposals[proposal].creationTime == 0) revert InvalidProposal(); - - delete proposals[proposal]; - - proposalStates[proposal].processed = true; - } - - function updateGovSettings( - uint256 _votingPeriod, - uint256 _gracePeriod, - uint256 _quorum, - uint256 _supermajority, - uint256[2] calldata _typeSetting - ) public payable virtual { - if (!extensions[msg.sender]) - if (msg.sender != address(this)) revert Unauthorized(); - - if (_votingPeriod != 0) - if (_votingPeriod <= 365 days) - votingPeriod = uint120(_votingPeriod); - - if (_gracePeriod <= 365 days) gracePeriod = uint120(_gracePeriod); - - if (_quorum <= 100) quorum = uint8(_quorum); - - if (_supermajority > 51) - if (_supermajority <= 100) supermajority = uint8(_supermajority); - - if (_typeSetting[0] <= 11) - if (_typeSetting[1] <= 3) - proposalVoteTypes[ProposalType(_typeSetting[0])] = VoteType( - _typeSetting[1] - ); - - emit GovSettingsUpdated( - _votingPeriod, - _gracePeriod, - _quorum, - _supermajority, - _typeSetting - ); - } -} diff --git a/src/extensions/dao/KaliFactory.sol b/src/extensions/dao/KaliFactory.sol deleted file mode 100644 index c276ef82..00000000 --- a/src/extensions/dao/KaliFactory.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {Multicallable, Call, KeepTokenManager, Kali} from "./Kali.sol"; -import {LibClone} from "./../../utils/LibClone.sol"; - -/// @notice Kali Factory. -contract KaliFactory is Multicallable { - /// ----------------------------------------------------------------------- - /// Library Usage - /// ----------------------------------------------------------------------- - - using LibClone for address; - - /// ----------------------------------------------------------------------- - /// Events - /// ----------------------------------------------------------------------- - - event Deployed( - Kali indexed kali, - KeepTokenManager token, - uint256 tokenId, - bytes32 name, - Call[] calls, - string daoURI, - uint120[4] govSettings - ); - - /// ----------------------------------------------------------------------- - /// Immutables - /// ----------------------------------------------------------------------- - - address internal immutable kaliTemplate; - - /// ----------------------------------------------------------------------- - /// Constructor - /// ----------------------------------------------------------------------- - - constructor(address _kaliTemplate) payable { - kaliTemplate = _kaliTemplate; - } - - /// ----------------------------------------------------------------------- - /// Deployment Logic - /// ----------------------------------------------------------------------- - - function determineKali( - KeepTokenManager token, - uint256 tokenId, - bytes32 name - ) public view virtual returns (address) { - return - kaliTemplate.predictDeterministicAddress( - abi.encodePacked(token, tokenId, name), - name, - address(this) - ); - } - - function deployKali( - KeepTokenManager _token, - uint256 _tokenId, - bytes32 _name, // create2 salt. - Call[] calldata _calls, - string calldata _daoURI, - uint120[4] calldata _govSettings - ) public payable virtual { - Kali kali = Kali( - kaliTemplate.cloneDeterministic( - abi.encodePacked(_token, _tokenId, _name), - _name - ) - ); - - kali.initialize{value: msg.value}(_calls, _daoURI, _govSettings); - - emit Deployed( - kali, - _token, - _tokenId, - _name, - _calls, - _daoURI, - _govSettings - ); - } -} diff --git a/src/extensions/dao/utils/KaliExtension.sol b/src/extensions/dao/utils/KaliExtension.sol deleted file mode 100644 index d2a6c598..00000000 --- a/src/extensions/dao/utils/KaliExtension.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @notice Contract helper for Kali DAO extensions. -abstract contract KaliExtension { - function setExtension(bytes calldata extensionData) public payable virtual; -} diff --git a/src/extensions/storage/DataRoom.sol b/src/extensions/storage/DataRoom.sol deleted file mode 100644 index 0f5f6896..00000000 --- a/src/extensions/storage/DataRoom.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @title DataRoom -/// @notice Data room for on-chain orgs. -/// @author audsssy.eth -contract DataRoom { - /// ----------------------------------------------------------------------- - /// Events - /// ----------------------------------------------------------------------- - - event PermissionSet( - address indexed dao, - address indexed account, - bool permissioned - ); - - event RecordSet(address indexed dao, string data, address indexed caller); - - /// ----------------------------------------------------------------------- - /// Custom Errors - /// ----------------------------------------------------------------------- - - error Unauthorized(); - - error LengthMismatch(); - - error InvalidRoom(); - - /// ----------------------------------------------------------------------- - /// DataRoom Storage - /// ----------------------------------------------------------------------- - - mapping(address => string[]) public room; - - mapping(address => mapping(address => bool)) public authorized; - - /// ----------------------------------------------------------------------- - /// Constructor - /// ----------------------------------------------------------------------- - - constructor() payable {} - - /// ----------------------------------------------------------------------- - /// DataRoom Logic - /// ----------------------------------------------------------------------- - - /// @notice Record data on-chain. - /// @param account Identifier of a Room. - /// @param data The data to record. - /// @dev Calls are permissioned to those authorized to access a Room. - function setRecord( - address account, - string[] calldata data - ) public payable virtual { - // Initialize Room. - if (account == msg.sender && !authorized[account][msg.sender]) { - authorized[account][msg.sender] = true; - } - - _authorized(account, msg.sender); - - for (uint256 i; i < data.length; ) { - room[account].push(data[i]); - - emit RecordSet(account, data[i], msg.sender); - - // Unchecked because the only math done is incrementing - // the array index counter which cannot possibly overflow. - unchecked { - ++i; - } - } - } - - /// @notice Retrieve data from a Room. - /// @param account Identifier of a Room. - /// @return The array of data associated with a Room. - function getRoom( - address account - ) public view virtual returns (string[] memory) { - return room[account]; - } - - /// @notice Initialize a Room or authorize users to a Room. - /// @param account Identifier of a Room. - /// @param users Users to be authorized or deauthorized to access a Room. - /// @param authorize Authorization status. - /// @dev Calls are permissioned to the authorized accounts of a Room. - function setPermission( - address account, - address[] calldata users, - bool[] calldata authorize - ) public payable virtual { - if (account == address(0)) revert InvalidRoom(); - - // Initialize Room. - if (account == msg.sender && !authorized[account][msg.sender]) { - authorized[account][msg.sender] = true; - } - - _authorized(account, msg.sender); - - uint256 numUsers = users.length; - - if (numUsers != authorize.length) revert LengthMismatch(); - - if (numUsers != 0) { - for (uint i; i < numUsers; ) { - authorized[account][users[i]] = authorize[i]; - - emit PermissionSet(account, users[i], authorize[i]); - - // Unchecked because the only math done is incrementing - // the array index counter which cannot possibly overflow. - unchecked { - ++i; - } - } - } - } - - /// ----------------------------------------------------------------------- - /// Internal Functions - /// ----------------------------------------------------------------------- - - /// @notice Helper function to check access to a Room. - /// @param account Identifier of a Room. - /// @param user The user in question. - function _authorized( - address account, - address user - ) internal view virtual returns (bool) { - if (authorized[account][user]) return true; - else revert Unauthorized(); - } -} diff --git a/src/extensions/utils/KeepTokenManager.sol b/src/extensions/utils/KeepTokenManager.sol index 91c86824..0eb877f5 100644 --- a/src/extensions/utils/KeepTokenManager.sol +++ b/src/extensions/utils/KeepTokenManager.sol @@ -12,7 +12,7 @@ abstract contract KeepTokenManager { function transferable(uint256 id) public view virtual returns (bool); - function getPriorVotes( + function getPastVotes( address account, uint256 id, uint256 timestamp diff --git a/src/extensions/metadata/PermissionRemoteFetcher.sol b/src/extensions/validate/PermissionRemoteFetcher.sol similarity index 100% rename from src/extensions/metadata/PermissionRemoteFetcher.sol rename to src/extensions/validate/PermissionRemoteFetcher.sol diff --git a/src/extensions/metadata/URIRemoteFetcher.sol b/src/extensions/validate/URIRemoteFetcher.sol similarity index 97% rename from src/extensions/metadata/URIRemoteFetcher.sol rename to src/extensions/validate/URIRemoteFetcher.sol index b6e00c98..b07212a8 100644 --- a/src/extensions/metadata/URIRemoteFetcher.sol +++ b/src/extensions/validate/URIRemoteFetcher.sol @@ -73,7 +73,7 @@ contract URIRemoteFetcher { /// https://eips.ethereum.org/EIPS/eip-55 function toHexStringChecksummed( address value - ) internal pure virtual returns (string memory str) { + ) internal pure returns (string memory str) { str = toHexString(value); /// @solidity memory-safe-assembly assembly { @@ -108,7 +108,7 @@ contract URIRemoteFetcher { /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. function toHexString( address value - ) internal pure virtual returns (string memory str) { + ) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { @@ -123,7 +123,7 @@ contract URIRemoteFetcher { /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix( address value - ) internal pure virtual returns (string memory str) { + ) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { str := mload(0x40) diff --git a/src/extensions/metadata/Validator.sol b/src/extensions/validate/Validator.sol similarity index 96% rename from src/extensions/metadata/Validator.sol rename to src/extensions/validate/Validator.sol index 2b1fdb07..60325830 100644 --- a/src/extensions/metadata/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; +import {Ownable} from "../../utils/Ownable.sol"; import {UserOperation} from "../../Keep.sol"; -import {Owned} from "../utils/Owned.sol"; /// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. -contract Validator is Owned(tx.origin) { +contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- diff --git a/src/utils/LibClone.sol b/src/utils/LibClone.sol deleted file mode 100644 index b6e466d9..00000000 --- a/src/utils/LibClone.sol +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @notice Minimal proxy library with immutable args operations. -/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibClone.sol) -library LibClone { - /// ----------------------------------------------------------------------- - /// Custom Errors - /// ----------------------------------------------------------------------- - - /// @dev Unable to deploy the clone. - error DeploymentFailed(); - - /// ----------------------------------------------------------------------- - /// Clone Operations - /// ----------------------------------------------------------------------- - - /// @dev Deploys a deterministic clone of `implementation`, - /// using immutable arguments encoded in `data`, with `salt`. - function cloneDeterministic( - address implementation, - bytes memory data, - bytes32 salt - ) internal returns (address instance) { - assembly { - // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(data, 0x60)) - let mBefore2 := mload(sub(data, 0x40)) - let mBefore1 := mload(sub(data, 0x20)) - let dataLength := mload(data) - let dataEnd := add(add(data, 0x20), dataLength) - let mAfter1 := mload(dataEnd) - - // +2 bytes for telling how much data there is appended to the call. - let extraLength := add(dataLength, 2) - - // Write the bytecode before the data. - mstore(data, 0x5af43d3d93803e606057fd5bf3) - // Write the address of the implementation. - mstore(sub(data, 0x0d), implementation) - // Write the rest of the bytecode. - mstore( - sub(data, 0x21), - or( - shl(0x48, extraLength), - 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 - ) - ) - // `keccak256("ReceiveETH(uint256)")`. - mstore( - sub(data, 0x3a), - 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff - ) - mstore( - sub(data, 0x5a), - or( - shl(0x78, add(extraLength, 0x62)), - 0x6100003d81600a3d39f336602c57343d527f - ) - ) - mstore(dataEnd, shl(0xf0, extraLength)) - - // Create the instance. - instance := create2( - 0, - sub(data, 0x4c), - add(extraLength, 0x6c), - salt - ) - - // If `instance` is zero, revert. - if iszero(instance) { - // Store the function selector of `DeploymentFailed()`. - mstore(0x00, 0x30116425) - // Revert with (offset, size). - revert(0x1c, 0x04) - } - - // Restore the overwritten memory surrounding `data`. - mstore(dataEnd, mAfter1) - mstore(data, dataLength) - mstore(sub(data, 0x20), mBefore1) - mstore(sub(data, 0x40), mBefore2) - mstore(sub(data, 0x60), mBefore3) - } - } - - /// @dev Returns the address of the deterministic clone of - /// `implementation` using immutable arguments encoded in `data`, with `salt`, by `deployer`. - function predictDeterministicAddress( - address implementation, - bytes memory data, - bytes32 salt, - address deployer - ) internal pure returns (address predicted) { - assembly { - // Compute the boundaries of the data and cache the memory slots around it. - let mBefore3 := mload(sub(data, 0x60)) - let mBefore2 := mload(sub(data, 0x40)) - let mBefore1 := mload(sub(data, 0x20)) - let dataLength := mload(data) - let dataEnd := add(add(data, 0x20), dataLength) - let mAfter1 := mload(dataEnd) - - // +2 bytes for telling how much data there is appended to the call. - let extraLength := add(dataLength, 2) - - // Write the bytecode before the data. - mstore(data, 0x5af43d3d93803e606057fd5bf3) - // Write the address of the implementation. - mstore(sub(data, 0x0d), implementation) - // Write the rest of the bytecode. - mstore( - sub(data, 0x21), - or( - shl(0x48, extraLength), - 0x593da1005b363d3d373d3d3d3d610000806062363936013d73 - ) - ) - // `keccak256("ReceiveETH(uint256)")`. - mstore( - sub(data, 0x3a), - 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff - ) - mstore( - sub(data, 0x5a), - or( - shl(0x78, add(extraLength, 0x62)), - 0x6100003d81600a3d39f336602c57343d527f - ) - ) - mstore(dataEnd, shl(0xf0, extraLength)) - - // Compute and store the bytecode hash. - mstore(0x35, keccak256(sub(data, 0x4c), add(extraLength, 0x6c))) - mstore8(0x00, 0xff) // Write the prefix. - mstore(0x01, shl(96, deployer)) - mstore(0x15, salt) - predicted := keccak256(0x00, 0x55) - // Restore the part of the free memory pointer that has been overwritten. - mstore(0x35, 0) - - // Restore the overwritten memory surrounding `data`. - mstore(dataEnd, mAfter1) - mstore(data, dataLength) - mstore(sub(data, 0x20), mBefore1) - mstore(sub(data, 0x40), mBefore2) - mstore(sub(data, 0x60), mBefore3) - } - } -} diff --git a/src/extensions/utils/Owned.sol b/src/utils/Ownable.sol similarity index 93% rename from src/extensions/utils/Owned.sol rename to src/utils/Ownable.sol index 62462e90..a39f9d84 100644 --- a/src/extensions/utils/Owned.sol +++ b/src/utils/Ownable.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.4; /// @notice Simple single owner authorization mixin that implements ERC173. -/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/auth/Owned.sol) -abstract contract Owned { +abstract contract Ownable { /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 8bc48561..24c790e0 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -6,7 +6,7 @@ import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; /// @dev Extensions. -import {Validator} from "../src/extensions/metadata/Validator.sol"; +import {Validator} from "../src/extensions/validate/Validator.sol"; /// @dev Mocks. import {MockERC20} from "@solady/test/utils/mocks/MockERC20.sol"; diff --git a/test/Owned.t.sol b/test/Ownable.t.sol similarity index 59% rename from test/Owned.t.sol rename to test/Ownable.t.sol index 6b889f42..99f90953 100644 --- a/test/Owned.t.sol +++ b/test/Ownable.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {MockOwned} from "./utils/mocks/MockOwned.sol"; +import {MockOwnable} from "./utils/mocks/MockOwnable.sol"; import "@std/Test.sol"; -contract OwnedTest is Test, MockOwned { - MockOwned mockOwned; +contract OwnableTest is Test, MockOwnable { + MockOwnable immutable mockOwnable = new MockOwnable(); - function setUp() public payable { - mockOwned = new MockOwned(); - } + function setUp() public payable {} function testTransferOwnership() public payable { testTransferOwnership(address(0xBEEF)); @@ -21,26 +19,26 @@ contract OwnedTest is Test, MockOwned { } function testCallFunctionAsOwner() public payable { - mockOwned.updateFlag(); + mockOwnable.updateFlag(); } function testTransferOwnership(address newOwner) public payable { - mockOwned.transferOwnership(newOwner); + mockOwnable.transferOwnership(newOwner); - assertEq(mockOwned.owner(), newOwner); + assertEq(mockOwnable.owner(), newOwner); } function testCallFunctionAsNonOwner(address owner) public payable { vm.assume(owner != address(this)); - mockOwned.transferOwnership(owner); + mockOwnable.transferOwnership(owner); vm.expectRevert(Unauthorized.selector); - mockOwned.updateFlag(); + mockOwnable.updateFlag(); } function testERC165Support() public payable { // ERC173 selector. - assert(mockOwned.supportsInterface(0x7f5828d0)); + assert(mockOwnable.supportsInterface(0x7f5828d0)); } } diff --git a/test/utils/mocks/MockOwned.sol b/test/utils/mocks/MockOwnable.sol similarity index 61% rename from test/utils/mocks/MockOwned.sol rename to test/utils/mocks/MockOwnable.sol index 850b3fb2..b4d9a934 100644 --- a/test/utils/mocks/MockOwned.sol +++ b/test/utils/mocks/MockOwnable.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {Owned} from "../../../src/extensions/utils/Owned.sol"; +import {Ownable} from "../../../src/utils/Ownable.sol"; -contract MockOwned is Owned(msg.sender) { +contract MockOwnable is Ownable(msg.sender) { bool public flag; function updateFlag() public payable virtual onlyOwner { From ee83d4cce24c5c86bdd812547e9a1bf954252650 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:18:56 +0000 Subject: [PATCH 38/67] =?UTF-8?q?=E2=9A=A1=20Optimize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 18 +++++++------- src/Keep.sol | 58 ++++++++++++++++++++------------------------- src/KeepFactory.sol | 37 +++++++++++++---------------- src/KeepToken.sol | 22 +++++++---------- 4 files changed, 60 insertions(+), 75 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 768a0ab4..7b6fb432 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,5 +1,5 @@ -KeepFactoryTest:testDeploy() (gas: 166574) -KeepFactoryTest:testDetermination() (gas: 170992) +KeepFactoryTest:testDeploy() (gas: 166562) +KeepFactoryTest:testDetermination() (gas: 170976) KeepTest:testBalanceOf() (gas: 116128) KeepTest:testBalanceOfBatch() (gas: 125425) KeepTest:testBalanceOfSigner() (gas: 49553) @@ -7,22 +7,22 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39072 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247486) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 4745043) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83742) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103048) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83715) +KeepTest:testCannotRepeatKeepSetup() (gas: 4745227) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83732) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83705) KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15103, ~: 15103) KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15189, ~: 15189) KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179916, ~: 183872) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180258, ~: 183872) KeepTest:testExecuteDelegateCall() (gas: 46549) KeepTest:testExecuteEthCall() (gas: 72362) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68015) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) KeepTest:testName() (gas: 10086) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 88039) +KeepTest:testNonceIncrementAfterExecute() (gas: 88013) KeepTest:testQuorum() (gas: 23893) KeepTest:testReceiveBatchERC1155() (gas: 44293) MulticallableTest:testMulticallableBenchmark() (gas: 28510) diff --git a/src/Keep.sol b/src/Keep.sol index b57be7b7..ef1b0ca0 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -404,22 +404,18 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) returndatacopy(0, 0, returndatasize()) if iszero(success) { - revert(0, 0) + revert(0, returndatasize()) } return(0, returndatasize()) } } else if (op == Operation.create) { /// @solidity memory-safe-assembly assembly { - let createdAddress := create( - value, - add(data, 0x20), - mload(data) - ) - if iszero(createdAddress) { + let created := create(value, add(data, 0x20), mload(data)) + if iszero(created) { revert(0, 0) } - mstore(0, createdAddress) + mstore(0, created) return(0, 0x20) } } else { @@ -494,16 +490,15 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 userOpHash, uint256 missingAccountFunds ) public payable virtual returns (uint256 validationData) { - // Ensure request comes from known `entrypoint`. + // Check request comes from `entrypoint`. if (msg.sender != validator.entryPoint()) revert Unauthorized(); // Return keccak256 hash of ERC191 signed data. - bytes32 hash; /// @solidity memory-safe-assembly assembly { mstore(0x20, userOpHash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. - hash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + userOpHash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } // Shift nonce to get branch between `validator` or signer verification. @@ -512,11 +507,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) { validationData = validator.validateUserOp( userOp, - hash, + userOpHash, missingAccountFunds ); } else { - validationData = _validateSignatures(hash, userOp.signature); + validationData = _validateSignatures(userOpHash, userOp.signature); } // Send any missing funds to `entrypoint` (msg.sender). @@ -578,48 +573,47 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } function _splitSigs( - bytes memory signatures + bytes memory sigs ) internal pure virtual returns (bytes[] memory split) { /// @solidity memory-safe-assembly assembly { - // Check if signatures.length % 65 == 0. - if iszero(eq(mod(mload(signatures), 65), 0)) { + // Check if sigs.length % 65 == 0. + if iszero(eq(mod(mload(sigs), 65), 0)) { // If not, revert with InvalidSignature. mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } - // Calculate signaturesCount in assembly. - let signaturesCount := div(mload(signatures), 65) + // Calculate count in assembly. + let count := div(mload(sigs), 65) // Allocate memory for the split array. split := mload(0x40) // Current free memory pointer (using mload(0x40) instead of msize). - mstore(split, signaturesCount) // Store the length of the split array. + mstore(split, count) // Store the length of the split array. - let sigPtr := add(signatures, 0x20) // Pointer to start of signatures data. + let sigPtr := add(sigs, 0x20) // Pointer to start of sigs data. let splitDataPtr := add(split, 0x20) // Pointer to the data section of the split array. for { let i := 0 - } lt(i, signaturesCount) { + } lt(i, count) { i := add(i, 1) } { - // Fetch the current free memory pointer. - let sigMemory := mload(0x40) + let m := mload(0x40) // Cache the free memory pointer. // Store the pointer to the new memory in the split array's data section. - mstore(splitDataPtr, sigMemory) + mstore(splitDataPtr, m) - // Store the length and the data for the signature. - mstore(sigMemory, 65) - mstore(add(sigMemory, 0x20), mload(sigPtr)) - mstore(add(sigMemory, 0x40), mload(add(sigPtr, 0x20))) - mstore8(add(sigMemory, 0x60), mload(add(sigPtr, 0x40))) + // Store the length and the data for the sig. + mstore(m, 65) + mstore(add(m, 0x20), mload(sigPtr)) + mstore(add(m, 0x40), mload(add(sigPtr, 0x20))) + mstore8(add(m, 0x60), mload(add(sigPtr, 0x40))) // Move the pointers for the next iteration. - mstore(0x40, add(sigMemory, 0x61)) // Update free memory pointer. - sigPtr := add(sigPtr, 65) // Move to the next signature. - splitDataPtr := add(splitDataPtr, 0x20) // Move to the next position in the split data section. + mstore(0x40, add(m, 0x61)) // Update free memory pointer. + sigPtr := add(sigPtr, 65) // Move to the next sig. + splitDataPtr := add(splitDataPtr, 0x20) // Move to the next position. } } } diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 6b1f59ce..4fae353e 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -55,13 +55,6 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { let dataEnd := add(add(data, 0x20), dataLength) let mAfter1 := mload(dataEnd) - // Do a out-of-gas revert if `extraLength` is more than 2 bytes (super unlikely). - returndatacopy( - returndatasize(), - returndatasize(), - gt(dataLength, 0xfffd) - ) - // +2 bytes for telling how much data there is appended to the call. let extraLength := add(dataLength, 2) @@ -83,18 +76,25 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) mstore( - sub(data, 0x5a), + // Do a out-of-gas revert if `extraLength` is too big. 0xffff - 0x62 + 0x01 = 0xff9e. + // The actual EVM limit may be smaller and may change over time. + sub(data, add(0x59, lt(extraLength, 0xff9e))), or( shl(0x78, add(extraLength, 0x62)), - 0x6100003d81600a3d39f336602c57343d527f + 0xfd6100003d81600a3d39f336602c57343d527f ) ) mstore(dataEnd, shl(0xf0, extraLength)) // Create the instance. - keep := create2(0, sub(data, 0x4c), add(extraLength, 0x6c), name) + keep := create2( + callvalue(), + sub(data, 0x4c), + add(extraLength, 0x6c), + name + ) - // If `instance` is zero, revert. + // If `keep` is zero, revert. if iszero(keep) { // Store the function selector of `DeploymentFailed()`. mstore(0x00, 0x30116425) @@ -110,7 +110,7 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { mstore(sub(data, 0x60), mBefore3) } - keep.initialize{value: msg.value}(calls, signers, threshold); + keep.initialize(calls, signers, threshold); emit Deployed(keep, threshold); } @@ -130,13 +130,6 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { let dataEnd := add(add(data, 0x20), dataLength) let mAfter1 := mload(dataEnd) - // Do a out-of-gas revert if `extraLength` is more than 2 bytes (super unlikely). - returndatacopy( - returndatasize(), - returndatasize(), - gt(dataLength, 0xfffd) - ) - // +2 bytes for telling how much data there is appended to the call. let extraLength := add(dataLength, 2) @@ -158,10 +151,12 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff ) mstore( - sub(data, 0x5a), + // Do a out-of-gas revert if `extraLength` is too big. 0xffff - 0x62 + 0x01 = 0xff9e. + // The actual EVM limit may be smaller and may change over time. + sub(data, add(0x59, lt(extraLength, 0xff9e))), or( shl(0x78, add(extraLength, 0x62)), - 0x6100003d81600a3d39f336602c57343d527f + 0xfd6100003d81600a3d39f336602c57343d527f ) ) mstore(dataEnd, shl(0xf0, extraLength)) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 8aabc016..34a64b73 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -166,9 +166,9 @@ abstract contract KeepToken { } signer { } { - let m := mload(0x40) // Load the free memory pointer. + let m := mload(0x40) mstore(0x00, hash) - mstore(0x20, and(v, 0xff)) // `v`. Must be the lower 8 bits. + mstore(0x20, and(v, 0xff)) // `v`. mstore(0x40, r) // `r`. mstore(0x60, s) // `s`. let t := staticcall( @@ -185,26 +185,20 @@ abstract contract KeepToken { mstore(0x40, m) // Restore the free memory pointer. break } - mstore(0x60, 0) // Restore the zero slot. - mstore(0x40, m) // Restore the free memory pointer. let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) - mstore(add(m, 0x24), 0x40) // The offset of the `signature` in the calldata. + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), 65) // Length of the signature. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), s) // `s`. mstore8(add(m, 0xa4), v) // `v`. - if iszero( and( - and( - // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). - eq(mload(0x00), f), - // Whether the returndata is exactly 0x20 bytes (1 word) long. - eq(returndatasize(), 0x20) - ), + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. @@ -213,7 +207,7 @@ abstract contract KeepToken { signer, // The `signer` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. - 0x00, // Offset of returndata. + d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) @@ -221,6 +215,8 @@ abstract contract KeepToken { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. break } } From aba2b80456e6e762843904b115b9f86aefef46de Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:34:28 +0530 Subject: [PATCH 39/67] ~~ tidy and simplify erroring --- src/Keep.sol | 147 +++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 81 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index ef1b0ca0..329cf3f1 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -76,7 +76,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Emitted when Keep relays calls. event Multirelayed(Call[] calls); - /// @dev Emitted when quorum threshold is updated. + /// @dev Emitted when signature `quorum` threshold is updated. event QuorumSet(uint256 threshold); /// ----------------------------------------------------------------------- @@ -86,15 +86,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Throws if `initialize()` is called more than once. error AlreadyInit(); - /// @dev Throws if quorum exceeds `totalSupply(SIGN_KEY)`. - error QuorumOverSupply(); - - /// @dev Throws if quorum with `threshold = 0` is set. + /// @dev Throws if `quorum` exceeds `totalSupply(SIGN_KEY)` or is zero. error InvalidThreshold(); - /// @dev Throws if `execute()` doesn't complete operation. - error ExecuteFailed(); - /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- @@ -102,7 +96,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Core ID key permission. uint256 internal constant CORE_KEY = uint32(type(KeepToken).interfaceId); - /// @dev External validation for ERC1155 `uri()` and ERC4337 permissioning. + /// @dev External validation for ERC1155 `uri()` & ERC4337 permissioning. Keep internal immutable validator; /// @dev Record of states verifying `execute()`. @@ -112,7 +106,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint120 public quorum; /// @dev Internal ID metadata mapping. - mapping(uint256 => string) internal _uris; + mapping(uint256 id => string meta) internal _uri; /// @dev ERC4337 entrypoint. function entryPoint() public view virtual returns (address) { @@ -150,16 +144,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev ERC165 interface detection. /// @param interfaceId ID to check. - /// @return result Fetch detection success. + /// @return supported Status field. function supportsInterface( bytes4 interfaceId - ) public view virtual returns (bool result) { + ) public view virtual returns (bool supported) { /// @solidity memory-safe-assembly assembly { let s := shr(224, interfaceId) // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c, // ERC721TokenReceiver: 0x150b7a02, ERC1155TokenReceiver: 0x4e2312e0 - result := or( + supported := or( or( or( or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), @@ -190,10 +184,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @notice Create Keep template. - /// @param _validator ERC1155/ERC4337 fetcher. + /// @param _validator ERC1155/4337 sidecar. constructor(Keep _validator) payable { validator = _validator; - // Deploy as singleton. quorum = 1; } @@ -211,7 +204,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (threshold == 0) revert InvalidThreshold(); - if (threshold > signers.length) revert QuorumOverSupply(); + if (threshold > signers.length) revert InvalidThreshold(); if (calls.length != 0) { for (uint256 i; i < calls.length; ) { @@ -266,7 +259,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @param to Address to send operation to. /// @param value Amount of ETH to send in operation. /// @param data Payload to send in operation. - /// @param sigs Array of Keep signatures in ascending order by addresses. + /// @param sigs Array of Keep signatures in ascending order. function execute( Operation op, address to, @@ -275,9 +268,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { Signature[] calldata sigs ) public payable virtual { uint120 txNonce; - // Unchecked because the only math done is incrementing - // Keep nonce which cannot realistically overflow. + // Keep `nonce` which cannot realistically overflow. unchecked { emit Executed(txNonce = nonce++, op, to, value, data); } @@ -302,17 +294,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) ); - // Start zero in loop to ensure ascending addresses. + // Memo zero `user` in loop for ascending order. address previous; - - // Validation runs up to quorum threshold. + // Memo `quorum` threshold for loop length. uint256 threshold = quorum; - - // Store outside loop for gas optimization. + // Memo `sig` outside loop for gas optimization. Signature calldata sig; + // Check enough valid `sig` to pass `quorum`. for (uint256 i; i < threshold; ) { - // Load signature items. + // Load `user` details. sig = sigs[i]; address user = sig.user; @@ -320,13 +311,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // This also confirms non-zero `user`. if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); - // Check signature recovery. + // Check `user` `sig` recovery. _checkSig(hash, user, sig.v, sig.r, sig.s); - // Check against duplicates. + // Check against `user` duplicates. if (previous >= user) revert Unauthorized(); - // Memo signature for next iteration until quorum. + // Memo for next iteration until quorum. previous = user; // An array can't have a total length @@ -339,7 +330,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _execute(op, to, value, data); } - /// @notice Relay operation from Keep via `execute()` or as ID key holder. + /// @notice Relay operation from Keep `execute()` or as `authorized()`. /// @param call Keep operation as struct of `op, to, value, data`. function relay(Call calldata call) public payable virtual { _authorized(); @@ -349,7 +340,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { emit Relayed(call); } - /// @notice Relay operations from Keep via `execute()` or as ID key holder. + /// @notice Relay operations from Keep `execute()` or as `authorized()`. /// @param calls Keep operations as struct arrays of `op, to, value, data`. function multirelay(Call[] calldata calls) public payable virtual { _authorized(); @@ -424,13 +415,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if lt(mload(data), 0x20) { revert(0, 0) } - let salt := mload(add(data, 0x20)) - let len := sub(mload(data), 0x20) let created := create2( value, add(add(data, 0x20), 0x20), - len, - salt + sub(mload(data), 0x20), + mload(add(data, 0x20)) ) if iszero(created) { revert(0, 0) @@ -457,15 +446,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Fallback to check nested contract signatures. address user; assembly { - // Ensure signature has at least 20 bytes for an address. + // Check signature has 20 bytes for address. if lt(mload(signature), 20) { revert(0, 0) } - - // Extract the first 20 bytes into the address. + // Extract the first 20 bytes into address. let word := mload(add(signature, 0x20)) user := shr(96, word) - // Update `signature` to the remaining bytes. mstore(signature, sub(mload(signature), 20)) mstore(add(signature, 0x20), add(add(signature, 0x20), 20)) @@ -490,7 +477,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 userOpHash, uint256 missingAccountFunds ) public payable virtual returns (uint256 validationData) { - // Check request comes from `entrypoint`. + // Check request comes from `entrypoint()`. if (msg.sender != validator.entryPoint()) revert Unauthorized(); // Return keccak256 hash of ERC191 signed data. @@ -501,7 +488,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { userOpHash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } - // Shift nonce to get branch between `validator` or signer verification. + // Shift `userOp.nonce` to branch between `validator` & signature check. if ( userOp.nonce >> 64 == uint256(uint32(this.validateUserOp.selector)) ) { @@ -514,7 +501,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { validationData = _validateSignatures(userOpHash, userOp.signature); } - // Send any missing funds to `entrypoint` (msg.sender). + // Send any missing funds to `entrypoint()` (msg.sender). if (missingAccountFunds != 0) { assembly { pop( @@ -534,33 +521,32 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function _validateSignatures( bytes32 hash, - bytes calldata sigs + bytes calldata sig ) internal view virtual returns (uint256) { - // Validation runs up to quorum threshold. + // Memo `quorum` threshold for loop length. uint256 threshold = quorum; - // Early check for single signer. + // Early check for single `sig`. if (threshold == 1) - return balanceOf[_recoverSigner(hash, sigs)][SIGN_KEY] != 0 ? 0 : 1; - - // Split signatures if batched. - bytes[] memory split = _splitSigs(sigs); + return balanceOf[_recoverSigner(hash, sig)][SIGN_KEY] != 0 ? 0 : 1; - // Start zero in loop to ensure ascending addresses. + // Memo split `sig` if batched. + bytes[] memory sigs = _splitSigs(sig); + // Memo zero `user` in loop for ascending order. address previous; - // Check enough valid signatures to pass `quorum`. + // Check enough valid `sigs` to pass `quorum`. for (uint256 i; i < threshold; ) { - address signer = _recoverSigner(hash, split[i]); + address user = _recoverSigner(hash, sigs[i]); // Check against duplicates. - if (previous >= signer) revert Unauthorized(); + if (previous >= user) revert Unauthorized(); // Memo signature for next iteration until quorum. - previous = signer; + previous = user; - // If not keyholding signer, `SIG_VALIDATION_FAILED`. - if (balanceOf[signer][SIGN_KEY] == 0) return 1; + // If not keyholding `user`, `SIG_VALIDATION_FAILED`. + if (balanceOf[user][SIGN_KEY] == 0) return 1; // An array can't have a total length // larger than the max uint256 value. @@ -573,38 +559,38 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } function _splitSigs( - bytes memory sigs - ) internal pure virtual returns (bytes[] memory split) { + bytes memory sig + ) internal pure virtual returns (bytes[] memory sigs) { /// @solidity memory-safe-assembly assembly { - // Check if sigs.length % 65 == 0. - if iszero(eq(mod(mload(sigs), 65), 0)) { + // Check if `sig.length % 65 == 0`. + if iszero(eq(mod(mload(sig), 65), 0)) { // If not, revert with InvalidSignature. mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } // Calculate count in assembly. - let count := div(mload(sigs), 65) + let count := div(mload(sig), 65) - // Allocate memory for the split array. - split := mload(0x40) // Current free memory pointer (using mload(0x40) instead of msize). - mstore(split, count) // Store the length of the split array. + // Allocate memory for split array. + sigs := mload(0x40) // Current free memory pointer (using mload(0x40) instead of msize). + mstore(sigs, count) // Store length of the sigs array. - let sigPtr := add(sigs, 0x20) // Pointer to start of sigs data. - let splitDataPtr := add(split, 0x20) // Pointer to the data section of the split array. + let sigPtr := add(sig, 0x20) // Pointer to start of `sig` data. + let splitDataPtr := add(sigs, 0x20) // Pointer to data section of `sigs` array. for { let i := 0 } lt(i, count) { i := add(i, 1) } { - let m := mload(0x40) // Cache the free memory pointer. + let m := mload(0x40) // Cache free memory pointer. - // Store the pointer to the new memory in the split array's data section. + // Store pointer to new memory in `sigs` array's data section. mstore(splitDataPtr, m) - // Store the length and the data for the sig. + // Store length and data for the `sig`. mstore(m, 65) mstore(add(m, 0x20), mload(sigPtr)) mstore(add(m, 0x40), mload(add(sigPtr, 0x20))) @@ -612,27 +598,27 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Move the pointers for the next iteration. mstore(0x40, add(m, 0x61)) // Update free memory pointer. - sigPtr := add(sigPtr, 65) // Move to the next sig. - splitDataPtr := add(splitDataPtr, 0x20) // Move to the next position. + sigPtr := add(sigPtr, 65) // Move to next `sig`. + splitDataPtr := add(splitDataPtr, 0x20) // Move to next position. } } } function _recoverSigner( bytes32 hash, - bytes memory signature + bytes memory sig ) internal view virtual returns (address signer) { /// @solidity memory-safe-assembly assembly { - let m := mload(0x40) // Cache the free memory pointer. - let signatureLength := mload(signature) + let m := mload(0x40) // Cache free memory pointer. + let signatureLength := mload(sig) mstore(0x00, hash) - mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. - mstore(0x40, mload(add(signature, 0x20))) // `r`. - mstore(0x60, mload(add(signature, 0x40))) // `s`. + mstore(0x20, byte(0, mload(add(sig, 0x60)))) // `v`. + mstore(0x40, mload(add(sig, 0x20))) // `r`. + mstore(0x60, mload(add(sig, 0x40))) // `s`. signer := mload( staticcall( - gas(), // Amount of gas left for the transaction. + gas(), // Amount of gas left for transaction. eq(signatureLength, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. @@ -686,7 +672,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _burn(from, id, amount); if (id == SIGN_KEY) - if (quorum > totalSupply[SIGN_KEY]) revert QuorumOverSupply(); + if (quorum > totalSupply[SIGN_KEY]) revert InvalidThreshold(); } /// ----------------------------------------------------------------------- @@ -699,8 +685,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _authorized(); if (threshold == 0) revert InvalidThreshold(); - - if (threshold > totalSupply[SIGN_KEY]) revert QuorumOverSupply(); + if (threshold > totalSupply[SIGN_KEY]) revert InvalidThreshold(); quorum = uint120(threshold); @@ -754,7 +739,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) public payable virtual { _authorized(); - _uris[id] = tokenURI; + _uri[id] = tokenURI; emit URI(tokenURI, id); } From 24b48b4768b4cf9d688c5e36ec70705867db6734 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:36:11 +0530 Subject: [PATCH 40/67] =?UTF-8?q?=F0=9F=A5=A2=20tighten=20name=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 tighten name fetch --- src/KeepToken.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 34a64b73..fe3812e1 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.4; /// @notice ERC1155 interface to receive tokens. -/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/tokens/ERC1155/ERC1155.sol) abstract contract ERC1155TokenReceiver { function onERC1155Received( address, @@ -25,9 +24,8 @@ abstract contract ERC1155TokenReceiver { } } -/// @notice Modern, minimalist, and gas-optimized ERC1155 implementation with Compound-style voting and flexible permissioning scheme. +/// @notice ERC1155 token with Governor-style checkpointing, delegation and transfer restriction scheme. /// @author Modified from ERC1155V (https://github.com/kalidao/ERC1155V/blob/main/src/ERC1155V.sol) -/// @author Modified from Compound (https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol) abstract contract KeepToken { /// ----------------------------------------------------------------------- /// Events @@ -258,15 +256,17 @@ abstract contract KeepToken { function name() public pure virtual returns (string memory) { uint256 n; - /// @solidity memory-safe-assembly assembly { - n := sub( - calldatasize(), - add(shr(240, calldataload(sub(calldatasize(), 2))), 2) + n := calldataload( + add( + sub( + calldatasize(), + add(shr(240, calldataload(sub(calldatasize(), 2))), 2) + ), + 2 + ) ) - - n := calldataload(add(n, 2)) } return string(abi.encodePacked(n)); From 5a67dbef84cba16e932db71d57f6b883e540d398 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:14:25 +0000 Subject: [PATCH 41/67] =?UTF-8?q?=F0=9F=A7=B9=20tidy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 6 +++--- package.json | 4 ++-- pnpm-lock.yaml | 34 +++++++++++++++++----------------- src/Keep.sol | 2 +- test/Keep.t.sol | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 7b6fb432..910530aa 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -7,7 +7,7 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39072 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247486) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 4745227) +KeepTest:testCannotRepeatKeepSetup() (gas: 4744218) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83732) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83705) @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180258, ~: 183872) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180289, ~: 183872) KeepTest:testExecuteDelegateCall() (gas: 46549) KeepTest:testExecuteEthCall() (gas: 72362) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) @@ -38,4 +38,4 @@ OwnableTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 13975, ~: 13994) OwnableTest:testCallFunctionAsOwner() (gas: 8296) OwnableTest:testERC165Support() (gas: 3373) OwnableTest:testTransferOwnership() (gas: 10855) -OwnableTest:testTransferOwnership(address) (runs: 256, μ: 10928, ~: 10947) \ No newline at end of file +OwnableTest:testTransferOwnership(address) (runs: 256, μ: 10917, ~: 10947) \ No newline at end of file diff --git a/package.json b/package.json index f60630c5..9de4be4d 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "snapshot": "forge clean && forge snapshot --optimize --optimizer-runs 9999999" }, "devDependencies": { - "prettier": "^3.0.1", + "prettier": "^3.0.3", "prettier-plugin-solidity": "1.1.3", - "solhint": "^3.6.1" + "solhint": "^3.6.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19fcfd5a..479ba513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,22 +6,22 @@ settings: devDependencies: prettier: - specifier: ^3.0.1 - version: 3.0.1 + specifier: ^3.0.3 + version: 3.0.3 prettier-plugin-solidity: specifier: 1.1.3 - version: 1.1.3(prettier@3.0.1) + version: 1.1.3(prettier@3.0.3) solhint: - specifier: ^3.6.1 - version: 3.6.1 + specifier: ^3.6.2 + version: 3.6.2 packages: - /@babel/code-frame@7.22.10: - resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.22.10 + '@babel/highlight': 7.22.13 chalk: 2.4.2 dev: true @@ -30,8 +30,8 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/highlight@7.22.10: - resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} + /@babel/highlight@7.22.13: + resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.22.5 @@ -323,7 +323,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.22.10 + '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -339,14 +339,14 @@ packages: engines: {node: '>=4'} dev: true - /prettier-plugin-solidity@1.1.3(prettier@3.0.1): + /prettier-plugin-solidity@1.1.3(prettier@3.0.3): resolution: {integrity: sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==} engines: {node: '>=12'} peerDependencies: prettier: '>=2.3.0 || >=3.0.0-alpha.0' dependencies: '@solidity-parser/parser': 0.16.1 - prettier: 3.0.1 + prettier: 3.0.3 semver: 7.5.4 solidity-comments-extractor: 0.0.7 dev: true @@ -359,8 +359,8 @@ packages: dev: true optional: true - /prettier@3.0.1: - resolution: {integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==} + /prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true dev: true @@ -397,8 +397,8 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /solhint@3.6.1: - resolution: {integrity: sha512-pS7Pl11Ujiew9XWaLDH0U+AFc6iK1RtLV0YETSpjHZXjUaNYi32mY+pi8Ap9vqmNfWodWKtG0bVQpatq84mL4g==} + /solhint@3.6.2: + resolution: {integrity: sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==} hasBin: true dependencies: '@solidity-parser/parser': 0.16.1 diff --git a/src/Keep.sol b/src/Keep.sol index 329cf3f1..80c8033f 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -117,7 +117,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @param id ID to fetch from. /// @return tokenURI Metadata. function uri(uint256 id) public view virtual returns (string memory) { - string memory tokenURI = _uris[id]; + string memory tokenURI = _uri[id]; if (bytes(tokenURI).length > 0) return tokenURI; else return validator.uri(id); diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 24c790e0..a83223b6 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -380,7 +380,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { } function testCannotSetupWithExcessiveQuorum() public payable { - vm.expectRevert(QuorumOverSupply.selector); + vm.expectRevert(InvalidThreshold.selector); factory.deployKeep(mockName, calls, signers, 3); } From f646c51a4f382b8e58b31fc311da55a1fb6e25a2 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:52:07 +0000 Subject: [PATCH 42/67] =?UTF-8?q?=E2=9C=8D=EF=B8=8F=20erc6066=20support=20?= =?UTF-8?q?for=20nft=20signed=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 46 ++++++++++++++++---------------- src/Keep.sol | 73 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 910530aa..920039a2 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,30 +1,30 @@ KeepFactoryTest:testDeploy() (gas: 166562) KeepFactoryTest:testDetermination() (gas: 170976) -KeepTest:testBalanceOf() (gas: 116128) -KeepTest:testBalanceOfBatch() (gas: 125425) -KeepTest:testBalanceOfSigner() (gas: 49553) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390728) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247486) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) -KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 4744218) +KeepTest:testBalanceOf() (gas: 116086) +KeepTest:testBalanceOfBatch() (gas: 125383) +KeepTest:testBalanceOfSigner() (gas: 49511) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390752) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247532) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) +KeepTest:testCannotMintToZeroAddress() (gas: 119039) +KeepTest:testCannotRepeatKeepSetup() (gas: 4911884) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83732) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83705) -KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15103, ~: 15103) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15189, ~: 15189) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180289, ~: 183872) -KeepTest:testExecuteDelegateCall() (gas: 46549) -KeepTest:testExecuteEthCall() (gas: 72362) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) -KeepTest:testName() (gas: 10086) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103060) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83749) +KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15148, ~: 15148) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15211, ~: 15211) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380784) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241942) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154575, ~: 154575) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180637, ~: 183896) +KeepTest:testExecuteDelegateCall() (gas: 46571) +KeepTest:testExecuteEthCall() (gas: 72384) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68026) +KeepTest:testName() (gas: 10064) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 88013) -KeepTest:testQuorum() (gas: 23893) -KeepTest:testReceiveBatchERC1155() (gas: 44293) +KeepTest:testNonceIncrementAfterExecute() (gas: 88057) +KeepTest:testQuorum() (gas: 23937) +KeepTest:testReceiveBatchERC1155() (gas: 44271) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) diff --git a/src/Keep.sol b/src/Keep.sol index 80c8033f..4d1ad6f8 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -76,9 +76,12 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Emitted when Keep relays calls. event Multirelayed(Call[] calls); - /// @dev Emitted when signature `quorum` threshold is updated. + /// @dev Emitted when `quorum` updates. event QuorumSet(uint256 threshold); + /// @dev Emitted when signature revoked. + event SignatureRevoked(address indexed user, bytes32 hash); + /// ----------------------------------------------------------------------- /// Custom Errors /// ----------------------------------------------------------------------- @@ -108,6 +111,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Internal ID metadata mapping. mapping(uint256 id => string meta) internal _uri; + /// @dev Contract signature hash revocation status. + mapping(address user => mapping(bytes32 hash => bool)) public revoked; + /// @dev ERC4337 entrypoint. function entryPoint() public view virtual returns (address) { return validator.entryPoint(); @@ -436,38 +442,71 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function isValidSignature( bytes32 hash, - bytes memory signature + bytes memory sig ) public view virtual returns (bytes4) { + // Recover EOA signer for initial checks. + address user = _recoverSigner(hash, sig); + + // Check if `sig` for `hash` was revoked. + if (revoked[user][hash]) return 0xffffffff; + // Check SIGN_KEY balance. // This also confirms non-zero signer. - if (balanceOf[_recoverSigner(hash, signature)][SIGN_KEY] != 0) - return this.isValidSignature.selector; + if (balanceOf[user][SIGN_KEY] != 0) + // `bytes4(keccak256("isValidSignature(bytes32,bytes)")`. + return 0x1626ba7e; // Fallback to check nested contract signatures. - address user; + // Overwrite `user` with first 20 bytes and `sig` + // with remaining data. assembly { - // Check signature has 20 bytes for address. - if lt(mload(signature), 20) { - revert(0, 0) - } // Extract the first 20 bytes into address. - let word := mload(add(signature, 0x20)) + let word := mload(add(sig, 0x20)) user := shr(96, word) - // Update `signature` to the remaining bytes. - mstore(signature, sub(mload(signature), 20)) - mstore(add(signature, 0x20), add(add(signature, 0x20), 20)) + // Update `sig` to the remaining bytes. + mstore(sig, sub(mload(sig), 20)) + mstore(add(sig, 0x20), add(add(sig, 0x20), 20)) } + // Check SIGN_KEY balance of contract account. if (balanceOf[user][SIGN_KEY] != 0) - if ( - Keep(user).isValidSignature(hash, signature) == - this.isValidSignature.selector - ) return this.isValidSignature.selector; + if (Keep(user).isValidSignature(hash, sig) == 0x1626ba7e) + return 0x1626ba7e; // Otherwise, return error. return 0xffffffff; } + function revokeSignature( + bytes32 hash, + Signature calldata sig + ) public payable virtual { + _checkSig(hash, sig.user, sig.v, sig.r, sig.s); + + revoked[sig.user][hash] = true; + + emit SignatureRevoked(sig.user, hash); + } + + /// ----------------------------------------------------------------------- + /// ERC6066 Logic + /// ----------------------------------------------------------------------- + + /// @param id ID of signing NFT. + /// @param hash Hash of data signed. + /// @param data OPTIONAL arbitrary data. + function isValidSignature( + uint256 id, + bytes32 hash, + bytes calldata data + ) public view virtual returns (bytes4) { + // Check `id` balance. + // This also confirms non-zero signer. + if (balanceOf[_recoverSigner(hash, data)][id] != 0) return 0x12edb34f; + // Otherwise, return error. + else return 0xffffffff; + } + /// ----------------------------------------------------------------------- /// ERC4337 Logic /// ----------------------------------------------------------------------- From 27e7048efabd39b8836fc64d71ef6034eb9ce66b Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:44:09 +0000 Subject: [PATCH 43/67] ~~ wasvalid --- .gas-snapshot | 50 ++++++++++++++++++++++----------------------- src/Keep.sol | 56 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 920039a2..9d529625 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,30 +1,30 @@ -KeepFactoryTest:testDeploy() (gas: 166562) -KeepFactoryTest:testDetermination() (gas: 170976) -KeepTest:testBalanceOf() (gas: 116086) -KeepTest:testBalanceOfBatch() (gas: 125383) -KeepTest:testBalanceOfSigner() (gas: 49511) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390752) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247532) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) -KeepTest:testCannotMintToZeroAddress() (gas: 119039) -KeepTest:testCannotRepeatKeepSetup() (gas: 4911884) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83732) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103060) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83749) +KeepFactoryTest:testDeploy() (gas: 166540) +KeepFactoryTest:testDetermination() (gas: 170954) +KeepTest:testBalanceOf() (gas: 116128) +KeepTest:testBalanceOfBatch() (gas: 125381) +KeepTest:testBalanceOfSigner() (gas: 49553) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390706) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247508) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) +KeepTest:testCannotMintToZeroAddress() (gas: 119101) +KeepTest:testCannotRepeatKeepSetup() (gas: 5014932) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83710) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83683) KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15148, ~: 15148) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15211, ~: 15211) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380784) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241942) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154575, ~: 154575) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180637, ~: 183896) -KeepTest:testExecuteDelegateCall() (gas: 46571) -KeepTest:testExecuteEthCall() (gas: 72384) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68026) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15189, ~: 15189) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180630, ~: 183894) +KeepTest:testExecuteDelegateCall() (gas: 46549) +KeepTest:testExecuteEthCall() (gas: 72362) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) KeepTest:testName() (gas: 10064) -KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 88057) -KeepTest:testQuorum() (gas: 23937) -KeepTest:testReceiveBatchERC1155() (gas: 44271) +KeepTest:testNoKeepKeyCollision() (gas: 230) +KeepTest:testNonceIncrementAfterExecute() (gas: 88013) +KeepTest:testQuorum() (gas: 23893) +KeepTest:testReceiveBatchERC1155() (gas: 44293) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) diff --git a/src/Keep.sol b/src/Keep.sol index 4d1ad6f8..0e7b11d9 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -158,16 +158,14 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { assembly { let s := shr(224, interfaceId) // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c, - // ERC721TokenReceiver: 0x150b7a02, ERC1155TokenReceiver: 0x4e2312e0 + // ERC721TokenReceiver: 0x150b7a02, ERC1155TokenReceiver: 0x4e2312e0, + // ERC1271: 0x1626ba7e, ERC6066: 0x12edb34f supported := or( + or(or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), eq(s, 0x0e89341c)), or( - or( - or(eq(s, 0x01ffc9a7), eq(s, 0xd9b67a26)), - eq(s, 0x0e89341c) - ), - eq(s, 0x150b7a02) - ), - eq(s, 0x4e2312e0) + or(eq(s, 0x150b7a02), eq(s, 0x4e2312e0)), + or(eq(s, 0x1626ba7e), eq(s, 0x12edb34f)) + ) ) } } @@ -477,6 +475,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return 0xffffffff; } + /// ----------------------------------------------------------------------- + /// @notice Signature revocation. + /// @param hash Signed data Hash. + /// @param sig Signature payload. function revokeSignature( bytes32 hash, Signature calldata sig @@ -493,16 +495,46 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @param id ID of signing NFT. - /// @param hash Hash of data signed. - /// @param data OPTIONAL arbitrary data. + /// @param hash Signed data Hash. + /// @param sig Signature payload. function isValidSignature( uint256 id, bytes32 hash, - bytes calldata data + bytes calldata sig ) public view virtual returns (bytes4) { + // Recover EOA signer for initial checks. + address user = _recoverSigner(hash, sig); + // Check if `sig` for `hash` was revoked. + if (revoked[user][hash]) return 0xffffffff; // Check `id` balance. // This also confirms non-zero signer. - if (balanceOf[_recoverSigner(hash, data)][id] != 0) return 0x12edb34f; + if (balanceOf[user][id] != 0) return 0x12edb34f; + // Otherwise, return error. + return 0xffffffff; + } + + /// ----------------------------------------------------------------------- + /// Temporal Signature Logic + /// ----------------------------------------------------------------------- + + /// @param id ID of signing NFT. + /// @param hash Signed data Hash. + /// @param sig Signature payload. + /// @param timestamp Timeliness. + function wasValidSignature( + uint256 id, + bytes32 hash, + bytes calldata sig, + uint256 timestamp + ) public view virtual returns (bytes4) { + // Recover EOA signer for initial checks. + address user = _recoverSigner(hash, sig); + // Check if `sig` for `hash` was revoked. + if (revoked[user][hash]) return 0xffffffff; + // Check `id` balance against `timestamp`. + // This also confirms non-zero signer. + if (getPastVotes(user, id, timestamp) != 0) + return this.wasValidSignature.selector; // Otherwise, return error. else return 0xffffffff; } From b5699ba464c3122f2f04ac508e93ee04c20325da Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:26:07 +0000 Subject: [PATCH 44/67] ~~ --- .gas-snapshot | 2 +- src/Keep.sol | 18 +++++------- src/KeepToken.sol | 74 +++++++++++++++++++++-------------------------- 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 9d529625..b10f4ebb 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180630, ~: 183894) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180647, ~: 183894) KeepTest:testExecuteDelegateCall() (gas: 46549) KeepTest:testExecuteEthCall() (gas: 72362) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) diff --git a/src/Keep.sol b/src/Keep.sol index 0e7b11d9..83a73816 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -316,7 +316,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); // Check `user` `sig` recovery. - _checkSig(hash, user, sig.v, sig.r, sig.s); + _checkSig(user, hash, sig.v, sig.r, sig.s); // Check against `user` duplicates. if (previous >= user) revert Unauthorized(); @@ -483,7 +483,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, Signature calldata sig ) public payable virtual { - _checkSig(hash, sig.user, sig.v, sig.r, sig.s); + _checkSig(sig.user, hash, sig.v, sig.r, sig.s); revoked[sig.user][hash] = true; @@ -551,7 +551,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Check request comes from `entrypoint()`. if (msg.sender != validator.entryPoint()) revert Unauthorized(); - // Return keccak256 hash of ERC191 signed data. + // Return keccak256 hash of ERC191-signed data for `userOpHash`. /// @solidity memory-safe-assembly assembly { mstore(0x20, userOpHash) // Store into scratch space for keccak256. @@ -560,9 +560,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } // Shift `userOp.nonce` to branch between `validator` & signature check. - if ( - userOp.nonce >> 64 == uint256(uint32(this.validateUserOp.selector)) - ) { + if (userOp.nonce >> 64 == uint32(this.validateUserOp.selector)) { validationData = validator.validateUserOp( userOp, userOpHash, @@ -681,16 +679,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) internal view virtual returns (address signer) { /// @solidity memory-safe-assembly assembly { - let m := mload(0x40) // Cache free memory pointer. - let signatureLength := mload(sig) + let m := mload(0x40) // Cache the free memory pointer. + let sigLength := mload(sig) mstore(0x00, hash) mstore(0x20, byte(0, mload(add(sig, 0x60)))) // `v`. mstore(0x40, mload(add(sig, 0x20))) // `r`. mstore(0x60, mload(add(sig, 0x40))) // `s`. signer := mload( staticcall( - gas(), // Amount of gas left for transaction. - eq(signatureLength, 65), // Address of `ecrecover`. + gas(), // Amount of gas left for the transaction. + eq(sigLength, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. diff --git a/src/KeepToken.sol b/src/KeepToken.sol index fe3812e1..21ea15f3 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -1,29 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -/// @notice ERC1155 interface to receive tokens. -abstract contract ERC1155TokenReceiver { - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes calldata - ) public payable virtual returns (bytes4) { - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] calldata, - uint256[] calldata, - bytes calldata - ) public payable virtual returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } -} - /// @notice ERC1155 token with Governor-style checkpointing, delegation and transfer restriction scheme. /// @author Modified from ERC1155V (https://github.com/kalidao/ERC1155V/blob/main/src/ERC1155V.sol) abstract contract KeepToken { @@ -89,23 +66,14 @@ abstract contract KeepToken { /// ----------------------------------------------------------------------- error InvalidSignature(); - error LengthMismatch(); - error Unauthorized(); - error NonTransferable(); - error NotPermitted(); - error UnsafeRecipient(); - error InvalidRecipient(); - error ExpiredSig(); - error Undetermined(); - error Overflow(); /// ----------------------------------------------------------------------- @@ -150,18 +118,18 @@ abstract contract KeepToken { } function _checkSig( + address user, bytes32 hash, - address signer, uint8 v, bytes32 r, bytes32 s ) internal view virtual { /// @solidity memory-safe-assembly assembly { - // Clean the upper 96 bits of `signer` in case they are dirty. + // Clean the upper 96 bits of `user` in case they are dirty. for { - signer := shr(96, shl(96, signer)) - } signer { + user := shr(96, shl(96, user)) + } user { } { let m := mload(0x40) @@ -178,7 +146,7 @@ abstract contract KeepToken { 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + if iszero(or(iszero(returndatasize()), xor(user, mload(t)))) { mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break @@ -189,10 +157,11 @@ abstract contract KeepToken { mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. - mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x44), 65) // Length of the `signature`. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), s) // `s`. mstore8(add(m, 0xa4), v) // `v`. + if iszero( and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). @@ -202,7 +171,7 @@ abstract contract KeepToken { // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. - signer, // The `signer` address. + user, // The `user` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. d, // Offset of returndata. @@ -457,7 +426,7 @@ abstract contract KeepToken { ) ); - _checkSig(hash, owner, v, r, s); + _checkSig(owner, hash, v, r, s); } isApprovedForAll[owner][operator] = approved; @@ -584,7 +553,7 @@ abstract contract KeepToken { ) ); - _checkSig(hash, delegator, v, r, s); + _checkSig(delegator, hash, v, r, s); } _delegate(delegator, delegatee, id); @@ -792,3 +761,26 @@ abstract contract KeepToken { emit UserPermissionSet(msg.sender, to, id, on); } } + +/// @notice ERC1155 interface to receive tokens. +abstract contract ERC1155TokenReceiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) public payable virtual returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) public payable virtual returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} From b8b0206ad5a41addf6055ba737e5066bd1abc890 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:30:03 +0000 Subject: [PATCH 45/67] ~~ --- .gas-snapshot | 6 +++--- src/Keep.sol | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index b10f4ebb..dc251860 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -7,7 +7,7 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39070 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247508) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 5014932) +KeepTest:testCannotRepeatKeepSetup() (gas: 5015532) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83710) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83683) @@ -16,14 +16,14 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180647, ~: 183894) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180309, ~: 183894) KeepTest:testExecuteDelegateCall() (gas: 46549) KeepTest:testExecuteEthCall() (gas: 72362) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) KeepTest:testName() (gas: 10064) KeepTest:testNoKeepKeyCollision() (gas: 230) KeepTest:testNonceIncrementAfterExecute() (gas: 88013) -KeepTest:testQuorum() (gas: 23893) +KeepTest:testQuorum() (gas: 23881) KeepTest:testReceiveBatchERC1155() (gas: 44293) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) diff --git a/src/Keep.sol b/src/Keep.sol index 83a73816..9c4db77e 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -212,18 +212,18 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (calls.length != 0) { for (uint256 i; i < calls.length; ) { + // An array can't have a total length + // larger than the max uint256 value. + unchecked { + ++i; + } + _execute( calls[i].op, calls[i].to, calls[i].value, calls[i].data ); - - // An array can't have a total length - // larger than the max uint256 value. - unchecked { - ++i; - } } } @@ -339,9 +339,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function relay(Call calldata call) public payable virtual { _authorized(); - _execute(call.op, call.to, call.value, call.data); - emit Relayed(call); + + _execute(call.op, call.to, call.value, call.data); } /// @notice Relay operations from Keep `execute()` or as `authorized()`. @@ -350,13 +350,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _authorized(); for (uint256 i; i < calls.length; ) { - _execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data); - // An array can't have a total length // larger than the max uint256 value. unchecked { ++i; } + + _execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data); } emit Multirelayed(calls); From 508ade7513f9433d25bfd852275182dcc3fecbe0 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:50:21 +0000 Subject: [PATCH 46/67] ~~ --- .gas-snapshot | 20 ++++++++++---------- src/Keep.sol | 15 ++++++++++----- src/KeepToken.sol | 6 ++++-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index dc251860..74279233 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,13 +1,13 @@ -KeepFactoryTest:testDeploy() (gas: 166540) -KeepFactoryTest:testDetermination() (gas: 170954) +KeepFactoryTest:testDeploy() (gas: 166543) +KeepFactoryTest:testDetermination() (gas: 170957) KeepTest:testBalanceOf() (gas: 116128) KeepTest:testBalanceOfBatch() (gas: 125381) KeepTest:testBalanceOfSigner() (gas: 49553) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390706) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247508) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390756) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247506) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 5015532) +KeepTest:testCannotRepeatKeepSetup() (gas: 5018335) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83710) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83683) @@ -16,13 +16,13 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180309, ~: 183894) -KeepTest:testExecuteDelegateCall() (gas: 46549) -KeepTest:testExecuteEthCall() (gas: 72362) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68004) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180635, ~: 183894) +KeepTest:testExecuteDelegateCall() (gas: 46547) +KeepTest:testExecuteEthCall() (gas: 72360) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68002) KeepTest:testName() (gas: 10064) KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88013) +KeepTest:testNonceIncrementAfterExecute() (gas: 88011) KeepTest:testQuorum() (gas: 23881) KeepTest:testReceiveBatchERC1155() (gas: 44293) MulticallableTest:testMulticallableBenchmark() (gas: 28510) diff --git a/src/Keep.sol b/src/Keep.sol index 9c4db77e..90aa6475 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -210,8 +210,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (threshold > signers.length) revert InvalidThreshold(); + uint256 i; if (calls.length != 0) { - for (uint256 i; i < calls.length; ) { + for (i; i < calls.length; ) { // An array can't have a total length // larger than the max uint256 value. unchecked { @@ -225,13 +226,14 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { calls[i].data ); } + delete i; } address previous; address signer; uint256 supply; - for (uint256 i; i < signers.length; ) { + for (i; i < signers.length; ) { signer = signers[i]; // Prevent zero and duplicate signers. @@ -306,7 +308,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { Signature calldata sig; // Check enough valid `sig` to pass `quorum`. - for (uint256 i; i < threshold; ) { + uint256 i; + for (i; i < threshold; ) { // Load `user` details. sig = sigs[i]; address user = sig.user; @@ -349,7 +352,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function multirelay(Call[] calldata calls) public payable virtual { _authorized(); - for (uint256 i; i < calls.length; ) { + uint256 i; + for (i; i < calls.length; ) { // An array can't have a total length // larger than the max uint256 value. unchecked { @@ -605,7 +609,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { address previous; // Check enough valid `sigs` to pass `quorum`. - for (uint256 i; i < threshold; ) { + uint256 i; + for (i; i < threshold; ) { address user = _recoverSigner(hash, sigs[i]); // Check against duplicates. diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 21ea15f3..6ae73a32 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -255,7 +255,8 @@ abstract contract KeepToken { balances = new uint256[](owners.length); - for (uint256 i; i < owners.length; ) { + uint256 i; + for (i; i < owners.length; ) { balances[i] = balanceOf[owners[i]][ids[i]]; // Unchecked because the only math done is incrementing @@ -335,8 +336,9 @@ abstract contract KeepToken { // Storing these outside the loop saves ~15 gas per iteration. uint256 id; uint256 amount; + uint256 i; - for (uint256 i; i < ids.length; ) { + for (i; i < ids.length; ) { id = ids[i]; amount = amounts[i]; From a3379febed54cded4962a8bc3c6b09af9f3ee485 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:10:19 +0000 Subject: [PATCH 47/67] ~~ clean sig validation --- .gas-snapshot | 54 ++--- src/Keep.sol | 225 +++++++++--------- .../validate/PermissionRemoteFetcher.sol | 2 +- src/extensions/validate/URIRemoteFetcher.sol | 4 +- src/extensions/validate/Validator.sol | 10 +- 5 files changed, 149 insertions(+), 146 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 74279233..7354847f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,30 +1,30 @@ -KeepFactoryTest:testDeploy() (gas: 166543) -KeepFactoryTest:testDetermination() (gas: 170957) -KeepTest:testBalanceOf() (gas: 116128) -KeepTest:testBalanceOfBatch() (gas: 125381) -KeepTest:testBalanceOfSigner() (gas: 49553) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390756) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247506) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14791) -KeepTest:testCannotMintToZeroAddress() (gas: 119101) -KeepTest:testCannotRepeatKeepSetup() (gas: 5018335) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83710) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103038) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83683) -KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15148, ~: 15148) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15189, ~: 15189) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380804) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241962) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154617, ~: 154617) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180635, ~: 183894) -KeepTest:testExecuteDelegateCall() (gas: 46547) -KeepTest:testExecuteEthCall() (gas: 72360) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68002) -KeepTest:testName() (gas: 10064) -KeepTest:testNoKeepKeyCollision() (gas: 230) -KeepTest:testNonceIncrementAfterExecute() (gas: 88011) -KeepTest:testQuorum() (gas: 23881) -KeepTest:testReceiveBatchERC1155() (gas: 44293) +KeepFactoryTest:testDeploy() (gas: 166573) +KeepFactoryTest:testDetermination() (gas: 170987) +KeepTest:testBalanceOf() (gas: 116093) +KeepTest:testBalanceOfBatch() (gas: 125406) +KeepTest:testBalanceOfSigner() (gas: 49511) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390859) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247565) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) +KeepTest:testCannotMintToZeroAddress() (gas: 119050) +KeepTest:testCannotRepeatKeepSetup() (gas: 4803608) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83736) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103072) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83753) +KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15152, ~: 15152) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15215, ~: 15215) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380841) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241977) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154600, ~: 154600) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180663, ~: 183911) +KeepTest:testExecuteDelegateCall() (gas: 46577) +KeepTest:testExecuteEthCall() (gas: 72390) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68032) +KeepTest:testName() (gas: 10048) +KeepTest:testNoKeepKeyCollision() (gas: 208) +KeepTest:testNonceIncrementAfterExecute() (gas: 88075) +KeepTest:testQuorum() (gas: 23933) +KeepTest:testReceiveBatchERC1155() (gas: 44271) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) diff --git a/src/Keep.sol b/src/Keep.sol index 90aa6475..84829a4f 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -226,7 +226,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { calls[i].data ); } - delete i; } address previous; @@ -439,47 +438,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } /// ----------------------------------------------------------------------- - /// ERC1271 Logic + /// Revocation Logic /// ----------------------------------------------------------------------- - function isValidSignature( - bytes32 hash, - bytes memory sig - ) public view virtual returns (bytes4) { - // Recover EOA signer for initial checks. - address user = _recoverSigner(hash, sig); - - // Check if `sig` for `hash` was revoked. - if (revoked[user][hash]) return 0xffffffff; - - // Check SIGN_KEY balance. - // This also confirms non-zero signer. - if (balanceOf[user][SIGN_KEY] != 0) - // `bytes4(keccak256("isValidSignature(bytes32,bytes)")`. - return 0x1626ba7e; - - // Fallback to check nested contract signatures. - // Overwrite `user` with first 20 bytes and `sig` - // with remaining data. - assembly { - // Extract the first 20 bytes into address. - let word := mload(add(sig, 0x20)) - user := shr(96, word) - // Update `sig` to the remaining bytes. - mstore(sig, sub(mload(sig), 20)) - mstore(add(sig, 0x20), add(add(sig, 0x20), 20)) - } - - // Check SIGN_KEY balance of contract account. - if (balanceOf[user][SIGN_KEY] != 0) - if (Keep(user).isValidSignature(hash, sig) == 0x1626ba7e) - return 0x1626ba7e; - - // Otherwise, return error. - return 0xffffffff; - } - - /// ----------------------------------------------------------------------- /// @notice Signature revocation. /// @param hash Signed data Hash. /// @param sig Signature payload. @@ -495,52 +456,32 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } /// ----------------------------------------------------------------------- - /// ERC6066 Logic + /// ERC1271 Logic /// ----------------------------------------------------------------------- - /// @param id ID of signing NFT. - /// @param hash Signed data Hash. - /// @param sig Signature payload. function isValidSignature( - uint256 id, bytes32 hash, bytes calldata sig ) public view virtual returns (bytes4) { - // Recover EOA signer for initial checks. - address user = _recoverSigner(hash, sig); - // Check if `sig` for `hash` was revoked. - if (revoked[user][hash]) return 0xffffffff; - // Check `id` balance. - // This also confirms non-zero signer. - if (balanceOf[user][id] != 0) return 0x12edb34f; - // Otherwise, return error. - return 0xffffffff; + // Check `SIGN_KEY` as this denotes ownership. + if (_validate(hash, sig, SIGN_KEY) == 0) + return 0x1626ba7e; else return 0xffffffff; } /// ----------------------------------------------------------------------- - /// Temporal Signature Logic + /// ERC6066 Logic /// ----------------------------------------------------------------------- /// @param id ID of signing NFT. /// @param hash Signed data Hash. /// @param sig Signature payload. - /// @param timestamp Timeliness. - function wasValidSignature( + function isValidSignature( uint256 id, bytes32 hash, - bytes calldata sig, - uint256 timestamp + bytes calldata sig ) public view virtual returns (bytes4) { - // Recover EOA signer for initial checks. - address user = _recoverSigner(hash, sig); - // Check if `sig` for `hash` was revoked. - if (revoked[user][hash]) return 0xffffffff; - // Check `id` balance against `timestamp`. - // This also confirms non-zero signer. - if (getPastVotes(user, id, timestamp) != 0) - return this.wasValidSignature.selector; - // Otherwise, return error. - else return 0xffffffff; + if (_validate(hash, sig, id) == 0) + return 0x12edb34f; else return 0xffffffff; } /// ----------------------------------------------------------------------- @@ -564,14 +505,20 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } // Shift `userOp.nonce` to branch between `validator` & signature check. - if (userOp.nonce >> 64 == uint32(this.validateUserOp.selector)) { + if ( + userOp.nonce >> 64 == uint256(uint32(this.validateUserOp.selector)) + ) { validationData = validator.validateUserOp( userOp, userOpHash, missingAccountFunds ); } else { - validationData = _validateSignatures(userOpHash, userOp.signature); + validationData = _validate( + userOpHash, + userOp.signature, + SIGN_KEY + ); } // Send any missing funds to `entrypoint()` (msg.sender). @@ -592,16 +539,24 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } - function _validateSignatures( + function _validate( bytes32 hash, - bytes calldata sig - ) internal view virtual returns (uint256) { - // Memo `quorum` threshold for loop length. + bytes calldata sig, + uint256 id + ) internal view virtual returns (uint256 validationData) { + address user; uint256 threshold = quorum; // Early check for single `sig`. - if (threshold == 1) - return balanceOf[_recoverSigner(hash, sig)][SIGN_KEY] != 0 ? 0 : 1; + if (threshold == 1) { + (user, validationData) = _validateSig(hash, sig); + + if (validationData == 1) return 1; + + if (revoked[user][hash]) return 1; + + return balanceOf[user][id] != 0 ? 0 : 1; + } // Memo split `sig` if batched. bytes[] memory sigs = _splitSigs(sig); @@ -611,16 +566,20 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Check enough valid `sigs` to pass `quorum`. uint256 i; for (i; i < threshold; ) { - address user = _recoverSigner(hash, sigs[i]); + (user, validationData) = _validateSig(hash, sig); + + if (validationData == 1) return 1; + + if (revoked[user][hash]) return 1; // Check against duplicates. - if (previous >= user) revert Unauthorized(); + if (previous >= user) return 1; // Memo signature for next iteration until quorum. previous = user; // If not keyholding `user`, `SIG_VALIDATION_FAILED`. - if (balanceOf[user][SIGN_KEY] == 0) return 1; + if (balanceOf[user][id] == 0) return 1; // An array can't have a total length // larger than the max uint256 value. @@ -632,6 +591,80 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return 0; } + function _validateSig( + bytes32 hash, + bytes memory sig + ) internal view virtual returns (address user, uint256 validationData) { + bytes32 r; + bytes32 s; + uint8 v; + + assembly { + r := mload(add(sig, 0x20)) + s := mload(add(sig, 0x40)) + v := byte(0, mload(add(sig, 0x60))) + } + + if (v != 0) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + user := mload( + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + ) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } else { + user = address(uint160(uint256(r))); + + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. + + if iszero( + and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + user, // The `user` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + ) { + validationData := 1 + } + } + } + } + function _splitSigs( bytes memory sig ) internal pure virtual returns (bytes[] memory sigs) { @@ -678,38 +711,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } } - function _recoverSigner( - bytes32 hash, - bytes memory sig - ) internal view virtual returns (address signer) { - /// @solidity memory-safe-assembly - assembly { - let m := mload(0x40) // Cache the free memory pointer. - let sigLength := mload(sig) - mstore(0x00, hash) - mstore(0x20, byte(0, mload(add(sig, 0x60)))) // `v`. - mstore(0x40, mload(add(sig, 0x20))) // `r`. - mstore(0x60, mload(add(sig, 0x40))) // `s`. - signer := mload( - staticcall( - gas(), // Amount of gas left for the transaction. - eq(sigLength, 65), // Address of `ecrecover`. - 0x00, // Start of input. - 0x80, // Size of input. - 0x01, // Start of output. - 0x20 // Size of output. - ) - ) - // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. - if iszero(returndatasize()) { - mstore(0x00, 0x8baa579f) // `InvalidSignature()`. - revert(0x1c, 0x04) - } - mstore(0x60, 0) // Restore the zero slot. - mstore(0x40, m) // Restore the free memory pointer. - } - } - /// ----------------------------------------------------------------------- /// Mint/Burn Logic /// ----------------------------------------------------------------------- diff --git a/src/extensions/validate/PermissionRemoteFetcher.sol b/src/extensions/validate/PermissionRemoteFetcher.sol index 1d3a5ef4..30408c1d 100644 --- a/src/extensions/validate/PermissionRemoteFetcher.sol +++ b/src/extensions/validate/PermissionRemoteFetcher.sol @@ -16,7 +16,7 @@ struct Permission { contract PermissionRemoteFetcher { function validateUserOp( UserOperation calldata userOp, - bytes32 hash, + bytes32 userOpHash, uint256 missingAccountFunds ) public view virtual returns (uint256) { /*Permission memory permission = abi.decode(userOp.data, (Permission)); diff --git a/src/extensions/validate/URIRemoteFetcher.sol b/src/extensions/validate/URIRemoteFetcher.sol index b07212a8..78a1f62c 100644 --- a/src/extensions/validate/URIRemoteFetcher.sol +++ b/src/extensions/validate/URIRemoteFetcher.sol @@ -7,14 +7,14 @@ contract URIRemoteFetcher { constructor() payable {} - function uri(uint256 id) public view virtual returns (string memory) { + function uri(address origin, uint256 id) public view virtual returns (string memory) { return string( abi.encodePacked( "https://api.kali.gg/v1/keeps/", toString(chainId), "/", - toHexStringChecksummed(tx.origin), + toHexStringChecksummed(origin), "/", toString(id) ) diff --git a/src/extensions/validate/Validator.sol b/src/extensions/validate/Validator.sol index 60325830..57948c97 100644 --- a/src/extensions/validate/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.4; import {Ownable} from "../../utils/Ownable.sol"; import {UserOperation} from "../../Keep.sol"; +function getURI(address,uint256) returns (string memory) {} /// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. contract Validator is Ownable(tx.origin) { @@ -26,7 +27,7 @@ contract Validator is Ownable(tx.origin) { Validator internal uriRemoteValidator; - address internal entryPoint; + address public entryPoint; /// ----------------------------------------------------------------------- /// Constructor @@ -44,13 +45,13 @@ contract Validator is Ownable(tx.origin) { function validateUserOp( UserOperation calldata userOp, - bytes32 hash, + bytes32 userOpHash, uint256 missingAccountFunds ) public view virtual returns (uint256) { return permissionRemoteValidator.validateUserOp( userOp, - hash, + userOpHash, missingAccountFunds ); } @@ -66,9 +67,10 @@ contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// URI Remote Logic /// ----------------------------------------------------------------------- + function uri(address, uint256) public view virtual returns (string memory) {} function uri(uint256 id) public view virtual returns (string memory) { - return uriRemoteValidator.uri(id); + return uriRemoteValidator.uri(msg.sender, id); } function setURIRemoteValidator( From 47647dd7135df096cf36b907c800fd3695d781d4 Mon Sep 17 00:00:00 2001 From: shiv Date: Mon, 4 Sep 2023 13:20:53 +0000 Subject: [PATCH 48/67] s --- .github/workflows/tests.yml | 2 +- pnpm-lock.yaml | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37e21d48..a9eec891 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install dev dependencies - run: pnpm i + run: pnpm install --no-frozen-lockfile - name: Run formatter run: pnpm run format diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 479ba513..e81632a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,8 +25,8 @@ packages: chalk: 2.4.2 dev: true - /@babel/helper-validator-identifier@7.22.5: - resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + /@babel/helper-validator-identifier@7.22.15: + resolution: {integrity: sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==} engines: {node: '>=6.9.0'} dev: true @@ -34,7 +34,7 @@ packages: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.22.15 chalk: 2.4.2 js-tokens: 4.0.0 dev: true @@ -162,9 +162,14 @@ packages: engines: {node: '>=14'} dev: true - /cosmiconfig@8.2.0: - resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} + /cosmiconfig@8.3.3: + resolution: {integrity: sha512-/VY+0IvFoE47hwgKHu8feeBFIb1Z1mcJFiLrNwaJpLoLa9qwLVquMGMr2OUwQmhpJDtsSQSasg/TMv1imec9xA==} engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -407,7 +412,7 @@ packages: ast-parents: 0.0.1 chalk: 4.1.2 commander: 10.0.1 - cosmiconfig: 8.2.0 + cosmiconfig: 8.3.3 fast-diff: 1.3.0 glob: 8.1.0 ignore: 5.2.4 @@ -420,6 +425,8 @@ packages: text-table: 0.2.0 optionalDependencies: prettier: 2.8.8 + transitivePeerDependencies: + - typescript dev: true /solidity-comments-extractor@0.0.7: From 37bb52bc4bca0f72c43024a12e62ad7818483c42 Mon Sep 17 00:00:00 2001 From: shiv Date: Mon, 4 Sep 2023 13:27:54 +0000 Subject: [PATCH 49/67] formatter --- package.json | 2 +- src/Keep.sol | 14 +++++--------- src/extensions/validate/URIRemoteFetcher.sol | 5 ++++- src/extensions/validate/Validator.sol | 8 ++++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 9de4be4d..48b211ae 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "MIT", "private": false, "scripts": { - "format": "prettier --write src/**.sol src/utils/**.sol src/utils/interfaces/**.sol src/extensions/dao/**.sol src/extensions/dao/utils/**.sol src/extensions/metadata/**.sol src/extensions/mint/**.sol src/extensions/storage/**.sol src/extensions/utils/**.sol test/**.sol test/utils/mocks/**.sol flat/**.sol --plugin-search-dir=.", + "format": "prettier --write --plugin=prettier-plugin-solidity 'src/**/*.sol' 'test/**/*.sol' 'flat/**/*.sol'", "format:list": "prettier --list-different 'src/**/*.sol'", "format:check": "prettier --check 'src/**/*.sol'", "solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix --ignore-path .solhintignore", diff --git a/src/Keep.sol b/src/Keep.sol index 84829a4f..e69e269c 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -464,8 +464,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes calldata sig ) public view virtual returns (bytes4) { // Check `SIGN_KEY` as this denotes ownership. - if (_validate(hash, sig, SIGN_KEY) == 0) - return 0x1626ba7e; else return 0xffffffff; + if (_validate(hash, sig, SIGN_KEY) == 0) return 0x1626ba7e; + else return 0xffffffff; } /// ----------------------------------------------------------------------- @@ -480,8 +480,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, bytes calldata sig ) public view virtual returns (bytes4) { - if (_validate(hash, sig, id) == 0) - return 0x12edb34f; else return 0xffffffff; + if (_validate(hash, sig, id) == 0) return 0x12edb34f; + else return 0xffffffff; } /// ----------------------------------------------------------------------- @@ -514,11 +514,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { missingAccountFunds ); } else { - validationData = _validate( - userOpHash, - userOp.signature, - SIGN_KEY - ); + validationData = _validate(userOpHash, userOp.signature, SIGN_KEY); } // Send any missing funds to `entrypoint()` (msg.sender). diff --git a/src/extensions/validate/URIRemoteFetcher.sol b/src/extensions/validate/URIRemoteFetcher.sol index 78a1f62c..dca14be6 100644 --- a/src/extensions/validate/URIRemoteFetcher.sol +++ b/src/extensions/validate/URIRemoteFetcher.sol @@ -7,7 +7,10 @@ contract URIRemoteFetcher { constructor() payable {} - function uri(address origin, uint256 id) public view virtual returns (string memory) { + function uri( + address origin, + uint256 id + ) public view virtual returns (string memory) { return string( abi.encodePacked( diff --git a/src/extensions/validate/Validator.sol b/src/extensions/validate/Validator.sol index 57948c97..3d7e22ae 100644 --- a/src/extensions/validate/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.4; import {Ownable} from "../../utils/Ownable.sol"; import {UserOperation} from "../../Keep.sol"; -function getURI(address,uint256) returns (string memory) {} + +function getURI(address, uint256) returns (string memory) {} /// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. contract Validator is Ownable(tx.origin) { @@ -67,7 +68,10 @@ contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// URI Remote Logic /// ----------------------------------------------------------------------- - function uri(address, uint256) public view virtual returns (string memory) {} + function uri( + address, + uint256 + ) public view virtual returns (string memory) {} function uri(uint256 id) public view virtual returns (string memory) { return uriRemoteValidator.uri(msg.sender, id); From 05aa5d79c038c4bddd9e0be9a94043840e811f9f Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:51:26 +0000 Subject: [PATCH 50/67] ~~ --- .gas-snapshot | 4 ++-- src/Keep.sol | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 7354847f..4124d76e 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -7,7 +7,7 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39085 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247565) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) KeepTest:testCannotMintToZeroAddress() (gas: 119050) -KeepTest:testCannotRepeatKeepSetup() (gas: 4803608) +KeepTest:testCannotRepeatKeepSetup() (gas: 4797791) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83736) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103072) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83753) @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380841) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241977) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154600, ~: 154600) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180663, ~: 183911) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180648, ~: 183911) KeepTest:testExecuteDelegateCall() (gas: 46577) KeepTest:testExecuteEthCall() (gas: 72390) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68032) diff --git a/src/Keep.sol b/src/Keep.sol index e69e269c..a6a356f2 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -301,12 +301,12 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Memo zero `user` in loop for ascending order. address previous; - // Memo `quorum` threshold for loop length. + // Memo `quorum` `threshold` for loop length. uint256 threshold = quorum; // Memo `sig` outside loop for gas optimization. Signature calldata sig; - // Check enough valid `sig` to pass `quorum`. + // Check enough valid `sigs` to pass `threshold`. uint256 i; for (i; i < threshold; ) { // Load `user` details. @@ -554,15 +554,15 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return balanceOf[user][id] != 0 ? 0 : 1; } - // Memo split `sig` if batched. + // Memo split `sig` into `sigs`. bytes[] memory sigs = _splitSigs(sig); // Memo zero `user` in loop for ascending order. address previous; - // Check enough valid `sigs` to pass `quorum`. + // Check enough valid `sigs` to pass `threshold`. uint256 i; for (i; i < threshold; ) { - (user, validationData) = _validateSig(hash, sig); + (user, validationData) = _validateSig(hash, sigs[i]); if (validationData == 1) return 1; From 71234618adbcafb162ea08cc6c9d47864d9d2639 Mon Sep 17 00:00:00 2001 From: shiv Date: Mon, 4 Sep 2023 20:01:00 +0530 Subject: [PATCH 51/67] =?UTF-8?q?=F0=9F=A5=A2=20reorder=20branch=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 reorder branch case --- src/Keep.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index a6a356f2..725a0c5b 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -506,15 +506,15 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Shift `userOp.nonce` to branch between `validator` & signature check. if ( - userOp.nonce >> 64 == uint256(uint32(this.validateUserOp.selector)) + userOp.nonce >> 64 != uint256(uint32(this.validateUserOp.selector)) ) { + validationData = _validate(userOpHash, userOp.signature, SIGN_KEY); + } else { validationData = validator.validateUserOp( userOp, userOpHash, missingAccountFunds ); - } else { - validationData = _validate(userOpHash, userOp.signature, SIGN_KEY); } // Send any missing funds to `entrypoint()` (msg.sender). From d863fdcdeebc0ba798a97c5e03dfbcad2701e6f1 Mon Sep 17 00:00:00 2001 From: shiv Date: Tue, 5 Sep 2023 08:18:02 +0000 Subject: [PATCH 52/67] =?UTF-8?q?=F0=9F=A7=B9=20~~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 36 +++++++++++----------- src/Keep.sol | 73 +++++++++++++++++++++++---------------------- src/KeepFactory.sol | 8 ++--- test/Keep.t.sol | 13 ++++---- 4 files changed, 67 insertions(+), 63 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4124d76e..5323a433 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,30 +1,30 @@ -KeepFactoryTest:testDeploy() (gas: 166573) -KeepFactoryTest:testDetermination() (gas: 170987) +KeepFactoryTest:testDeploy() (gas: 166421) +KeepFactoryTest:testDetermination() (gas: 170835) KeepTest:testBalanceOf() (gas: 116093) -KeepTest:testBalanceOfBatch() (gas: 125406) +KeepTest:testBalanceOfBatch() (gas: 125384) KeepTest:testBalanceOfSigner() (gas: 49511) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390859) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247565) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390750) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247499) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) -KeepTest:testCannotMintToZeroAddress() (gas: 119050) -KeepTest:testCannotRepeatKeepSetup() (gas: 4797791) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83736) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103072) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83753) +KeepTest:testCannotMintToZeroAddress() (gas: 119161) +KeepTest:testCannotRepeatKeepSetup() (gas: 4781571) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83737) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103051) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83754) KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15152, ~: 15152) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15215, ~: 15215) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15260, ~: 15260) KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380841) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241977) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154600, ~: 154600) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180648, ~: 183911) -KeepTest:testExecuteDelegateCall() (gas: 46577) -KeepTest:testExecuteEthCall() (gas: 72390) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 68032) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180316, ~: 183911) +KeepTest:testExecuteDelegateCall() (gas: 48581) +KeepTest:testExecuteEthCall() (gas: 74394) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70036) KeepTest:testName() (gas: 10048) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 88075) -KeepTest:testQuorum() (gas: 23933) -KeepTest:testReceiveBatchERC1155() (gas: 44271) +KeepTest:testNonceIncrementAfterExecute() (gas: 90083) +KeepTest:testQuorum() (gas: 24319) +KeepTest:testReceiveBatchERC1155() (gas: 44228) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) MulticallableTest:testMulticallablePreservesMsgSender() (gas: 10980) diff --git a/src/Keep.sol b/src/Keep.sol index 725a0c5b..0b21fff9 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -76,8 +76,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Emitted when Keep relays calls. event Multirelayed(Call[] calls); - /// @dev Emitted when `quorum` updates. - event QuorumSet(uint256 threshold); + /// @dev Emitted when `quorum` `threshold` updates. + event ThresholdSet(uint256 id, uint256 quorum); /// @dev Emitted when signature revoked. event SignatureRevoked(address indexed user, bytes32 hash); @@ -105,11 +105,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Record of states verifying `execute()`. uint120 public nonce; - /// @dev SIGN_KEY threshold to `execute()`. - uint120 public quorum; - /// @dev Internal ID metadata mapping. - mapping(uint256 id => string meta) internal _uri; + mapping(uint256 id => string) internal _uri; + + /// @dev Internal ID `threshold` mapping. + mapping(uint256 id => uint256) public threshold; /// @dev Contract signature hash revocation status. mapping(address user => mapping(bytes32 hash => bool)) public revoked; @@ -192,27 +192,26 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { constructor(Keep _validator) payable { validator = _validator; // Deploy as singleton. - quorum = 1; + threshold[SIGN_KEY] = 1; } /// @notice Initialize Keep configuration. /// @param calls Initial Keep operations. /// @param signers Initial signer set. - /// @param threshold Initial quorum. + /// @param quorum Initial quorum. function initialize( Call[] calldata calls, address[] calldata signers, - uint256 threshold + uint256 quorum ) public payable virtual { - if (quorum != 0) revert AlreadyInit(); + if (threshold[SIGN_KEY] != 0) revert AlreadyInit(); - if (threshold == 0) revert InvalidThreshold(); + if (quorum == 0) revert InvalidThreshold(); - if (threshold > signers.length) revert InvalidThreshold(); + if (quorum > signers.length) revert InvalidThreshold(); - uint256 i; if (calls.length != 0) { - for (i; i < calls.length; ) { + for (uint256 i; i < calls.length; ) { // An array can't have a total length // larger than the max uint256 value. unchecked { @@ -232,7 +231,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { address signer; uint256 supply; - for (i; i < signers.length; ) { + for (uint256 i; i < signers.length; ) { signer = signers[i]; // Prevent zero and duplicate signers. @@ -252,7 +251,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } totalSupply[SIGN_KEY] = supply; - quorum = uint120(threshold); + threshold[SIGN_KEY] = quorum; } /// ----------------------------------------------------------------------- @@ -302,13 +301,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Memo zero `user` in loop for ascending order. address previous; // Memo `quorum` `threshold` for loop length. - uint256 threshold = quorum; + uint256 quorum = threshold[SIGN_KEY]; // Memo `sig` outside loop for gas optimization. Signature calldata sig; - // Check enough valid `sigs` to pass `threshold`. + // Check enough valid `sigs` to pass `quorum`. uint256 i; - for (i; i < threshold; ) { + for (i; i < quorum; ) { // Load `user` details. sig = sigs[i]; address user = sig.user; @@ -504,11 +503,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { userOpHash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } + uint32 key = uint32(userOp.nonce >> 64); + // Shift `userOp.nonce` to branch between `validator` & signature check. - if ( - userOp.nonce >> 64 != uint256(uint32(this.validateUserOp.selector)) - ) { - validationData = _validate(userOpHash, userOp.signature, SIGN_KEY); + if (key != uint32(this.validateUserOp.selector)) { + validationData = _validate(userOpHash, userOp.signature, key); } else { validationData = validator.validateUserOp( userOp, @@ -541,10 +540,12 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint256 id ) internal view virtual returns (uint256 validationData) { address user; - uint256 threshold = quorum; + uint256 quorum = threshold[id]; + + if (quorum == 0) return 1; // Early check for single `sig`. - if (threshold == 1) { + if (quorum == 1) { (user, validationData) = _validateSig(hash, sig); if (validationData == 1) return 1; @@ -559,9 +560,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Memo zero `user` in loop for ascending order. address previous; - // Check enough valid `sigs` to pass `threshold`. + // Check enough valid `sigs` to pass `quorum`. uint256 i; - for (i; i < threshold; ) { + for (i; i < quorum; ) { (user, validationData) = _validateSig(hash, sigs[i]); if (validationData == 1) return 1; @@ -743,24 +744,26 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _burn(from, id, amount); if (id == SIGN_KEY) - if (quorum > totalSupply[SIGN_KEY]) revert InvalidThreshold(); + if (threshold[SIGN_KEY] > totalSupply[SIGN_KEY]) + revert InvalidThreshold(); } /// ----------------------------------------------------------------------- /// Threshold Setting Logic /// ----------------------------------------------------------------------- - /// @notice Update Keep quorum threshold. - /// @param threshold Signature threshold for `execute()`. - function setQuorum(uint256 threshold) public payable virtual { + /// @notice Update Keep ID threshold. + /// @param id ID key to set threshold for. + /// @param quorum Signature threshold for operations. + function setThreshold(uint256 id, uint256 quorum) public payable virtual { _authorized(); - if (threshold == 0) revert InvalidThreshold(); - if (threshold > totalSupply[SIGN_KEY]) revert InvalidThreshold(); + if (quorum == 0) revert InvalidThreshold(); + if (quorum > totalSupply[id]) revert InvalidThreshold(); - quorum = uint120(threshold); + threshold[SIGN_KEY] = uint120(quorum); - emit QuorumSet(threshold); + emit ThresholdSet(id, quorum); } /// ----------------------------------------------------------------------- diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 4fae353e..ae5a187b 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -11,7 +11,7 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { /// Events /// ----------------------------------------------------------------------- - event Deployed(Keep indexed keep, uint256 threshold); + event Deployed(Keep indexed keep, uint256 quorum); /// ----------------------------------------------------------------------- /// Custom Errors @@ -41,7 +41,7 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { bytes32 name, // create2 salt. Call[] calldata calls, address[] calldata signers, - uint256 threshold + uint256 quorum ) public payable virtual returns (Keep keep) { bytes memory data = abi.encodePacked(name); address implementation = address(keepTemplate); @@ -110,9 +110,9 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { mstore(sub(data, 0x60), mBefore3) } - keep.initialize(calls, signers, threshold); + keep.initialize(calls, signers, quorum); - emit Deployed(keep, threshold); + emit Deployed(keep, quorum); } function determineKeep( diff --git a/test/Keep.t.sol b/test/Keep.t.sol index a83223b6..548a1987 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -410,12 +410,12 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { // } function testQuorum() public payable { - assert(keep.quorum() == 2); + assert(keep.threshold(SIGN_KEY) == 2); vm.prank(address(keep)); - keep.setQuorum(3); + keep.setThreshold(SIGN_KEY, 3); - assert(keep.quorum() == 3); + assert(keep.threshold(SIGN_KEY) == 3); } function testBalanceOf() public { @@ -503,8 +503,9 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { assert( keep.multirelay.selector != keep.mint.selector && keep.mint.selector != keep.burn.selector && - keep.burn.selector != keep.setQuorum.selector && - keep.setQuorum.selector != keep.setTransferability.selector && + keep.burn.selector != keep.setThreshold.selector && + keep.setThreshold.selector != + keep.setTransferability.selector && keep.setTransferability.selector != keep.setPermission.selector && keep.setPermission.selector != @@ -1011,7 +1012,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { assert(keep.totalSupply(SIGNER_KEY) == 3); assert(keep.totalSupply(1) == 0); - assert(keep.quorum() == 2); + assert(keep.threshold(SIGN_KEY) == 2); } // function testCannotMintToUnsafeAddress() public payable { From 3d593d2779d5ef47f1b9468a94abb1b9d5a51b41 Mon Sep 17 00:00:00 2001 From: shiv Date: Tue, 5 Sep 2023 08:47:16 +0000 Subject: [PATCH 53/67] nits --- .gas-snapshot | 4 ++-- src/Keep.sol | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 5323a433..365c7326 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -7,7 +7,7 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39075 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247499) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) KeepTest:testCannotMintToZeroAddress() (gas: 119161) -KeepTest:testCannotRepeatKeepSetup() (gas: 4781571) +KeepTest:testCannotRepeatKeepSetup() (gas: 4783380) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83737) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103051) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83754) @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380841) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241977) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154600, ~: 154600) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180316, ~: 183911) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180298, ~: 183911) KeepTest:testExecuteDelegateCall() (gas: 48581) KeepTest:testExecuteEthCall() (gas: 74394) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70036) diff --git a/src/Keep.sol b/src/Keep.sol index 0b21fff9..f018f0e2 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -503,21 +503,22 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { userOpHash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } - uint32 key = uint32(userOp.nonce >> 64); + // Shift `userOp.nonce` to get ID key. + uint32 id = uint32(userOp.nonce >> 64); - // Shift `userOp.nonce` to branch between `validator` & signature check. - if (key != uint32(this.validateUserOp.selector)) { - validationData = _validate(userOpHash, userOp.signature, key); - } else { + // Check signature `threshold` is met and validate users. + validationData = _validate(userOpHash, userOp.signature, id); + + // If `permissioned` ID key, send `userOp` for `validator` check. + if (permissioned[id]) validationData = validator.validateUserOp( userOp, userOpHash, missingAccountFunds ); - } // Send any missing funds to `entrypoint()` (msg.sender). - if (missingAccountFunds != 0) { + if (missingAccountFunds != 0) assembly { pop( call( @@ -531,7 +532,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) ) } - } } function _validate( From 2b738d91cd2aba311b0956857a67891afabda6cc Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:52:25 +0000 Subject: [PATCH 54/67] =?UTF-8?q?=E2=9C=82=EF=B8=8F=20~~=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitpod.yml | 2 -- package.json | 4 +-- src/Keep.sol | 4 +-- src/KeepFactory.sol | 4 +-- src/KeepToken.sol | 4 +-- src/extensions/mint/MintManager.sol | 31 ++++++++-------- src/extensions/utils/KeepTokenManager.sol | 35 ------------------- src/extensions/utils/ReentrancyGuard.sol | 20 ----------- .../validate/PermissionRemoteFetcher.sol | 4 +-- src/extensions/validate/URIRemoteFetcher.sol | 4 +-- src/extensions/validate/Validator.sol | 4 +-- src/utils/Multicallable.sol | 4 +-- src/utils/Ownable.sol | 4 +-- src/utils/interfaces/IKeep.sol | 4 +-- src/utils/interfaces/IKeepFactory.sol | 4 +-- test/Keep.t.sol | 4 +-- test/KeepFactory.t.sol | 4 +-- test/Multicallable.t.sol | 4 +-- test/Ownable.t.sol | 4 +-- test/utils/helpers.sol | 10 ------ test/utils/mocks/MockMulticallable.sol | 4 +-- test/utils/mocks/MockOwnable.sol | 4 +-- .../utils/mocks/MockUnsafeERC1155Receiver.sol | 4 +-- 23 files changed, 51 insertions(+), 119 deletions(-) delete mode 100644 .gitpod.yml delete mode 100644 src/extensions/utils/KeepTokenManager.sol delete mode 100644 src/extensions/utils/ReentrancyGuard.sol delete mode 100644 test/utils/helpers.sol diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 98974eb2..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,2 +0,0 @@ -tasks: - - init: curl -L https://foundry.paradigm.xyz | bash && source /home/gitpod/.bashrc && foundryup && pnpm i diff --git a/package.json b/package.json index 48b211ae..72388a66 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "url": "git+https://github.com/kalidao/keep.git" }, "author": "KaliCo LLC ", - "license": "MIT", + "license": "AGPL-3.0-only", "private": false, "scripts": { - "format": "prettier --write --plugin=prettier-plugin-solidity 'src/**/*.sol' 'test/**/*.sol' 'flat/**/*.sol'", + "format": "prettier --write --plugin=prettier-plugin-solidity 'src/**/*.sol' 'src/extensions/validate/*.sol' 'src/extensions/mint/*.sol' 'test/**/*.sol' 'flat/**/*.sol'", "format:list": "prettier --list-different 'src/**/*.sol'", "format:check": "prettier --check 'src/**/*.sol'", "solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix --ignore-path .solhintignore", diff --git a/src/Keep.sol b/src/Keep.sol index f018f0e2..94941c42 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {ERC1155TokenReceiver, KeepToken} from "./KeepToken.sol"; import {Multicallable} from "./utils/Multicallable.sol"; diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index ae5a187b..02ae17ef 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {Multicallable, Call, Keep} from "./Keep.sol"; import {Ownable} from "./utils/Ownable.sol"; diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 6ae73a32..5a537f72 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; /// @notice ERC1155 token with Governor-style checkpointing, delegation and transfer restriction scheme. /// @author Modified from ERC1155V (https://github.com/kalidao/ERC1155V/blob/main/src/ERC1155V.sol) diff --git a/src/extensions/mint/MintManager.sol b/src/extensions/mint/MintManager.sol index e77919b8..e68c25f0 100644 --- a/src/extensions/mint/MintManager.sol +++ b/src/extensions/mint/MintManager.sol @@ -1,18 +1,17 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; -import {KeepTokenManager} from "../utils/KeepTokenManager.sol"; import {Multicallable} from "../../utils/Multicallable.sol"; +import {IKeep} from "../../utils/interfaces/IKeep.sol"; /// @title Mint Manager /// @notice ERC1155 token ID mint permission manager. -/// @author z0r0z.eth contract MintManager is Multicallable { - event Approved( - address indexed source, - address indexed manager, - uint256 id, - bool approve + event Authorized( + address indexed src, + address indexed usr, + uint256 indexed id, + bool on ); error Unauthorized(); @@ -20,25 +19,25 @@ contract MintManager is Multicallable { mapping(address => mapping(address => mapping(uint256 => bool))) public approved; - function approve( - address manager, + function authorize( + address usr, uint256 id, bool on ) public payable virtual { - approved[msg.sender][manager][id] = on; + approved[msg.sender][usr][id] = on; - emit Approved(msg.sender, manager, id, on); + emit Authorized(msg.sender, usr, id, on); } function mint( - address source, + address src, address to, uint256 id, uint256 amount, bytes calldata data ) public payable virtual { - if (!approved[source][msg.sender][id]) revert Unauthorized(); + if (!approved[src][msg.sender][id]) revert Unauthorized(); - KeepTokenManager(source).mint(to, id, amount, data); + IKeep(src).mint(to, id, amount, data); } } diff --git a/src/extensions/utils/KeepTokenManager.sol b/src/extensions/utils/KeepTokenManager.sol deleted file mode 100644 index 0eb877f5..00000000 --- a/src/extensions/utils/KeepTokenManager.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @notice Contract helper for Keep token management. -abstract contract KeepTokenManager { - function balanceOf( - address account, - uint256 id - ) public view virtual returns (uint256); - - function totalSupply(uint256 id) public view virtual returns (uint256); - - function transferable(uint256 id) public view virtual returns (bool); - - function getPastVotes( - address account, - uint256 id, - uint256 timestamp - ) public view virtual returns (uint256); - - function mint( - address to, - uint256 id, - uint256 amount, - bytes calldata data - ) public payable virtual; - - function burn( - address from, - uint256 id, - uint256 amount - ) public payable virtual; - - function setTransferability(uint256 id, bool on) public payable virtual; -} diff --git a/src/extensions/utils/ReentrancyGuard.sol b/src/extensions/utils/ReentrancyGuard.sol deleted file mode 100644 index 681f715c..00000000 --- a/src/extensions/utils/ReentrancyGuard.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/// @notice Reentrancy protection for contracts. -/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/ReentrancyGuard.sol) -abstract contract ReentrancyGuard { - error Reentrancy(); - - uint256 internal locked = 1; - - modifier nonReentrant() virtual { - if (locked == 2) revert Reentrancy(); - - locked = 2; - - _; - - locked = 1; - } -} diff --git a/src/extensions/validate/PermissionRemoteFetcher.sol b/src/extensions/validate/PermissionRemoteFetcher.sol index 30408c1d..7910808d 100644 --- a/src/extensions/validate/PermissionRemoteFetcher.sol +++ b/src/extensions/validate/PermissionRemoteFetcher.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {UserOperation} from "../../Keep.sol"; diff --git a/src/extensions/validate/URIRemoteFetcher.sol b/src/extensions/validate/URIRemoteFetcher.sol index dca14be6..8c13f334 100644 --- a/src/extensions/validate/URIRemoteFetcher.sol +++ b/src/extensions/validate/URIRemoteFetcher.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; /// @notice Remote metadata fetcher for ERC1155. contract URIRemoteFetcher { diff --git a/src/extensions/validate/Validator.sol b/src/extensions/validate/Validator.sol index 3d7e22ae..d059f92f 100644 --- a/src/extensions/validate/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {Ownable} from "../../utils/Ownable.sol"; import {UserOperation} from "../../Keep.sol"; diff --git a/src/utils/Multicallable.sol b/src/utils/Multicallable.sol index 7bbb7e7e..b93f185d 100644 --- a/src/utils/Multicallable.sol +++ b/src/utils/Multicallable.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; /// @notice Contract that enables a single call to call multiple methods on itself. /// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Multicallable.sol) diff --git a/src/utils/Ownable.sol b/src/utils/Ownable.sol index a39f9d84..4be7d0ef 100644 --- a/src/utils/Ownable.sol +++ b/src/utils/Ownable.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; /// @notice Simple single owner authorization mixin that implements ERC173. abstract contract Ownable { diff --git a/src/utils/interfaces/IKeep.sol b/src/utils/interfaces/IKeep.sol index 50201c17..29fbc6f1 100644 --- a/src/utils/interfaces/IKeep.sol +++ b/src/utils/interfaces/IKeep.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; interface IKeep { event ApprovalForAll( diff --git a/src/utils/interfaces/IKeepFactory.sol b/src/utils/interfaces/IKeepFactory.sol index 60224d82..0c79960b 100644 --- a/src/utils/interfaces/IKeepFactory.sol +++ b/src/utils/interfaces/IKeepFactory.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; interface IKeepFactory { event Deployed(address indexed keep, address[] signers, uint256 threshold); diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 548a1987..9e76e3f8 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; /// @dev Core. import {KeepToken, Operation, Call, Signature, Keep} from "../src/Keep.sol"; diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index 4b941f56..af914ecd 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {Call, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; diff --git a/test/Multicallable.t.sol b/test/Multicallable.t.sol index 1d7a3c90..a50b097a 100644 --- a/test/Multicallable.t.sol +++ b/test/Multicallable.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import "forge-std/Test.sol"; import {MockMulticallable} from "./utils/mocks/MockMulticallable.sol"; diff --git a/test/Ownable.t.sol b/test/Ownable.t.sol index 99f90953..3b7b7c10 100644 --- a/test/Ownable.t.sol +++ b/test/Ownable.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {MockOwnable} from "./utils/mocks/MockOwnable.sol"; diff --git a/test/utils/helpers.sol b/test/utils/helpers.sol deleted file mode 100644 index ec09f831..00000000 --- a/test/utils/helpers.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -bytes32 constant mockName = bytes32(abi.encodePacked("Yo")); - -contract TestHelpers { - function getName() public pure returns (bytes32) { - return bytes32(abi.encodePacked("TEST")); - } -} diff --git a/test/utils/mocks/MockMulticallable.sol b/test/utils/mocks/MockMulticallable.sol index 1b862ec9..5b576f64 100644 --- a/test/utils/mocks/MockMulticallable.sol +++ b/test/utils/mocks/MockMulticallable.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import "../../../src/utils/Multicallable.sol"; diff --git a/test/utils/mocks/MockOwnable.sol b/test/utils/mocks/MockOwnable.sol index b4d9a934..9ca8ac6f 100644 --- a/test/utils/mocks/MockOwnable.sol +++ b/test/utils/mocks/MockOwnable.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; import {Ownable} from "../../../src/utils/Ownable.sol"; diff --git a/test/utils/mocks/MockUnsafeERC1155Receiver.sol b/test/utils/mocks/MockUnsafeERC1155Receiver.sol index dd7348de..c471ee58 100644 --- a/test/utils/mocks/MockUnsafeERC1155Receiver.sol +++ b/test/utils/mocks/MockUnsafeERC1155Receiver.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; contract MockUnsafeERC1155Receiver { function onERC1155Received( From b3e60fc60998576c849ac07bca1950d8be1cd9ad Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:55:17 +0000 Subject: [PATCH 55/67] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Change=20LICENSE=20t?= =?UTF-8?q?o=20AGPL-3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 682 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 661 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index a2668bfe..29ebfa54 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,661 @@ -MIT License - -Copyright (c) 2022, 2023 KaliCo LLC. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file From 980de0e07ac00e231f3975ac70d94fe1f0caccf5 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:19:14 +0000 Subject: [PATCH 56/67] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refresh=20alot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 52 +++++++------- src/Keep.sol | 159 ++++++++++++++++++++--------------------- src/KeepFactory.sol | 3 +- src/KeepToken.sol | 6 +- test/Keep.t.sol | 39 +++++----- test/KeepFactory.t.sol | 5 +- 6 files changed, 129 insertions(+), 135 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 365c7326..f4f54f37 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,29 +1,29 @@ -KeepFactoryTest:testDeploy() (gas: 166421) -KeepFactoryTest:testDetermination() (gas: 170835) -KeepTest:testBalanceOf() (gas: 116093) -KeepTest:testBalanceOfBatch() (gas: 125384) -KeepTest:testBalanceOfSigner() (gas: 49511) -KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390750) -KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247499) -KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14813) -KeepTest:testCannotMintToZeroAddress() (gas: 119161) -KeepTest:testCannotRepeatKeepSetup() (gas: 4783380) -KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83737) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 103051) -KeepTest:testCannotSetupWithZeroQuorum() (gas: 83754) -KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15152, ~: 15152) -KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15260, ~: 15260) -KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380841) -KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241977) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154600, ~: 154600) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180298, ~: 183911) -KeepTest:testExecuteDelegateCall() (gas: 48581) -KeepTest:testExecuteEthCall() (gas: 74394) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70036) -KeepTest:testName() (gas: 10048) +KeepFactoryTest:testDeploy() (gas: 166237) +KeepFactoryTest:testDetermination() (gas: 170382) +KeepTest:testBalanceOf() (gas: 116179) +KeepTest:testBalanceOfBatch() (gas: 125382) +KeepTest:testBalanceOfSigner() (gas: 49553) +KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390770) +KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247497) +KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) +KeepTest:testCannotMintToZeroAddress() (gas: 119242) +KeepTest:testCannotRepeatKeepSetup() (gas: 4762085) +KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83526) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102818) +KeepTest:testCannotSetupWithZeroQuorum() (gas: 83543) +KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15130, ~: 15130) +KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15261, ~: 15261) +KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) +KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180269, ~: 183865) +KeepTest:testExecuteDelegateCall() (gas: 48537) +KeepTest:testExecuteEthCall() (gas: 74372) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70014) +KeepTest:testName() (gas: 9812) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 90083) -KeepTest:testQuorum() (gas: 24319) +KeepTest:testNonceIncrementAfterExecute() (gas: 90061) +KeepTest:testQuorum() (gas: 24370) KeepTest:testReceiveBatchERC1155() (gas: 44228) MulticallableTest:testMulticallableBenchmark() (gas: 28510) MulticallableTest:testMulticallableOriginalBenchmark() (gas: 37767) @@ -38,4 +38,4 @@ OwnableTest:testCallFunctionAsNonOwner(address) (runs: 256, μ: 13975, ~: 13994) OwnableTest:testCallFunctionAsOwner() (gas: 8296) OwnableTest:testERC165Support() (gas: 3373) OwnableTest:testTransferOwnership() (gas: 10855) -OwnableTest:testTransferOwnership(address) (runs: 256, μ: 10917, ~: 10947) \ No newline at end of file +OwnableTest:testTransferOwnership(address) (runs: 256, μ: 10928, ~: 10947) \ No newline at end of file diff --git a/src/Keep.sol b/src/Keep.sol index 94941c42..39c7646b 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -61,7 +61,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// Events /// ----------------------------------------------------------------------- - /// @dev Emitted when Keep executes call. + /// @dev Emitted when Keep executes SIGN_KEY op. event Executed( uint256 indexed nonce, Operation op, @@ -70,14 +70,14 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes data ); - /// @dev Emitted when Keep relays call. + /// @dev Emitted when Keep relays op. event Relayed(Call call); - /// @dev Emitted when Keep relays calls. + /// @dev Emitted when Keep relays ops. event Multirelayed(Call[] calls); - /// @dev Emitted when `quorum` `threshold` updates. - event ThresholdSet(uint256 id, uint256 quorum); + /// @dev Emitted when `quorum` threshold updates. + event QuorumSet(uint256 id, uint256 quorum); /// @dev Emitted when signature revoked. event SignatureRevoked(address indexed user, bytes32 hash); @@ -89,7 +89,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Throws if `initialize()` is called more than once. error AlreadyInit(); - /// @dev Throws if `quorum` exceeds `totalSupply(SIGN_KEY)` or is zero. + /// @dev Throws if `quorum` exceeds `totalSupply()` or is zero. error InvalidThreshold(); /// ----------------------------------------------------------------------- @@ -108,11 +108,11 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// @dev Internal ID metadata mapping. mapping(uint256 id => string) internal _uri; - /// @dev Internal ID `threshold` mapping. - mapping(uint256 id => uint256) public threshold; + /// @dev Internal ID signature threshold mapping. + mapping(uint256 id => uint256) public quorum; - /// @dev Contract signature hash revocation status. - mapping(address user => mapping(bytes32 hash => bool)) public revoked; + /// @dev Internal signature hash revocation status. + mapping(address user => mapping(bytes32 hash => bool)) internal revoked; /// @dev ERC4337 entrypoint. function entryPoint() public view virtual returns (address) { @@ -129,7 +129,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { else return validator.uri(id); } - /// @dev Access control check for ID key balance holders. + /// @dev Access control check for ID key holders & other auth. /// Initializes with `address(this)` having implicit permission /// without writing to storage by checking `totalSupply()` is zero. /// Otherwise, this permission can be set to additional accounts, @@ -154,8 +154,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function supportsInterface( bytes4 interfaceId ) public view virtual returns (bool supported) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let s := shr(224, interfaceId) // ERC165: 0x01ffc9a7, ERC1155: 0xd9b67a26, ERC1155MetadataURI: 0x0e89341c, // ERC721TokenReceiver: 0x150b7a02, ERC1155TokenReceiver: 0x4e2312e0, @@ -188,27 +187,28 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @notice Create Keep template. - /// @param _validator ERC1155/4337 sidecar. + /// @param _validator ERC1155/4337 helper. constructor(Keep _validator) payable { + // Set as immutable. validator = _validator; // Deploy as singleton. - threshold[SIGN_KEY] = 1; + quorum[SIGN_KEY] = 999; } /// @notice Initialize Keep configuration. /// @param calls Initial Keep operations. /// @param signers Initial signer set. - /// @param quorum Initial quorum. + /// @param threshold Initial quorum. function initialize( Call[] calldata calls, address[] calldata signers, - uint256 quorum + uint256 threshold ) public payable virtual { - if (threshold[SIGN_KEY] != 0) revert AlreadyInit(); + if (quorum[SIGN_KEY] != 0) revert AlreadyInit(); - if (quorum == 0) revert InvalidThreshold(); + if (threshold == 0) revert InvalidThreshold(); - if (quorum > signers.length) revert InvalidThreshold(); + if (threshold > signers.length) revert InvalidThreshold(); if (calls.length != 0) { for (uint256 i; i < calls.length; ) { @@ -235,7 +235,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { signer = signers[i]; // Prevent zero and duplicate signers. - if (previous >= signer) revert Unauthorized(); + if (previous >= signer) revert InvalidArray(); previous = signer; @@ -251,19 +251,19 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } totalSupply[SIGN_KEY] = supply; - threshold[SIGN_KEY] = quorum; + quorum[SIGN_KEY] = threshold; } /// ----------------------------------------------------------------------- /// Execution Logic /// ----------------------------------------------------------------------- - /// @notice Execute operation from Keep with signatures. + /// @notice Execute Keep-structured operation. /// @param op Enum operation to execute. /// @param to Address to send operation to. /// @param value Amount of ETH to send in operation. /// @param data Payload to send in operation. - /// @param sigs Array of Keep signatures in ascending order. + /// @param sigs Ascending array of Keep signatures. function execute( Operation op, address to, @@ -278,7 +278,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { emit Executed(txNonce = nonce++, op, to, value, data); } - // Begin signature validation with hashed inputs. + // Derive operation hash. bytes32 hash = keccak256( abi.encodePacked( "\x19\x01", @@ -298,16 +298,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { ) ); - // Memo zero `user` in loop for ascending order. + // Memo zero `user` for ascending order. address previous; - // Memo `quorum` `threshold` for loop length. - uint256 quorum = threshold[SIGN_KEY]; - // Memo `sig` outside loop for gas optimization. + // Memo `quorum` threshold for loop length. + uint256 threshold = quorum[SIGN_KEY]; + // Memo signature outside loop for optimization. Signature calldata sig; // Check enough valid `sigs` to pass `quorum`. uint256 i; - for (i; i < quorum; ) { + for (i; i < threshold; ) { // Load `user` details. sig = sigs[i]; address user = sig.user; @@ -316,13 +316,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // This also confirms non-zero `user`. if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); - // Check `user` `sig` recovery. + // Check `user` `sig` recovery for `hash`. _checkSig(user, hash, sig.v, sig.r, sig.s); - // Check against `user` duplicates. - if (previous >= user) revert Unauthorized(); + // Check for `user` duplicates. + if (previous >= user) revert InvalidArray(); - // Memo for next iteration until quorum. + // Memo `user` for next loop until `quorum`. previous = user; // An array can't have a total length @@ -335,8 +335,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _execute(op, to, value, data); } - /// @notice Relay operation from Keep `execute()` or as `authorized()`. - /// @param call Keep operation as struct of `op, to, value, data`. + /// @notice Relay op from Keep `execute()` or as `authorized()`. + /// @param call Keep op as struct of `op, to, value, data`. function relay(Call calldata call) public payable virtual { _authorized(); @@ -345,8 +345,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _execute(call.op, call.to, call.value, call.data); } - /// @notice Relay operations from Keep `execute()` or as `authorized()`. - /// @param calls Keep operations as struct arrays of `op, to, value, data`. + /// @notice Relay ops from Keep `execute()` or as `authorized()`. + /// @param calls Keep ops as struct arrays of `op, to, value, data`. function multirelay(Call[] calldata calls) public payable virtual { _authorized(); @@ -371,7 +371,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes memory data ) internal virtual { if (op == Operation.call) { - /// @solidity memory-safe-assembly + // todo: gas() thing assembly { let success := call( gas(), @@ -389,7 +389,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return(0, returndatasize()) } } else if (op == Operation.delegatecall) { - /// @solidity memory-safe-assembly assembly { let success := delegatecall( gas(), @@ -406,7 +405,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return(0, returndatasize()) } } else if (op == Operation.create) { - /// @solidity memory-safe-assembly assembly { let created := create(value, add(data, 0x20), mload(data)) if iszero(created) { @@ -416,11 +414,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { return(0, 0x20) } } else { - /// @solidity memory-safe-assembly assembly { - if lt(mload(data), 0x20) { - revert(0, 0) - } let created := create2( value, add(add(data, 0x20), 0x20), @@ -441,8 +435,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @notice Signature revocation. - /// @param hash Signed data Hash. - /// @param sig Signature payload. + /// @param hash Signed data hash. + /// @param sig Keep-structured sig. function revokeSignature( bytes32 hash, Signature calldata sig @@ -462,7 +456,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, bytes calldata sig ) public view virtual returns (bytes4) { - // Check `SIGN_KEY` as this denotes ownership. + // Check `SIGN_KEY` as this denotes Keep ownership. if (_validate(hash, sig, SIGN_KEY) == 0) return 0x1626ba7e; else return 0xffffffff; } @@ -472,7 +466,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// @param id ID of signing NFT. - /// @param hash Signed data Hash. + /// @param hash Signed data hash. /// @param sig Signature payload. function isValidSignature( uint256 id, @@ -495,9 +489,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Check request comes from `entrypoint()`. if (msg.sender != validator.entryPoint()) revert Unauthorized(); - // Return keccak256 hash of ERC191-signed data for `userOpHash`. - /// @solidity memory-safe-assembly - assembly { + // Build ERC191-structured `userOpHash`. + assembly ("memory-safe") { mstore(0x20, userOpHash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. userOpHash := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. @@ -506,16 +499,12 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Shift `userOp.nonce` to get ID key. uint32 id = uint32(userOp.nonce >> 64); - // Check signature `threshold` is met and validate users. + // Check signature `quorum` is met and validate auth. validationData = _validate(userOpHash, userOp.signature, id); - // If `permissioned` ID key, send `userOp` for `validator` check. - if (permissioned[id]) - validationData = validator.validateUserOp( - userOp, - userOpHash, - missingAccountFunds - ); + // If permissioned ID key (4337-10000), send `userOp` for `validator` check. + if (id > 4336 && id < 10001) + validationData = validator.validateUserOp(userOp, userOpHash, id); // Send any missing funds to `entrypoint()` (msg.sender). if (missingAccountFunds != 0) @@ -540,12 +529,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { uint256 id ) internal view virtual returns (uint256 validationData) { address user; - uint256 quorum = threshold[id]; + uint256 threshold = quorum[id]; - if (quorum == 0) return 1; + // Return invalid if inactive. + if (threshold == 0) return 1; - // Early check for single `sig`. - if (quorum == 1) { + // Return early for single `sig`. + if (threshold == 1) { (user, validationData) = _validateSig(hash, sig); if (validationData == 1) return 1; @@ -557,22 +547,22 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Memo split `sig` into `sigs`. bytes[] memory sigs = _splitSigs(sig); - // Memo zero `user` in loop for ascending order. + // Memo zero `user` for ascending order. address previous; // Check enough valid `sigs` to pass `quorum`. uint256 i; - for (i; i < quorum; ) { + for (i; i < threshold; ) { (user, validationData) = _validateSig(hash, sigs[i]); if (validationData == 1) return 1; if (revoked[user][hash]) return 1; - // Check against duplicates. + // Check for `user` duplicates. if (previous >= user) return 1; - // Memo signature for next iteration until quorum. + // Memo `user` for next loop until `quorum`. previous = user; // If not keyholding `user`, `SIG_VALIDATION_FAILED`. @@ -596,15 +586,16 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 s; uint8 v; - assembly { + // todo: doublecheck memsafety + assembly ("memory-safe") { r := mload(add(sig, 0x20)) s := mload(add(sig, 0x40)) v := byte(0, mload(add(sig, 0x60))) } + // Check `v` to branch ecrecover or ERC1271. if (v != 0) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) @@ -624,10 +615,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { mstore(0x40, m) // Restore the free memory pointer. } } else { + // Derive `user` from `r`. user = address(uint160(uint256(r))); - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let m := mload(0x40) let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. @@ -665,7 +656,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { function _splitSigs( bytes memory sig ) internal pure virtual returns (bytes[] memory sigs) { - /// @solidity memory-safe-assembly + // todo: check memsafe assembly { // Check if `sig.length % 65 == 0`. if iszero(eq(mod(mload(sig), 65), 0)) { @@ -744,7 +735,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _burn(from, id, amount); if (id == SIGN_KEY) - if (threshold[SIGN_KEY] > totalSupply[SIGN_KEY]) + if (quorum[SIGN_KEY] > totalSupply[SIGN_KEY]) revert InvalidThreshold(); } @@ -752,23 +743,25 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// Threshold Setting Logic /// ----------------------------------------------------------------------- - /// @notice Update Keep ID threshold. - /// @param id ID key to set threshold for. - /// @param quorum Signature threshold for operations. - function setThreshold(uint256 id, uint256 quorum) public payable virtual { + /// @notice Update Keep ID quorum threshold. + /// @param id ID key to set quorum threshold for. + /// @param threshold Signature threshold for quorum. + function setQuorum(uint256 id, uint256 threshold) public payable virtual { _authorized(); - if (quorum == 0) revert InvalidThreshold(); - if (quorum > totalSupply[id]) revert InvalidThreshold(); + if (threshold == 0) revert InvalidThreshold(); + + if (threshold > totalSupply[id]) revert InvalidThreshold(); - threshold[SIGN_KEY] = uint120(quorum); + quorum[id] = uint120(threshold); - emit ThresholdSet(id, quorum); + emit QuorumSet(id, threshold); } /// ----------------------------------------------------------------------- /// ID Setting Logic /// ----------------------------------------------------------------------- + // todo: rephrase for recognizability /// @notice ID transferability setting. /// @param id ID to set transferability for. diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 02ae17ef..2d1086b8 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.19; import {Multicallable, Call, Keep} from "./Keep.sol"; -import {Ownable} from "./utils/Ownable.sol"; -import {Validator} from "./extensions/validate/Validator.sol"; +import {Ownable, Validator} from "./extensions/validate/Validator.sol"; /// @notice Keep Factory. contract KeepFactory is Multicallable, Ownable(tx.origin) { diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 5a537f72..0ae65e8f 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -66,7 +66,7 @@ abstract contract KeepToken { /// ----------------------------------------------------------------------- error InvalidSignature(); - error LengthMismatch(); + error InvalidArray(); error Unauthorized(); error NonTransferable(); error NotPermitted(); @@ -251,7 +251,7 @@ abstract contract KeepToken { address[] calldata owners, uint256[] calldata ids ) public view virtual returns (uint256[] memory balances) { - if (owners.length != ids.length) revert LengthMismatch(); + if (owners.length != ids.length) revert InvalidArray(); balances = new uint256[](owners.length); @@ -328,7 +328,7 @@ abstract contract KeepToken { uint256[] calldata amounts, bytes calldata data ) public payable virtual { - if (ids.length != amounts.length) revert LengthMismatch(); + if (ids.length != amounts.length) revert InvalidArray(); if (msg.sender != from) if (!isApprovedForAll[from][msg.sender]) revert Unauthorized(); diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 9e76e3f8..425a6d60 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -14,18 +14,20 @@ import {MockERC721} from "@solady/test/utils/mocks/MockERC721.sol"; import {MockERC1155} from "@solady/test/utils/mocks/MockERC1155.sol"; import {MockERC1271Wallet} from "@solady/test/utils/mocks/MockERC1271Wallet.sol"; import {MockUnsafeERC1155Receiver} from "./utils/mocks/MockUnsafeERC1155Receiver.sol"; -import {mockName, TestHelpers} from "./utils/helpers.sol"; /// @dev Test framework. import "@std/Test.sol"; -contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { +contract KeepTest is Keep(Keep(address(0))), Test { /// ----------------------------------------------------------------------- /// Keep Storage/Logic /// ----------------------------------------------------------------------- using stdStorage for StdStorage; + bytes32 internal constant name1 = keccak256("TEST1"); + bytes32 internal constant name2 = keccak256("TEST2"); + address internal keepAddr; address internal keepAddrRepeat; @@ -231,10 +233,10 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { // Initialize Keep from factory. // The factory is fully tested in KeepFactory.t.sol. - (keepAddr, ) = factory.determineKeep(getName()); + (keepAddr, ) = factory.determineKeep(name1); keep = Keep(keepAddr); - factory.deployKeep(getName(), calls, setupSigners, 2); + factory.deployKeep(name1, calls, setupSigners, 2); // Mint mock smart wallet a signer ID key. vm.prank(address(keep)); @@ -366,9 +368,9 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { function testCannotRepeatKeepSetup() public payable { keepRepeat = new Keep(Keep(mockUriValidator)); - (keepAddrRepeat, ) = factory.determineKeep(mockName); + (keepAddrRepeat, ) = factory.determineKeep(name2); keepRepeat = Keep(keepAddrRepeat); - factory.deployKeep(mockName, calls, signers, 2); + factory.deployKeep(name2, calls, signers, 2); vm.expectRevert(AlreadyInit.selector); keepRepeat.initialize(calls, signers, 2); @@ -376,12 +378,12 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { function testCannotSetupWithZeroQuorum() public payable { vm.expectRevert(InvalidThreshold.selector); - factory.deployKeep(mockName, calls, signers, 0); + factory.deployKeep(name2, calls, signers, 0); } function testCannotSetupWithExcessiveQuorum() public payable { vm.expectRevert(InvalidThreshold.selector); - factory.deployKeep(mockName, calls, signers, 3); + factory.deployKeep(name2, calls, signers, 3); } function testCannotSetupWithOutOfOrderSigners() public payable { @@ -389,8 +391,8 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { outOfOrderSigners[0] = alice > bob ? alice : bob; outOfOrderSigners[1] = alice > bob ? bob : alice; - vm.expectRevert(Unauthorized.selector); - factory.deployKeep(mockName, calls, outOfOrderSigners, 2); + vm.expectRevert(InvalidArray.selector); + factory.deployKeep(name2, calls, outOfOrderSigners, 2); } /// ----------------------------------------------------------------------- @@ -398,7 +400,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { /// ----------------------------------------------------------------------- function testName() public { - assertEq(keep.name(), string(abi.encodePacked(getName()))); + assertEq(keep.name(), string(abi.encodePacked(name1))); } // function testKeepNonce() public view { @@ -410,12 +412,12 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { // } function testQuorum() public payable { - assert(keep.threshold(SIGN_KEY) == 2); + assert(keep.quorum(SIGN_KEY) == 2); vm.prank(address(keep)); - keep.setThreshold(SIGN_KEY, 3); + keep.setQuorum(SIGN_KEY, 3); - assert(keep.threshold(SIGN_KEY) == 3); + assert(keep.quorum(SIGN_KEY) == 3); } function testBalanceOf() public { @@ -469,7 +471,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { ids[0] = 0; uint256[] memory balances = new uint256[](2); - vm.expectRevert(LengthMismatch.selector); + vm.expectRevert(InvalidArray.selector); balances = keep.balanceOfBatch(owners, ids); } @@ -503,9 +505,8 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { assert( keep.multirelay.selector != keep.mint.selector && keep.mint.selector != keep.burn.selector && - keep.burn.selector != keep.setThreshold.selector && - keep.setThreshold.selector != - keep.setTransferability.selector && + keep.burn.selector != keep.setQuorum.selector && + keep.setQuorum.selector != keep.setTransferability.selector && keep.setTransferability.selector != keep.setPermission.selector && keep.setPermission.selector != @@ -1012,7 +1013,7 @@ contract KeepTest is Keep(Keep(address(0))), Test, TestHelpers { assert(keep.totalSupply(SIGNER_KEY) == 3); assert(keep.totalSupply(1) == 0); - assert(keep.threshold(SIGN_KEY) == 2); + assert(keep.quorum(SIGN_KEY) == 2); } // function testCannotMintToUnsafeAddress() public payable { diff --git a/test/KeepFactory.t.sol b/test/KeepFactory.t.sol index af914ecd..49173da6 100644 --- a/test/KeepFactory.t.sol +++ b/test/KeepFactory.t.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.19; import {Call, Keep} from "../src/Keep.sol"; import {KeepFactory} from "../src/KeepFactory.sol"; -import {mockName, TestHelpers} from "./utils/helpers.sol"; import "@std/Test.sol"; -contract KeepFactoryTest is Test, TestHelpers { +contract KeepFactoryTest is Test { KeepFactory immutable factory = new KeepFactory(); + bytes32 internal constant mockName = keccak256("TEST1"); + /// @dev Users. address constant alice = address(0xa); From 06b8946d7d728e29088cba65810f6b42ca997084 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:51:44 +0530 Subject: [PATCH 57/67] =?UTF-8?q?=F0=9F=A5=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 --- src/extensions/validate/Validator.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/extensions/validate/Validator.sol b/src/extensions/validate/Validator.sol index d059f92f..62bc84a9 100644 --- a/src/extensions/validate/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.19; import {Ownable} from "../../utils/Ownable.sol"; import {UserOperation} from "../../Keep.sol"; -function getURI(address, uint256) returns (string memory) {} - /// @notice Open-ended metadata for ERC1155 and ERC4337 permission fetching. contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- @@ -68,6 +66,7 @@ contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// URI Remote Logic /// ----------------------------------------------------------------------- + function uri( address, uint256 From 498e1f569be9a1b35475f82f397d1a7030d57cd8 Mon Sep 17 00:00:00 2001 From: shiv Date: Wed, 6 Sep 2023 12:09:03 +0530 Subject: [PATCH 58/67] =?UTF-8?q?=F0=9F=A5=A2=20=20is=20memsafe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 is memsafe --- src/Keep.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Keep.sol b/src/Keep.sol index 39c7646b..524f377b 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -586,7 +586,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 s; uint8 v; - // todo: doublecheck memsafety assembly ("memory-safe") { r := mload(add(sig, 0x20)) s := mload(add(sig, 0x40)) From 7c81a70f0485e026cee7725219d618be360ce677 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 14:26:50 +0000 Subject: [PATCH 59/67] =?UTF-8?q?=F0=9F=94=91=20SIGN=20->=20EXEC=5FKEY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 2 +- src/extensions/validate/Validator.sol | 2 +- test/Keep.t.sol | 116 +++++++++++++------------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index f4f54f37..452416ce 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180269, ~: 183865) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180616, ~: 183865) KeepTest:testExecuteDelegateCall() (gas: 48537) KeepTest:testExecuteEthCall() (gas: 74372) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70014) diff --git a/src/extensions/validate/Validator.sol b/src/extensions/validate/Validator.sol index 62bc84a9..15131e13 100644 --- a/src/extensions/validate/Validator.sol +++ b/src/extensions/validate/Validator.sol @@ -66,7 +66,7 @@ contract Validator is Ownable(tx.origin) { /// ----------------------------------------------------------------------- /// URI Remote Logic /// ----------------------------------------------------------------------- - + function uri( address, uint256 diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 425a6d60..623ec78d 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -48,7 +48,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { uint256 internal chainId; - uint256 internal immutable SIGNER_KEY = uint32(keep.execute.selector); + uint256 internal immutable EXEC_KEY = uint32(keep.execute.selector); bytes32 internal constant PERMIT_TYPEHASH = keccak256( @@ -240,7 +240,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // Mint mock smart wallet a signer ID key. vm.prank(address(keep)); - keep.mint(address(mockERC1271Wallet), SIGNER_KEY, 1, ""); + keep.mint(address(mockERC1271Wallet), EXEC_KEY, 1, ""); // Store chainId. chainId = block.chainid; @@ -351,15 +351,15 @@ contract KeepTest is Keep(Keep(address(0))), Test { // function testSignerSetup() public payable { // // Check users. - // assert(keep.balanceOf(alice, SIGNER_KEY) == 1); - // assert(keep.balanceOf(bob, SIGNER_KEY) == 1); - // assert(keep.balanceOf(charlie, SIGNER_KEY) == 0); + // assert(keep.balanceOf(alice, EXEC_KEY) == 1); + // assert(keep.balanceOf(bob, EXEC_KEY) == 1); + // assert(keep.balanceOf(charlie, EXEC_KEY) == 0); // // Also check smart wallet. - // assert(keep.balanceOf(address(mockERC1271Wallet), SIGNER_KEY) == 1); + // assert(keep.balanceOf(address(mockERC1271Wallet), EXEC_KEY) == 1); // // Check supply. - // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(EXEC_KEY) == 3); // assert(keep.totalSupply(42069) == 0); // } @@ -412,12 +412,12 @@ contract KeepTest is Keep(Keep(address(0))), Test { // } function testQuorum() public payable { - assert(keep.quorum(SIGN_KEY) == 2); + assert(keep.quorum(EXEC_KEY) == 2); vm.prank(address(keep)); - keep.setQuorum(SIGN_KEY, 3); + keep.setQuorum(EXEC_KEY, 3); - assert(keep.quorum(SIGN_KEY) == 3); + assert(keep.quorum(EXEC_KEY) == 3); } function testBalanceOf() public { @@ -430,12 +430,12 @@ contract KeepTest is Keep(Keep(address(0))), Test { } function testBalanceOfSigner() public { - assert(keep.balanceOf(charlie, SIGNER_KEY) == 0); + assert(keep.balanceOf(charlie, EXEC_KEY) == 0); vm.prank(address(keep)); - keep.mint(charlie, SIGNER_KEY, 1, ""); + keep.mint(charlie, EXEC_KEY, 1, ""); - assert(keep.balanceOf(charlie, SIGNER_KEY) == 1); + assert(keep.balanceOf(charlie, EXEC_KEY) == 1); } function testBalanceOfBatch() public { @@ -445,7 +445,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { uint256[] memory ids = new uint256[](2); ids[0] = 0; - ids[1] = SIGNER_KEY; + ids[1] = EXEC_KEY; uint256[] memory balances = new uint256[](2); balances = keep.balanceOfBatch(owners, ids); @@ -485,12 +485,12 @@ contract KeepTest is Keep(Keep(address(0))), Test { // } // function testTotalSignerSupply() public {f - // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(EXEC_KEY) == 3); // vm.prank(address(keep)); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); - // assert(keep.totalSupply(SIGNER_KEY) == 4); + // assert(keep.totalSupply(EXEC_KEY) == 4); // } // function testSupportsInterface() public { @@ -953,7 +953,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // uint256 amount, // bytes calldata data // ) public payable { - // vm.assume(id != SIGNER_KEY); + // vm.assume(id != EXEC_KEY); // vm.assume(id != uint32(type(KeepToken).interfaceId)); // CORE_KEY // vm.assume(keep.totalSupply(uint32(type(KeepToken).interfaceId)) == 0); // amount = bound(amount, 0, type(uint216).max); @@ -969,15 +969,15 @@ contract KeepTest is Keep(Keep(address(0))), Test { // } // function testMintExecuteIdKey() public payable { - // uint256 executeTotalSupply = keep.totalSupply(SIGNER_KEY); - // uint256 executeBalance = keep.balanceOf(charlie, SIGNER_KEY); + // uint256 executeTotalSupply = keep.totalSupply(EXEC_KEY); + // uint256 executeBalance = keep.balanceOf(charlie, EXEC_KEY); // uint256 preQuorum = keep.quorum(); // vm.prank(address(keep)); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); - // assert(keep.balanceOf(charlie, SIGNER_KEY) == executeBalance + 1); - // assert(keep.totalSupply(SIGNER_KEY) == executeTotalSupply + 1); + // assert(keep.balanceOf(charlie, EXEC_KEY) == executeBalance + 1); + // assert(keep.totalSupply(EXEC_KEY) == executeTotalSupply + 1); // assert(keep.quorum() == preQuorum); // } @@ -999,33 +999,33 @@ contract KeepTest is Keep(Keep(address(0))), Test { // } function testCannotMintToZeroAddress() public payable { - assert(keep.totalSupply(SIGNER_KEY) == 3); + assert(keep.totalSupply(EXEC_KEY) == 3); startHoax(address(keep), address(keep), type(uint256).max); vm.expectRevert(InvalidRecipient.selector); - keep.mint(address(0), SIGNER_KEY, 1, ""); + keep.mint(address(0), EXEC_KEY, 1, ""); vm.expectRevert(InvalidRecipient.selector); keep.mint(address(0), 1, 1, ""); - assert(keep.balanceOf(address(0), SIGNER_KEY) == 0); + assert(keep.balanceOf(address(0), EXEC_KEY) == 0); assert(keep.balanceOf(address(0), 1) == 0); - assert(keep.totalSupply(SIGNER_KEY) == 3); + assert(keep.totalSupply(EXEC_KEY) == 3); assert(keep.totalSupply(1) == 0); - assert(keep.quorum(SIGN_KEY) == 2); + assert(keep.quorum(EXEC_KEY) == 2); } // function testCannotMintToUnsafeAddress() public payable { - // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(EXEC_KEY) == 3); // startHoax(address(keep), address(keep), type(uint256).max); // vm.expectRevert(); - // keep.mint(address(mockDai), SIGNER_KEY, 1, ""); + // keep.mint(address(mockDai), EXEC_KEY, 1, ""); // vm.expectRevert(UnsafeRecipient.selector); - // keep.mint(address(mockUnsafeERC1155Receiver), SIGNER_KEY, 1, ""); + // keep.mint(address(mockUnsafeERC1155Receiver), EXEC_KEY, 1, ""); // vm.expectRevert(); // keep.mint(address(mockDai), 1, 1, ""); @@ -1033,10 +1033,10 @@ contract KeepTest is Keep(Keep(address(0))), Test { // vm.expectRevert(UnsafeRecipient.selector); // keep.mint(address(mockUnsafeERC1155Receiver), 1, 1, ""); - // assert(keep.balanceOf(address(0), SIGNER_KEY) == 0); + // assert(keep.balanceOf(address(0), EXEC_KEY) == 0); // assert(keep.balanceOf(address(0), 1) == 0); - // assert(keep.totalSupply(SIGNER_KEY) == 3); + // assert(keep.totalSupply(EXEC_KEY) == 3); // assert(keep.totalSupply(1) == 0); // assert(keep.quorum() == 2); @@ -1064,17 +1064,17 @@ contract KeepTest is Keep(Keep(address(0))), Test { // function testCannotMintOverflowExecuteID() public payable { // startHoax(address(keep), address(keep), type(uint256).max); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); // vm.expectRevert(Overflow.selector); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); - // keep.burn(charlie, SIGNER_KEY, 1); + // keep.burn(charlie, EXEC_KEY, 1); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); // vm.expectRevert(Overflow.selector); - // keep.mint(charlie, SIGNER_KEY, 1, ""); + // keep.mint(charlie, EXEC_KEY, 1, ""); // } // function testBurn() public payable { @@ -1088,10 +1088,10 @@ contract KeepTest is Keep(Keep(address(0))), Test { // function testBurnSigner() public payable { // vm.prank(address(keep)); - // keep.burn(alice, SIGNER_KEY, 1); + // keep.burn(alice, EXEC_KEY, 1); - // assert(keep.balanceOf(alice, SIGNER_KEY) == 0); - // assert(keep.totalSupply(SIGNER_KEY) == 2); + // assert(keep.balanceOf(alice, EXEC_KEY) == 0); + // assert(keep.totalSupply(EXEC_KEY) == 2); // } // function testCannotBurnUnderflow() public payable { @@ -1195,7 +1195,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { amount = bound(amount, 0, type(uint216).max); if ( - id == SIGNER_KEY && + id == EXEC_KEY && (keep.balanceOf(userA, id) != 0 || keep.balanceOf(userB, id) != 0) ) { amount = amount - 1; @@ -1231,7 +1231,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { amount = bound(amount, 0, type(uint216).max); if ( - id == SIGNER_KEY && + id == EXEC_KEY && (keep.balanceOf(userA, id) != 0 || keep.balanceOf(userB, id) != 0) ) { amount = amount - 1; @@ -1270,7 +1270,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { amount = bound(amount, 0, type(uint216).max); if ( - id == SIGNER_KEY && + id == EXEC_KEY && (keep.balanceOf(userA, id) != 0 || keep.balanceOf(userB, id) != 0) ) { amount = amount - 1; @@ -1365,7 +1365,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { vm.assume(userA.code.length == 0); vm.assume(userB.code.length == 0); vm.assume(userC.code.length == 0); - vm.assume(id != SIGNER_KEY); + vm.assume(id != EXEC_KEY); vm.assume(id != CORE_KEY); amount = bound(amount, 0, type(uint216).max); @@ -1441,27 +1441,27 @@ contract KeepTest is Keep(Keep(address(0))), Test { /* function testCannotTransferExecuteOverflow() public payable { startHoax(address(keep), address(keep), type(uint256).max); - keep.setTransferability(SIGNER_KEY, true); - keep.mint(charlie, SIGNER_KEY, 1, ""); + keep.setTransferability(EXEC_KEY, true); + keep.mint(charlie, EXEC_KEY, 1, ""); vm.prank(charlie); - keep.safeTransferFrom(charlie, address(0xBeef), SIGNER_KEY, 1, ""); + keep.safeTransferFrom(charlie, address(0xBeef), EXEC_KEY, 1, ""); startHoax(address(keep), address(keep), type(uint256).max); - keep.mint(charlie, SIGNER_KEY, 1, ""); + keep.mint(charlie, EXEC_KEY, 1, ""); vm.prank(charlie); vm.expectRevert(Overflow.selector); - keep.safeTransferFrom(charlie, address(0xBeef), SIGNER_KEY, 1, ""); + keep.safeTransferFrom(charlie, address(0xBeef), EXEC_KEY, 1, ""); startHoax(address(keep), address(keep), type(uint256).max); - keep.burn(address(0xBeef), SIGNER_KEY, 1); + keep.burn(address(0xBeef), EXEC_KEY, 1); }*/ /* function testCannotTransferKeepTokenNonTransferable( uint256 id ) public payable { - vm.assume(id != SIGNER_KEY); + vm.assume(id != EXEC_KEY); vm.prank(address(keep)); keep.mint(charlie, id, 1, ""); @@ -1519,7 +1519,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { uint256 id ) public payable { vm.assume(id != 1816876358); - vm.assume(id != SIGNER_KEY); + vm.assume(id != EXEC_KEY); vm.prank(address(keep)); keep.setTransferability(id, true); @@ -1552,7 +1552,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { amount = bound(amount, 0, type(uint216).max); if ( - id == SIGNER_KEY && + id == EXEC_KEY && (keep.balanceOf(userA, id) != 0 || keep.balanceOf(userB, id) != 0) ) { amount = amount - 1; @@ -1681,7 +1681,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // ) public payable { // vm.assume(user != address(0)); // vm.assume(user.code.length == 0); - // vm.assume(id != SIGNER_KEY); + // vm.assume(id != EXEC_KEY); // amount = bound(amount, 0, type(uint216).max); @@ -1712,7 +1712,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // vm.assume(userA != userB); // vm.assume(userA.code.length == 0); // vm.assume(userB.code.length == 0); - // vm.assume(id != SIGNER_KEY); + // vm.assume(id != EXEC_KEY); // vm.warp(1665378008); @@ -1769,7 +1769,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // vm.assume(userA != userB); // vm.assume(userA.code.length == 0); // vm.assume(userB.code.length == 0); - // vm.assume(id != SIGNER_KEY); + // vm.assume(id != EXEC_KEY); // vm.assume(id != CORE_KEY); // vm.warp(1665378008); @@ -1901,7 +1901,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { // amount = bound(amount, 0, type(uint216).max); // vm.assume(userB != address(0)); // vm.assume(userB.code.length == 0); - // vm.assume(id != SIGNER_KEY); + // vm.assume(id != EXEC_KEY); // uint256 privateKey = 0xBEEF; // address userA = vm.addr(0xBEEF); From aabd490368c50fdbdf6b609e735a03d2fbbe5e64 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:29:55 +0000 Subject: [PATCH 60/67] ~~ --- .gas-snapshot | 4 ++-- src/Keep.sol | 28 ++++++++++++++-------------- src/KeepToken.sol | 24 ++++++++++++------------ test/Keep.t.sol | 2 -- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 452416ce..3bda240b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,7 +1,7 @@ KeepFactoryTest:testDeploy() (gas: 166237) KeepFactoryTest:testDetermination() (gas: 170382) KeepTest:testBalanceOf() (gas: 116179) -KeepTest:testBalanceOfBatch() (gas: 125382) +KeepTest:testBalanceOfBatch() (gas: 125388) KeepTest:testBalanceOfSigner() (gas: 49553) KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 390770) KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247497) @@ -16,7 +16,7 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180616, ~: 183865) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180258, ~: 183865) KeepTest:testExecuteDelegateCall() (gas: 48537) KeepTest:testExecuteEthCall() (gas: 74372) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70014) diff --git a/src/Keep.sol b/src/Keep.sol index 524f377b..b84457e4 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -61,7 +61,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// Events /// ----------------------------------------------------------------------- - /// @dev Emitted when Keep executes SIGN_KEY op. + /// @dev Emitted when Keep executes EXEC_KEY op. event Executed( uint256 indexed nonce, Operation op, @@ -192,7 +192,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Set as immutable. validator = _validator; // Deploy as singleton. - quorum[SIGN_KEY] = 999; + quorum[EXEC_KEY] = 999; } /// @notice Initialize Keep configuration. @@ -204,7 +204,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { address[] calldata signers, uint256 threshold ) public payable virtual { - if (quorum[SIGN_KEY] != 0) revert AlreadyInit(); + if (quorum[EXEC_KEY] != 0) revert AlreadyInit(); if (threshold == 0) revert InvalidThreshold(); @@ -239,19 +239,19 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { previous = signer; - emit TransferSingle(tx.origin, address(0), signer, SIGN_KEY, 1); + emit TransferSingle(tx.origin, address(0), signer, EXEC_KEY, 1); // An array can't have a total length // larger than the max uint256 value. unchecked { - ++balanceOf[signer][SIGN_KEY]; + ++balanceOf[signer][EXEC_KEY]; ++supply; ++i; } } - totalSupply[SIGN_KEY] = supply; - quorum[SIGN_KEY] = threshold; + totalSupply[EXEC_KEY] = supply; + quorum[EXEC_KEY] = threshold; } /// ----------------------------------------------------------------------- @@ -301,7 +301,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Memo zero `user` for ascending order. address previous; // Memo `quorum` threshold for loop length. - uint256 threshold = quorum[SIGN_KEY]; + uint256 threshold = quorum[EXEC_KEY]; // Memo signature outside loop for optimization. Signature calldata sig; @@ -312,9 +312,9 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { sig = sigs[i]; address user = sig.user; - // Check SIGN_KEY balance. + // Check EXEC_KEY balance. // This also confirms non-zero `user`. - if (balanceOf[user][SIGN_KEY] == 0) revert Unauthorized(); + if (balanceOf[user][EXEC_KEY] == 0) revert Unauthorized(); // Check `user` `sig` recovery for `hash`. _checkSig(user, hash, sig.v, sig.r, sig.s); @@ -456,8 +456,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes32 hash, bytes calldata sig ) public view virtual returns (bytes4) { - // Check `SIGN_KEY` as this denotes Keep ownership. - if (_validate(hash, sig, SIGN_KEY) == 0) return 0x1626ba7e; + // Check `EXEC_KEY` as this denotes Keep ownership. + if (_validate(hash, sig, EXEC_KEY) == 0) return 0x1626ba7e; else return 0xffffffff; } @@ -733,8 +733,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { _burn(from, id, amount); - if (id == SIGN_KEY) - if (quorum[SIGN_KEY] > totalSupply[SIGN_KEY]) + if (id == EXEC_KEY) + if (quorum[EXEC_KEY] > totalSupply[EXEC_KEY]) revert InvalidThreshold(); } diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 0ae65e8f..5ddf1a1e 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -193,7 +193,7 @@ abstract contract KeepToken { /// ID Storage /// ----------------------------------------------------------------------- - uint256 internal constant SIGN_KEY = uint32(0x6c4b5546); // `execute()` + uint256 internal constant EXEC_KEY = uint32(0x6c4b5546); // `execute()`. mapping(uint256 => uint256) public totalSupply; @@ -292,9 +292,9 @@ abstract contract KeepToken { if (!userPermissioned[to][id] || !userPermissioned[from][id]) revert NotPermitted(); - // If not transferring SIGN_KEY, update delegation balance. - // Otherwise, prevent transfer to SIGN_KEY holder. - if (id != SIGN_KEY) + // If not transferring EXEC_KEY, update delegation balance. + // Otherwise, prevent transfer to EXEC_KEY holder. + if (id != EXEC_KEY) _moveDelegates(delegates(from, id), delegates(to, id), id, amount); else if (balanceOf[to][id] != 0) revert Overflow(); @@ -348,9 +348,9 @@ abstract contract KeepToken { if (!userPermissioned[to][id] || !userPermissioned[from][id]) revert NotPermitted(); - // If not transferring SIGN_KEY, update delegation balance. - // Otherwise, prevent transfer to SIGN_KEY holder. - if (id != SIGN_KEY) + // If not transferring EXEC_KEY, update delegation balance. + // Otherwise, prevent transfer to EXEC_KEY holder. + if (id != EXEC_KEY) _moveDelegates( delegates(from, id), delegates(to, id), @@ -694,9 +694,9 @@ abstract contract KeepToken { ) internal virtual { _safeCastTo216(totalSupply[id] += amount); - // If not minting SIGN_KEY, update delegation balance. - // Otherwise, prevent minting to SIGN_KEY holder. - if (id != SIGN_KEY) + // If not minting EXEC_KEY, update delegation balance. + // Otherwise, prevent minting to EXEC_KEY holder. + if (id != EXEC_KEY) _moveDelegates(address(0), delegates(to, id), id, amount); else if (balanceOf[to][id] != 0) revert Overflow(); @@ -732,8 +732,8 @@ abstract contract KeepToken { emit TransferSingle(msg.sender, from, address(0), id, amount); - // If not burning SIGN_KEY, update delegation balance. - if (id != SIGN_KEY) + // If not burning EXEC_KEY, update delegation balance. + if (id != EXEC_KEY) _moveDelegates(delegates(from, id), address(0), id, amount); } diff --git a/test/Keep.t.sol b/test/Keep.t.sol index 623ec78d..d4a284ea 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -48,8 +48,6 @@ contract KeepTest is Keep(Keep(address(0))), Test { uint256 internal chainId; - uint256 internal immutable EXEC_KEY = uint32(keep.execute.selector); - bytes32 internal constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)" From 513e12361d3670b89fc0ccc168221bd4b4101240 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:31:45 +0000 Subject: [PATCH 61/67] weird gas() savings --- .gas-snapshot | 12 ++++++------ src/Keep.sol | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 3bda240b..f91ad2ab 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -15,14 +15,14 @@ KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (ru KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15261, ~: 15261) KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180258, ~: 183865) -KeepTest:testExecuteDelegateCall() (gas: 48537) -KeepTest:testExecuteEthCall() (gas: 74372) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70014) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154568, ~: 154576) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180229, ~: 183865) +KeepTest:testExecuteDelegateCall() (gas: 48536) +KeepTest:testExecuteEthCall() (gas: 74371) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70013) KeepTest:testName() (gas: 9812) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 90061) +KeepTest:testNonceIncrementAfterExecute() (gas: 90060) KeepTest:testQuorum() (gas: 24370) KeepTest:testReceiveBatchERC1155() (gas: 44228) MulticallableTest:testMulticallableBenchmark() (gas: 28510) diff --git a/src/Keep.sol b/src/Keep.sol index b84457e4..fc87e9a0 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -379,7 +379,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { value, add(data, 0x20), mload(data), - 0, + gas(), 0 ) returndatacopy(0, 0, returndatasize()) @@ -395,7 +395,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { to, add(data, 0x20), mload(data), - 0, + gas(), 0 ) returndatacopy(0, 0, returndatasize()) From fe7e6a203a938da7f763f51e6886734580ef9c74 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:49:16 +0000 Subject: [PATCH 62/67] ~~ 0x00 formatting --- .gas-snapshot | 4 ++-- src/Keep.sol | 35 +++++++++++++++++------------------ src/KeepFactory.sol | 2 +- src/KeepToken.sol | 8 +++----- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index f91ad2ab..9c063ffb 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -15,8 +15,8 @@ KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (ru KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15261, ~: 15261) KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) -KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154568, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180229, ~: 183865) +KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180273, ~: 183865) KeepTest:testExecuteDelegateCall() (gas: 48536) KeepTest:testExecuteEthCall() (gas: 74371) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70013) diff --git a/src/Keep.sol b/src/Keep.sol index fc87e9a0..e06975ce 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -371,7 +371,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes memory data ) internal virtual { if (op == Operation.call) { - // todo: gas() thing assembly { let success := call( gas(), @@ -380,13 +379,13 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { add(data, 0x20), mload(data), gas(), - 0 + 0x00 ) - returndatacopy(0, 0, returndatasize()) + returndatacopy(0x00, 0x00, returndatasize()) if iszero(success) { - revert(0, returndatasize()) + revert(0x00, returndatasize()) } - return(0, returndatasize()) + return(0x00, returndatasize()) } } else if (op == Operation.delegatecall) { assembly { @@ -396,22 +395,22 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { add(data, 0x20), mload(data), gas(), - 0 + 0x00 ) - returndatacopy(0, 0, returndatasize()) + returndatacopy(0x00, 0x00, returndatasize()) if iszero(success) { - revert(0, returndatasize()) + revert(0x00, returndatasize()) } - return(0, returndatasize()) + return(0x00, returndatasize()) } } else if (op == Operation.create) { assembly { let created := create(value, add(data, 0x20), mload(data)) if iszero(created) { - revert(0, 0) + revert(0x00, 0x00) } - mstore(0, created) - return(0, 0x20) + mstore(0x00, created) + return(0x00, 0x20) } } else { assembly { @@ -422,10 +421,10 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { mload(add(data, 0x20)) ) if iszero(created) { - revert(0, 0) + revert(0x00, 0x00) } - mstore(0, created) - return(0, 0x20) + mstore(0x00, created) + return(0x00, 0x20) } } } @@ -589,7 +588,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { assembly ("memory-safe") { r := mload(add(sig, 0x20)) s := mload(add(sig, 0x40)) - v := byte(0, mload(add(sig, 0x60))) + v := byte(0x00, mload(add(sig, 0x60))) } // Check `v` to branch ecrecover or ERC1271. @@ -610,7 +609,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { 0x20 // Size of output. ) ) - mstore(0x60, 0) // Restore the zero slot. + mstore(0x60, 0x00) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } else { @@ -658,7 +657,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // todo: check memsafe assembly { // Check if `sig.length % 65 == 0`. - if iszero(eq(mod(mload(sig), 65), 0)) { + if iszero(eq(mod(mload(sig), 65), 0x00)) { // If not, revert with InvalidSignature. mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) diff --git a/src/KeepFactory.sol b/src/KeepFactory.sol index 2d1086b8..1b056826 100644 --- a/src/KeepFactory.sol +++ b/src/KeepFactory.sol @@ -168,7 +168,7 @@ contract KeepFactory is Multicallable, Ownable(tx.origin) { keep := keccak256(0x00, 0x55) deployed := extcodesize(keep) // Restore the part of the free memory pointer that has been overwritten. - mstore(0x35, 0) + mstore(0x35, 0x00) // Restore the overwritten memory surrounding `data`. mstore(dataEnd, mAfter1) diff --git a/src/KeepToken.sol b/src/KeepToken.sol index 5ddf1a1e..241f140a 100644 --- a/src/KeepToken.sol +++ b/src/KeepToken.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.19; -/// @notice ERC1155 token with Governor-style checkpointing, delegation and transfer restriction scheme. -/// @author Modified from ERC1155V (https://github.com/kalidao/ERC1155V/blob/main/src/ERC1155V.sol) +/// @notice ERC1155 token with checkpointing, delegation and transfer controls. abstract contract KeepToken { /// ----------------------------------------------------------------------- /// Events @@ -147,7 +146,7 @@ abstract contract KeepToken { ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(or(iszero(returndatasize()), xor(user, mload(t)))) { - mstore(0x60, 0) // Restore the zero slot. + mstore(0x60, 0x00) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } @@ -225,8 +224,7 @@ abstract contract KeepToken { function name() public pure virtual returns (string memory) { uint256 n; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { n := calldataload( add( sub( From bfcc642013d1243e2357ed54fe1740877a3b2a11 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:54:34 +0000 Subject: [PATCH 63/67] optimize by setting in emit --- .gas-snapshot | 10 +++++----- src/Keep.sol | 10 ++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 9c063ffb..23d4575d 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,5 +1,5 @@ -KeepFactoryTest:testDeploy() (gas: 166237) -KeepFactoryTest:testDetermination() (gas: 170382) +KeepFactoryTest:testDeploy() (gas: 166225) +KeepFactoryTest:testDetermination() (gas: 170370) KeepTest:testBalanceOf() (gas: 116179) KeepTest:testBalanceOfBatch() (gas: 125388) KeepTest:testBalanceOfSigner() (gas: 49553) @@ -7,16 +7,16 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39077 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247497) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) KeepTest:testCannotMintToZeroAddress() (gas: 119242) -KeepTest:testCannotRepeatKeepSetup() (gas: 4762085) +KeepTest:testCannotRepeatKeepSetup() (gas: 4756664) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83526) -KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102818) +KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102812) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83543) KeepTest:testCannotSpendKeepTokenDelegateBySigAfterDeadline(address,uint256) (runs: 256, μ: 15130, ~: 15130) KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, μ: 15261, ~: 15261) KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180273, ~: 183865) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179900, ~: 183865) KeepTest:testExecuteDelegateCall() (gas: 48536) KeepTest:testExecuteEthCall() (gas: 74371) KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70013) diff --git a/src/Keep.sol b/src/Keep.sol index e06975ce..aea5f845 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -191,6 +191,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { constructor(Keep _validator) payable { // Set as immutable. validator = _validator; + // Deploy as singleton. quorum[EXEC_KEY] = 999; } @@ -239,12 +240,17 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { previous = signer; - emit TransferSingle(tx.origin, address(0), signer, EXEC_KEY, 1); + emit TransferSingle( + tx.origin, + address(0), + signer, + EXEC_KEY, + balanceOf[signer][EXEC_KEY] = 1 + ); // An array can't have a total length // larger than the max uint256 value. unchecked { - ++balanceOf[signer][EXEC_KEY]; ++supply; ++i; } From 8c9bde53ae156d533313a3328bd3d3a3e82b861b Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:59:56 +0000 Subject: [PATCH 64/67] optimize nonce --- .gas-snapshot | 12 ++++++------ src/Keep.sol | 6 +++--- test/Keep.t.sol | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 23d4575d..f43f6674 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -7,7 +7,7 @@ KeepTest:testCannotBatchTransferKeepERC1155ToUnsafeContractAddress() (gas: 39077 KeepTest:testCannotBatchTransferKeepERC1155ToZeroAddress() (gas: 247497) KeepTest:testCannotFetchMismatchedLengthBalanceOfBatch() (gas: 14769) KeepTest:testCannotMintToZeroAddress() (gas: 119242) -KeepTest:testCannotRepeatKeepSetup() (gas: 4756664) +KeepTest:testCannotRepeatKeepSetup() (gas: 4731394) KeepTest:testCannotSetupWithExcessiveQuorum() (gas: 83526) KeepTest:testCannotSetupWithOutOfOrderSigners() (gas: 102812) KeepTest:testCannotSetupWithZeroQuorum() (gas: 83543) @@ -16,13 +16,13 @@ KeepTest:testCannotSpendKeepTokenPermitAfterDeadline(address,bool) (runs: 256, KeepTest:testCannotTransferKeepERC1155ToUnsafeContractAddress() (gas: 380817) KeepTest:testCannotTransferKeepERC1155ToZeroAddress() (gas: 241953) KeepTest:testCannotTransferKeepTokenWithUnderflow(uint256) (runs: 256, μ: 154576, ~: 154576) -KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 179900, ~: 183865) -KeepTest:testExecuteDelegateCall() (gas: 48536) -KeepTest:testExecuteEthCall() (gas: 74371) -KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 70013) +KeepTest:testCannotTransferKeepTokenWithoutPermission(address,address,uint256,uint256) (runs: 256, μ: 180588, ~: 183865) +KeepTest:testExecuteDelegateCall() (gas: 48482) +KeepTest:testExecuteEthCall() (gas: 74317) +KeepTest:testExecuteTokenCallWithContractSignatures() (gas: 69959) KeepTest:testName() (gas: 9812) KeepTest:testNoKeepKeyCollision() (gas: 208) -KeepTest:testNonceIncrementAfterExecute() (gas: 90060) +KeepTest:testNonceIncrementAfterExecute() (gas: 89818) KeepTest:testQuorum() (gas: 24370) KeepTest:testReceiveBatchERC1155() (gas: 44228) MulticallableTest:testMulticallableBenchmark() (gas: 28510) diff --git a/src/Keep.sol b/src/Keep.sol index aea5f845..034c3cff 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -103,7 +103,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { Keep internal immutable validator; /// @dev Record of states verifying `execute()`. - uint120 public nonce; + uint256 public nonce; /// @dev Internal ID metadata mapping. mapping(uint256 id => string) internal _uri; @@ -277,7 +277,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { bytes calldata data, Signature[] calldata sigs ) public payable virtual { - uint120 txNonce; + uint256 txNonce; // Unchecked because the only math done is incrementing // Keep `nonce` which cannot realistically overflow. unchecked { @@ -292,7 +292,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { keccak256( abi.encode( keccak256( - "Execute(uint8 op,address to,uint256 value,bytes data,uint120 nonce)" + "Execute(uint8 op,address to,uint256 value,bytes data,uint256 nonce)" ), op, to, diff --git a/test/Keep.t.sol b/test/Keep.t.sol index d4a284ea..e5da2163 100644 --- a/test/Keep.t.sol +++ b/test/Keep.t.sol @@ -106,7 +106,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { address to, uint256 value, bytes memory data, - uint120 nonce, + uint256 nonce, bytes32 domainSeparator ) internal pure returns (bytes32) { return @@ -117,7 +117,7 @@ contract KeepTest is Keep(Keep(address(0))), Test { keccak256( abi.encode( keccak256( - "Execute(uint8 op,address to,uint256 value,bytes data,uint120 nonce)" + "Execute(uint8 op,address to,uint256 value,bytes data,uint256 nonce)" ), op, to, From 7336cebb2100a845953635c5e4e58d4d25477cc0 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Thu, 7 Sep 2023 01:34:59 +0530 Subject: [PATCH 65/67] =?UTF-8?q?=F0=9F=A5=A2=20nit=20unused=20casting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥢 nit unused casting --- src/Keep.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 034c3cff..601f7bf4 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -757,7 +757,7 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { if (threshold > totalSupply[id]) revert InvalidThreshold(); - quorum[id] = uint120(threshold); + quorum[id] = threshold; emit QuorumSet(id, threshold); } @@ -765,7 +765,6 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { /// ----------------------------------------------------------------------- /// ID Setting Logic /// ----------------------------------------------------------------------- - // todo: rephrase for recognizability /// @notice ID transferability setting. /// @param id ID to set transferability for. From 8ee4dc8eb752d3d2de5cd53fbd79f0260f495afb Mon Sep 17 00:00:00 2001 From: shiv Date: Thu, 7 Sep 2023 13:52:23 +0530 Subject: [PATCH 66/67] bit shift for xtra alidator check flag / title nit --- src/Keep.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 601f7bf4..360e5201 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -5,7 +5,8 @@ import {ERC1155TokenReceiver, KeepToken} from "./KeepToken.sol"; import {Multicallable} from "./utils/Multicallable.sol"; /// @title Keep -/// @notice Tokenized multisig wallet. +/// @notice Multitoken signature auth system. +/// @dev Optimized for multisig operations. /// @author z0r0z.eth /// @custom:coauthor @ControlCplusControlV /// @custom:coauthor boredretard.eth @@ -507,8 +508,8 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { // Check signature `quorum` is met and validate auth. validationData = _validate(userOpHash, userOp.signature, id); - // If permissioned ID key (4337-10000), send `userOp` for `validator` check. - if (id > 4336 && id < 10001) + // Extract 'validator flag' in 33rd bit. If set, `validator` check `userOp` with `id`. + if (userOp.nonce & (1 << 32) != 0) validationData = validator.validateUserOp(userOp, userOpHash, id); // Send any missing funds to `entrypoint()` (msg.sender). From 0e1ecca0d7fdd458e2f265656818fe0d95541f34 Mon Sep 17 00:00:00 2001 From: Shivanshi Tyagi Date: Thu, 7 Sep 2023 14:37:00 +0530 Subject: [PATCH 67/67] =?UTF-8?q?=E2=9C=93=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keep.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Keep.sol b/src/Keep.sol index 360e5201..303dc868 100644 --- a/src/Keep.sol +++ b/src/Keep.sol @@ -503,13 +503,14 @@ contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable { } // Shift `userOp.nonce` to get ID key. - uint32 id = uint32(userOp.nonce >> 64); + uint256 key = (userOp.nonce >> 64); + uint256 id = key >> 191; // Check signature `quorum` is met and validate auth. validationData = _validate(userOpHash, userOp.signature, id); - // Extract 'validator flag' in 33rd bit. If set, `validator` check `userOp` with `id`. - if (userOp.nonce & (1 << 32) != 0) + // Extract 'validator flag' in 192rd bit. If set, `validator` check `userOp` with `id`. + if ((key & (1 << 191)) != 0) validationData = validator.validateUserOp(userOp, userOpHash, id); // Send any missing funds to `entrypoint()` (msg.sender).