Skip to content

Commit

Permalink
Merge 4c05522 into 96d9261
Browse files Browse the repository at this point in the history
  • Loading branch information
Aboudjem authored Aug 13, 2024
2 parents 96d9261 + 4c05522 commit e596ba9
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 27 deletions.
34 changes: 20 additions & 14 deletions contracts/base/ExecutionHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ contract ExecutionHelper is IExecutionHelperEventsAndErrors {
exec = executions[i];
bool success;
(success, result[i]) = _tryExecute(exec.target, exec.value, exec.callData);
if (!success) emit TryExecuteUnsuccessful(i, result[i]);
if (!success) emit TryExecuteUnsuccessful(exec.callData, result[i]);
}
}

Expand Down Expand Up @@ -140,8 +140,10 @@ contract ExecutionHelper is IExecutionHelperEventsAndErrors {
function _handleSingleExecution(bytes calldata executionCalldata, ExecType execType) internal {
(address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle();
if (execType == EXECTYPE_DEFAULT) _execute(target, value, callData);
else if (execType == EXECTYPE_TRY) _tryExecute(target, value, callData);
else revert UnsupportedExecType(execType);
else if (execType == EXECTYPE_TRY) {
(bool success, bytes memory result) = _tryExecute(target, value, callData);
if (!success) emit TryExecuteUnsuccessful(callData, result);
} else revert UnsupportedExecType(execType);
}

/// @dev Executes a batch of transactions based on the specified execution type.
Expand All @@ -160,14 +162,16 @@ contract ExecutionHelper is IExecutionHelperEventsAndErrors {
function _handleDelegateCallExecution(bytes calldata executionCalldata, ExecType execType) internal {
(address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall();
if (execType == EXECTYPE_DEFAULT) _executeDelegatecall(delegate, callData);
else if (execType == EXECTYPE_TRY) _tryExecuteDelegatecall(delegate, callData);
else revert UnsupportedExecType(execType);
else if (execType == EXECTYPE_TRY) {
(bool success, bytes memory result) = _tryExecuteDelegatecall(delegate, callData);
if (!success) emit TryDelegateCallUnsuccessful(callData, result);
} else revert UnsupportedExecType(execType);
}

/// @dev Executes a single transaction based on the specified execution type.
/// @param executionCalldata The calldata containing the transaction details (target address, value, and data).
/// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure).
function _handleSingleExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns(bytes[] memory returnData) {
function _handleSingleExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) {
(address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle();
returnData = new bytes[](1);
bool success;
Expand All @@ -176,16 +180,16 @@ contract ExecutionHelper is IExecutionHelperEventsAndErrors {
returnData[0] = _execute(target, value, callData);
} else if (execType == EXECTYPE_TRY) {
(success, returnData[0]) = _tryExecute(target, value, callData);
if (!success) emit TryExecuteUnsuccessful(0, returnData[0]);
if (!success) emit TryExecuteUnsuccessful(callData, returnData[0]);
} else {
revert UnsupportedExecType(execType);
}
}

/// @dev Executes a batch of transactions based on the specified execution type.
/// @dev Executes a batch of transactions based on the specified execution type.
/// @param executionCalldata The calldata for a batch of transactions.
/// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure).
function _handleBatchExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns(bytes[] memory returnData){
function _handleBatchExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) {
Execution[] calldata executions = executionCalldata.decodeBatch();
if (execType == EXECTYPE_DEFAULT) returnData = _executeBatch(executions);
else if (execType == EXECTYPE_TRY) returnData = _tryExecuteBatch(executions);
Expand All @@ -195,16 +199,18 @@ contract ExecutionHelper is IExecutionHelperEventsAndErrors {
/// @dev Executes a single transaction based on the specified execution type.
/// @param executionCalldata The calldata containing the transaction details (target address, value, and data).
/// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure).
function _handleDelegateCallExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns(bytes[] memory returnData) {
function _handleDelegateCallExecutionAndReturnData(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
(address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall();
returnData = new bytes[](1);
bool success;
if (execType == EXECTYPE_DEFAULT) {
if (execType == EXECTYPE_DEFAULT) {
returnData[0] = _executeDelegatecall(delegate, callData);
}
else if (execType == EXECTYPE_TRY) {
} else if (execType == EXECTYPE_TRY) {
(success, returnData[0]) = _tryExecuteDelegatecall(delegate, callData);
if (!success) emit TryDelegateCallUnsuccessful(0, returnData[0]);
if (!success) emit TryDelegateCallUnsuccessful(callData, returnData[0]);
}
else revert UnsupportedExecType(execType);
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { ExecType } from "../../lib/ModeLib.sol";

interface IExecutionHelperEventsAndErrors {
/// @notice Event emitted when a transaction fails to execute successfully.
event TryExecuteUnsuccessful(uint256 batchExecutionindex, bytes result);
event TryExecuteUnsuccessful(bytes callData, bytes result);

/// @notice Event emitted when a transaction fails to execute successfully.
event TryDelegateCallUnsuccessful(uint256 batchExecutionindex, bytes result);
event TryDelegateCallUnsuccessful(bytes callData, bytes result);

/// @notice Error thrown when an execution with an unsupported ExecType was made.
/// @param execType The unsupported execution type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ contract TestAccountExecution_ExecuteFromExecutor is TestAccountExecution_Base {

// Expect the TryExecuteUnsuccessful event to be emitted
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(1, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[1].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

// Execute batch operation via MockExecutor
mockExecutor.tryExecuteBatchViaAccount(BOB_ACCOUNT, executions);
Expand All @@ -400,7 +400,7 @@ contract TestAccountExecution_ExecuteFromExecutor is TestAccountExecution_Base {

// Expect the TryExecuteUnsuccessful event to be emitted
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(1, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[1].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

// Prank and execute batch operation via BOB_ACCOUNT
prank(address(mockExecutor));
Expand All @@ -423,9 +423,9 @@ contract TestAccountExecution_ExecuteFromExecutor is TestAccountExecution_Base {

// Expect the TryExecuteUnsuccessful event to be emitted for each failure
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(0, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[0].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(1, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[1].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

// Execute batch operation via MockExecutor
mockExecutor.tryExecuteBatchViaAccount(BOB_ACCOUNT, executions);
Expand All @@ -445,7 +445,7 @@ contract TestAccountExecution_ExecuteFromExecutor is TestAccountExecution_Base {

// Expect the TryExecuteUnsuccessful event to be emitted
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(0, "");
emit TryExecuteUnsuccessful(executions[0].callData, "");

// Execute batch operation via MockExecutor
mockExecutor.tryExecuteBatchViaAccount(BOB_ACCOUNT, executions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract TestAccountExecution_TryExecuteBatch is TestAccountExecution_Base {
PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_TRY, executions, address(VALIDATOR_MODULE));

vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(1, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[1].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
ENTRYPOINT.handleOps(userOps, payable(BOB.addr));

assertEq(counter.getNumber(), 2, "Counter should have been incremented even after revert operation in batch execution");
Expand All @@ -63,7 +63,7 @@ contract TestAccountExecution_TryExecuteBatch is TestAccountExecution_Base {
// Execute batch operation
prank(address(BOB_ACCOUNT));
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(1, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[1].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

BOB_ACCOUNT.execute(ModeLib.encodeTryBatch(), abi.encode(executions));

Expand All @@ -82,7 +82,7 @@ contract TestAccountExecution_TryExecuteBatch is TestAccountExecution_Base {
PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_TRY, executions, address(VALIDATOR_MODULE));

vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(0, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
emit TryExecuteUnsuccessful(executions[0].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));
ENTRYPOINT.handleOps(userOps, payable(BOB.addr));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,56 @@ contract TestAccountExecution_TryExecuteSingle is TestAccountExecution_Base {
assertEq(token.balanceOf(ALICE.addr), transferFromAmount, "TransferFrom did not execute correctly");
assertEq(token.allowance(address(BOB_ACCOUNT), CHARLIE.addr), approvalAmount - transferFromAmount, "Allowance not updated correctly");
}

/// @notice Tests if the TryExecuteUnsuccessful event is emitted correctly when execution fails.
function test_TryExecuteSingle_EmitTryExecuteUnsuccessful() public {
// Initial state assertion
assertEq(counter.getNumber(), 0, "Counter should start at 0");

Execution[] memory execution = new Execution[](1);
execution[0] = Execution(address(counter), 0, abi.encodeWithSelector(Counter.revertOperation.selector));

PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_TRY, execution, address(VALIDATOR_MODULE));

// Expect the TryExecuteUnsuccessful event to be emitted with specific data
vm.expectEmit(true, true, true, true);
emit TryExecuteUnsuccessful(execution[0].callData, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

ENTRYPOINT.handleOps(userOps, payable(BOB.addr));

// Asserting the counter did not increment
assertEq(counter.getNumber(), 0, "Counter should not have been incremented after revert");
}

/// @notice Tests if the TryDelegateCallUnsuccessful event is emitted correctly when delegate call execution fails.
function test_TryExecuteDelegateCall_EmitTryDelegateCallUnsuccessful() public {
// Create calldata for the account to execute a failing delegate call
Execution[] memory execution = new Execution[](1);
execution[0] = Execution(address(counter), 0, abi.encodeWithSelector(Counter.revertOperation.selector));

// Build UserOperation for delegate call execution
PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_TRY, execution, address(VALIDATOR_MODULE));

// Create delegate call data
bytes memory userOpCalldata = abi.encodeCall(
Nexus.execute,
(
ModeLib.encode(CALLTYPE_DELEGATECALL, EXECTYPE_TRY, MODE_DEFAULT, ModePayload.wrap(0x00)),
abi.encodePacked(address(counter), execution[0].callData)
)
);

userOps[0].callData = userOpCalldata;

// Sign the operation
bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOps[0]);
userOps[0].signature = signMessage(BOB, userOpHash);

// Expect the TryDelegateCallUnsuccessful event to be emitted
vm.expectEmit(true, true, true, true);
emit TryDelegateCallUnsuccessful(0, abi.encodeWithSignature("Error(string)", "Counter: Revert operation"));

// Execute the operation
ENTRYPOINT.handleOps(userOps, payable(BOB.addr));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ TestAccountExecution_TryExecuteSingle
│ └── it should transfer ETH correctly
├── when executing a token transfer in single execution
│ └── it should transfer tokens correctly
└── when executing approve and transferFrom in single execution
└── it should update balances and allowances correctly
├── when executing approve and transferFrom in single execution
│ └── it should update balances and allowances correctly
├── when execution fails
│ └── it should emit the TryExecuteUnsuccessful event
└── when delegate call execution fails
└── it should emit the TryDelegateCallUnsuccessful event
3 changes: 2 additions & 1 deletion test/foundry/utils/EventsAndErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ contract EventsAndErrors {
event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason);
event PreCheckCalled();
event PostCheckCalled();
event TryExecuteUnsuccessful(uint256 batchExecutionindex, bytes result);
event TryExecuteUnsuccessful(bytes callData, bytes result);
event TryDelegateCallUnsuccessful(uint256 batchExecutionindex, bytes result);

// ==========================
// General Errors
Expand Down
69 changes: 69 additions & 0 deletions test/hardhat/smart-account/Nexus.Basics.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,75 @@ describe("Nexus Basic Specs", function () {

expect(isValid).to.equal("0x1626ba7e");
});

it("Should check signature validity using smart account isValidSignature", async function () {
const isModuleInstalled = await smartAccount.isModuleInstalled(
ModuleType.Validation,
await validatorModule.getAddress(),
ethers.hexlify("0x"),
);
expect(isModuleInstalled).to.be.true;

// 1. Convert foundry util to ts code (as below)

const data = keccak256("0x1234");

// Define constants as per the original Solidity function
const DOMAIN_NAME = "Nexus";
const DOMAIN_VERSION = "1.0.0-beta";
const DOMAIN_TYPEHASH =
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
const PARENT_TYPEHASH = "PersonalSign(bytes prefixed)";
const ALICE_ACCOUNT = smartAccountAddress;
const network = await ethers.provider.getNetwork();
const chainId = network.chainId;

// Calculate the domain separator
const domainSeparator = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "bytes32", "bytes32", "uint256", "address"],
[
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_TYPEHASH)),
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_NAME)),
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_VERSION)),
chainId,
ALICE_ACCOUNT,
],
),
);

// Calculate the parent struct hash
const parentStructHash = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "bytes32"],
[ethers.keccak256(ethers.toUtf8Bytes(PARENT_TYPEHASH)), data],
),
);

// Calculate the final hash
const resultHash = ethers.keccak256(
ethers.concat(["0x1901", domainSeparator, parentStructHash]),
);

console.log(
"being signed",
ethers.hashMessage(ethers.getBytes(resultHash)),
);

const signature = await smartAccountOwner.signMessage(
ethers.getBytes(resultHash),
);

const isValid = await smartAccount.isValidSignature(
data,
solidityPacked(
["address", "bytes"],
[await validatorModule.getAddress(), signature],
),
);

expect(isValid).to.equal("0x1626ba7e");
});
});

describe("Smart Account check Only Entrypoint actions", function () {
Expand Down

0 comments on commit e596ba9

Please sign in to comment.