Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(create): add performCreate[2] method. #168

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -376,6 +377,68 @@ 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
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)
}
}
}

/// @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
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.
Expand Down
31 changes: 31 additions & 0 deletions test/account/UpgradeableModularAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
andysim3d marked this conversation as resolved.
Show resolved Hide resolved
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-deploy with same salt got reverted
account1.performCreate2(0, initCode, salt);
}

// Internal Functions

function _printStorageReadsAndWrites(address addr) internal {
Expand Down
Loading