Skip to content
Merged

Dev #47

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
60 changes: 49 additions & 11 deletions src/Kernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ contract Kernel is EIP712, Compatibility, KernelStorage {
}
}

/// @notice Executes a function call to an external contract with delegatecall
/// @param to The address of the target contract
/// @param data The call data to be sent
function executeDelegateCall(address to, bytes memory data) external payable {
if (msg.sender != address(entryPoint) && msg.sender != address(this) && !_checkCaller()) {
revert NotAuthorizedCaller();
}
assembly {
let success := delegatecall(gas(), to, add(data, 0x20), mload(data), 0, 0)
returndatacopy(0, 0, returndatasize())
switch success
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

/// @notice Executes a function call to an external contract batched
/// @param calls The calls to be executed, in order
/// @dev operation deprecated param, use executeBatch for batched transaction
Expand Down Expand Up @@ -235,29 +251,51 @@ contract Kernel is EIP712, Compatibility, KernelStorage {
}
}

function validateSignature(bytes32 hash, bytes calldata signature) public view returns(ValidationData) {
function validateSignature(bytes32 hash, bytes calldata signature) public view returns (ValidationData) {
return _validateSignature(hash, signature);
}

function _domainSeparator() internal view override returns (bytes32) {
// Obtain the name and version from the _domainNameAndVersion function.
(string memory _name, string memory _version) = _domainNameAndVersion();
bytes32 nameHash = keccak256(bytes(_name));
bytes32 versionHash = keccak256(bytes(_version));

// Use the proxy address for the EIP-712 domain separator.
address proxyAddress = address(this);

// Construct the domain separator with name, version, chainId, and proxy address.
bytes32 typeHash =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, proxyAddress));
}

/// @notice Checks if a signature is valid
/// @dev This function checks if a signature is valid based on the hash of the data signed.
/// @param hash The hash of the data that was signed
/// @param signature The signature to be validated
/// @return The magic value 0x1626ba7e if the signature is valid, otherwise returns 0xffffffff.
function isValidSignature(bytes32 hash, bytes calldata signature) public view returns (bytes4) {
ValidationData validationData = validateSignature(hash, signature);
// Include the proxy address in the domain separator
bytes32 domainSeparator = _domainSeparator();

// Recreate the signed message hash with the correct domain separator
bytes32 signedMessageHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, hash));

ValidationData validationData = validateSignature(signedMessageHash, signature);
(ValidAfter validAfter, ValidUntil validUntil, address result) = parseValidationData(validationData);
if (ValidAfter.unwrap(validAfter) > block.timestamp) {
return 0xffffffff;
}
if (ValidUntil.unwrap(validUntil) < block.timestamp) {
return 0xffffffff;
}
if (result != address(0)) {

// Check if the signature is valid within the specified time frame and the result is successful
if (
ValidAfter.unwrap(validAfter) <= block.timestamp && ValidUntil.unwrap(validUntil) >= block.timestamp
&& result == address(0)
) {
// If all checks pass, return the ERC1271 magic value for a valid signature
return 0x1626ba7e;
} else {
// If any check fails, return the failure magic value
return 0xffffffff;
}

return 0x1626ba7e;
}

function _checkCaller() internal view returns (bool) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ValidationData} from "./Types.sol";

// constants for kernel metadata
string constant KERNEL_NAME = "Kernel";
string constant KERNEL_VERSION = "0.2.2";
string constant KERNEL_VERSION = "0.2.3";

// ERC4337 constants
uint256 constant SIG_VALIDATION_FAILED_UINT = 1;
Expand Down
47 changes: 19 additions & 28 deletions src/validator/SessionKeyValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ contract SessionKeyValidator is IKernelValidator {
}

function invalidateNonce(uint128 nonce) public {
require(
nonce > nonces[msg.sender].invalidNonce && nonce <= nonces[msg.sender].lastNonce,
"SessionKeyValidator: invalid nonce"
);
require(nonce > nonces[msg.sender].invalidNonce, "SessionKeyValidator: invalid nonce");
nonces[msg.sender].invalidNonce = nonce;
nonces[msg.sender].lastNonce = nonce;
}

function disable(bytes calldata _data) external payable {
Expand Down Expand Up @@ -113,7 +111,14 @@ contract SessionKeyValidator is IKernelValidator {
function _getPermissions(bytes calldata _sig) internal pure returns (Permission[] calldata permissions) {
assembly {
permissions.offset := add(add(_sig.offset, 0x20), calldataload(_sig.offset))
permissions.length := calldataload(permissions.offset)
permissions.length := calldataload(add(_sig.offset, calldataload(_sig.offset)))
}
}

function _getProofs(bytes calldata _sig) internal pure returns (bytes32[][] calldata proofs) {
assembly {
proofs.length := calldataload(add(_sig.offset, calldataload(add(_sig.offset, 0x20))))
proofs.offset := add(add(_sig.offset, 0x20), calldataload(add(_sig.offset, 0x20)))
}
}

Expand Down Expand Up @@ -152,8 +157,7 @@ contract SessionKeyValidator is IKernelValidator {
if (bytes4(callData[0:4]) == Kernel.execute.selector) {
(Permission calldata permission, bytes32[] calldata merkleProof) = _getPermission(userOp.signature[85:]);
require(
address(bytes20(userOp.callData[16:36])) == permission.target,
"SessionKeyValidator: target mismatch"
address(bytes20(userOp.callData[16:36])) == permission.target, "SessionKeyValidator: target mismatch"
);
require(
uint256(bytes32(userOp.callData[36:68])) <= permission.valueLimit,
Expand All @@ -178,10 +182,9 @@ contract SessionKeyValidator is IKernelValidator {
return _verifyUserOpHash(sessionKey, validAfter, session.validUntil);
} else if (bytes4(callData[0:4]) == Kernel.executeBatch.selector) {
Permission[] calldata permissions = _getPermissions(userOp.signature[85:]);
(, bytes32[] memory merkleProof, bool[] memory flags, uint256[] memory index) =
abi.decode(userOp.signature[85:], (Permission[], bytes32[], bool[], uint256[]));
(bytes32[] memory leaves, ValidAfter validAfter) = _verifyParams(sessionKey, callData, permissions, index);
if (!MerkleProofLib.verifyMultiProof(merkleProof, session.merkleRoot, leaves, flags)) {
bytes32[][] calldata merkleProof = _getProofs(userOp.signature[85:]);
(ValidAfter validAfter, bool verifyFailed) = _verifyParams(sessionKey, callData, permissions, merkleProof);
if (verifyFailed) {
return SIG_VALIDATION_FAILED;
}
return _verifyUserOpHash(sessionKey, validAfter, session.validUntil);
Expand Down Expand Up @@ -224,43 +227,31 @@ contract SessionKeyValidator is IKernelValidator {
address sessionKey,
bytes calldata callData,
Permission[] calldata _permissions,
uint256[] memory index
) internal returns (bytes32[] memory leaves, ValidAfter maxValidAfter) {
bytes32[][] calldata _merkleProof
) internal returns (ValidAfter maxValidAfter, bool verifyFailed) {
Call[] calldata calls;
assembly {
calls.offset := add(add(callData.offset, 0x24), calldataload(add(callData.offset, 4)))
calls.length := calldataload(add(add(callData.offset, 4), calldataload(add(callData.offset, 4))))
}
uint256 i = 0;
leaves = _generateLeaves(index);
SessionData storage session = sessionData[sessionKey][msg.sender];
maxValidAfter = session.validAfter;
for (i = 0; i < calls.length; i++) {
Call calldata call = calls[i];
Permission calldata permission = _permissions[i];
require(
call.to == permission.target, "SessionKeyValidator: target mismatch"
);
require(call.to == permission.target, "SessionKeyValidator: target mismatch");
require(uint256(bytes32(call.value)) <= permission.valueLimit, "SessionKeyValidator: value limit exceeded");
require(verifyPermission(call.data, permission), "SessionKeyValidator: permission verification failed");
ValidAfter validAfter =
_updateValidAfter(permission, keccak256(abi.encodePacked(session.nonce, permission.index)));
if (ValidAfter.unwrap(validAfter) > ValidAfter.unwrap(maxValidAfter)) {
maxValidAfter = validAfter;
}
leaves[index[i]] = keccak256(abi.encode(permission));
}
}

function _generateLeaves(uint256[] memory _indexes) internal pure returns (bytes32[] memory leaves) {
uint256 maxIndex;
uint256 i = 0;
for (i = 0; i < _indexes.length; i++) {
if (_indexes[i] > maxIndex) {
maxIndex = _indexes[i];
if (!MerkleProofLib.verify(_merkleProof[i], session.merkleRoot, keccak256(abi.encode(permission)))) {
return (maxValidAfter, true);
}
}
leaves = new bytes32[](maxIndex + 1);
}

function verifyPermission(bytes calldata data, Permission calldata permission) internal pure returns (bool) {
Expand Down
10 changes: 6 additions & 4 deletions src/validator/WeightedECDSAValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
proposal.status = ProposalStatus.Rejected;
}

function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingFunds)
function validateUserOp(UserOperation calldata userOp, bytes32, uint256)
external
payable
returns (ValidationData validationData)
Expand All @@ -149,7 +149,9 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
for (uint256 i = 0; i < sigCount; i++) {
// last sig is for userOpHash verification
signer = ECDSA.recover(
_hashTypedData(keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))),
_hashTypedData(
keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))
),
sig[i * 65:(i + 1) * 65]
);
vote = voteStatus[callDataAndNonceHash][signer][msg.sender];
Expand All @@ -173,11 +175,11 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
}
}

function validCaller(address, bytes calldata) external view override returns (bool) {
function validCaller(address, bytes calldata) external pure override returns (bool) {
return false;
}

function validateSignature(bytes32 hash, bytes calldata signature) external view returns (ValidationData) {
function validateSignature(bytes32, bytes calldata) external pure returns (ValidationData) {
return SIG_VALIDATION_FAILED;
}
}
3 changes: 1 addition & 2 deletions test/foundry/KernelLiteECDSA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ contract KernelECDSATest is KernelTestBase {
function test_transfer_ownership() external {
address newOwner = makeAddr("new owner");
UserOperation memory op = entryPoint.fillUserOp(
address(kernel),
abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner)
address(kernel), abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner)
);
op.signature = signUserOp(op);
UserOperation[] memory ops = new UserOperation[](1);
Expand Down
33 changes: 29 additions & 4 deletions test/foundry/KernelTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {IKernelValidator} from "src/interfaces/IKernelValidator.sol";

import {Call, ExecutionDetail} from "src/common/Structs.sol";
import {ValidationData, ValidUntil, ValidAfter} from "src/common/Types.sol";
import {KERNEL_VERSION, KERNEL_NAME} from "src/common/Constants.sol";

import {ERC4337Utils} from "test/foundry/utils/ERC4337Utils.sol";
import {Test} from "forge-std/Test.sol";
Expand Down Expand Up @@ -97,7 +98,24 @@ abstract contract KernelTestBase is Test {
}
}

function test_external_call_execute_delegatecall_success() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(validCallers[i]);
kernel.executeDelegateCall(validCallers[i], "");
}
}

function test_external_call_execute_delegatecall_fail() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(address(uint160(validCallers[i]) + 1));
vm.expectRevert();
kernel.executeDelegateCall(validCallers[i], "");
}
}

function test_external_call_execute_delegatecall_option_fail() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(validCallers[i]);
Expand Down Expand Up @@ -143,8 +161,8 @@ abstract contract KernelTestBase is Test {
(bytes1 fields, string memory name, string memory version,, address verifyingContract, bytes32 salt,) =
kernel.eip712Domain();
assertEq(fields, bytes1(hex"0f"));
assertEq(name, "Kernel");
assertEq(version, "0.2.2");
assertEq(name, KERNEL_NAME);
assertEq(version, KERNEL_VERSION);
assertEq(verifyingContract, address(kernel));
assertEq(salt, bytes32(0));
}
Expand Down Expand Up @@ -177,9 +195,16 @@ abstract contract KernelTestBase is Test {
}

function test_validate_signature() external {
Kernel kernel2 = Kernel(payable(factory.createAccount(address(kernelImpl), getInitializeData(), 3)));
bytes32 hash = keccak256(abi.encodePacked("hello world"));
bytes memory sig = signHash(hash);
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), hash
)
);
bytes memory sig = signHash(digest);
assertEq(kernel.isValidSignature(hash, sig), Kernel.isValidSignature.selector);
assertEq(kernel2.isValidSignature(hash, sig), bytes4(0xffffffff));
}

function test_fail_validate_wrongsignature() external {
Expand Down Expand Up @@ -471,7 +496,7 @@ abstract contract KernelTestBase is Test {
return keccak256(
abi.encodePacked(
"\x19\x01",
ERC4337Utils._buildDomainSeparator("Kernel", "0.2.2", address(kernel)),
ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)),
ERC4337Utils.getStructHash(sig, validUntil, validAfter, validator, executor, enableData)
)
);
Expand Down
5 changes: 4 additions & 1 deletion test/foundry/utils/Merkle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function _getRoot(bytes32[] memory data) pure returns (bytes32) {
return data[0];
}

function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes32[] memory) {
function _getProof(bytes32[] memory data, uint256 nodeIndex, bool wrongProof) pure returns (bytes32[] memory) {
require(data.length > 1);

bytes32[] memory result = new bytes32[](64);
Expand All @@ -33,6 +33,9 @@ function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes
assembly {
mstore(result, pos)
}
if (wrongProof) {
result[0] = result[0] ^ bytes32(uint256(0x01));
}

return result;
}
Expand Down
Loading