diff --git a/contracts/smart-wallet/utils/AccountCore.sol b/contracts/smart-wallet/utils/AccountCore.sol index cae42ecf7..aa3ebd00e 100644 --- a/contracts/smart-wallet/utils/AccountCore.sol +++ b/contracts/smart-wallet/utils/AccountCore.sol @@ -50,9 +50,6 @@ contract AccountCore is IAccountCore, Initializable, Multicall, BaseAccount, ERC Constructor, Initializer, Modifiers //////////////////////////////////////////////////////////////*/ - // solhint-disable-next-line no-empty-blocks - receive() external payable virtual {} - constructor(IEntryPoint _entrypoint, address _factory) EIP712("Account", "1") { _disableInitializers(); factory = _factory; diff --git a/lib/dynamic-contracts b/lib/dynamic-contracts index 35a084c66..c94e68328 160000 --- a/lib/dynamic-contracts +++ b/lib/dynamic-contracts @@ -1 +1 @@ -Subproject commit 35a084c66413b6e2401834220174f635bb34516a +Subproject commit c94e68328c7126f511523d48d9cf8d95eb089dc0 diff --git a/package.json b/package.json index ab3f595d4..8a93dc886 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@openzeppelin/contracts": "4.7.3", "@openzeppelin/contracts-upgradeable": "4.7.3", "@primitivefi/hardhat-dodoc": "^0.2.0", - "@thirdweb-dev/dynamic-contracts": "^1.1.2", + "@thirdweb-dev/dynamic-contracts": "^1.1.3", "@thirdweb-dev/sdk": "3.10.33", "@typechain/ethers-v5": "^10.0.0", "@typechain/hardhat": "^4.0.0", diff --git a/src/test/smart-wallet/Account.t.sol b/src/test/smart-wallet/Account.t.sol index 2f116c8d6..9d03873d9 100644 --- a/src/test/smart-wallet/Account.t.sol +++ b/src/test/smart-wallet/Account.t.sol @@ -1,554 +1,563 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -// // Test utils -// import "../utils/BaseTest.sol"; - -// // Account Abstraction setup for smart wallets. -// import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; -// import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; - -// // Target -// import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; -// import { AccountFactory, Account } from "contracts/smart-wallet/non-upgradeable/AccountFactory.sol"; - -// /// @dev This is a dummy contract to test contract interactions with Account. -// contract Number { -// uint256 public num; - -// function setNum(uint256 _num) public { -// num = _num; -// } - -// function doubleNum() public { -// num *= 2; -// } - -// function incrementNum() public { -// num += 1; -// } -// } - -// contract SimpleAccountTest is BaseTest { -// // Target contracts -// EntryPoint private entrypoint; -// AccountFactory private accountFactory; - -// // Mocks -// Number internal numberContract; - -// // Test params -// uint256 private accountAdminPKey = 100; -// address private accountAdmin; - -// uint256 private accountSignerPKey = 200; -// address private accountSigner; - -// uint256 private nonSignerPKey = 300; -// address private nonSigner; - -// // UserOp terminology: `sender` is the smart wallet. -// address private sender = 0xBB956D56140CA3f3060986586A2631922a4B347E; -// address payable private beneficiary = payable(address(0x45654)); - -// bytes32 private uidCache = bytes32("random uid"); - -// event AccountCreated(address indexed account, address indexed accountAdmin); - -// function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) -// internal -// view -// returns (bytes memory signature) -// { -// bytes32 typehashSignerPermissionRequest = keccak256( -// "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" -// ); -// bytes32 nameHash = keccak256(bytes("Account")); -// bytes32 versionHash = keccak256(bytes("1")); -// bytes32 typehashEip712 = keccak256( -// "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" -// ); -// bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); - -// bytes memory encodedRequest = abi.encode( -// typehashSignerPermissionRequest, -// _req.signer, -// keccak256(abi.encodePacked(_req.approvedTargets)), -// _req.nativeTokenLimitPerTransaction, -// _req.permissionStartTimestamp, -// _req.permissionEndTimestamp, -// _req.reqValidityStartTimestamp, -// _req.reqValidityEndTimestamp, -// _req.uid -// ); -// bytes32 structHash = keccak256(encodedRequest); -// bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); -// signature = abi.encodePacked(r, s, v); -// } - -// function _setupUserOp( -// uint256 _signerPKey, -// bytes memory _initCode, -// bytes memory _callDataForEntrypoint -// ) internal returns (UserOperation[] memory ops) { -// uint256 nonce = entrypoint.getNonce(sender, 0); - -// // Get user op fields -// UserOperation memory op = UserOperation({ -// sender: sender, -// nonce: nonce, -// initCode: _initCode, -// callData: _callDataForEntrypoint, -// callGasLimit: 500_000, -// verificationGasLimit: 500_000, -// preVerificationGas: 500_000, -// maxFeePerGas: 0, -// maxPriorityFeePerGas: 0, -// paymasterAndData: bytes(""), -// signature: bytes("") -// }); - -// // Sign UserOp -// bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); -// bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); -// bytes memory userOpSignature = abi.encodePacked(r, s, v); - -// address recoveredSigner = ECDSA.recover(msgHash, v, r, s); -// address expectedSigner = vm.addr(_signerPKey); -// assertEq(recoveredSigner, expectedSigner); - -// op.signature = userOpSignature; - -// // Store UserOp -// ops = new UserOperation[](1); -// ops[0] = op; -// } - -// function _setupUserOpExecute( -// uint256 _signerPKey, -// bytes memory _initCode, -// address _target, -// uint256 _value, -// bytes memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "execute(address,uint256,bytes)", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function _setupUserOpExecuteBatch( -// uint256 _signerPKey, -// bytes memory _initCode, -// address[] memory _target, -// uint256[] memory _value, -// bytes[] memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "executeBatch(address[],uint256[],bytes[])", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function setUp() public override { -// super.setUp(); - -// // Setup signers. -// accountAdmin = vm.addr(accountAdminPKey); -// vm.deal(accountAdmin, 100 ether); - -// accountSigner = vm.addr(accountSignerPKey); -// nonSigner = vm.addr(nonSignerPKey); - -// // Setup contracts -// entrypoint = new EntryPoint(); -// // deploy account factory -// accountFactory = new AccountFactory(IEntryPoint(payable(address(entrypoint)))); -// // deploy dummy contract -// numberContract = new Number(); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: creating an account -// //////////////////////////////////////////////////////////////*/ - -// /// @dev Create an account by directly calling the factory. -// function test_state_createAccount_viaFactory() public { -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// accountFactory.createAccount(accountAdmin, bytes("")); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /// @dev Create an account via Entrypoint. -// function test_state_createAccount_viaEntrypoint() public { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, bytes("")); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: performing a contract call -// //////////////////////////////////////////////////////////////*/ - -// function _setup_executeTransaction() internal { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, bytes("")); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); -// } - -// /// @dev Perform a state changing transaction directly via account. -// function test_state_executeTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// vm.prank(accountAdmin); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch directly via account. -// function test_state_executeBatchTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// vm.prank(accountAdmin); -// Account(payable(account)).executeBatch(targets, values, callData); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint. -// function test_state_executeTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountAdminPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountAdminPKey, -// bytes(""), -// targets, -// values, -// callData -// ); +// Test utils +import "../utils/BaseTest.sol"; + +// Account Abstraction setup for smart wallets. +import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; +import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; + +// Target +import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; +import { AccountFactory } from "contracts/smart-wallet/non-upgradeable/AccountFactory.sol"; +import { Account as SimpleAccount } from "contracts/smart-wallet/non-upgradeable/Account.sol"; + +/// @dev This is a dummy contract to test contract interactions with Account. +contract Number { + uint256 public num; + + function setNum(uint256 _num) public { + num = _num; + } + + function doubleNum() public { + num *= 2; + } + + function incrementNum() public { + num += 1; + } +} + +contract SimpleAccountTest is BaseTest { + // Target contracts + EntryPoint private entrypoint; + AccountFactory private accountFactory; + + // Mocks + Number internal numberContract; + + // Test params + uint256 private accountAdminPKey = 100; + address private accountAdmin; + + uint256 private accountSignerPKey = 200; + address private accountSigner; + + uint256 private nonSignerPKey = 300; + address private nonSigner; + + // UserOp terminology: `sender` is the smart wallet. + address private sender = 0xBB956D56140CA3f3060986586A2631922a4B347E; + address payable private beneficiary = payable(address(0x45654)); + + bytes32 private uidCache = bytes32("random uid"); + + event AccountCreated(address indexed account, address indexed accountAdmin); + + function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) + internal + view + returns (bytes memory signature) + { + bytes32 typehashSignerPermissionRequest = keccak256( + "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" + ); + bytes32 nameHash = keccak256(bytes("Account")); + bytes32 versionHash = keccak256(bytes("1")); + bytes32 typehashEip712 = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); + + bytes memory encodedRequest = abi.encode( + typehashSignerPermissionRequest, + _req.signer, + keccak256(abi.encodePacked(_req.approvedTargets)), + _req.nativeTokenLimitPerTransaction, + _req.permissionStartTimestamp, + _req.permissionEndTimestamp, + _req.reqValidityStartTimestamp, + _req.reqValidityEndTimestamp, + _req.uid + ); + bytes32 structHash = keccak256(encodedRequest); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); + signature = abi.encodePacked(r, s, v); + } + + function _setupUserOp( + uint256 _signerPKey, + bytes memory _initCode, + bytes memory _callDataForEntrypoint + ) internal returns (UserOperation[] memory ops) { + uint256 nonce = entrypoint.getNonce(sender, 0); + + // Get user op fields + UserOperation memory op = UserOperation({ + sender: sender, + nonce: nonce, + initCode: _initCode, + callData: _callDataForEntrypoint, + callGasLimit: 500_000, + verificationGasLimit: 500_000, + preVerificationGas: 500_000, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + // Sign UserOp + bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); + bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); + bytes memory userOpSignature = abi.encodePacked(r, s, v); + + address recoveredSigner = ECDSA.recover(msgHash, v, r, s); + address expectedSigner = vm.addr(_signerPKey); + assertEq(recoveredSigner, expectedSigner); + + op.signature = userOpSignature; + + // Store UserOp + ops = new UserOperation[](1); + ops[0] = op; + } + + function _setupUserOpExecute( + uint256 _signerPKey, + bytes memory _initCode, + address _target, + uint256 _value, + bytes memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "execute(address,uint256,bytes)", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function _setupUserOpExecuteBatch( + uint256 _signerPKey, + bytes memory _initCode, + address[] memory _target, + uint256[] memory _value, + bytes[] memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "executeBatch(address[],uint256[],bytes[])", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function setUp() public override { + super.setUp(); + + // Setup signers. + accountAdmin = vm.addr(accountAdminPKey); + vm.deal(accountAdmin, 100 ether); + + accountSigner = vm.addr(accountSignerPKey); + nonSigner = vm.addr(nonSignerPKey); + + // Setup contracts + entrypoint = new EntryPoint(); + // deploy account factory + accountFactory = new AccountFactory(IEntryPoint(payable(address(entrypoint)))); + // deploy dummy contract + numberContract = new Number(); + } + + /*/////////////////////////////////////////////////////////////// + Test: creating an account + //////////////////////////////////////////////////////////////*/ + + /// @dev Create an account by directly calling the factory. + function test_state_createAccount_viaFactory() public { + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + accountFactory.createAccount(accountAdmin, bytes("")); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /// @dev Create an account via Entrypoint. + function test_state_createAccount_viaEntrypoint() public { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, bytes("")); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /*/////////////////////////////////////////////////////////////// + Test: performing a contract call + //////////////////////////////////////////////////////////////*/ + + function _setup_executeTransaction() internal { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, bytes("")); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + } + + /// @dev Perform a state changing transaction directly via account. + function test_state_executeTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch directly via account. + function test_state_executeBatchTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).executeBatch(targets, values, callData); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint. + function test_state_executeTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountAdminPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountAdminPKey, + bytes(""), + targets, + values, + callData + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountSignerPKey, + bytes(""), + targets, + values, + callData + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. + function test_state_executeTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. + function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + vm.expectRevert(); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + } + + /// @dev Revert: non-admin performs a state changing transaction directly via account contract. + function test_revert_executeTransaction_nonSigner_viaDirectCall() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); + assertEq(numberContract.num(), 0); -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); - -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); - -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); - -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountSignerPKey, -// bytes(""), -// targets, -// values, -// callData -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. -// function test_state_executeTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); - -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); - -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. -// function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { -// _setup_executeTransaction(); + vm.prank(accountSigner); + vm.expectRevert("Account: not admin or EntryPoint."); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + } -// assertEq(numberContract.num(), 0); + /*/////////////////////////////////////////////////////////////// + Test: receiving and sending native tokens + //////////////////////////////////////////////////////////////*/ -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// vm.expectRevert(); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// } - -// /// @dev Revert: non-admin performs a state changing transaction directly via account contract. -// function test_revert_executeTransaction_nonSigner_viaDirectCall() public { -// _setup_executeTransaction(); + /// @dev Send native tokens to an account. + function test_state_accountReceivesNativeTokens() public { + _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); + assertEq(address(account).balance, 0); -// assertEq(numberContract.num(), 0); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory data) = payable(account).call{ value: 1000 }(""); -// vm.prank(accountSigner); -// vm.expectRevert("Account: not admin or EntryPoint."); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); -// } + // Silence warning: Return value of low-level calls not used. + (success, data) = (success, data); -// /*/////////////////////////////////////////////////////////////// -// Test: receiving and sending native tokens -// //////////////////////////////////////////////////////////////*/ + assertEq(address(account).balance, 1000); + } -// /// @dev Send native tokens to an account. -// function test_state_accountReceivesNativeTokens() public { -// _setup_executeTransaction(); + /// @dev Transfer native tokens out of an account. + function test_state_transferOutsNativeTokens() public { + _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + uint256 value = 1000; -// assertEq(address(account).balance, 0); + address account = accountFactory.getAddress(accountAdmin, bytes("")); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory data) = payable(account).call{ value: value }(""); + assertEq(address(account).balance, value); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory data) = payable(account).call{ value: 1000 }(""); + // Silence warning: Return value of low-level calls not used. + (success, data) = (success, data); -// // Silence warning: Return value of low-level calls not used. -// (success, data) = (success, data); + address recipient = address(0x3456); -// assertEq(address(account).balance, 1000); -// } + UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); -// /// @dev Transfer native tokens out of an account. -// function test_state_transferOutsNativeTokens() public { -// _setup_executeTransaction(); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + assertEq(address(account).balance, 0); + assertEq(recipient.balance, value); + } -// uint256 value = 1000; + /// @dev Add and remove a deposit for the account from the Entrypoint. -// address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory data) = payable(account).call{ value: value }(""); -// assertEq(address(account).balance, value); + function test_state_addAndWithdrawDeposit() public { + _setup_executeTransaction(); -// // Silence warning: Return value of low-level calls not used. -// (success, data) = (success, data); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// address recipient = address(0x3456); + assertEq(SimpleAccount(payable(account)).getDeposit(), 0); -// UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); + vm.prank(accountAdmin); + SimpleAccount(payable(account)).addDeposit{ value: 1000 }(); + assertEq(SimpleAccount(payable(account)).getDeposit(), 1000); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// assertEq(address(account).balance, 0); -// assertEq(recipient.balance, value); -// } + vm.prank(accountAdmin); + SimpleAccount(payable(account)).withdrawDepositTo(payable(accountSigner), 500); + assertEq(SimpleAccount(payable(account)).getDeposit(), 500); + } -// /// @dev Add and remove a deposit for the account from the Entrypoint. + /*/////////////////////////////////////////////////////////////// + Test: receiving ERC-721 and ERC-1155 NFTs + //////////////////////////////////////////////////////////////*/ -// function test_state_addAndWithdrawDeposit() public { -// _setup_executeTransaction(); + /// @dev Send an ERC-721 NFT to an account. + function test_state_receiveERC721NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + assertEq(erc721.balanceOf(account), 0); -// assertEq(Account(payable(account)).getDeposit(), 0); + erc721.mint(account, 1); -// vm.prank(accountAdmin); -// Account(payable(account)).addDeposit{ value: 1000 }(); -// assertEq(Account(payable(account)).getDeposit(), 1000); + assertEq(erc721.balanceOf(account), 1); + } -// vm.prank(accountAdmin); -// Account(payable(account)).withdrawDepositTo(payable(accountSigner), 500); -// assertEq(Account(payable(account)).getDeposit(), 500); -// } + /// @dev Send an ERC-1155 NFT to an account. + function test_state_receiveERC1155NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// /*/////////////////////////////////////////////////////////////// -// Test: receiving ERC-721 and ERC-1155 NFTs -// //////////////////////////////////////////////////////////////*/ + assertEq(erc1155.balanceOf(account, 0), 0); -// /// @dev Send an ERC-721 NFT to an account. -// function test_state_receiveERC721NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + erc1155.mint(account, 0, 1); -// assertEq(erc721.balanceOf(account), 0); - -// erc721.mint(account, 1); - -// assertEq(erc721.balanceOf(account), 1); -// } - -// /// @dev Send an ERC-1155 NFT to an account. -// function test_state_receiveERC1155NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(erc1155.balanceOf(account, 0), 0); - -// erc1155.mint(account, 0, 1); - -// assertEq(erc1155.balanceOf(account, 0), 1); -// } -// } + assertEq(erc1155.balanceOf(account, 0), 1); + } +} diff --git a/src/test/smart-wallet/DynamicAccount.t.sol b/src/test/smart-wallet/DynamicAccount.t.sol index 01536e24a..ab8294c6f 100644 --- a/src/test/smart-wallet/DynamicAccount.t.sol +++ b/src/test/smart-wallet/DynamicAccount.t.sol @@ -1,649 +1,661 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -// // Test utils -// import "../utils/BaseTest.sol"; -// import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol"; -// import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; -// import { AccountPermissions } from "contracts/dynamic-contracts/extension/AccountPermissions.sol"; -// import { AccountExtension } from "contracts/smart-wallet/utils/AccountExtension.sol"; - -// // Account Abstraction setup for smart wallets. -// import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; -// import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; - -// // Target -// import { Account } from "contracts/smart-wallet/non-upgradeable/Account.sol"; -// import { DynamicAccountFactory, DynamicAccount } from "contracts/smart-wallet/dynamic/DynamicAccountFactory.sol"; - -// /// @dev This is a dummy contract to test contract interactions with Account. -// contract Number { -// uint256 public num; - -// function setNum(uint256 _num) public { -// num = _num; -// } - -// function doubleNum() public { -// num *= 2; -// } - -// function incrementNum() public { -// num += 1; -// } -// } - -// contract NFTRejector { -// function onERC721Received( -// address, -// address, -// uint256, -// bytes memory -// ) public virtual returns (bytes4) { -// revert("NFTs not accepted"); -// } -// } - -// contract DynamicAccountTest is BaseTest { -// // Target contracts -// EntryPoint private entrypoint; -// DynamicAccountFactory private accountFactory; - -// // Mocks -// Number internal numberContract; - -// // Test params -// uint256 private accountAdminPKey = 100; -// address private accountAdmin; - -// uint256 private accountSignerPKey = 200; -// address private accountSigner; - -// uint256 private nonSignerPKey = 300; -// address private nonSigner; - -// bytes internal data = bytes(""); - -// // UserOp terminology: `sender` is the smart wallet. -// address private sender = 0xbC12AEae5E1b1a80401dd20A6728f7a01a3A6166; -// address payable private beneficiary = payable(address(0x45654)); - -// bytes32 private uidCache = bytes32("random uid"); - -// event AccountCreated(address indexed account, address indexed accountAdmin); - -// function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) -// internal -// view -// returns (bytes memory signature) -// { -// bytes32 typehashSignerPermissionRequest = keccak256( -// "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" -// ); -// bytes32 nameHash = keccak256(bytes("Account")); -// bytes32 versionHash = keccak256(bytes("1")); -// bytes32 typehashEip712 = keccak256( -// "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" -// ); -// bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); - -// bytes memory encodedRequest = abi.encode( -// typehashSignerPermissionRequest, -// _req.signer, -// keccak256(abi.encodePacked(_req.approvedTargets)), -// _req.nativeTokenLimitPerTransaction, -// _req.permissionStartTimestamp, -// _req.permissionEndTimestamp, -// _req.reqValidityStartTimestamp, -// _req.reqValidityEndTimestamp, -// _req.uid -// ); -// bytes32 structHash = keccak256(encodedRequest); -// bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); -// signature = abi.encodePacked(r, s, v); -// } - -// function _setupUserOp( -// uint256 _signerPKey, -// bytes memory _initCode, -// bytes memory _callDataForEntrypoint -// ) internal returns (UserOperation[] memory ops) { -// uint256 nonce = entrypoint.getNonce(sender, 0); - -// // Get user op fields -// UserOperation memory op = UserOperation({ -// sender: sender, -// nonce: nonce, -// initCode: _initCode, -// callData: _callDataForEntrypoint, -// callGasLimit: 500_000, -// verificationGasLimit: 500_000, -// preVerificationGas: 500_000, -// maxFeePerGas: 0, -// maxPriorityFeePerGas: 0, -// paymasterAndData: bytes(""), -// signature: bytes("") -// }); - -// // Sign UserOp -// bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); -// bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); -// bytes memory userOpSignature = abi.encodePacked(r, s, v); - -// address recoveredSigner = ECDSA.recover(msgHash, v, r, s); -// address expectedSigner = vm.addr(_signerPKey); -// assertEq(recoveredSigner, expectedSigner); - -// op.signature = userOpSignature; - -// // Store UserOp -// ops = new UserOperation[](1); -// ops[0] = op; -// } - -// function _setupUserOpExecute( -// uint256 _signerPKey, -// bytes memory _initCode, -// address _target, -// uint256 _value, -// bytes memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "execute(address,uint256,bytes)", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function _setupUserOpExecuteBatch( -// uint256 _signerPKey, -// bytes memory _initCode, -// address[] memory _target, -// uint256[] memory _value, -// bytes[] memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "executeBatch(address[],uint256[],bytes[])", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function setUp() public override { -// super.setUp(); - -// // Setup signers. -// accountAdmin = vm.addr(accountAdminPKey); -// vm.deal(accountAdmin, 100 ether); - -// accountSigner = vm.addr(accountSignerPKey); -// nonSigner = vm.addr(nonSignerPKey); - -// // Setup contracts -// entrypoint = new EntryPoint(); - -// // Setting up default extension. -// IExtension.Extension memory defaultExtension; - -// defaultExtension.metadata = IExtension.ExtensionMetadata({ -// name: "AccountExtension", -// metadataURI: "ipfs://AccountExtension", -// implementation: address(new AccountExtension(address(entrypoint))) -// }); - -// defaultExtension.functions = new IExtension.ExtensionFunction[](5); - -// defaultExtension.functions[0] = IExtension.ExtensionFunction( -// AccountExtension.supportsInterface.selector, -// "supportsInterface(bytes4)" -// ); -// defaultExtension.functions[1] = IExtension.ExtensionFunction( -// AccountExtension.execute.selector, -// "execute(address,uint256,bytes)" -// ); -// defaultExtension.functions[2] = IExtension.ExtensionFunction( -// AccountExtension.executeBatch.selector, -// "executeBatch(address[],uint256[],bytes[])" -// ); -// defaultExtension.functions[3] = IExtension.ExtensionFunction( -// ERC721Holder.onERC721Received.selector, -// "onERC721Received(address,address,uint256,bytes)" -// ); -// defaultExtension.functions[4] = IExtension.ExtensionFunction( -// ERC1155Holder.onERC1155Received.selector, -// "onERC1155Received(address,address,uint256,uint256,bytes)" -// ); - -// IExtension.Extension[] memory extensions = new IExtension.Extension[](1); -// extensions[0] = defaultExtension; - -// // deploy account factory -// accountFactory = new DynamicAccountFactory(IEntryPoint(payable(address(entrypoint))), extensions); -// // deploy dummy contract -// numberContract = new Number(); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: creating an account -// //////////////////////////////////////////////////////////////*/ - -// /// @dev Create an account by directly calling the factory. -// function test_state_createAccount_viaFactory() public { -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// accountFactory.createAccount(accountAdmin, data); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /// @dev Create an account via Entrypoint. -// function test_state_createAccount_viaEntrypoint() public { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: performing a contract call -// //////////////////////////////////////////////////////////////*/ - -// function _setup_executeTransaction() internal { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); -// } - -// /// @dev Perform a state changing transaction directly via account. -// function test_state_executeTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// vm.prank(accountAdmin); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch directly via account. -// function test_state_executeBatchTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// vm.prank(accountAdmin); -// Account(payable(account)).executeBatch(targets, values, callData); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint. -// function test_state_executeTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountAdminPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountAdminPKey, -// bytes(""), -// targets, -// values, -// callData -// ); +// Test utils +import "../utils/BaseTest.sol"; +import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol"; +import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; +import { AccountPermissions } from "contracts/dynamic-contracts/extension/AccountPermissions.sol"; +import { AccountExtension } from "contracts/smart-wallet/utils/AccountExtension.sol"; + +// Account Abstraction setup for smart wallets. +import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; +import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; + +// Target +import { Account as SimpleAccount } from "contracts/smart-wallet/non-upgradeable/Account.sol"; +import { DynamicAccountFactory, DynamicAccount } from "contracts/smart-wallet/dynamic/DynamicAccountFactory.sol"; + +/// @dev This is a dummy contract to test contract interactions with Account. +contract Number { + uint256 public num; + + function setNum(uint256 _num) public { + num = _num; + } + + function doubleNum() public { + num *= 2; + } + + function incrementNum() public { + num += 1; + } +} + +contract NFTRejector { + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual returns (bytes4) { + revert("NFTs not accepted"); + } +} + +contract DynamicAccountTest is BaseTest { + // Target contracts + EntryPoint private entrypoint; + DynamicAccountFactory private accountFactory; + + // Mocks + Number internal numberContract; + + // Test params + uint256 private accountAdminPKey = 100; + address private accountAdmin; + + uint256 private accountSignerPKey = 200; + address private accountSigner; + + uint256 private nonSignerPKey = 300; + address private nonSigner; + + bytes internal data = bytes(""); + + // UserOp terminology: `sender` is the smart wallet. + address private sender = 0xbC12AEae5E1b1a80401dd20A6728f7a01a3A6166; + address payable private beneficiary = payable(address(0x45654)); + + bytes32 private uidCache = bytes32("random uid"); + + event AccountCreated(address indexed account, address indexed accountAdmin); + + function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) + internal + view + returns (bytes memory signature) + { + bytes32 typehashSignerPermissionRequest = keccak256( + "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" + ); + bytes32 nameHash = keccak256(bytes("Account")); + bytes32 versionHash = keccak256(bytes("1")); + bytes32 typehashEip712 = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); + + bytes memory encodedRequest = abi.encode( + typehashSignerPermissionRequest, + _req.signer, + keccak256(abi.encodePacked(_req.approvedTargets)), + _req.nativeTokenLimitPerTransaction, + _req.permissionStartTimestamp, + _req.permissionEndTimestamp, + _req.reqValidityStartTimestamp, + _req.reqValidityEndTimestamp, + _req.uid + ); + bytes32 structHash = keccak256(encodedRequest); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); + signature = abi.encodePacked(r, s, v); + } + + function _setupUserOp( + uint256 _signerPKey, + bytes memory _initCode, + bytes memory _callDataForEntrypoint + ) internal returns (UserOperation[] memory ops) { + uint256 nonce = entrypoint.getNonce(sender, 0); + + // Get user op fields + UserOperation memory op = UserOperation({ + sender: sender, + nonce: nonce, + initCode: _initCode, + callData: _callDataForEntrypoint, + callGasLimit: 500_000, + verificationGasLimit: 500_000, + preVerificationGas: 500_000, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + // Sign UserOp + bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); + bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); + bytes memory userOpSignature = abi.encodePacked(r, s, v); + + address recoveredSigner = ECDSA.recover(msgHash, v, r, s); + address expectedSigner = vm.addr(_signerPKey); + assertEq(recoveredSigner, expectedSigner); + + op.signature = userOpSignature; + + // Store UserOp + ops = new UserOperation[](1); + ops[0] = op; + } + + function _setupUserOpExecute( + uint256 _signerPKey, + bytes memory _initCode, + address _target, + uint256 _value, + bytes memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "execute(address,uint256,bytes)", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function _setupUserOpExecuteBatch( + uint256 _signerPKey, + bytes memory _initCode, + address[] memory _target, + uint256[] memory _value, + bytes[] memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "executeBatch(address[],uint256[],bytes[])", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function setUp() public override { + super.setUp(); + + // Setup signers. + accountAdmin = vm.addr(accountAdminPKey); + vm.deal(accountAdmin, 100 ether); + + accountSigner = vm.addr(accountSignerPKey); + nonSigner = vm.addr(nonSignerPKey); + + // Setup contracts + entrypoint = new EntryPoint(); + + // Setting up default extension. + IExtension.Extension memory defaultExtension; + + defaultExtension.metadata = IExtension.ExtensionMetadata({ + name: "AccountExtension", + metadataURI: "ipfs://AccountExtension", + implementation: address(new AccountExtension(address(entrypoint))) + }); + + defaultExtension.functions = new IExtension.ExtensionFunction[](6); + + defaultExtension.functions[0] = IExtension.ExtensionFunction( + AccountExtension.supportsInterface.selector, + "supportsInterface(bytes4)" + ); + defaultExtension.functions[1] = IExtension.ExtensionFunction( + AccountExtension.execute.selector, + "execute(address,uint256,bytes)" + ); + defaultExtension.functions[2] = IExtension.ExtensionFunction( + AccountExtension.executeBatch.selector, + "executeBatch(address[],uint256[],bytes[])" + ); + defaultExtension.functions[3] = IExtension.ExtensionFunction( + ERC721Holder.onERC721Received.selector, + "onERC721Received(address,address,uint256,bytes)" + ); + defaultExtension.functions[4] = IExtension.ExtensionFunction( + ERC1155Holder.onERC1155Received.selector, + "onERC1155Received(address,address,uint256,uint256,bytes)" + ); + defaultExtension.functions[5] = IExtension.ExtensionFunction( + bytes4(0), // Selector for `receive()` function. + "receive()" + ); + + IExtension.Extension[] memory extensions = new IExtension.Extension[](1); + extensions[0] = defaultExtension; + + // deploy account factory + accountFactory = new DynamicAccountFactory(IEntryPoint(payable(address(entrypoint))), extensions); + // deploy dummy contract + numberContract = new Number(); + } + + /*/////////////////////////////////////////////////////////////// + Test: creating an account + //////////////////////////////////////////////////////////////*/ + + /// @dev Create an account by directly calling the factory. + function test_state_createAccount_viaFactory() public { + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + accountFactory.createAccount(accountAdmin, data); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /// @dev Create an account via Entrypoint. + function test_state_createAccount_viaEntrypoint() public { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /*/////////////////////////////////////////////////////////////// + Test: performing a contract call + //////////////////////////////////////////////////////////////*/ + + function _setup_executeTransaction() internal { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + } + + /// @dev Perform a state changing transaction directly via account. + function test_state_executeTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch directly via account. + function test_state_executeBatchTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).executeBatch(targets, values, callData); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint. + function test_state_executeTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountAdminPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountAdminPKey, + bytes(""), + targets, + values, + callData + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountSignerPKey, + bytes(""), + targets, + values, + callData + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. + function test_state_executeTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. + function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + vm.expectRevert(); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + } + + /// @dev Revert: non-admin performs a state changing transaction directly via account contract. + function test_revert_executeTransaction_nonSigner_viaDirectCall() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + assertEq(numberContract.num(), 0); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); - -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); - -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); - -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountSignerPKey, -// bytes(""), -// targets, -// values, -// callData -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. -// function test_state_executeTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); - -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); - -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. -// function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { -// _setup_executeTransaction(); + vm.prank(accountSigner); + vm.expectRevert("Account: not admin or EntryPoint."); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + } -// assertEq(numberContract.num(), 0); + /*/////////////////////////////////////////////////////////////// + Test: receiving and sending native tokens + //////////////////////////////////////////////////////////////*/ -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); + /// @dev Send native tokens to an account. + function test_state_accountReceivesNativeTokens() public { + _setup_executeTransaction(); -// vm.expectRevert(); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// } - -// /// @dev Revert: non-admin performs a state changing transaction directly via account contract. -// function test_revert_executeTransaction_nonSigner_viaDirectCall() public { -// _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + assertEq(address(account).balance, 0); -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory ret) = payable(account).call{ value: 1000 }(""); -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); + // Silence warning: Return value of low-level calls not used. + (success, ret) = (success, ret); -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); -// assertEq(numberContract.num(), 0); + assertEq(address(account).balance, 1000); + } -// vm.prank(accountSigner); -// vm.expectRevert("Account: not admin or EntryPoint."); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); -// } + /// @dev Transfer native tokens out of an account. + function test_state_transferOutsNativeTokens() public { + _setup_executeTransaction(); -// /*/////////////////////////////////////////////////////////////// -// Test: receiving and sending native tokens -// //////////////////////////////////////////////////////////////*/ + uint256 value = 1000; -// /// @dev Send native tokens to an account. -// function test_state_accountReceivesNativeTokens() public { -// _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory ret) = payable(account).call{ value: value }(""); + assertEq(address(account).balance, value); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + // Silence warning: Return value of low-level calls not used. + (success, ret) = (success, ret); -// assertEq(address(account).balance, 0); + address recipient = address(0x3456); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory ret) = payable(account).call{ value: 1000 }(""); + UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); -// // Silence warning: Return value of low-level calls not used. -// (success, ret) = (success, ret); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + assertEq(address(account).balance, 0); + assertEq(recipient.balance, value); + } -// assertEq(address(account).balance, 1000); -// } + /// @dev Add and remove a deposit for the account from the Entrypoint. -// /// @dev Transfer native tokens out of an account. -// function test_state_transferOutsNativeTokens() public { -// _setup_executeTransaction(); + function test_state_addAndWithdrawDeposit() public { + _setup_executeTransaction(); -// uint256 value = 1000; + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory ret) = payable(account).call{ value: value }(""); -// assertEq(address(account).balance, value); + assertEq(SimpleAccount(payable(account)).getDeposit(), 0); -// // Silence warning: Return value of low-level calls not used. -// (success, ret) = (success, ret); + vm.prank(accountAdmin); + SimpleAccount(payable(account)).addDeposit{ value: 1000 }(); + assertEq(SimpleAccount(payable(account)).getDeposit(), 1000); -// address recipient = address(0x3456); + vm.prank(accountAdmin); + SimpleAccount(payable(account)).withdrawDepositTo(payable(accountSigner), 500); + assertEq(SimpleAccount(payable(account)).getDeposit(), 500); + } -// UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); + /*/////////////////////////////////////////////////////////////// + Test: receiving ERC-721 and ERC-1155 NFTs + //////////////////////////////////////////////////////////////*/ -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// assertEq(address(account).balance, 0); -// assertEq(recipient.balance, value); -// } + /// @dev Send an ERC-721 NFT to an account. + function test_state_receiveERC721NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// /// @dev Add and remove a deposit for the account from the Entrypoint. + assertEq(erc721.balanceOf(account), 0); -// function test_state_addAndWithdrawDeposit() public { -// _setup_executeTransaction(); + erc721.mint(account, 1); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + assertEq(erc721.balanceOf(account), 1); + } -// assertEq(Account(payable(account)).getDeposit(), 0); + /// @dev Send an ERC-1155 NFT to an account. + function test_state_receiveERC1155NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// Account(payable(account)).addDeposit{ value: 1000 }(); -// assertEq(Account(payable(account)).getDeposit(), 1000); + assertEq(erc1155.balanceOf(account, 0), 0); -// vm.prank(accountAdmin); -// Account(payable(account)).withdrawDepositTo(payable(accountSigner), 500); -// assertEq(Account(payable(account)).getDeposit(), 500); -// } + erc1155.mint(account, 0, 1); -// /*/////////////////////////////////////////////////////////////// -// Test: receiving ERC-721 and ERC-1155 NFTs -// //////////////////////////////////////////////////////////////*/ + assertEq(erc1155.balanceOf(account, 0), 1); + } -// /// @dev Send an ERC-721 NFT to an account. -// function test_state_receiveERC721NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + /*/////////////////////////////////////////////////////////////// + Test: change an extension on the account + //////////////////////////////////////////////////////////////*/ -// assertEq(erc721.balanceOf(account), 0); + /// @dev Make the account reject ERC-721 NFTs instead of accepting them. + function test_scenario_changeExtensionForFunction() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// erc721.mint(account, 1); + // The account can initially receive NFTs. + assertEq(erc721.balanceOf(account), 0); + erc721.mint(account, 1); + assertEq(erc721.balanceOf(account), 1); -// assertEq(erc721.balanceOf(account), 1); -// } + // Make the account reject ERC-721 NFTs going forward. + IExtension.Extension memory extension; -// /// @dev Send an ERC-1155 NFT to an account. -// function test_state_receiveERC1155NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + extension.metadata = IExtension.ExtensionMetadata({ + name: "NFTRejector", + metadataURI: "ipfs://NFTRejector", + implementation: address(new NFTRejector()) + }); -// assertEq(erc1155.balanceOf(account, 0), 0); + extension.functions = new IExtension.ExtensionFunction[](1); -// erc1155.mint(account, 0, 1); + extension.functions[0] = IExtension.ExtensionFunction( + NFTRejector.onERC721Received.selector, + "onERC721Received(address,address,uint256,bytes)" + ); -// assertEq(erc1155.balanceOf(account, 0), 1); -// } + vm.prank(accountAdmin); + DynamicAccount(payable(account)).addExtension(extension); -// /*/////////////////////////////////////////////////////////////// -// Test: change an extension on the account -// //////////////////////////////////////////////////////////////*/ - -// /// @dev Make the account reject ERC-721 NFTs instead of accepting them. -// function test_scenario_changeExtensionForFunction() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// // The account can initially receive NFTs. -// assertEq(erc721.balanceOf(account), 0); -// erc721.mint(account, 1); -// assertEq(erc721.balanceOf(account), 1); - -// // Make the account reject ERC-721 NFTs going forward. -// IExtension.Extension memory extension; - -// extension.metadata = IExtension.ExtensionMetadata({ -// name: "NFTRejector", -// metadataURI: "ipfs://NFTRejector", -// implementation: address(new NFTRejector()) -// }); - -// extension.functions = new IExtension.ExtensionFunction[](1); - -// extension.functions[0] = IExtension.ExtensionFunction( -// NFTRejector.onERC721Received.selector, -// "onERC721Received(address,address,uint256,bytes)" -// ); - -// vm.prank(accountAdmin); -// DynamicAccount(payable(account)).addExtension(extension); - -// // Transfer NFTs to the account -// erc721.mint(accountSigner, 1); -// assertEq(erc721.ownerOf(1), accountSigner); -// vm.prank(accountSigner); -// vm.expectRevert("NFTs not accepted"); -// erc721.safeTransferFrom(accountSigner, account, 1); -// } -// } + // Transfer NFTs to the account + erc721.mint(accountSigner, 1); + assertEq(erc721.ownerOf(1), accountSigner); + vm.prank(accountSigner); + vm.expectRevert("NFTs not accepted"); + erc721.safeTransferFrom(accountSigner, account, 1); + } +} diff --git a/src/test/smart-wallet/ManagedAccount.t.sol b/src/test/smart-wallet/ManagedAccount.t.sol index b68c2924e..6c3917d16 100644 --- a/src/test/smart-wallet/ManagedAccount.t.sol +++ b/src/test/smart-wallet/ManagedAccount.t.sol @@ -1,652 +1,664 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -// // Test utils -// import "../utils/BaseTest.sol"; -// import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol"; -// import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; -// import { AccountPermissions } from "contracts/dynamic-contracts/extension/AccountPermissions.sol"; -// import { AccountExtension } from "contracts/smart-wallet/utils/AccountExtension.sol"; - -// // Account Abstraction setup for smart wallets. -// import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; -// import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; - -// // Target -// import { Account } from "contracts/smart-wallet/non-upgradeable/Account.sol"; -// import { ManagedAccountFactory, ManagedAccount } from "contracts/smart-wallet/managed/ManagedAccountFactory.sol"; - -// /// @dev This is a dummy contract to test contract interactions with Account. -// contract Number { -// uint256 public num; - -// function setNum(uint256 _num) public { -// num = _num; -// } - -// function doubleNum() public { -// num *= 2; -// } - -// function incrementNum() public { -// num += 1; -// } -// } - -// contract NFTRejector { -// function onERC721Received( -// address, -// address, -// uint256, -// bytes memory -// ) public virtual returns (bytes4) { -// revert("NFTs not accepted"); -// } -// } - -// contract ManagedAccountTest is BaseTest { -// // Target contracts -// EntryPoint private entrypoint; -// ManagedAccountFactory private accountFactory; - -// // Mocks -// Number internal numberContract; - -// // Test params -// address private factoryDeployer = address(0x9876); -// bytes internal data = bytes(""); - -// uint256 private accountAdminPKey = 100; -// address private accountAdmin; - -// uint256 private accountSignerPKey = 200; -// address private accountSigner; - -// uint256 private nonSignerPKey = 300; -// address private nonSigner; - -// // UserOp terminology: `sender` is the smart wallet. -// address private sender = 0x56C860085A6A0AEd5137b17a185160865bf6a75A; -// address payable private beneficiary = payable(address(0x45654)); - -// bytes32 private uidCache = bytes32("random uid"); - -// event AccountCreated(address indexed account, address indexed accountAdmin); - -// function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) -// internal -// view -// returns (bytes memory signature) -// { -// bytes32 typehashSignerPermissionRequest = keccak256( -// "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" -// ); -// bytes32 nameHash = keccak256(bytes("Account")); -// bytes32 versionHash = keccak256(bytes("1")); -// bytes32 typehashEip712 = keccak256( -// "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" -// ); -// bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); - -// bytes memory encodedRequest = abi.encode( -// typehashSignerPermissionRequest, -// _req.signer, -// keccak256(abi.encodePacked(_req.approvedTargets)), -// _req.nativeTokenLimitPerTransaction, -// _req.permissionStartTimestamp, -// _req.permissionEndTimestamp, -// _req.reqValidityStartTimestamp, -// _req.reqValidityEndTimestamp, -// _req.uid -// ); -// bytes32 structHash = keccak256(encodedRequest); -// bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); -// signature = abi.encodePacked(r, s, v); -// } - -// function _setupUserOp( -// uint256 _signerPKey, -// bytes memory _initCode, -// bytes memory _callDataForEntrypoint -// ) internal returns (UserOperation[] memory ops) { -// uint256 nonce = entrypoint.getNonce(sender, 0); - -// // Get user op fields -// UserOperation memory op = UserOperation({ -// sender: sender, -// nonce: nonce, -// initCode: _initCode, -// callData: _callDataForEntrypoint, -// callGasLimit: 500_000, -// verificationGasLimit: 500_000, -// preVerificationGas: 500_000, -// maxFeePerGas: 0, -// maxPriorityFeePerGas: 0, -// paymasterAndData: bytes(""), -// signature: bytes("") -// }); - -// // Sign UserOp -// bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); -// bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); - -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); -// bytes memory userOpSignature = abi.encodePacked(r, s, v); - -// address recoveredSigner = ECDSA.recover(msgHash, v, r, s); -// address expectedSigner = vm.addr(_signerPKey); -// assertEq(recoveredSigner, expectedSigner); - -// op.signature = userOpSignature; - -// // Store UserOp -// ops = new UserOperation[](1); -// ops[0] = op; -// } - -// function _setupUserOpExecute( -// uint256 _signerPKey, -// bytes memory _initCode, -// address _target, -// uint256 _value, -// bytes memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "execute(address,uint256,bytes)", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function _setupUserOpExecuteBatch( -// uint256 _signerPKey, -// bytes memory _initCode, -// address[] memory _target, -// uint256[] memory _value, -// bytes[] memory _callData -// ) internal returns (UserOperation[] memory) { -// bytes memory callDataForEntrypoint = abi.encodeWithSignature( -// "executeBatch(address[],uint256[],bytes[])", -// _target, -// _value, -// _callData -// ); - -// return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); -// } - -// function setUp() public override { -// super.setUp(); - -// // Setup signers. -// accountAdmin = vm.addr(accountAdminPKey); -// vm.deal(accountAdmin, 100 ether); - -// accountSigner = vm.addr(accountSignerPKey); -// nonSigner = vm.addr(nonSignerPKey); - -// // Setup contracts -// entrypoint = new EntryPoint(); - -// // Setting up default extension. -// IExtension.Extension memory defaultExtension; - -// defaultExtension.metadata = IExtension.ExtensionMetadata({ -// name: "AccountExtension", -// metadataURI: "ipfs://AccountExtension", -// implementation: address(new AccountExtension(address(entrypoint))) -// }); - -// defaultExtension.functions = new IExtension.ExtensionFunction[](5); - -// defaultExtension.functions[0] = IExtension.ExtensionFunction( -// AccountExtension.supportsInterface.selector, -// "supportsInterface(bytes4)" -// ); -// defaultExtension.functions[1] = IExtension.ExtensionFunction( -// AccountExtension.execute.selector, -// "execute(address,uint256,bytes)" -// ); -// defaultExtension.functions[2] = IExtension.ExtensionFunction( -// AccountExtension.executeBatch.selector, -// "executeBatch(address[],uint256[],bytes[])" -// ); -// defaultExtension.functions[3] = IExtension.ExtensionFunction( -// ERC721Holder.onERC721Received.selector, -// "onERC721Received(address,address,uint256,bytes)" -// ); -// defaultExtension.functions[4] = IExtension.ExtensionFunction( -// ERC1155Holder.onERC1155Received.selector, -// "onERC1155Received(address,address,uint256,uint256,bytes)" -// ); - -// IExtension.Extension[] memory extensions = new IExtension.Extension[](1); -// extensions[0] = defaultExtension; - -// // deploy account factory -// vm.prank(factoryDeployer); -// accountFactory = new ManagedAccountFactory(IEntryPoint(payable(address(entrypoint))), extensions); -// // deploy dummy contract -// numberContract = new Number(); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: creating an account -// //////////////////////////////////////////////////////////////*/ - -// /// @dev Create an account by directly calling the factory. -// function test_state_createAccount_viaFactory() public { -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// accountFactory.createAccount(accountAdmin, data); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /// @dev Create an account via Entrypoint. -// function test_state_createAccount_viaEntrypoint() public { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// vm.expectEmit(true, true, false, true); -// emit AccountCreated(sender, accountAdmin); -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); - -// address[] memory allAccounts = accountFactory.getAllAccounts(); -// assertEq(allAccounts.length, 1); -// assertEq(allAccounts[0], sender); -// } - -// /*/////////////////////////////////////////////////////////////// -// Test: performing a contract call -// //////////////////////////////////////////////////////////////*/ - -// function _setup_executeTransaction() internal { -// bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); -// bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); - -// UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( -// accountAdminPKey, -// initCode, -// address(0), -// 0, -// bytes("") -// ); - -// EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); -// } - -// /// @dev Perform a state changing transaction directly via account. -// function test_state_executeTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// vm.prank(accountAdmin); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch directly via account. -// function test_state_executeBatchTransaction() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// vm.prank(accountAdmin); -// Account(payable(account)).executeBatch(targets, values, callData); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint. -// function test_state_executeTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountAdminPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); +// Test utils +import "../utils/BaseTest.sol"; +import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol"; +import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol"; +import { AccountPermissions } from "contracts/dynamic-contracts/extension/AccountPermissions.sol"; +import { AccountExtension } from "contracts/smart-wallet/utils/AccountExtension.sol"; + +// Account Abstraction setup for smart wallets. +import { EntryPoint, IEntryPoint } from "contracts/smart-wallet/utils/Entrypoint.sol"; +import { UserOperation } from "contracts/smart-wallet/utils/UserOperation.sol"; + +// Target +import { Account as SimpleAccount } from "contracts/smart-wallet/non-upgradeable/Account.sol"; +import { ManagedAccountFactory, ManagedAccount } from "contracts/smart-wallet/managed/ManagedAccountFactory.sol"; + +/// @dev This is a dummy contract to test contract interactions with Account. +contract Number { + uint256 public num; + + function setNum(uint256 _num) public { + num = _num; + } + + function doubleNum() public { + num *= 2; + } + + function incrementNum() public { + num += 1; + } +} + +contract NFTRejector { + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual returns (bytes4) { + revert("NFTs not accepted"); + } +} + +contract ManagedAccountTest is BaseTest { + // Target contracts + EntryPoint private entrypoint; + ManagedAccountFactory private accountFactory; + + // Mocks + Number internal numberContract; + + // Test params + address private factoryDeployer = address(0x9876); + bytes internal data = bytes(""); + + uint256 private accountAdminPKey = 100; + address private accountAdmin; + + uint256 private accountSignerPKey = 200; + address private accountSigner; + + uint256 private nonSignerPKey = 300; + address private nonSigner; + + // UserOp terminology: `sender` is the smart wallet. + address private sender = 0x56C860085A6A0AEd5137b17a185160865bf6a75A; + address payable private beneficiary = payable(address(0x45654)); + + bytes32 private uidCache = bytes32("random uid"); + + event AccountCreated(address indexed account, address indexed accountAdmin); + + function _signSignerPermissionRequest(IAccountPermissions.SignerPermissionRequest memory _req) + internal + view + returns (bytes memory signature) + { + bytes32 typehashSignerPermissionRequest = keccak256( + "SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)" + ); + bytes32 nameHash = keccak256(bytes("Account")); + bytes32 versionHash = keccak256(bytes("1")); + bytes32 typehashEip712 = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + bytes32 domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, sender)); + + bytes memory encodedRequest = abi.encode( + typehashSignerPermissionRequest, + _req.signer, + keccak256(abi.encodePacked(_req.approvedTargets)), + _req.nativeTokenLimitPerTransaction, + _req.permissionStartTimestamp, + _req.permissionEndTimestamp, + _req.reqValidityStartTimestamp, + _req.reqValidityEndTimestamp, + _req.uid + ); + bytes32 structHash = keccak256(encodedRequest); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(accountAdminPKey, typedDataHash); + signature = abi.encodePacked(r, s, v); + } + + function _setupUserOp( + uint256 _signerPKey, + bytes memory _initCode, + bytes memory _callDataForEntrypoint + ) internal returns (UserOperation[] memory ops) { + uint256 nonce = entrypoint.getNonce(sender, 0); + + // Get user op fields + UserOperation memory op = UserOperation({ + sender: sender, + nonce: nonce, + initCode: _initCode, + callData: _callDataForEntrypoint, + callGasLimit: 500_000, + verificationGasLimit: 500_000, + preVerificationGas: 500_000, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + // Sign UserOp + bytes32 opHash = EntryPoint(entrypoint).getUserOpHash(op); + bytes32 msgHash = ECDSA.toEthSignedMessageHash(opHash); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPKey, msgHash); + bytes memory userOpSignature = abi.encodePacked(r, s, v); + + address recoveredSigner = ECDSA.recover(msgHash, v, r, s); + address expectedSigner = vm.addr(_signerPKey); + assertEq(recoveredSigner, expectedSigner); + + op.signature = userOpSignature; + + // Store UserOp + ops = new UserOperation[](1); + ops[0] = op; + } + + function _setupUserOpExecute( + uint256 _signerPKey, + bytes memory _initCode, + address _target, + uint256 _value, + bytes memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "execute(address,uint256,bytes)", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function _setupUserOpExecuteBatch( + uint256 _signerPKey, + bytes memory _initCode, + address[] memory _target, + uint256[] memory _value, + bytes[] memory _callData + ) internal returns (UserOperation[] memory) { + bytes memory callDataForEntrypoint = abi.encodeWithSignature( + "executeBatch(address[],uint256[],bytes[])", + _target, + _value, + _callData + ); + + return _setupUserOp(_signerPKey, _initCode, callDataForEntrypoint); + } + + function setUp() public override { + super.setUp(); + + // Setup signers. + accountAdmin = vm.addr(accountAdminPKey); + vm.deal(accountAdmin, 100 ether); + + accountSigner = vm.addr(accountSignerPKey); + nonSigner = vm.addr(nonSignerPKey); + + // Setup contracts + entrypoint = new EntryPoint(); + + // Setting up default extension. + IExtension.Extension memory defaultExtension; + + defaultExtension.metadata = IExtension.ExtensionMetadata({ + name: "AccountExtension", + metadataURI: "ipfs://AccountExtension", + implementation: address(new AccountExtension(address(entrypoint))) + }); + + defaultExtension.functions = new IExtension.ExtensionFunction[](6); + + defaultExtension.functions[0] = IExtension.ExtensionFunction( + AccountExtension.supportsInterface.selector, + "supportsInterface(bytes4)" + ); + defaultExtension.functions[1] = IExtension.ExtensionFunction( + AccountExtension.execute.selector, + "execute(address,uint256,bytes)" + ); + defaultExtension.functions[2] = IExtension.ExtensionFunction( + AccountExtension.executeBatch.selector, + "executeBatch(address[],uint256[],bytes[])" + ); + defaultExtension.functions[3] = IExtension.ExtensionFunction( + ERC721Holder.onERC721Received.selector, + "onERC721Received(address,address,uint256,bytes)" + ); + defaultExtension.functions[4] = IExtension.ExtensionFunction( + ERC1155Holder.onERC1155Received.selector, + "onERC1155Received(address,address,uint256,uint256,bytes)" + ); + defaultExtension.functions[5] = IExtension.ExtensionFunction( + bytes4(0), // Selector for `receive()` function. + "receive()" + ); + + IExtension.Extension[] memory extensions = new IExtension.Extension[](1); + extensions[0] = defaultExtension; + + // deploy account factory + vm.prank(factoryDeployer); + accountFactory = new ManagedAccountFactory(IEntryPoint(payable(address(entrypoint))), extensions); + // deploy dummy contract + numberContract = new Number(); + } + + /*/////////////////////////////////////////////////////////////// + Test: creating an account + //////////////////////////////////////////////////////////////*/ + + /// @dev Create an account by directly calling the factory. + function test_state_createAccount_viaFactory() public { + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + accountFactory.createAccount(accountAdmin, data); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /// @dev Create an account via Entrypoint. + function test_state_createAccount_viaEntrypoint() public { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + vm.expectEmit(true, true, false, true); + emit AccountCreated(sender, accountAdmin); + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + + address[] memory allAccounts = accountFactory.getAllAccounts(); + assertEq(allAccounts.length, 1); + assertEq(allAccounts[0], sender); + } + + /*/////////////////////////////////////////////////////////////// + Test: performing a contract call + //////////////////////////////////////////////////////////////*/ + + function _setup_executeTransaction() internal { + bytes memory initCallData = abi.encodeWithSignature("createAccount(address,bytes)", accountAdmin, data); + bytes memory initCode = abi.encodePacked(abi.encodePacked(address(accountFactory)), initCallData); + + UserOperation[] memory userOpCreateAccount = _setupUserOpExecute( + accountAdminPKey, + initCode, + address(0), + 0, + bytes("") + ); + + EntryPoint(entrypoint).handleOps(userOpCreateAccount, beneficiary); + } + + /// @dev Perform a state changing transaction directly via account. + function test_state_executeTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch directly via account. + function test_state_executeBatchTransaction() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + vm.prank(accountAdmin); + SimpleAccount(payable(account)).executeBatch(targets, values, callData); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint. + function test_state_executeTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountAdminPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountAdminPKey, + bytes(""), + targets, + values, + callData + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), count); + } + + /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. + function test_state_executeTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + + assertEq(numberContract.num(), 42); + } + + /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. + function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + UserOperation[] memory userOp = _setupUserOpExecute( + accountSignerPKey, + bytes(""), + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + + vm.expectRevert(); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + } + + /// @dev Perform many state changing transactions in a batch via Entrypoint. + function test_state_executeBatchTransaction_viaAccountSigner() public { + _setup_executeTransaction(); + + assertEq(numberContract.num(), 0); + + uint256 count = 3; + address[] memory targets = new address[](count); + uint256[] memory values = new uint256[](count); + bytes[] memory callData = new bytes[](count); + + for (uint256 i = 0; i < count; i += 1) { + targets[i] = address(numberContract); + values[i] = 0; + callData[i] = abi.encodeWithSignature("incrementNum()", i); + } + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); + + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); + + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); + UserOperation[] memory userOp = _setupUserOpExecuteBatch( + accountSignerPKey, + bytes(""), + targets, + values, + callData + ); -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountAdminPKey, -// bytes(""), -// targets, -// values, -// callData -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), count); -// } - -// /// @dev Perform a state changing transaction via Entrypoint and a SIGNER_ROLE holder. -// function test_state_executeTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); + EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); - -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); - -// assertEq(numberContract.num(), 42); -// } - -// /// @dev Revert: perform a state changing transaction via Entrypoint without appropriate permissions. -// function test_revert_executeTransaction_nonSigner_viaEntrypoint() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// UserOperation[] memory userOp = _setupUserOpExecute( -// accountSignerPKey, -// bytes(""), -// address(numberContract), -// 0, -// abi.encodeWithSignature("setNum(uint256)", 42) -// ); - -// vm.expectRevert(); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// } - -// /// @dev Perform many state changing transactions in a batch via Entrypoint. -// function test_state_executeBatchTransaction_viaAccountSigner() public { -// _setup_executeTransaction(); - -// assertEq(numberContract.num(), 0); - -// uint256 count = 3; -// address[] memory targets = new address[](count); -// uint256[] memory values = new uint256[](count); -// bytes[] memory callData = new bytes[](count); - -// for (uint256 i = 0; i < count; i += 1) { -// targets[i] = address(numberContract); -// values[i] = 0; -// callData[i] = abi.encodeWithSignature("incrementNum()", i); -// } - -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); - -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); + assertEq(numberContract.num(), count); + } + + /// @dev Revert: non-admin performs a state changing transaction directly via account contract. + function test_revert_executeTransaction_nonSigner_viaDirectCall() public { + _setup_executeTransaction(); + + address account = accountFactory.getAddress(accountAdmin, bytes("")); + + address[] memory approvedTargets = new address[](1); + approvedTargets[0] = address(numberContract); -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); + IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( + accountSigner, + approvedTargets, + 1 ether, + 0, + type(uint128).max, + 0, + type(uint128).max, + uidCache + ); -// UserOperation[] memory userOp = _setupUserOpExecuteBatch( -// accountSignerPKey, -// bytes(""), -// targets, -// values, -// callData -// ); + vm.prank(accountAdmin); + bytes memory sig = _signSignerPermissionRequest(permissionsReq); + SimpleAccount(payable(account)).setPermissionsForSigner(permissionsReq, sig); -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); + assertEq(numberContract.num(), 0); -// assertEq(numberContract.num(), count); -// } + vm.prank(accountSigner); + vm.expectRevert("Account: not admin or EntryPoint."); + SimpleAccount(payable(account)).execute( + address(numberContract), + 0, + abi.encodeWithSignature("setNum(uint256)", 42) + ); + } -// /// @dev Revert: non-admin performs a state changing transaction directly via account contract. -// function test_revert_executeTransaction_nonSigner_viaDirectCall() public { -// _setup_executeTransaction(); + /*/////////////////////////////////////////////////////////////// + Test: receiving and sending native tokens + //////////////////////////////////////////////////////////////*/ -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + /// @dev Send native tokens to an account. + function test_state_accountReceivesNativeTokens() public { + _setup_executeTransaction(); -// address[] memory approvedTargets = new address[](1); -// approvedTargets[0] = address(numberContract); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// IAccountPermissions.SignerPermissionRequest memory permissionsReq = IAccountPermissions.SignerPermissionRequest( -// accountSigner, -// approvedTargets, -// 1 ether, -// 0, -// type(uint128).max, -// 0, -// type(uint128).max, -// uidCache -// ); + assertEq(address(account).balance, 0); -// vm.prank(accountAdmin); -// bytes memory sig = _signSignerPermissionRequest(permissionsReq); -// Account(payable(account)).setPermissionsForSigner(permissionsReq, sig); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory ret) = payable(account).call{ value: 1000 }(""); -// assertEq(numberContract.num(), 0); + assertEq(address(account).balance, 1000); -// vm.prank(accountSigner); -// vm.expectRevert("Account: not admin or EntryPoint."); -// Account(payable(account)).execute(address(numberContract), 0, abi.encodeWithSignature("setNum(uint256)", 42)); -// } + // Silence warning: Return value of low-level calls not used. + (success, ret) = (success, ret); + } -// /*/////////////////////////////////////////////////////////////// -// Test: receiving and sending native tokens -// //////////////////////////////////////////////////////////////*/ + /// @dev Transfer native tokens out of an account. + function test_state_transferOutsNativeTokens() public { + _setup_executeTransaction(); -// /// @dev Send native tokens to an account. -// function test_state_accountReceivesNativeTokens() public { -// _setup_executeTransaction(); + uint256 value = 1000; -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + address account = accountFactory.getAddress(accountAdmin, bytes("")); + vm.prank(accountAdmin); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory ret) = payable(account).call{ value: value }(""); + assertEq(address(account).balance, value); -// assertEq(address(account).balance, 0); + // Silence warning: Return value of low-level calls not used. + (success, ret) = (success, ret); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory ret) = payable(account).call{ value: 1000 }(""); + address recipient = address(0x3456); -// assertEq(address(account).balance, 1000); + UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); -// // Silence warning: Return value of low-level calls not used. -// (success, ret) = (success, ret); -// } + EntryPoint(entrypoint).handleOps(userOp, beneficiary); + assertEq(address(account).balance, 0); + assertEq(recipient.balance, value); + } -// /// @dev Transfer native tokens out of an account. -// function test_state_transferOutsNativeTokens() public { -// _setup_executeTransaction(); + /// @dev Add and remove a deposit for the account from the Entrypoint. -// uint256 value = 1000; + function test_state_addAndWithdrawDeposit() public { + _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// // solhint-disable-next-line avoid-low-level-calls -// (bool success, bytes memory ret) = payable(account).call{ value: value }(""); -// assertEq(address(account).balance, value); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// // Silence warning: Return value of low-level calls not used. -// (success, ret) = (success, ret); + assertEq(SimpleAccount(payable(account)).getDeposit(), 0); -// address recipient = address(0x3456); + vm.prank(accountAdmin); + SimpleAccount(payable(account)).addDeposit{ value: 1000 }(); + assertEq(SimpleAccount(payable(account)).getDeposit(), 1000); -// UserOperation[] memory userOp = _setupUserOpExecute(accountAdminPKey, bytes(""), recipient, value, bytes("")); + vm.prank(accountAdmin); + SimpleAccount(payable(account)).withdrawDepositTo(payable(accountSigner), 500); + assertEq(SimpleAccount(payable(account)).getDeposit(), 500); + } -// EntryPoint(entrypoint).handleOps(userOp, beneficiary); -// assertEq(address(account).balance, 0); -// assertEq(recipient.balance, value); -// } + /*/////////////////////////////////////////////////////////////// + Test: receiving ERC-721 and ERC-1155 NFTs + //////////////////////////////////////////////////////////////*/ -// /// @dev Add and remove a deposit for the account from the Entrypoint. + /// @dev Send an ERC-721 NFT to an account. + function test_state_receiveERC721NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// function test_state_addAndWithdrawDeposit() public { -// _setup_executeTransaction(); + assertEq(erc721.balanceOf(account), 0); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + erc721.mint(account, 1); -// assertEq(Account(payable(account)).getDeposit(), 0); + assertEq(erc721.balanceOf(account), 1); + } -// vm.prank(accountAdmin); -// Account(payable(account)).addDeposit{ value: 1000 }(); -// assertEq(Account(payable(account)).getDeposit(), 1000); + /// @dev Send an ERC-1155 NFT to an account. + function test_state_receiveERC1155NFT() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// vm.prank(accountAdmin); -// Account(payable(account)).withdrawDepositTo(payable(accountSigner), 500); -// assertEq(Account(payable(account)).getDeposit(), 500); -// } + assertEq(erc1155.balanceOf(account, 0), 0); -// /*/////////////////////////////////////////////////////////////// -// Test: receiving ERC-721 and ERC-1155 NFTs -// //////////////////////////////////////////////////////////////*/ + erc1155.mint(account, 0, 1); -// /// @dev Send an ERC-721 NFT to an account. -// function test_state_receiveERC721NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + assertEq(erc1155.balanceOf(account, 0), 1); + } -// assertEq(erc721.balanceOf(account), 0); + /*/////////////////////////////////////////////////////////////// + Test: change an extension on the account + //////////////////////////////////////////////////////////////*/ -// erc721.mint(account, 1); + /// @dev Make the account reject ERC-721 NFTs instead of accepting them. + function test_scenario_changeExtensionForFunction() public { + _setup_executeTransaction(); + address account = accountFactory.getAddress(accountAdmin, bytes("")); -// assertEq(erc721.balanceOf(account), 1); -// } + // The account can initially receive NFTs. + assertEq(erc721.balanceOf(account), 0); + erc721.mint(account, 1); + assertEq(erc721.balanceOf(account), 1); -// /// @dev Send an ERC-1155 NFT to an account. -// function test_state_receiveERC1155NFT() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); + // Make the account reject ERC-721 NFTs going forward. + IExtension.Extension memory extension; -// assertEq(erc1155.balanceOf(account, 0), 0); + extension.metadata = IExtension.ExtensionMetadata({ + name: "NFTRejector", + metadataURI: "ipfs://NFTRejector", + implementation: address(new NFTRejector()) + }); -// erc1155.mint(account, 0, 1); + extension.functions = new IExtension.ExtensionFunction[](1); -// assertEq(erc1155.balanceOf(account, 0), 1); -// } + extension.functions[0] = IExtension.ExtensionFunction( + NFTRejector.onERC721Received.selector, + "onERC721Received(address,address,uint256,bytes)" + ); -// /*/////////////////////////////////////////////////////////////// -// Test: change an extension on the account -// //////////////////////////////////////////////////////////////*/ + vm.prank(factoryDeployer); + accountFactory.addExtension(extension); -// /// @dev Make the account reject ERC-721 NFTs instead of accepting them. -// function test_scenario_changeExtensionForFunction() public { -// _setup_executeTransaction(); -// address account = accountFactory.getAddress(accountAdmin, bytes("")); - -// // The account can initially receive NFTs. -// assertEq(erc721.balanceOf(account), 0); -// erc721.mint(account, 1); -// assertEq(erc721.balanceOf(account), 1); - -// // Make the account reject ERC-721 NFTs going forward. -// IExtension.Extension memory extension; - -// extension.metadata = IExtension.ExtensionMetadata({ -// name: "NFTRejector", -// metadataURI: "ipfs://NFTRejector", -// implementation: address(new NFTRejector()) -// }); - -// extension.functions = new IExtension.ExtensionFunction[](1); - -// extension.functions[0] = IExtension.ExtensionFunction( -// NFTRejector.onERC721Received.selector, -// "onERC721Received(address,address,uint256,bytes)" -// ); - -// vm.prank(factoryDeployer); -// accountFactory.addExtension(extension); - -// // Transfer NFTs to the account -// erc721.mint(accountSigner, 1); -// assertEq(erc721.ownerOf(1), accountSigner); -// vm.prank(accountSigner); -// vm.expectRevert("NFTs not accepted"); -// erc721.safeTransferFrom(accountSigner, account, 1); -// } -// } + // Transfer NFTs to the account + erc721.mint(accountSigner, 1); + assertEq(erc721.ownerOf(1), accountSigner); + vm.prank(accountSigner); + vm.expectRevert("NFTs not accepted"); + erc721.safeTransferFrom(accountSigner, account, 1); + } +} diff --git a/yarn.lock b/yarn.lock index 2530c1b73..d98b0d9c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1178,10 +1178,10 @@ resolved "https://registry.yarnpkg.com/@thirdweb-dev/contracts/-/contracts-3.6.0.tgz#f85cf5468f387fa436fef826e07a6b6c7ea29279" integrity sha512-WPKyTb4Pqz8WZpQQ2RZEboIQIiki0YZruJBOvW20VfTZIt5rGUT2g9TkF6ca9O/SCdd1FM6ziQ7k0SV12QSxwg== -"@thirdweb-dev/dynamic-contracts@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@thirdweb-dev/dynamic-contracts/-/dynamic-contracts-1.1.2.tgz#9a7ff4d9e7f5f9f4eac96e21b0eabdd52c95f3ef" - integrity sha512-ZTVpWPZcF8BDMHYFfpLo3vjrVR0QgQe1AUTd+1uq/gtdYG/DB8dZBCfmR4wnOnkUQDvLLtIOc4PqpqyccVL2vQ== +"@thirdweb-dev/dynamic-contracts@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@thirdweb-dev/dynamic-contracts/-/dynamic-contracts-1.1.3.tgz#74d12b2d3e83685cecfeb8af08252d98b66f148d" + integrity sha512-lQjFYsPUrdCvbkiOMPdmNlY+h6W/3Our1ukcnVClMisd4yP+G/uFCXQOSzbFVLtxrPD8cUppI/sqW6KWqvmVnw== "@thirdweb-dev/generated-abis@0.0.1": version "0.0.1"