From c2b041fa6461441e320461b10ebb5c5d514a6859 Mon Sep 17 00:00:00 2001 From: ross <92001561+z0r0z@users.noreply.github.com> Date: Sun, 21 Jan 2024 07:55:39 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=90=20Scaling=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 37 +-- test/Dagon.t.sol | 189 ++++++++------- test/DagonScales.t.sol | 29 +++ test/utils/mocks/MockDagon.sol | 426 +++++++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+), 110 deletions(-) create mode 100644 test/DagonScales.t.sol create mode 100644 test/utils/mocks/MockDagon.sol diff --git a/.gas-snapshot b/.gas-snapshot index c1680b9..224172a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,37 +1,38 @@ -DagonTest:testBurn(address,uint96) (runs: 256, μ: 152620, ~: 152620) -DagonTest:testDeploy() (gas: 2076167) +DagonScalesTest:testIsValidSignatureALOT() (gas: 34615892725) +DagonTest:testBurn(address,uint96) (runs: 256, μ: 152642, ~: 152642) +DagonTest:testDeploy() (gas: 2071165) DagonTest:testFailBurnOverBalance(address,uint96) (runs: 256, μ: 187133, ~: 188222) DagonTest:testFailBurnOverThreshold(address,uint96) (runs: 256, μ: 219319, ~: 220408) -DagonTest:testFailInvalidThresholdExceedsSupply() (gas: 159181) +DagonTest:testFailInvalidThresholdExceedsSupply() (gas: 159203) DagonTest:testFailInvalidThresholdExceedsSupply2() (gas: 164582) DagonTest:testFailInvalidThresholdNull() (gas: 158981) DagonTest:testFailIsValidSignature2of3ForInsufficientSignatures() (gas: 199625) -DagonTest:testFailIsValidSignatureOutOfOrder() (gas: 236780) DagonTest:testFailIsValidSignatureWeighted() (gas: 228757) DagonTest:testFailIsValidSignatureWeightedERC1155() (gas: 37189) DagonTest:testFailIsValidSignatureWeightedERC20() (gas: 237823) DagonTest:testFailIsValidSignatureWeightedERC6909() (gas: 230379) DagonTest:testFailIsValidSignatureWeightedERC721() (gas: 208687) DagonTest:testFailSetTokenInvalidStd(address) (runs: 256, μ: 158162, ~: 158162) -DagonTest:testFailTransferFromInactiveAuth(address,address,uint96) (runs: 256, μ: 191674, ~: 192374) -DagonTest:testFailTransferOverBalance(address,address,uint96) (runs: 256, μ: 186437, ~: 187137) -DagonTest:testInstall() (gas: 137296) -DagonTest:testIsValidSignature() (gas: 150112) +DagonTest:testFailTransferFromInactiveAuth(address,address,uint96) (runs: 256, μ: 191618, ~: 192396) +DagonTest:testFailTransferOverBalance(address,address,uint96) (runs: 256, μ: 186281, ~: 187137) +DagonTest:testInstall() (gas: 137318) +DagonTest:testIsValidSignature() (gas: 150134) DagonTest:testIsValidSignature2of3() (gas: 195901) DagonTest:testIsValidSignature3of3() (gas: 203429) -DagonTest:testIsValidSignatureMany() (gas: 433972) -DagonTest:testIsValidSignatureOnchain() (gas: 196826) -DagonTest:testIsValidSignatureVeryMany() (gas: 1236384) +DagonTest:testIsValidSignatureMany() (gas: 433994) +DagonTest:testIsValidSignatureOnchain() (gas: 196848) +DagonTest:testIsValidSignatureVeryMany() (gas: 1236406) DagonTest:testIsValidSignatureWeighted() (gas: 232474) -DagonTest:testIsValidSignatureWeightedERC1155() (gas: 247360) -DagonTest:testIsValidSignatureWeightedERC20() (gas: 247096) +DagonTest:testIsValidSignatureWeightedERC1155() (gas: 247382) +DagonTest:testIsValidSignatureWeightedERC20() (gas: 247118) DagonTest:testIsValidSignatureWeightedERC6909() (gas: 247788) -DagonTest:testIsValidSignatureWeightedERC721() (gas: 208344) -DagonTest:testNameAndSymbolAndDecimals(uint256) (runs: 256, μ: 15425, ~: 15425) +DagonTest:testIsValidSignatureWeightedERC721() (gas: 208366) +DagonTest:testNameAndSymbolAndDecimals(uint256) (runs: 256, μ: 15447, ~: 15447) DagonTest:testSetAuth(address) (runs: 256, μ: 144744, ~: 144744) DagonTest:testSetThreshold() (gas: 147272) -DagonTest:testSetToken(address) (runs: 256, μ: 148604, ~: 148604) +DagonTest:testSetToken(address) (runs: 256, μ: 148626, ~: 148626) DagonTest:testSetURI() (gas: 165570) +DagonTest:testSpoofSignatures(bytes) (runs: 256, μ: 16508, ~: 16437) DagonTest:testTransfer(address,address,uint88) (runs: 256, μ: 175931, ~: 176787) -DagonTest:testTransferWithAuth(address,address,uint96) (runs: 256, μ: 179459, ~: 180256) -DagonTest:testUserVoted() (gas: 194800) \ No newline at end of file +DagonTest:testTransferWithAuth(address,address,uint96) (runs: 256, μ: 179558, ~: 180278) +DagonTest:testUserVoted() (gas: 194822) \ No newline at end of file diff --git a/test/Dagon.t.sol b/test/Dagon.t.sol index 2494b26..ee8adb6 100644 --- a/test/Dagon.t.sol +++ b/test/Dagon.t.sol @@ -78,7 +78,7 @@ contract DagonTest is Test { NaniAccount internal account; uint256 internal accountId; - Dagon internal owners; + Dagon internal dagon; address internal constant _ENTRY_POINT = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; @@ -143,7 +143,7 @@ contract DagonTest is Test { accountId = uint256(uint160(address(account))); - owners = new Dagon(); + dagon = new Dagon(); erc20 = address(new MockERC20("TEST", "TEST", 18)); MockERC20(erc20).mint(alice, 40 ether); @@ -173,13 +173,13 @@ contract DagonTest is Test { } function testDeploy() public { - owners = new Dagon(); + new Dagon(); } function testNameAndSymbolAndDecimals(uint256 id) public { - assertEq(owners.name(id), ""); - assertEq(owners.symbol(id), ""); - assertEq(owners.decimals(id), 18); + assertEq(dagon.name(id), ""); + assertEq(dagon.symbol(id), ""); + assertEq(dagon.decimals(id), 18); } function testInstall() public { @@ -200,98 +200,103 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); - assertEq(account.ownershipHandoverExpiresAt(address(owners)), block.timestamp + 2 days); - assertEq(owners.balanceOf(alice, accountId), 1); + assertEq(account.ownershipHandoverExpiresAt(address(dagon)), block.timestamp + 2 days); + assertEq(dagon.balanceOf(alice, accountId), 1); vm.prank(alice); account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); (address setTkn, uint88 setThreshold, Dagon.Standard setStd) = - owners.getSettings(address(account)); + dagon.getSettings(address(account)); assertEq(address(setTkn), address(setting.token)); assertEq(uint256(setThreshold), uint256(setting.threshold)); assertEq(uint8(setStd), uint8(setting.standard)); - assertEq(owners.tokenURI(accountId), ""); - (,,, IAuth authority) = owners.getMetadata(address(account)); + assertEq(dagon.tokenURI(accountId), ""); + (,,, IAuth authority) = dagon.getMetadata(address(account)); assertEq(address(authority), address(0)); } function testSetThreshold() public { testInstall(); vm.prank(address(account)); - owners.mint(alice, 1); + dagon.mint(alice, 1); vm.prank(address(account)); - owners.setThreshold(2); - (, uint88 setThreshold,) = owners.getSettings(address(account)); + dagon.setThreshold(2); + (, uint88 setThreshold,) = dagon.getSettings(address(account)); assertEq(setThreshold, 2); } + function testSpoofSignatures(bytes calldata spoof) public payable { + bytes32 hash; // Empty hash. + assertEq(bytes4(0xffffffff), account.isValidSignature(hash, spoof)); + } + function testFailInvalidThresholdNull() public { testInstall(); vm.prank(address(account)); - owners.setThreshold(0); + dagon.setThreshold(0); } function testFailInvalidThresholdExceedsSupply() public { testInstall(); vm.prank(address(account)); - owners.setThreshold(2); + dagon.setThreshold(2); } function testFailInvalidThresholdExceedsSupply2() public { testInstall(); vm.prank(address(account)); - owners.mint(alice, 1); + dagon.mint(alice, 1); vm.prank(address(account)); - owners.setThreshold(3); - (, uint88 setThreshold,) = owners.getSettings(address(account)); + dagon.setThreshold(3); + (, uint88 setThreshold,) = dagon.getSettings(address(account)); assertEq(setThreshold, 3); } function testSetURI() public { testInstall(); vm.prank(address(account)); - owners.setURI("TEST"); - assertEq(owners.tokenURI(accountId), "TEST"); + dagon.setURI("TEST"); + assertEq(dagon.tokenURI(accountId), "TEST"); } function testSetToken(address tkn) public { Dagon.Standard std = Dagon.Standard.DAGON; testInstall(); vm.prank(address(account)); - owners.setToken(tkn, std); - (address setTkn,, Dagon.Standard setStd) = owners.getSettings(address(account)); + dagon.setToken(tkn, std); + (address setTkn,, Dagon.Standard setStd) = dagon.getSettings(address(account)); assertEq(address(tkn), address(setTkn)); assertEq(uint8(std), uint8(setStd)); std = Dagon.Standard.ERC20; vm.prank(address(account)); - owners.setToken(tkn, std); - (setTkn,, setStd) = owners.getSettings(address(account)); + dagon.setToken(tkn, std); + (setTkn,, setStd) = dagon.getSettings(address(account)); assertEq(address(tkn), address(setTkn)); } function testFailSetTokenInvalidStd(address tkn) public { testInstall(); vm.prank(address(account)); - owners.setToken(tkn, Dagon.Standard(uint8(5))); + dagon.setToken(tkn, Dagon.Standard(uint8(5))); } function testSetAuth(IAuth auth) public { testInstall(); vm.prank(address(account)); - owners.setAuth(auth); - (,,, IAuth authority) = owners.getMetadata(address(account)); + dagon.setAuth(auth); + (,,, IAuth authority) = dagon.getMetadata(address(account)); assertEq(address(auth), address(authority)); } @@ -302,12 +307,12 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); - assertEq(owners.balanceOf(from, accountId), amount); + dagon.mint(from, amount); + assertEq(dagon.balanceOf(from, accountId), amount); vm.prank(from); - owners.transfer(to, accountId, amount); - assertEq(owners.balanceOf(from, accountId), 0); - assertEq(owners.balanceOf(to, accountId), amount); + dagon.transfer(to, accountId, amount); + assertEq(dagon.balanceOf(from, accountId), 0); + assertEq(dagon.balanceOf(to, accountId), amount); } function testFailTransferOverBalance(address from, address to, uint96 amount) public { @@ -315,9 +320,9 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); + dagon.mint(from, amount); vm.prank(from); - owners.transfer(to, accountId, amount + 1); + dagon.transfer(to, accountId, amount + 1); } function testTransferWithAuth(address from, address to, uint96 amount) public { @@ -325,11 +330,11 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); + dagon.mint(from, amount); vm.prank(address(account)); - owners.setAuth(IAuth(mockAuth)); + dagon.setAuth(IAuth(mockAuth)); vm.prank(from); - owners.transfer(to, accountId, amount); + dagon.transfer(to, accountId, amount); } function testFailTransferFromInactiveAuth(address from, address to, uint96 amount) public { @@ -337,11 +342,11 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); + dagon.mint(from, amount); vm.prank(address(account)); - owners.setAuth(IAuth(address(4269))); + dagon.setAuth(IAuth(address(4269))); vm.prank(from); - owners.transfer(to, accountId, amount); + dagon.transfer(to, accountId, amount); } function testBurn(address from, uint96 amount) public { @@ -349,11 +354,11 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); - assertEq(owners.balanceOf(from, accountId), amount); + dagon.mint(from, amount); + assertEq(dagon.balanceOf(from, accountId), amount); vm.prank(address(account)); - owners.burn(from, amount); - assertEq(owners.balanceOf(from, accountId), 0); + dagon.burn(from, amount); + assertEq(dagon.balanceOf(from, accountId), 0); } function testFailBurnOverBalance(address from, uint96 amount) public { @@ -361,10 +366,10 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); - assertEq(owners.balanceOf(from, accountId), amount); + dagon.mint(from, amount); + assertEq(dagon.balanceOf(from, accountId), amount); vm.prank(address(account)); - owners.burn(from, amount + 1); + dagon.burn(from, amount + 1); } function testFailBurnOverThreshold(address from, uint96 amount) public { @@ -372,12 +377,12 @@ contract DagonTest is Test { vm.assume(amount < type(uint96).max); testInstall(); vm.prank(address(account)); - owners.mint(from, amount); - assertEq(owners.balanceOf(from, accountId), amount); + dagon.mint(from, amount); + assertEq(dagon.balanceOf(from, accountId), amount); vm.prank(address(account)); - owners.burn(from, amount); + dagon.burn(from, amount); vm.expectRevert(Dagon.InvalidSetting.selector); - owners.burn(alice, 1); + dagon.burn(alice, 1); } function testIsValidSignature() public { @@ -405,7 +410,7 @@ contract DagonTest is Test { bytes memory signature = abi.encodePacked(alice, _sign(alicePk, _toEthSignedMessageHash(userOpHash))); - owners.vote(address(account), userOpHash, signature); + dagon.vote(address(account), userOpHash, signature); vm.prank(_ENTRY_POINT); uint256 validationData = account.validateUserOp(userOp, userOpHash, 0); @@ -423,10 +428,10 @@ contract DagonTest is Test { bytes memory signature = abi.encodePacked(alice, _sign(alicePk, _toEthSignedMessageHash(userOpHash))); - owners.vote(address(account), userOpHash, signature); + dagon.vote(address(account), userOpHash, signature); assertEq( - owners.voted(alice, _toEthSignedMessageHash(userOpHash)), - owners.balanceOf(alice, uint256(uint160(address(account)))) + dagon.voted(alice, _toEthSignedMessageHash(userOpHash)), + dagon.balanceOf(alice, uint256(uint160(address(account)))) ); } @@ -458,7 +463,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -467,7 +472,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -516,7 +521,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -525,7 +530,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -589,7 +594,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -598,7 +603,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -731,7 +736,7 @@ contract DagonTest is Test { // Execute the Dagon install vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -741,7 +746,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); // Prepare for the signature validation @@ -792,7 +797,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -801,7 +806,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -846,7 +851,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -855,7 +860,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -907,7 +912,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -916,7 +921,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -961,7 +966,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -970,7 +975,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1022,7 +1027,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1031,7 +1036,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1073,7 +1078,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1082,7 +1087,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1129,7 +1134,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1138,7 +1143,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1183,7 +1188,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1192,7 +1197,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1244,14 +1249,14 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), 0, abi.encodeWithSelector(Dagon.install.selector, setting, meta) + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, setting, meta) ); vm.prank(alice); account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1296,7 +1301,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1305,7 +1310,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1357,7 +1362,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1366,7 +1371,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1379,7 +1384,7 @@ contract DagonTest is Test { assertEq(validationData, 0x00); } - function testFailIsValidSignatureOutOfOrder() public payable { + /*function testFailIsValidSignatureOutOfOrder() public payable { Dagon.Ownership[] memory _owners = new Dagon.Ownership[](4); _owners[0].owner = alice; _owners[0].shares = 40; @@ -1409,7 +1414,7 @@ contract DagonTest is Test { vm.prank(alice); account.execute( - address(owners), + address(dagon), 0, abi.encodeWithSelector(Dagon.install.selector, _owners, setting, meta) ); @@ -1418,7 +1423,7 @@ contract DagonTest is Test { account.execute( address(account), 0, - abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(owners)) + abi.encodeWithSelector(account.completeOwnershipHandover.selector, address(dagon)) ); NaniAccount.UserOperation memory userOp; @@ -1436,7 +1441,7 @@ contract DagonTest is Test { vm.prank(_ENTRY_POINT); uint256 validationData = account.validateUserOp(userOp, userOpHash, 0); assertEq(validationData, 0x00); - } + }*/ /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/DagonScales.t.sol b/test/DagonScales.t.sol new file mode 100644 index 0000000..7fe93db --- /dev/null +++ b/test/DagonScales.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import "@forge/Test.sol"; + +import {MockDagon} from "./utils/mocks/MockDagon.sol"; + +contract DagonScalesTest is Test { + MockDagon internal dagon; + + function setUp() public payable { + dagon = new MockDagon(); + } + + function testIsValidSignatureALOT() public payable { + bytes memory packedSigs; // Build packed sigs. + uint8 v; + bytes32 r; + bytes32 s; + uint256 i = 1; + unchecked { + for (i; i < 1774; ++i) { + (v, r, s) = vm.sign(i, bytes32(0)); + packedSigs = abi.encodePacked(packedSigs, vm.addr(i), r, s, v); + } + } + assertEq(dagon.isValidSignature.selector, dagon.isValidSignature(bytes32(0), packedSigs)); + } +} diff --git a/test/utils/mocks/MockDagon.sol b/test/utils/mocks/MockDagon.sol new file mode 100644 index 0000000..c9d8841 --- /dev/null +++ b/test/utils/mocks/MockDagon.sol @@ -0,0 +1,426 @@ +// ᗪᗩGOᑎ 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.23; + +import {ERC6909} from "@solady/src/tokens/ERC6909.sol"; +import {SignatureCheckerLib} from "@solady/src/utils/SignatureCheckerLib.sol"; + +// Note: We remove duplicate sig check to do scaling tests in `isValidSignature`. + +/// @notice Simple ownership singleton for smart accounts. +/// @dev Integration is best by means of the ERC173 and ERC1271 methods. +/// @custom:version 0.0.0 +contract MockDagon is ERC6909 { + /// ======================= CUSTOM ERRORS ======================= /// + + /// @dev Inputs are invalid for an ownership setting. + error InvalidSetting(); + + /// =========================== EVENTS =========================== /// + + /// @dev Logs new authority contract for an account. + event AuthSet(address indexed account, IAuth auth); + + /// @dev Logs new token uri settings for an account. + event URISet(address indexed account, string uri); + + /// @dev Logs new ownership threshold for an account. + event ThresholdSet(address indexed account, uint88 threshold); + + /// @dev Logs new token ownership standard for an account. + event TokenSet(address indexed account, address token, Standard standard); + + /// ========================== STRUCTS ========================== /// + + /// @dev The account token metadata struct. + struct Metadata { + string name; + string symbol; + string tokenURI; + IAuth authority; + uint96 totalSupply; + } + + /// @dev The account ownership shares struct. + struct Ownership { + address owner; + uint96 shares; + } + + /// @dev The account ownership settings struct. + struct Settings { + address token; + uint88 threshold; + Standard standard; + } + + /// @dev The ERC4337 user operation (userOp) struct. + struct UserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + uint256 callGasLimit; + uint256 verificationGasLimit; + uint256 preVerificationGas; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + bytes paymasterAndData; + bytes signature; + } + + /// =========================== ENUMS =========================== /// + + /// @dev The token standard interface enum. + enum Standard { + DAGON, + ERC20, + ERC721, + ERC1155, + ERC6909 + } + + /// ========================== STORAGE ========================== /// + + /// @dev Stores mapping of metadata settings to account token IDs. + /// note: IDs are unique to addresses (`uint256(uint160(account))`). + mapping(uint256 id => Metadata) internal _metadata; + + /// @dev Stores mapping of ownership settings to accounts. + mapping(address account => Settings) internal _settings; + + /// @dev Stores mapping of voting tallies to signed userOp hashes. + mapping(bytes32 signedHash => uint256) public votingTally; + + /// @dev Stores mapping of account owner voting shares cast on signed userOp hashes. + mapping(address owner => mapping(bytes32 signedHash => uint256 shares)) public voted; + + /// ================= ERC6909 METADATA & SUPPLY ================= /// + + /// @dev Returns the name for token `id` using this contract. + function name(uint256 id) public view virtual override(ERC6909) returns (string memory) { + return _metadata[id].name; + } + + /// @dev Returns the symbol for token `id` using this contract. + function symbol(uint256 id) public view virtual override(ERC6909) returns (string memory) { + return _metadata[id].symbol; + } + + /// @dev Returns the URI for token `id` using this contract. + function tokenURI(uint256 id) public view virtual override(ERC6909) returns (string memory) { + return _metadata[id].tokenURI; + } + + /// @dev Returns the total supply for token `id` using this contract. + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _metadata[id].totalSupply; + } + + /// ======================== CONSTRUCTOR ======================== /// + + /// @dev Constructs + /// this implementation. + constructor() payable {} + + /// =================== VALIDATION OPERATIONS =================== /// + + /// @dev Validates ERC1271 signature with additional auth logic flow among owners. + /// note: This implementation is designed to be the ERC-173-owner-of-4337-accounts. + function isValidSignature(bytes32 hash, bytes calldata signature) + public + view + virtual + returns (bytes4) + { + Settings memory set = _settings[msg.sender]; + if (signature.length != 0) { + unchecked { + uint256 pos; + address prev; + address owner; + uint256 tally; + for (uint256 i; i != signature.length / 85; ++i) { + if ( + SignatureCheckerLib.isValidSignatureNowCalldata( + owner = address(bytes20(signature[pos:pos + 20])), + hash, + signature[pos + 20:pos + 85] + ) //&& prev < owner // Check double voting. + ) { + pos += 85; + prev = owner; + tally += set.standard == Standard.DAGON + ? balanceOf(owner, uint256(uint160(msg.sender))) + : set.standard == Standard.ERC20 || set.standard == Standard.ERC721 + ? _balanceOf(set.token, owner) + : _balanceOf(set.token, owner, uint256(uint160(msg.sender))); + } else { + return 0xffffffff; // Failure code. + } + } + return _validateReturn(tally >= set.threshold); + } + } + return _validateReturn(votingTally[hash] >= set.threshold); + } + + /// @dev Validates ERC4337 userOp with additional auth logic flow among owners. + /// note: This is expected to be called in a validator plugin-like userOp flow. + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 /*missingAccountFunds*/ + ) public payable virtual returns (uint256 validationData) { + IAuth auth = _metadata[uint256(uint160(msg.sender))].authority; + if (auth != IAuth(address(0))) { + (address target, uint256 value, bytes memory data) = + abi.decode(userOp.callData[4:], (address, uint256, bytes)); + auth.validateCall(msg.sender, target, value, data); + } + if ( + isValidSignature( + SignatureCheckerLib.toEthSignedMessageHash(userOpHash), userOp.signature + ) != this.isValidSignature.selector + ) validationData = 0x01; // Failure code. + } + + /// @dev Returns validated signature result within the conventional ERC1271 syntax. + function _validateReturn(bool success) internal pure virtual returns (bytes4 result) { + assembly ("memory-safe") { + // `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`. + result := shl(224, or(0x1626ba7e, sub(0, iszero(success)))) + } + } + + /// ===================== VOTING OPERATIONS ===================== /// + + /// @dev Casts account owner voting shares on a given ERC4337 userOp hash. + function vote(address account, bytes32 userOpHash, bytes calldata signature) + public + payable + virtual + returns (uint256) + { + Settings memory set = _settings[account]; + bytes32 hash = SignatureCheckerLib.toEthSignedMessageHash(userOpHash); + unchecked { + uint256 pos; + address owner; + uint256 tally; + for (uint256 i; i != signature.length / 85; ++i) { + if ( + SignatureCheckerLib.isValidSignatureNowCalldata( + owner = address(bytes20(signature[pos:pos + 20])), + hash, + signature[pos + 20:pos + 85] + ) && voted[owner][hash] == 0 // Check double voting. + ) { + pos += 85; + tally += voted[owner][hash] = set.standard == Standard.DAGON + ? balanceOf(owner, uint256(uint160(account))) + : set.standard == Standard.ERC20 || set.standard == Standard.ERC721 + ? _balanceOf(set.token, owner) + : _balanceOf(set.token, owner, uint256(uint160(account))); + } + } + return votingTally[hash] += tally; // Return latest total tally. + } + } + + /// ======================== INSTALLATION ======================== /// + + /// @dev Initializes ownership settings for the caller account. + /// note: Finalizes with transfer request in two-step pattern. + /// See, e.g., Ownable.sol: + /// https://github.com/Vectorized/solady/blob/main/src/auth/Ownable.sol + function install(Ownership[] calldata owners, Settings calldata setting, Metadata calldata meta) + public + payable + virtual + { + uint256 id = uint256(uint160(msg.sender)); + if (owners.length != 0) { + uint96 supply; + for (uint256 i; i != owners.length;) { + supply += owners[i].shares; + _mint(owners[i].owner, id, owners[i].shares); + unchecked { + ++i; + } + } + _metadata[id].totalSupply += supply; + } + setToken(setting.token, setting.standard); + setThreshold(setting.threshold); + if (bytes(meta.name).length != 0) { + _metadata[id].name = meta.name; + _metadata[id].symbol = meta.symbol; + } + if (bytes(meta.tokenURI).length != 0) setURI(meta.tokenURI); + if (meta.authority != IAuth(address(0))) _metadata[id].authority = meta.authority; + try IOwnable(msg.sender).requestOwnershipHandover() {} catch {} // Avoid revert. + } + + /// ===================== OWNERSHIP SETTINGS ===================== /// + + /// @dev Returns the account settings. + function getSettings(address account) public view virtual returns (address, uint88, Standard) { + Settings storage set = _settings[account]; + return (set.token, set.threshold, set.standard); + } + + /// @dev Sets new authority contract for the caller account. + function setAuth(IAuth auth) public payable virtual { + emit AuthSet(msg.sender, (_metadata[uint256(uint160(msg.sender))].authority = auth)); + } + + /// @dev Sets new token ownership interface standard for the caller account. + function setToken(address token, Standard standard) public payable virtual { + emit TokenSet( + msg.sender, + _settings[msg.sender].token = token, + _settings[msg.sender].standard = standard + ); + } + + /// @dev Sets new ownership threshold for the caller account. + function setThreshold(uint88 threshold) public payable virtual { + Settings storage set = _settings[msg.sender]; + if ( + threshold + > ( + set.standard == Standard.DAGON + ? totalSupply(uint256(uint160(msg.sender))) + : set.standard == Standard.ERC20 || set.standard == Standard.ERC721 + ? _totalSupply(set.token) + : _totalSupply(set.token, uint256(uint160(msg.sender))) + ) || threshold == 0 + ) revert InvalidSetting(); + emit ThresholdSet(msg.sender, (set.threshold = threshold)); + } + + /// ====================== TOKEN OPERATIONS ====================== /// + + /// @dev Returns the account metadata. + function getMetadata(address account) + public + view + virtual + returns (string memory, string memory, string memory, IAuth) + { + Metadata storage meta = _metadata[uint256(uint160(account))]; + return (meta.name, meta.symbol, meta.tokenURI, meta.authority); + } + + /// @dev Mints shares for an owner of the caller account. + function mint(address owner, uint96 shares) public payable virtual { + uint256 id = uint256(uint160(msg.sender)); + _metadata[id].totalSupply += shares; + _mint(owner, id, shares); + } + + /// @dev Burns shares from an owner of the caller account. + function burn(address owner, uint96 shares) public payable virtual { + uint256 id = uint256(uint160(msg.sender)); + unchecked { + if (_settings[msg.sender].threshold > (_metadata[id].totalSupply -= shares)) { + revert InvalidSetting(); + } + } + _burn(owner, id, shares); + } + + /// @dev Sets new token URI metadata for the caller account. + function setURI(string calldata uri) public payable virtual { + emit URISet(msg.sender, (_metadata[uint256(uint160(msg.sender))].tokenURI = uri)); + } + + /// =================== EXTERNAL TOKEN HELPERS =================== /// + + /// @dev Returns the amount of ERC20/721 `token` owned by `account`. + function _balanceOf(address token, address account) + internal + view + virtual + returns (uint256 amount) + { + assembly ("memory-safe") { + mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. + mstore(0x14, account) // Store the `account` argument. + pop(staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)) + amount := mload(0x20) + } + } + + /// @dev Returns the amount of ERC1155/6909 `token` `id` owned by `account`. + function _balanceOf(address token, address account, uint256 id) + internal + view + virtual + returns (uint256 amount) + { + assembly ("memory-safe") { + mstore(0x00, 0x00fdd58e000000000000000000000000) // `balanceOf(address,uint256)`. + mstore(0x14, account) // Store the `account` argument. + mstore(0x34, id) // Store the `id` argument. + pop(staticcall(gas(), token, 0x10, 0x44, 0x20, 0x20)) + amount := mload(0x20) + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @dev Returns the total supply of ERC20/721 `token`. + function _totalSupply(address token) internal view virtual returns (uint256 supply) { + assembly ("memory-safe") { + mstore(0x00, 0x18160ddd) // `totalSupply()`. + pop(staticcall(gas(), token, 0x1c, 0x04, 0x20, 0x20)) + supply := mload(0x20) + } + } + + /// @dev Returns the total supply of ERC1155/6909 `token` `id`. + function _totalSupply(address token, uint256 id) + internal + view + virtual + returns (uint256 supply) + { + assembly ("memory-safe") { + mstore(0x00, 0xbd85b039) // `totalSupply(uint256)`. + mstore(0x20, id) // Store the `id` argument. + pop(staticcall(gas(), token, 0x1c, 0x24, 0x20, 0x20)) + supply := mload(0x20) + } + } + + /// ========================= OVERRIDES ========================= /// + + /// @dev Hook that is called before any transfer of tokens. + /// This includes minting and burning. Also requests authority for token transfers. + function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount) + internal + virtual + override(ERC6909) + { + IAuth auth = _metadata[id].authority; + if (auth != IAuth(address(0))) auth.validateTransfer(from, to, id, amount); + } +} + +/// @notice Simple authority interface for contracts. +interface IAuth { + function validateTransfer(address, address, uint256, uint256) + external + payable + returns (uint256); + function validateCall(address, address, uint256, bytes calldata) + external + payable + returns (uint256); +} + +/// @notice Simple ownership interface for handover requests. +interface IOwnable { + function requestOwnershipHandover() external payable; +}