From 92149cc5769f5ebbe8d978d25403b3f1e703b7e5 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 30 Jul 2024 15:08:20 -0400 Subject: [PATCH 1/2] feat(create): add performCreate[2] method. --- src/account/UpgradeableModularAccount.sol | 47 ++++++++++++++++++++ test/account/UpgradeableModularAccount.t.sol | 31 +++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index fd336d32..2beb83bf 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -105,6 +105,7 @@ contract UpgradeableModularAccount is error UnrecognizedFunction(bytes4 selector); error UserOpNotFromEntryPoint(); error UserOpValidationFunctionMissing(bytes4 selector); + error CreateFailed(); constructor(IEntryPoint anEntryPoint) { _ENTRY_POINT = anEntryPoint; @@ -376,6 +377,52 @@ contract UpgradeableModularAccount is _postNativeFunction(postExecHooks, postHookArgs); } + /// + /// @param value The value to send to the new contract constructor + /// @param initCode The initCode to deploy. + function performCreate(uint256 value, bytes calldata initCode) + external + payable + virtual + returns (address createdAddr) + { + assembly ("memory-safe") { + let fmp := mload(0x40) + let len := initCode.length + calldatacopy(fmp, initCode.offset, len) + + createdAddr := create(value, fmp, len) + + if iszero(createdAddr) { + mstore(0x00, 0x7e16b8cd) + revert(0x1c, 0x04) + } + } + } + + /// + /// @param value The value to send to the new contract constructor. + /// @param initCode The initCode to deploy. + /// @param salt The salt to use for the create2 operation. + function performCreate2(uint256 value, bytes calldata initCode, bytes32 salt) + external + payable + virtual + returns (address createdAddr) + { + assembly ("memory-safe") { + let fmp := mload(0x40) + let len := initCode.length + calldatacopy(fmp, initCode.offset, len) + + createdAddr := create2(value, fmp, len, salt) + if iszero(createdAddr) { + mstore(0x00, 0x7e16b8cd) + revert(0x1c, 0x04) + } + } + } + /// @inheritdoc IERC777Recipient /// @dev Runtime validation is bypassed for this function, but we still allow pre and post exec hooks to be /// assigned and run. diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index ca75f149..36bde6c4 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -516,6 +516,37 @@ contract UpgradeableModularAccountTest is Test { vm.stopPrank(); } + function testCreate() public { + address account = factory.createAccount(0, owners1); + + address expectedAddr = computeCreateAddress(account, vm.getNonce(account)); + address returnedAddr = account1.performCreate( + 0, abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(entryPoint))) + ); + + assertEq(address(UpgradeableModularAccount(payable(expectedAddr)).entryPoint()), address(entryPoint)); + assertEq(returnedAddr, expectedAddr); + } + + function testCreate2() public { + address account = factory.createAccount(0, owners1); + + bytes memory initCode = + abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(entryPoint))); + bytes32 initCodeHash = keccak256(initCode); + bytes32 salt = bytes32(hex"01234b"); + + address expectedAddr = computeCreate2Address(salt, initCodeHash, address(account)); + address returnedAddr = account1.performCreate2(0, initCode, salt); + + assertEq(address(UpgradeableModularAccount(payable(expectedAddr)).entryPoint()), address(entryPoint)); + assertEq(returnedAddr, expectedAddr); + + vm.expectRevert(UpgradeableModularAccount.CreateFailed.selector); + // multi-depoly with same salt got reverted + account1.performCreate2(0, initCode, salt); + } + // Internal Functions function _printStorageReadsAndWrites(address addr) internal { From fd14220f419808b643d904d894510f5549cbf8c1 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 14 Aug 2024 09:14:48 -0700 Subject: [PATCH 2/2] feat(create): reorder code, enhance documentation. --- src/account/UpgradeableModularAccount.sol | 22 +++++++++++++++++--- test/account/UpgradeableModularAccount.t.sol | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 2beb83bf..d9be8f0c 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -93,6 +93,7 @@ contract UpgradeableModularAccount is event ModularAccountInitialized(IEntryPoint indexed entryPoint); error AlwaysDenyRule(); + error CreateFailed(); error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); error NativeTokenSpendingNotPermitted(address plugin); @@ -105,7 +106,6 @@ contract UpgradeableModularAccount is error UnrecognizedFunction(bytes4 selector); error UserOpNotFromEntryPoint(); error UserOpValidationFunctionMissing(bytes4 selector); - error CreateFailed(); constructor(IEntryPoint anEntryPoint) { _ENTRY_POINT = anEntryPoint; @@ -377,9 +377,17 @@ contract UpgradeableModularAccount is _postNativeFunction(postExecHooks, postHookArgs); } - /// + /// @notice Create a contract. /// @param value The value to send to the new contract constructor /// @param initCode The initCode to deploy. + /// @return createdAddr The created contract address. + /// + /// @dev Assembly procedure: + /// 1. Load the free memory pointer. + /// 2. Get the initCode length. + /// 3. Copy the initCode from callata to memory at the free memory pointer. + /// 4. Create the contract. + /// 5. If creation failed (the address returned is zero), revert with CreateFailed(). function performCreate(uint256 value, bytes calldata initCode) external payable @@ -400,10 +408,18 @@ contract UpgradeableModularAccount is } } - /// + /// @notice Creates a contract using create2 deterministic deployment. /// @param value The value to send to the new contract constructor. /// @param initCode The initCode to deploy. /// @param salt The salt to use for the create2 operation. + /// @return createdAddr The created contract address. + /// + /// @dev Assembly procedure: + /// 1. Load the free memory pointer. + /// 2. Get the initCode length. + /// 3. Copy the initCode from callata to memory at the free memory pointer. + /// 4. Create the contract using Create2 with the passed salt parameter. + /// 5. If creation failed (the address returned is zero), revert with CreateFailed(). function performCreate2(uint256 value, bytes calldata initCode, bytes32 salt) external payable diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 36bde6c4..12aee466 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -543,7 +543,7 @@ contract UpgradeableModularAccountTest is Test { assertEq(returnedAddr, expectedAddr); vm.expectRevert(UpgradeableModularAccount.CreateFailed.selector); - // multi-depoly with same salt got reverted + // multi-deploy with same salt got reverted account1.performCreate2(0, initCode, salt); }