Skip to content

Commit ebe761a

Browse files
authored
feat: add contract creation execution functions (#61)
1 parent dd4187d commit ebe761a

File tree

2 files changed

+155
-4
lines changed

2 files changed

+155
-4
lines changed

src/common/BaseLightAccount.sol

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ abstract contract BaseLightAccount is BaseAccount, TokenCallbackHandler, UUPSUpg
2121
}
2222

2323
error ArrayLengthMismatch();
24+
error CreateFailed();
2425
error InvalidSignatureType();
2526
error NotAuthorized(address caller);
2627
error ZeroAddressNotAllowed();
@@ -75,6 +76,71 @@ abstract contract BaseLightAccount is BaseAccount, TokenCallbackHandler, UUPSUpg
7576
}
7677
}
7778

79+
/// @notice Creates a contract.
80+
/// @param value The value to send to the new contract constructor.
81+
/// @param initCode The initCode to deploy.
82+
/// @return createdAddr The created contract address.
83+
///
84+
/// @dev Assembly procedure:
85+
/// 1. Load the free memory pointer.
86+
/// 2. Get the initCode length.
87+
/// 3. Copy the initCode from callata to memory at the free memory pointer.
88+
/// 4. Create the contract.
89+
/// 5. If creation failed (the address returned is zero), revert with CreateFailed().
90+
function performCreate(uint256 value, bytes calldata initCode)
91+
external
92+
payable
93+
virtual
94+
onlyAuthorized
95+
returns (address createdAddr)
96+
{
97+
assembly ("memory-safe") {
98+
let fmp := mload(0x40)
99+
let len := initCode.length
100+
calldatacopy(fmp, initCode.offset, len)
101+
102+
createdAddr := create(value, fmp, len)
103+
104+
if iszero(createdAddr) {
105+
mstore(0x00, 0x7e16b8cd)
106+
revert(0x1c, 0x04)
107+
}
108+
}
109+
}
110+
111+
/// @notice Creates a contract using create2 deterministic deployment.
112+
/// @param value The value to send to the new contract constructor.
113+
/// @param initCode The initCode to deploy.
114+
/// @param salt The salt to use for the create2 operation.
115+
/// @return createdAddr The created contract address.
116+
///
117+
/// @dev Assembly procedure:
118+
/// 1. Load the free memory pointer.
119+
/// 2. Get the initCode length.
120+
/// 3. Copy the initCode from callata to memory at the free memory pointer.
121+
/// 4. Create the contract using Create2 with the passed salt parameter.
122+
/// 5. If creation failed (the address returned is zero), revert with CreateFailed().
123+
function performCreate2(uint256 value, bytes calldata initCode, bytes32 salt)
124+
external
125+
payable
126+
virtual
127+
onlyAuthorized
128+
returns (address createdAddr)
129+
{
130+
assembly ("memory-safe") {
131+
let fmp := mload(0x40)
132+
let len := initCode.length
133+
calldatacopy(fmp, initCode.offset, len)
134+
135+
createdAddr := create2(value, fmp, len, salt)
136+
137+
if iszero(createdAddr) {
138+
mstore(0x00, 0x7e16b8cd)
139+
revert(0x1c, 0x04)
140+
}
141+
}
142+
}
143+
78144
/// @notice Deposit more funds for this account in the entry point.
79145
function addDeposit() external payable {
80146
entryPoint().depositTo{value: msg.value}(address(this));
@@ -122,11 +188,25 @@ abstract contract BaseLightAccount is BaseAccount, TokenCallbackHandler, UUPSUpg
122188
return success ? SIG_VALIDATION_SUCCESS : SIG_VALIDATION_FAILED;
123189
}
124190

191+
/// @dev Assembly procedure:
192+
/// 1. Execute the call, passing:
193+
/// 1. The gas
194+
/// 2. The target address
195+
/// 3. The call value
196+
/// 4. The pointer to the start location of the callData in memory
197+
/// 5. The length of the calldata
198+
/// 2. If the call failed, bubble up the revert reason by doing the following:
199+
/// 1. Load the free memory pointer
200+
/// 2. Copy the return data (which is the revert reason) to memory at the free memory pointer
201+
/// 3. Revert with the copied return data
125202
function _call(address target, uint256 value, bytes memory data) internal {
126-
(bool success, bytes memory result) = target.call{value: value}(data);
127-
if (!success) {
128-
assembly ("memory-safe") {
129-
revert(add(result, 32), mload(result))
203+
assembly ("memory-safe") {
204+
let succ := call(gas(), target, value, add(data, 0x20), mload(data), 0x00, 0)
205+
206+
if iszero(succ) {
207+
let fmp := mload(0x40)
208+
returndatacopy(fmp, 0x00, returndatasize())
209+
revert(fmp, returndatasize())
130210
}
131211
}
132212
}

test/LightAccount.t.sol

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,77 @@ contract LightAccountTest is Test {
477477
assertEq(initialized, 1);
478478
}
479479

480+
function testRevertCreate_IncorrectCaller() public {
481+
vm.expectRevert(abi.encodeWithSelector(BaseLightAccount.NotAuthorized.selector, address(this)));
482+
account.performCreate(0, hex"1234");
483+
}
484+
485+
function testRevertCreate_CreateFailed() public {
486+
vm.prank(eoaAddress);
487+
vm.expectRevert(BaseLightAccount.CreateFailed.selector);
488+
account.performCreate(0, hex"3d3dfd");
489+
}
490+
491+
function testRevertCreate2_IncorrectCaller() public {
492+
vm.expectRevert(abi.encodeWithSelector(BaseLightAccount.NotAuthorized.selector, address(this)));
493+
account.performCreate2(0, hex"1234", bytes32(0));
494+
}
495+
496+
function testRevertCreate2_CreateFailed() public {
497+
vm.prank(eoaAddress);
498+
vm.expectRevert(BaseLightAccount.CreateFailed.selector);
499+
account.performCreate2(0, hex"3d3dfd", bytes32(0));
500+
}
501+
502+
function testCreate() public {
503+
vm.prank(eoaAddress);
504+
address expected = vm.computeCreateAddress(address(account), vm.getNonce(address(account)));
505+
506+
address returnedAddress =
507+
account.performCreate(0, abi.encodePacked(type(LightAccount).creationCode, abi.encode(address(entryPoint))));
508+
assertEq(address(LightAccount(payable(expected)).entryPoint()), address(entryPoint));
509+
assertEq(returnedAddress, expected);
510+
}
511+
512+
function testCreateValue() public {
513+
vm.prank(eoaAddress);
514+
address expected = vm.computeCreateAddress(address(account), vm.getNonce(address(account)));
515+
516+
uint256 value = 1 ether;
517+
deal(address(account), value);
518+
519+
address returnedAddress = account.performCreate(value, "");
520+
assertEq(returnedAddress, expected);
521+
assertEq(returnedAddress.balance, value);
522+
}
523+
524+
function testCreate2() public {
525+
vm.prank(eoaAddress);
526+
bytes memory initCode = abi.encodePacked(type(LightAccount).creationCode, abi.encode(address(entryPoint)));
527+
bytes32 initCodeHash = keccak256(initCode);
528+
bytes32 salt = bytes32(hex"04546b");
529+
address expected = vm.computeCreate2Address(salt, initCodeHash, address(account));
530+
531+
address returnedAddress = account.performCreate2(0, initCode, salt);
532+
assertEq(address(LightAccount(payable(expected)).entryPoint()), address(entryPoint));
533+
assertEq(returnedAddress, expected);
534+
}
535+
536+
function testCreate2Value() public {
537+
vm.prank(eoaAddress);
538+
bytes memory initCode = "";
539+
bytes32 initCodeHash = keccak256(initCode);
540+
bytes32 salt = bytes32(hex"04546b");
541+
address expected = vm.computeCreate2Address(salt, initCodeHash, address(account));
542+
543+
uint256 value = 1 ether;
544+
deal(address(account), value);
545+
546+
address returnedAddress = account.performCreate2(value, initCode, salt);
547+
assertEq(returnedAddress, expected);
548+
assertEq(returnedAddress.balance, value);
549+
}
550+
480551
function _useContractOwner() internal {
481552
vm.prank(eoaAddress);
482553
account.transferOwnership(address(contractOwner));

0 commit comments

Comments
 (0)