-
Notifications
You must be signed in to change notification settings - Fork 56
Add EIP-712 support for Account v2 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: jw/account-v2
Are you sure you want to change the base?
Changes from 4 commits
0aad6b0
e037035
1c3bfe6
5718d93
3ef7af2
fb86fab
600be8f
7d856f7
7de324f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import "openzeppelin-contracts/token/ERC1155/IERC1155Receiver.sol"; | |
import "openzeppelin-contracts/interfaces/IERC1271.sol"; | ||
import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol"; | ||
import "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol"; | ||
import "openzeppelin-contracts/utils/cryptography/EIP712.sol"; | ||
|
||
import {BaseAccount as BaseERC4337Account, IEntryPoint, UserOperation} from "account-abstraction/core/BaseAccount.sol"; | ||
|
||
|
@@ -34,7 +35,8 @@ contract Account is | |
IERC721Receiver, | ||
IERC1155Receiver, | ||
UUPSUpgradeable, | ||
BaseERC4337Account | ||
BaseERC4337Account, | ||
EIP712 | ||
{ | ||
using ECDSA for bytes32; | ||
|
||
|
@@ -81,9 +83,12 @@ contract Account is | |
_; | ||
} | ||
|
||
constructor(address _guardian, address entryPoint_) { | ||
if (_guardian == address(0) || entryPoint_ == address(0)) | ||
constructor(address _guardian, address entryPoint_, string memory _name, string memory _version) | ||
EIP712(_name, _version) | ||
{ | ||
if (_guardian == address(0) || entryPoint_ == address(0)) { | ||
revert InvalidInput(); | ||
} | ||
|
||
_entryPoint = entryPoint_; | ||
guardian = _guardian; | ||
|
@@ -352,10 +357,26 @@ contract Account is | |
UserOperation calldata userOp, | ||
bytes32 userOpHash | ||
) internal view override returns (uint256 validationData) { | ||
bool isValid = this.isValidSignature( | ||
userOpHash.toEthSignedMessageHash(), | ||
userOp.signature | ||
) == IERC1271.isValidSignature.selector; | ||
bytes32 hashStruct = keccak256( | ||
abi.encode( | ||
keccak256( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the hash of this typed data a public constant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made an immutable variable so the hash is not computed each time |
||
"UserOp(address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData)" | ||
0xyanc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
), | ||
userOp.sender, | ||
userOp.nonce, | ||
userOp.initCode, | ||
userOp.callData, | ||
userOp.callGasLimit, | ||
userOp.verificationGasLimit, | ||
userOp.preVerificationGas, | ||
userOp.maxFeePerGas, | ||
userOp.maxPriorityFeePerGas, | ||
userOp.paymasterAndData | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep! Also passed into the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had to copy the UserOperation in memory as I get "Stack too deep" error otherwise |
||
) | ||
); | ||
|
||
bool isValid = | ||
this.isValidSignature(_hashTypedDataV4(hashStruct), userOp.signature) == IERC1271.isValidSignature.selector; | ||
|
||
if (isValid) { | ||
return 0; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,13 @@ contract AccountERC4337Test is Test { | |
function setUp() public { | ||
entryPoint = new EntryPoint(); | ||
guardian = new AccountGuardian(); | ||
implementation = new Account(address(guardian), address(entryPoint)); | ||
implementation = new Account( | ||
address(guardian), | ||
address(entryPoint), | ||
"ERC6551-Account", | ||
"1" | ||
); | ||
|
||
registry = new ERC6551Registry(); | ||
|
||
tokenCollection = new MockERC721(); | ||
|
@@ -135,12 +141,9 @@ contract AccountERC4337Test is Test { | |
paymasterAndData: "", | ||
signature: "" | ||
}); | ||
bytes32 opHash = _buildOpHash(op, accountAddress); | ||
|
||
bytes32 opHash = entryPoint.getUserOpHash(op); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign( | ||
1, | ||
opHash.toEthSignedMessageHash() | ||
); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(1, opHash); | ||
|
||
bytes memory signature = abi.encodePacked(r, s, v); | ||
op.signature = signature; | ||
|
@@ -196,11 +199,8 @@ contract AccountERC4337Test is Test { | |
signature: "" | ||
}); | ||
|
||
bytes32 opHash = entryPoint.getUserOpHash(op); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign( | ||
1, | ||
opHash.toEthSignedMessageHash() | ||
); | ||
bytes32 opHash = _buildOpHash(op, accountAddress); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(1, opHash); | ||
|
||
bytes memory signature = abi.encodePacked(r, s, v); | ||
op.signature = signature; | ||
|
@@ -256,11 +256,8 @@ contract AccountERC4337Test is Test { | |
signature: "" | ||
}); | ||
|
||
bytes32 opHash = entryPoint.getUserOpHash(op); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign( | ||
1, | ||
opHash.toEthSignedMessageHash() | ||
); | ||
bytes32 opHash = _buildOpHash(op, accountAddress); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(1, opHash); | ||
|
||
// invalidate signature | ||
bytes memory signature = abi.encodePacked(r, s, v + 1); | ||
|
@@ -276,4 +273,39 @@ contract AccountERC4337Test is Test { | |
|
||
assertEq(accountAddress.balance, 1 ether); | ||
} | ||
|
||
function _buildOpHash( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we expose this as a public getter on the account contract? So that it can be accessed easily by clients. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exposed buildUserOp712Hash(UserOperation memory op, bytes32 opHash) in Account.sol |
||
UserOperation memory op, | ||
address accountAddress | ||
) private view returns (bytes32 opHash) { | ||
bytes32 domainSeparator = keccak256( | ||
abi.encode( | ||
keccak256( | ||
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" | ||
), | ||
keccak256(bytes("ERC6551-Account")), | ||
keccak256(bytes("1")), | ||
block.chainid, | ||
address(accountAddress) | ||
) | ||
); | ||
bytes32 hashStruct = keccak256( | ||
abi.encode( | ||
keccak256( | ||
"UserOp(address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData)" | ||
), | ||
op.sender, | ||
op.nonce, | ||
op.initCode, | ||
op.callData, | ||
op.callGasLimit, | ||
op.verificationGasLimit, | ||
op.preVerificationGas, | ||
op.maxFeePerGas, | ||
op.maxPriorityFeePerGas, | ||
op.paymasterAndData | ||
) | ||
); | ||
opHash = ECDSA.toTypedDataHash(domainSeparator, hashStruct); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the
name
andversion
fields constant instead of passed as constructor arguments? Makes it easier to reason about when deploying :)