-
Notifications
You must be signed in to change notification settings - Fork 682
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
AA-521 EntryPoint support for eip-7702 #529
base: develop
Are you sure you want to change the base?
Changes from all commits
db002af
edb2255
dc43285
fc0c4e7
263a232
5b052ae
38d4714
5f0a6b7
68894e4
317d26b
2617f03
5052317
b8a9d26
d9b3f77
24b5473
fafeca8
f98cd95
fb06bc3
69499d0
bac3113
e54c2a7
146dbbe
0860e41
f25a26b
ee54899
33b294a
ea09602
8dafd94
c026b2e
1442357
b088f05
6459a16
192be39
26d6a0b
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 |
---|---|---|
@@ -1,8 +1,8 @@ | ||
module.exports = { | ||
skipFiles: [ | ||
"test", | ||
"samples/bls/lib", | ||
"utils/Exec.sol" | ||
"utils/Exec.sol", | ||
"samples" | ||
], | ||
configureYulOptimizer: true, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
pragma solidity ^0.8; | ||
|
||
import "../interfaces/PackedUserOperation.sol"; | ||
import "../core/UserOperationLib.sol"; | ||
// SPDX-License-Identifier: MIT | ||
|
||
// EIP-7702 code prefix. Also, we use this prefix as a marker in the initCode. To specify this account is EIP-7702. | ||
uint256 constant EIP7702_PREFIX = 0xef0100; | ||
|
||
using UserOperationLib for PackedUserOperation; | ||
|
||
//get alternate InitCodeHash (just for UserOp hash) when using EIP-7702 | ||
function _getEip7702InitCodeHashOverride(PackedUserOperation calldata userOp) view returns (bytes32) { | ||
bytes calldata initCode = userOp.initCode; | ||
if (!_isEip7702InitCode(initCode)) { | ||
return 0; | ||
} | ||
address delegate = _getEip7702Delegate(userOp.getSender()); | ||
if (initCode.length <= 20) | ||
return keccak256(abi.encodePacked(delegate)); | ||
else | ||
return keccak256(abi.encodePacked(delegate, initCode[20 :])); | ||
} | ||
|
||
|
||
function _isEip7702InitCode(bytes calldata initCode) pure returns (bool) { | ||
|
||
if (initCode.length < 2) { | ||
return false; | ||
} | ||
uint256 initCodeStart; | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly ("memory-safe") { | ||
initCodeStart := calldataload(initCode.offset) | ||
} | ||
// make sure first 20 bytes of initCode are "0xff0100" (padded with zeros) | ||
// initCode can be shorter (e.g. only 3), but then it is already zero-padded. | ||
return (initCodeStart >> (256 - 160)) == ((EIP7702_PREFIX << (160 - 24))); | ||
} | ||
|
||
/** | ||
* get the EIP-7702 delegate from contract code. | ||
* requires EXTCODECOPY pr: https://github.com/ethereum/EIPs/pull/9248 (not yet merged or implemented) | ||
**/ | ||
function _getEip7702Delegate(address sender) view returns (address) { | ||
|
||
uint256 senderCode; | ||
|
||
// solhint-disable-next-line no-inline-assembly | ||
assembly ("memory-safe") { | ||
extcodecopy(sender, 0, 0, 32) | ||
senderCode := mload(0) | ||
} | ||
Comment on lines
+49
to
+53
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. You don't need any assembly, you can just do 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.
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.
I think Solidity now supports just converting
I am concerned some up-and-coming L2s may be a little less rigorous in their implementation of EIP-3541 and that would allow some kind of an attack down the line. It would be dirt-cheap to just double-check. |
||
// senderCode is the first 32 bytes of the sender's code | ||
// If it is an EIP-7702 delegate, then top 24 bits are the EIP7702_PREFIX | ||
// next 160 bytes are the delegate address | ||
if (senderCode >> (256 - 24) != EIP7702_PREFIX) { | ||
// instead of just "not an EIP-7702 delegate", if some info. | ||
require(sender.code.length > 0, "sender has no code"); | ||
//temp: sanity check for current EIP-7702 implementation. | ||
require(sender.code.length == 23, "EIP-7702 delegate-length"); | ||
revert("not an EIP-7702 delegate"); | ||
} | ||
return address(uint160(senderCode >> (256 - 160 - 24))); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// SPDX-License-Identifier: MIT | ||
// based on: https://gist.github.com/frangio/e40305b9f99de290b73750dff5ebe50a | ||
pragma solidity ^0.8; | ||
|
||
import "../interfaces/PackedUserOperation.sol"; | ||
import "../core/Helpers.sol"; | ||
import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
import "@openzeppelin/contracts/interfaces/IERC1271.sol"; | ||
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "../core/BaseAccount.sol"; | ||
|
||
/** | ||
* Simple7702Account.sol | ||
* A minimal account to be used with EIP-7702 (for batching) and ERC-4337 (for gas sponsoring) | ||
*/ | ||
contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC721Holder { | ||
|
||
// temporary address of entryPoint v0.8 | ||
function entryPoint() public pure override returns (IEntryPoint) { | ||
return IEntryPoint(0x6F4F5099a64044D69EB7419d66760fD4106fcE3C); | ||
} | ||
|
||
/** | ||
* Make this account callable through ERC-4337 EntryPoint. | ||
* The UserOperation should be signed by this account's private key. | ||
*/ | ||
function _validateSignature( | ||
PackedUserOperation calldata userOp, | ||
bytes32 userOpHash | ||
) internal virtual override returns (uint256 validationData) { | ||
|
||
if (address(this) != ECDSA.recover(userOpHash, userOp.signature)) { | ||
return SIG_VALIDATION_FAILED; | ||
} | ||
return 0; | ||
} | ||
|
||
function _requireFromSelfOrEntryPoint() internal view virtual { | ||
require( | ||
msg.sender == address(this) || | ||
msg.sender == address(entryPoint()), | ||
"not from self or EntryPoint" | ||
); | ||
} | ||
|
||
struct Call { | ||
address target; | ||
uint256 value; | ||
bytes data; | ||
} | ||
|
||
function execute(Call[] calldata calls) external { | ||
_requireFromSelfOrEntryPoint(); | ||
|
||
for (uint256 i = 0; i < calls.length; i++) { | ||
Call calldata call = calls[i]; | ||
(bool ok, bytes memory ret) = call.target.call{value: call.value}(call.data); | ||
if (!ok) { | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { revert(add(ret, 32), mload(ret)) } | ||
} | ||
} | ||
} | ||
|
||
function supportsInterface(bytes4 id) public override(ERC1155Holder, IERC165) pure returns (bool) { | ||
return | ||
id == type(IERC165).interfaceId || | ||
id == type(IAccount).interfaceId || | ||
id == type(IERC1271).interfaceId || | ||
id == type(IERC1155Receiver).interfaceId || | ||
id == type(IERC721Receiver).interfaceId; | ||
} | ||
|
||
//deliberately return the same signature as returned by the EOA itself: This way, | ||
// ERC-1271 can be used regardless if the account currently has this code or not. | ||
function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) { | ||
return ECDSA.recover(hash, signature) == address(this) ? this.isValidSignature.selector : bytes4(0); | ||
} | ||
|
||
receive() external payable { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
pragma solidity ^0.8.23; | ||
// SPDX-License-Identifier: MIT | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "../core/BaseAccount.sol"; | ||
import "../core/Eip7702Support.sol"; | ||
|
||
contract TestEip7702DelegateAccount is BaseAccount { | ||
|
||
IEntryPoint private immutable _entryPoint; | ||
bool public testInitCalled; | ||
|
||
constructor(IEntryPoint anEntryPoint) { | ||
_entryPoint = anEntryPoint; | ||
} | ||
|
||
function testInit() public { | ||
testInitCalled = true; | ||
} | ||
|
||
function entryPoint() public view override virtual returns (IEntryPoint) { | ||
return _entryPoint; | ||
} | ||
|
||
// Require the function call went through EntryPoint or owner | ||
function _requireFromEntryPointOrOwner() internal view { | ||
require(msg.sender == address(this) || msg.sender == address(entryPoint()), "account: not Owner or EntryPoint"); | ||
} | ||
|
||
/** | ||
* execute a transaction (called directly from owner, or by entryPoint) | ||
* @param dest destination address to call | ||
* @param value the value to pass in this call | ||
* @param func the calldata to pass in this call | ||
*/ | ||
function execute(address dest, uint256 value, bytes calldata func) external { | ||
_requireFromEntryPointOrOwner(); | ||
(bool success,) = dest.call{value: value}(func); | ||
require(success, "call failed"); | ||
} | ||
|
||
function _validateSignature( | ||
PackedUserOperation calldata userOp, | ||
bytes32 userOpHash | ||
) internal virtual override returns (uint256 validationData) { | ||
if (userOp.initCode.length > 20) { | ||
require(testInitCalled, "testInit not called"); | ||
} | ||
if (ECDSA.recover(userOpHash, userOp.signature) == address(this)) { | ||
return 0; | ||
} | ||
return 1; | ||
} | ||
} |
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.
If you write this function without the assembly tricks, how much more gas does it spend? This code is too elaborate for the task of "compare first three bytes".