Skip to content

Commit f21af4c

Browse files
authored
include actual interfaces for ICoinbaseSmartWallet (ourzora#457)
1 parent 55c2312 commit f21af4c

File tree

10 files changed

+519
-16
lines changed

10 files changed

+519
-16
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

4-
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
4+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
55

66
contract ZoraAccountManager is ERC1967Proxy {
7-
constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) { }
7+
constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {}
88
}

packages/smart-wallet/src/ZoraAccountManagerImpl.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

4-
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
5-
import { UUPSUpgradeable, ERC1967Utils } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
4+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
5+
import {UUPSUpgradeable, ERC1967Utils} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
66

7-
import { IEntryPoint } from "./interfaces/IEntryPoint.sol";
8-
import { ISmartWalletFactory } from "./interfaces/ISmartWalletFactory.sol";
7+
import {IEntryPoint} from "./interfaces/IEntryPoint.sol";
8+
import {ICoinbaseSmartWalletFactory} from "./interfaces/ICoinbaseSmartWalletFactory.sol";
99

1010
contract ZoraAccountManagerImpl is UUPSUpgradeable, OwnableUpgradeable {
1111
IEntryPoint public constant entryPoint = IEntryPoint(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789);
12-
ISmartWalletFactory public constant smartWalletFactory = ISmartWalletFactory(0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a);
12+
ICoinbaseSmartWalletFactory public constant smartWalletFactory = ICoinbaseSmartWalletFactory(0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a);
1313

14-
constructor() initializer { }
14+
constructor() initializer {}
1515

1616
function initialize(address initialOwner) public initializer {
1717
__Ownable_init(initialOwner);
@@ -51,5 +51,5 @@ contract ZoraAccountManagerImpl is UUPSUpgradeable, OwnableUpgradeable {
5151
return ERC1967Utils.getImplementation();
5252
}
5353

54-
function _authorizeUpgrade(address) internal override onlyOwner { }
54+
function _authorizeUpgrade(address) internal override onlyOwner {}
5555
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.7.5;
3+
4+
/**
5+
* User Operation struct
6+
* @param sender - The sender account of this request.
7+
* @param nonce - Unique value the sender uses to verify it is not a replay.
8+
* @param initCode - If set, the account contract will be created by this constructor/
9+
* @param callData - The method call to execute on this account.
10+
* @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call.
11+
* @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid.
12+
* Covers batch overhead.
13+
* @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters.
14+
* @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data
15+
* The paymaster will pay for the transaction instead of the sender.
16+
* @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID.
17+
*/
18+
struct PackedUserOperation {
19+
address sender;
20+
uint256 nonce;
21+
bytes initCode;
22+
bytes callData;
23+
bytes32 accountGasLimits;
24+
uint256 preVerificationGas;
25+
bytes32 gasFees;
26+
bytes paymasterAndData;
27+
bytes signature;
28+
}
29+
30+
interface IAccount {
31+
/**
32+
* Validate user's signature and nonce
33+
* the entryPoint will make the call to the recipient only if this validation call returns successfully.
34+
* signature failure should be reported by returning SIG_VALIDATION_FAILED (1).
35+
* This allows making a "simulation call" without a valid signature
36+
* Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure.
37+
*
38+
* @dev Must validate caller is the entryPoint.
39+
* Must validate the signature and nonce
40+
* @param userOp - The operation that is about to be executed.
41+
* @param userOpHash - Hash of the user's request data. can be used as the basis for signature.
42+
* @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint.
43+
* This is the minimum amount to transfer to the sender(entryPoint) to be
44+
* able to make the call. The excess is left as a deposit in the entrypoint
45+
* for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()".
46+
* In case there is a paymaster in the request (or the current deposit is high
47+
* enough), this value will be zero.
48+
* @return validationData - Packaged ValidationData structure. use `_packValidationData` and
49+
* `_unpackValidationData` to encode and decode.
50+
* <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure,
51+
* otherwise, an address of an "authorizer" contract.
52+
* <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite"
53+
* <6-byte> validAfter - First timestamp this operation is valid
54+
* If an account doesn't use time-range, it is enough to
55+
* return SIG_VALIDATION_FAILED value (1) for signature failure.
56+
* Note that the validation code cannot use block.timestamp (or block.number) directly.
57+
*/
58+
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData);
59+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.23;
3+
4+
import {IAccount} from "./IAccount.sol";
5+
import {UserOperation} from "./UserOperation.sol";
6+
import {IUUPSUpgradeable} from "./IUUPSUpgradeable.sol";
7+
8+
import {IERC1271} from "./IERC1271.sol";
9+
import {IMultiOwnable} from "./IMultiOwnable.sol";
10+
11+
/// @title Coinbase Smart Wallet - extracted interface
12+
///
13+
/// @notice ERC-4337-compatible smart account, based on Solady's ERC4337 account implementation
14+
/// with inspiration from Alchemy's LightAccount and Daimo's DaimoAccount.
15+
/// @dev This interface is the conversion of the CoinbaseSmartWallet.sol contract into an interface,
16+
// with all of its inherited classes converted into interfaces and included in this repo to minimize external dependencies.
17+
// The original contract was located at https://github.com/coinbase/smart-wallet/blob/6579faa7f7f7a7de75db92f93e92584764ae1aa2/src/CoinbaseSmartWallet.sol
18+
///
19+
/// @author Coinbase (https://github.com/coinbase/smart-wallet)
20+
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC4337.sol)
21+
interface ICoinbaseSmartWallet is IERC1271, IAccount, IMultiOwnable, IUUPSUpgradeable {
22+
/// @notice A wrapper struct used for signature validation so that callers
23+
/// can identify the owner that signed.
24+
struct SignatureWrapper {
25+
/// @dev The index of the owner that signed, see `MultiOwnable.ownerAtIndex`
26+
uint256 ownerIndex;
27+
/// @dev If `MultiOwnable.ownerAtIndex` is an Ethereum address, this should be `abi.encodePacked(r, s, v)`
28+
/// If `MultiOwnable.ownerAtIndex` is a external key, this should be `abi.encode(WebAuthnAuth)`.
29+
bytes signatureData;
30+
}
31+
32+
/// @notice Represents a call to make.
33+
struct Call {
34+
/// @dev The address to call.
35+
address target;
36+
/// @dev The value to send when making the call.
37+
uint256 value;
38+
/// @dev The data of the call.
39+
bytes data;
40+
}
41+
42+
/// @notice Thrown when `initialize` is called but the account already has had at least one owner.
43+
error Initialized();
44+
45+
/// @notice Thrown when a call is passed to `executeWithoutChainIdValidation` that is not allowed by
46+
/// `canSkipChainIdValidation`
47+
///
48+
/// @param selector The selector of the call.
49+
error SelectorNotAllowed(bytes4 selector);
50+
51+
/// @notice Thrown in validateUserOp if the key of `UserOperation.nonce` does not match the calldata.
52+
///
53+
/// @dev Calls to `this.executeWithoutChainIdValidation` MUST use `REPLAYABLE_NONCE_KEY` and
54+
/// calls NOT to `this.executeWithoutChainIdValidation` MUST NOT use `REPLAYABLE_NONCE_KEY`.
55+
///
56+
/// @param key The invalid `UserOperation.nonce` key.
57+
error InvalidNonceKey(uint256 key);
58+
59+
/// @notice Initializes the account with the `owners`.
60+
///
61+
/// @dev Reverts if the account has had at least one owner, i.e. has been initialized.
62+
///
63+
/// @param owners Array of initial owners for this account. Each item should be
64+
/// an ABI encoded Ethereum address, i.e. 32 bytes with 12 leading 0 bytes,
65+
/// or a 64 byte external key.
66+
function initialize(bytes[] calldata owners) external payable;
67+
68+
/// @notice Executes `calls` on this account (i.e. self call).
69+
///
70+
/// @dev Can only be called by the Entrypoint.
71+
/// @dev Reverts if the given call is not authorized to skip the chain ID validtion.
72+
/// @dev `validateUserOp()` will recompute the `userOpHash` without the chain ID before validating
73+
/// it if the `UserOperation.calldata` is calling this function. This allows certain UserOperations
74+
/// to be replayed for all accounts sharing the same address across chains. E.g. This may be
75+
/// useful for syncing owner changes.
76+
///
77+
/// @param calls An array of calldata to use for separate self calls.
78+
function executeWithoutChainIdValidation(bytes[] calldata calls) external payable;
79+
80+
/// @notice Executes the given call from this account.
81+
///
82+
/// @dev Can only be called by the Entrypoint or an owner of this account (including itself).
83+
///
84+
/// @param target The address to call.
85+
/// @param value The value to send with the call.
86+
/// @param data The data of the call.
87+
function execute(address target, uint256 value, bytes calldata data) external;
88+
89+
/// @notice Executes batch of `Call`s.
90+
///
91+
/// @dev Can only be called by the Entrypoint or an owner of this account (including itself).
92+
///
93+
/// @param calls The list of `Call`s to execute.
94+
function executeBatch(Call[] calldata calls) external;
95+
96+
/// @notice Returns the address of the EntryPoint v0.6.
97+
///
98+
/// @return The address of the EntryPoint v0.6
99+
function entryPoint() external view returns (address);
100+
101+
/// @notice Computes the hash of the `UserOperation` in the same way as EntryPoint v0.6, but
102+
/// leaves out the chain ID.
103+
///
104+
/// @dev This allows accounts to sign a hash that can be used on many chains.
105+
///
106+
/// @param userOp The `UserOperation` to compute the hash for.
107+
///
108+
/// @return The `UserOperation` hash, which does not depend on chain ID.
109+
function getUserOpHashWithoutChainId(UserOperation calldata userOp) external view returns (bytes32);
110+
111+
/// @notice Returns the implementation of the ERC1967 proxy.
112+
///
113+
/// @return $ The address of implementation contract.
114+
function implementation() external view returns (address $);
115+
116+
/// @notice Returns whether `functionSelector` can be called in `executeWithoutChainIdValidation`.
117+
///
118+
/// @param functionSelector The function selector to check.
119+
////
120+
/// @return `true` is the function selector is allowed to skip the chain ID validation, else `false`.
121+
function canSkipChainIdValidation(bytes4 functionSelector) external pure returns (bool);
122+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

4-
interface ISmartWalletFactory {
5-
function createAccount(bytes[] calldata owners, uint256 nonce) external;
4+
import {ICoinbaseSmartWallet} from "./ICoinbaseSmartWallet.sol";
5+
6+
interface ICoinbaseSmartWalletFactory {
7+
function createAccount(bytes[] calldata owners, uint256 nonce) external returns (ICoinbaseSmartWallet account);
8+
69
function getAddress(bytes[] calldata owners, uint256 nonce) external view returns (address);
710

811
function initCodeHash() external view returns (bytes32);
12+
913
function implementation() external view returns (address);
1014
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
// interface extracted from https://github.com/coinbase/smart-wallet/blob/main/src/ERC1271.sol
5+
6+
/// @title ERC-1271
7+
///
8+
/// @notice Abstract ERC-1271 implementation (based on Solady's) with guards to handle the same
9+
/// signer being used on multiple accounts.
10+
///
11+
/// @dev To prevent the same signature from being validated on different accounts owned by the samer signer,
12+
/// we introduce an anti cross-account-replay layer: the original hash is input into a new EIP-712 compliant
13+
/// hash. The domain separator of this outer hash contains the chain id and address of this contract, so that
14+
/// it cannot be used on two accounts (see `replaySafeHash()` for the implementation details).
15+
///
16+
/// @author Coinbase (https://github.com/coinbase/smart-wallet)
17+
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC1271.sol)
18+
interface IERC1271 {
19+
/// @notice Returns information about the `EIP712Domain` used to create EIP-712 compliant hashes.
20+
///
21+
/// @dev Follows ERC-5267 (see https://eips.ethereum.org/EIPS/eip-5267).
22+
///
23+
/// @return fields The bitmap of used fields.
24+
/// @return name The value of the `EIP712Domain.name` field.
25+
/// @return version The value of the `EIP712Domain.version` field.
26+
/// @return chainId The value of the `EIP712Domain.chainId` field.
27+
/// @return verifyingContract The value of the `EIP712Domain.verifyingContract` field.
28+
/// @return salt The value of the `EIP712Domain.salt` field.
29+
/// @return extensions The list of EIP numbers, that extends EIP-712 with new domain fields.
30+
function eip712Domain()
31+
external
32+
view
33+
returns (
34+
bytes1 fields,
35+
string memory name,
36+
string memory version,
37+
uint256 chainId,
38+
address verifyingContract,
39+
bytes32 salt,
40+
uint256[] memory extensions
41+
);
42+
43+
/// @notice Validates the `signature` against the given `hash`.
44+
///
45+
/// @dev This implementation follows ERC-1271. See https://eips.ethereum.org/EIPS/eip-1271.
46+
/// @dev IMPORTANT: Signature verification is performed on the hash produced AFTER applying the anti
47+
/// cross-account-replay layer on the given `hash` (i.e., verification is run on the replay-safe
48+
/// hash version).
49+
///
50+
/// @param hash The original hash.
51+
/// @param signature The signature of the replay-safe hash to validate.
52+
///
53+
/// @return result `0x1626ba7e` if validation succeeded, else `0xffffffff`.
54+
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 result);
55+
56+
/// @notice Wrapper around `_eip712Hash()` to produce a replay-safe hash fron the given `hash`.
57+
///
58+
/// @dev The returned EIP-712 compliant replay-safe hash is the result of:
59+
/// keccak256(
60+
/// \x19\x01 ||
61+
/// this.domainSeparator ||
62+
/// hashStruct(CoinbaseSmartWalletMessage({ hash: `hash`}))
63+
/// )
64+
///
65+
/// @param hash The original hash.
66+
///
67+
/// @return The corresponding replay-safe hash.
68+
function replaySafeHash(bytes32 hash) external view returns (bytes32);
69+
70+
/// @notice Returns the `domainSeparator` used to create EIP-712 compliant hashes.
71+
///
72+
/// @dev Implements domainSeparator = hashStruct(eip712Domain).
73+
/// See https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator.
74+
///
75+
/// @return The 32 bytes domain separator result.
76+
function domainSeparator() external view returns (bytes32);
77+
}

0 commit comments

Comments
 (0)