Skip to content

Commit

Permalink
Merge pull request #38 from erc7579/feature/eip-7702-alt
Browse files Browse the repository at this point in the history
feat: add alternative for 7702 implementation
  • Loading branch information
kopy-kat authored Nov 28, 2024
2 parents c47765c + 4dca4e0 commit 87e0264
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 35 deletions.
31 changes: 11 additions & 20 deletions src/MSAAdvanced.sol
Original file line number Diff line number Diff line change
Expand Up @@ -239,34 +239,22 @@ contract MSAAdvanced is IMSA, ExecutionHelper, ModuleManager, HookManager, Regis

// check if validator is enabled. If not terminate the validation phase.
if (!_isValidatorInstalled(validator)) {
// if the account is not initialized, then allow initialization with
// 7702 eoa signature
if (!isAlreadyInitialized()) {
(bytes memory initData, bytes memory eoaSignature, bytes memory signature) =
abi.decode(userOp.signature, (bytes, bytes, bytes));
(address bootstrap, bytes memory bootstrapCall) =
abi.decode(initData, (address, bytes));
address signer =
ECDSA.recover(userOpHash.toEthSignedMessageHash(), userOp.signature);

// Hash the initData and recover the signer
bytes32 hash = HashLib.hash(bootstrap, bootstrapCall);
address signer = ECDSA.recover(hash.toEthSignedMessageHash(), eoaSignature);

// check if the signer is the account
if (signer != address(this)) {
return VALIDATION_FAILED;
}

_initModuleManager();
_initAccount(bootstrap, bootstrapCall);

userOp.signature = signature;
return VALIDATION_SUCCESS;
} else {
return VALIDATION_FAILED;
}
} else {
// bubble up the return value of the validator module
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
}

// bubble up the return value of the validator module
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
}

/**
Expand Down Expand Up @@ -365,8 +353,11 @@ contract MSAAdvanced is IMSA, ExecutionHelper, ModuleManager, HookManager, Regis
* @param data. encoded data that can be used during the initialization phase
*/
function initializeAccount(bytes calldata data) public payable virtual {
// protect this function to only be callable when used with the proxy factory
Initializable.checkInitializable();
// protect this function to only be callable when used with the proxy factory or when
// account calls itself
if (msg.sender != address(this)) {
Initializable.checkInitializable();
}

// checks if already initialized and reverts before setting the state to initialized
_initModuleManager();
Expand Down
82 changes: 67 additions & 15 deletions test/advanced/EIP7702.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,27 @@ contract EIP7702 is TestBaseUtilAdvanced {
etch(account, address(implementation).code);
}

function _getInitData(uint256 eoaKey) internal view returns (bytes memory) {
function _getInitData() internal view returns (bytes memory) {
// Create config for initial modules
BootstrapConfig[] memory validators = makeBootstrapConfig(address(defaultValidator), "");
BootstrapConfig[] memory executors = makeBootstrapConfig(address(defaultExecutor), "");
BootstrapConfig memory hook = _makeBootstrapConfig(address(0), "");
BootstrapConfig[] memory fallbacks = makeBootstrapConfig(address(0), "");

// Create initcode and salt to be sent to Factory
bytes memory initData =
bootstrapSingleton._getInitMSACalldata(validators, executors, hook, fallbacks);
return bootstrapSingleton._getInitMSACalldata(validators, executors, hook, fallbacks);
}

(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
bytes32 hash = HashLib.hash(bootstrap, bootstrapCall);
function _getSignature(
uint256 eoaKey,
PackedUserOperation memory userOp
)
internal
view
returns (bytes memory)
{
bytes32 hash = entrypoint.getUserOpHash(userOp);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(eoaKey, hash.toEthSignedMessageHash());
bytes memory signature = abi.encodePacked(r, s, v);

return abi.encode(initData, signature, hex"41414141414141");
return abi.encodePacked(r, s, v);
}

function test_execSingle() public returns (address) {
Expand Down Expand Up @@ -80,7 +84,55 @@ contract EIP7702 is TestBaseUtilAdvanced {
userOp.nonce = nonce;
userOp.callData = userOpCalldata;

userOp.signature = _getInitData(eoaKey);
userOp.signature = _getSignature(eoaKey, userOp);
_doEIP7702(account);

// Create userOps array
PackedUserOperation[] memory userOps = new PackedUserOperation[](1);
userOps[0] = userOp;

// Send the userOp to the entrypoint
entrypoint.handleOps(userOps, payable(address(0x69)));

// Assert that the value was set ie that execution was successful
assertTrue(target.value() == 1337);
return account;
}

function test_initializeAndExecSingle() public returns (address) {
// Get the account, initcode and nonce
uint256 eoaKey = uint256(8);
address account = vm.addr(eoaKey);
vm.deal(account, 100 ether);

// Create calldata for the account to execute
bytes memory setValueOnTarget = abi.encodeCall(MockTarget.setValue, 1337);

bytes memory initData = _getInitData();

Execution[] memory executions = new Execution[](2);
executions[0] = Execution({
target: account,
value: 0,
callData: abi.encodeCall(MSAAdvanced.initializeAccount, initData)
});
executions[1] = Execution({ target: address(target), value: 0, callData: setValueOnTarget });

// Encode the call into the calldata for the userOp
bytes memory userOpCalldata = abi.encodeCall(
IERC7579Account.execute,
(ModeLib.encodeSimpleBatch(), ExecutionLib.encodeBatch(executions))
);

uint256 nonce = getNonce(account, address(defaultValidator));

// Create the userOp and add the data
PackedUserOperation memory userOp = getDefaultUserOp();
userOp.sender = address(account);
userOp.nonce = nonce;
userOp.callData = userOpCalldata;

userOp.signature = _getSignature(eoaKey, userOp);
_doEIP7702(account);

// Create userOps array
Expand Down Expand Up @@ -125,7 +177,7 @@ contract EIP7702 is TestBaseUtilAdvanced {
userOp.nonce = nonce;
userOp.callData = userOpCalldata;

userOp.signature = _getInitData(eoaKey);
userOp.signature = _getSignature(eoaKey, userOp);
_doEIP7702(account);

// Create userOps array
Expand All @@ -141,7 +193,7 @@ contract EIP7702 is TestBaseUtilAdvanced {
}

function test_execSingleFromExecutor() public {
address account = test_execSingle();
address account = test_initializeAndExecSingle();

bytes[] memory ret = defaultExecutor.executeViaAccount(
IERC7579Account(address(account)),
Expand All @@ -155,7 +207,7 @@ contract EIP7702 is TestBaseUtilAdvanced {
}

function test_execBatchFromExecutor() public {
address account = test_execSingle();
address account = test_initializeAndExecSingle();

bytes memory setValueOnTarget = abi.encodeCall(MockTarget.setValue, 1338);
Execution[] memory executions = new Execution[](2);
Expand Down Expand Up @@ -201,7 +253,7 @@ contract EIP7702 is TestBaseUtilAdvanced {
userOp.nonce = nonce;
userOp.callData = userOpCalldata;

userOp.signature = _getInitData(eoaKey);
userOp.signature = _getSignature(eoaKey, userOp);
_doEIP7702(account);

// Create userOps array
Expand All @@ -216,7 +268,7 @@ contract EIP7702 is TestBaseUtilAdvanced {
}

function test_delegateCall_fromExecutor() public {
address account = test_execSingle();
address account = test_initializeAndExecSingle();

// Create calldata for the account to execute
address valueTarget = makeAddr("valueTarget");
Expand Down

0 comments on commit 87e0264

Please sign in to comment.