From 92149cc5769f5ebbe8d978d25403b3f1e703b7e5 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 30 Jul 2024 15:08:20 -0400 Subject: [PATCH] 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 {