Skip to content
Open
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
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ via_ir = true
solc_version = "0.8.30"
optimizer = true
optimizer_runs = 20_000
bytecode_hash = "none"
cbor_metadata = false


[dependencies]
Expand Down
106 changes: 106 additions & 0 deletions src/interfaces/IERC7579Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

struct Execution {
address target;
uint256 value;
bytes callData;
}

interface IERC7579Account {
event ModuleInstalled(uint256 moduleTypeId, address module);
event ModuleUninstalled(uint256 moduleTypeId, address module);

/**
* @dev Executes a transaction on behalf of the account.
* This function is intended to be called by ERC-4337 EntryPoint.sol
* @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf
*
* @dev MSA MUST implement this function signature.
* If a mode is requested that is not supported by the Account, it MUST revert
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*/
function execute(bytes32 mode, bytes calldata executionCalldata) external payable;

/**
* @dev Executes a transaction on behalf of the account.
* This function is intended to be called by Executor Modules
* @dev Ensure adequate authorization control: i.e. onlyExecutorModule
*
* @dev MSA MUST implement this function signature.
* If a mode is requested that is not supported by the Account, it MUST revert
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*/
function executeFromExecutor(bytes32 mode, bytes calldata executionCalldata)
external
payable
returns (bytes[] memory returnData);

/**
* @dev ERC-1271 isValidSignature
* This function is intended to be used to validate a smart account signature
* and may forward the call to a validator module
*
* @param hash The hash of the data that is signed
* @param data The data that is signed
*/
function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4);

/**
* @dev installs a Module of a certain type on the smart account
* @dev Implement Authorization control of your choosing
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param initData arbitrary data that may be required on the module during `onInstall`
* initialization.
*/
function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable;

/**
* @dev uninstalls a Module of a certain type on the smart account
* @dev Implement Authorization control of your choosing
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param deInitData arbitrary data that may be required on the module during `onUninstall`
* de-initialization.
*/
function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable;

/**
* Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol)
* @param encodedMode the encoded mode
*/
function supportsExecutionMode(bytes32 encodedMode) external view returns (bool);

/**
* Function to check if the account supports installation of a certain module type Id
* @param moduleTypeId the module type ID according the ERC-7579 spec
*/
function supportsModule(uint256 moduleTypeId) external view returns (bool);

/**
* Function to check if the account has a certain module installed
* @param moduleTypeId the module type ID according the ERC-7579 spec
* Note: keep in mind that some contracts can be multiple module types at the same time. It
* thus may be necessary to query multiple module types
* @param module the module address
* @param additionalContext additional context data that the smart account may interpret to
* identify conditions under which the module is installed.
* usually this is not necessary, but for some special hooks that
* are stored in mappings, this param might be needed
*/
function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext)
external
view
returns (bool);

/**
* @dev Returns the account id of the smart account
* @return accountImplementationId the account id of the smart account
* the accountId should be structured like so:
* "vendorname.accountname.semver"
*/
function accountId() external view returns (string memory accountImplementationId);
}
155 changes: 155 additions & 0 deletions src/policies/PerChainPolicy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
pragma solidity ^0.8.0;

struct ChainPolicyArgs {
uint256[] chainIds;
bytes24[] callAddrAndSelector;
}

struct ChainPolicyConfig {
bool check;
bytes24[] callAddrAndSelector;
}
import {PolicyBase} from "src/base/PolicyBase.sol";
import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol";
import {IERC7579Account} from "src/interfaces/IERC7579Account.sol";
import {IAccountExecute} from "account-abstraction/interfaces/IAccountExecute.sol";
import {ExecMode, CallType, ExecType} from "src/types/Types.sol";
import {LibERC7579} from "solady/accounts/LibERC7579.sol";
import {CALLTYPE_SINGLE, CALLTYPE_BATCH} from "src/types/Constants.sol";

contract PerChainPolicy is PolicyBase {
error CallViolatesParamRule();
error NotSupported();
error AlreadyTaken();
error InvalidId();
mapping(bytes32 id => mapping(address account => ChainPolicyConfig)) public config;
mapping(bytes32 id => mapping(address account => bool)) public configured;
mapping(address account => uint256) public usedIds;

function isInitialized(address account) external view override returns (bool) {
return usedIds[account] > 0;
}

function _policyOninstall(bytes32 id, bytes calldata data) internal override {
if(configured[id][msg.sender]) {
revert AlreadyTaken();
}
configured[id][msg.sender] = true;
usedIds[msg.sender]++;
bytes1 mode = data[0];
if (mode == bytes1(0)) {
// check only on given chains
ChainPolicyArgs calldata args;
assembly {
args := add(data.offset, 1)
}
_installMode0(id, args);
} else if (mode == bytes1(0x01)) {
// check on all other chains than given chains
ChainPolicyArgs calldata args;
assembly {
args := add(data.offset, 1)
}
_installMode1(id, args);
} else {
revert NotSupported();
}
}

function _policyOnUninstall(bytes32 id, bytes calldata) internal override {
if(!configured[id][msg.sender]) {
revert InvalidId();
}
usedIds[msg.sender]--;
delete config[id][msg.sender];
}

function _installMode0(bytes32 id, ChainPolicyArgs calldata args) internal {
for (uint256 i = 0; i < args.chainIds.length; i++) {
if (args.chainIds[i] == block.chainid) {
config[id][msg.sender] = ChainPolicyConfig({check: true, callAddrAndSelector: args.callAddrAndSelector});
return;
}
}
// if not found, don't check
}

function _installMode1(bytes32 id, ChainPolicyArgs calldata args) internal {
for (uint256 i = 0; i < args.chainIds.length; i++) {
if (args.chainIds[i] == block.chainid) {
// if found, don't check
return;
}
}
config[id][msg.sender] = ChainPolicyConfig({check: true, callAddrAndSelector: args.callAddrAndSelector});
}

function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp)
external
payable
override
returns (uint256)
{
ChainPolicyConfig storage thisConfig = config[id][msg.sender];
if (thisConfig.check) {
_checkCallData(userOp.callData, thisConfig.callAddrAndSelector);
}
return 0;
}

function checkSignaturePolicy(bytes32 id, address, bytes32, bytes calldata)
external
view
override
returns (uint256)
{
ChainPolicyConfig memory thisConfig = config[id][msg.sender];
if (thisConfig.check) {
// this is not allowed for this id
return 1;
}
return 0;
}

function _checkCallData(bytes calldata callData, bytes24[] storage callAddrAndSelector) internal {
if (bytes4(callData[0:4]) == IAccountExecute.executeUserOp.selector) {
callData = callData[4:];
}
require(bytes4(callData[0:4]) == IERC7579Account.execute.selector);
bytes32 mode = bytes32(callData[4:36]);
bytes1 callType = LibERC7579.getCallType(mode);
bytes calldata executionCallData = callData[36:];
if (callType == CALLTYPE_SINGLE) {
(address target, uint256 value, bytes calldata cd) = LibERC7579.decodeSingle(executionCallData);
bool permissionPass = _checkPermission(target, cd, value, callAddrAndSelector);
if (!permissionPass) {
revert CallViolatesParamRule();
}
} else if (callType == CALLTYPE_BATCH) {
bytes32[] calldata pointers = LibERC7579.decodeBatch(executionCallData);
for (uint256 i = 0; i < pointers.length; i++) {
(address target, uint256 value, bytes calldata cd) = LibERC7579.getExecution(pointers, i);
bool permissionPass = _checkPermission(target, cd, value, callAddrAndSelector);
if (!permissionPass) {
revert CallViolatesParamRule();
}
}
} else {
revert NotSupported();
}
}

function _checkPermission(address target, bytes calldata data, uint256, bytes24[] storage allowed)
internal
returns (bool)
{
for (uint256 i = 0; i < allowed.length; i++) {
address t = address(bytes20(allowed[i]));
bytes4 selector = bytes4(uint32(uint192(allowed[i])));
if (target == t && bytes4(data[0:4]) == selector) {
return true;
}
}
return false;
}
}
11 changes: 8 additions & 3 deletions src/policies/SignaturePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ contract SignaturePolicy is PolicyBase, IStatelessValidatorWithSender {
mapping(bytes32 id => mapping(address => Status)) public status;
mapping(bytes32 id => mapping(address caller => mapping(address wallet => bool))) public allowedCaller;

function isModuleType(uint256 typeID) external pure override(IModule,PolicyBase) returns (bool) {
function isModuleType(uint256 typeID) external pure override(IModule, PolicyBase) returns (bool) {
return typeID == MODULE_TYPE_POLICY || typeID == MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER;
}

function isInitialized(address wallet) external view override(IModule,PolicyBase) returns (bool) {
function isInitialized(address wallet) external view override(IModule, PolicyBase) returns (bool) {
return usedIds[wallet] > 0;
}

function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) external payable override returns (uint256) {
function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp)
external
payable
override
returns (uint256)
{
return _validateUserOpPolicy(id, msg.sender);
}

Expand Down
10 changes: 4 additions & 6 deletions src/policies/TimelockPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -420,12 +420,10 @@ contract TimelockPolicy is PolicyBase, IStatelessValidator, IStatelessValidatorW
* @notice Internal function to validate user operation policy
* @dev Shared logic for both installed and stateless validator modes
*/
function _validateUserOpPolicy(
bytes32 id,
PackedUserOperation calldata userOp,
bytes calldata sig,
address account
) internal returns (uint256) {
function _validateUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp, bytes calldata sig, address account)
internal
returns (uint256)
{
TimelockConfig storage config = timelockConfig[id][account];
if (!config.initialized) return SIG_VALIDATION_FAILED_UINT;

Expand Down
7 changes: 3 additions & 4 deletions src/signers/ECDSASigner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ contract ECDSASigner is SignerBase, IStatelessValidator, IStatelessValidatorWith
returns (uint256)
{
address owner = signer[id][msg.sender];
return
_verifySignature(userOpHash, userOp.signature, owner)
? SIG_VALIDATION_SUCCESS_UINT
: SIG_VALIDATION_FAILED_UINT;
return _verifySignature(userOpHash, userOp.signature, owner)
? SIG_VALIDATION_SUCCESS_UINT
: SIG_VALIDATION_FAILED_UINT;
}

function checkSignature(bytes32 id, address sender, bytes32 hash, bytes calldata sig)
Expand Down
12 changes: 6 additions & 6 deletions src/signers/WeightedECDSASigner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
return _validateStatelessSignature(hash, signature, guardians, weights, threshold);
}

function validateSignatureWithDataWithSender(
address,
bytes32 hash,
bytes calldata signature,
bytes calldata data
) external view override(IStatelessValidatorWithSender) returns (bool) {
function validateSignatureWithDataWithSender(address, bytes32 hash, bytes calldata signature, bytes calldata data)
external
view
override(IStatelessValidatorWithSender)
returns (bool)
{
(address[] memory guardians, uint24[] memory weights, uint24 threshold) =
abi.decode(data, (address[], uint24[], uint24));
return _validateStatelessSignature(hash, signature, guardians, weights, threshold);
Expand Down
3 changes: 3 additions & 0 deletions src/types/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ uint256 constant MODULE_TYPE_SIGNER = 6;
uint256 constant MODULE_TYPE_STATELESS_VALIDATOR = 7;
uint256 constant MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER = 10;

bytes1 constant CALLTYPE_SINGLE = 0x00;
bytes1 constant CALLTYPE_BATCH = 0x01;

bytes4 constant ERC1271_MAGICVALUE = 0x1626ba7e;
bytes4 constant ERC1271_INVALID = 0xffffffff;
uint256 constant SIG_VALIDATION_FAILED_UINT = 1;
Expand Down
12 changes: 12 additions & 0 deletions src/types/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ type ValidUntil is uint48;
function packValidationData(ValidAfter validAfter, ValidUntil validUntil) pure returns (uint256) {
return uint256(ValidAfter.unwrap(validAfter)) << 208 | uint256(ValidUntil.unwrap(validUntil)) << 160;
}

// Custom type for improved developer experience
type ExecMode is bytes32;

type CallType is bytes1;

type ExecType is bytes1;

type ExecModeSelector is bytes4;

type ExecModePayload is bytes22;

7 changes: 3 additions & 4 deletions src/validators/ECDSAValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ contract ECDSAValidator is IValidator, IHook, IStatelessValidator, IStatelessVal
returns (uint256)
{
address owner = ecdsaValidatorStorage[msg.sender].owner;
return
_verifySignature(userOpHash, userOp.signature, owner)
? SIG_VALIDATION_SUCCESS_UINT
: SIG_VALIDATION_FAILED_UINT;
return _verifySignature(userOpHash, userOp.signature, owner)
? SIG_VALIDATION_SUCCESS_UINT
: SIG_VALIDATION_FAILED_UINT;
}

function isValidSignatureWithSender(address, bytes32 hash, bytes calldata sig)
Expand Down
Loading