EIP7702
EIP7702 允许扩展 EOA
地。
- 升级后的核心有点由于支持批量交易,允许
EOA
地址像合约一样执行底层call
- 允许
gas
代付,通过签名校验允许任意地址提交自身交易(只需要给自己转账,附加上calldata
) - 由于升级成合约,可以扩展更加灵活性的功能
EOA
地址扩展的合约 Code
为(0xef0100 || address)
,因为根据EIP3541不允许部署 0xef 开头的合约。
GetCodes
0xef0100
开头的 bytecodes
表示当前地址是扩展的 EOA
地址
func AccountCodes(address common.Address) string {
res, err := client.CodeAt(context.Background(), address, nil)
if err != nil {
log.Fatal(err)
}
log.Printf("codes: %s", hexutil.Encode(res))
//0xef010080296ff8d1ed46f8e3c7992664d13b833504c2bb
return hexutil.Encode(res)
}
因此,通过下面代码能够判断出当前地址是否是扩展的 EOA
地址
bytes3 internal constant _DELEGATION_PREFIX = 0xef0100;
function isEOA(address target) internal view returns (bool) {
bytes memory code = target.code;
// Add here a comment addressing the normal disclaimers around
// construction-time issues and zero-length contracts.
return (code.length == 0 || bytes3(code) == _DELEGATION_PREFIX);
}
/**
* @dev Returns the address of the delegate if `account` as an EIP-7702 delegation setup, or address(0) otherwise.
*/
function fetchDelegate(address account) internal view returns (address) {
bytes23 delegation = bytes23(account.code);
return bytes3(delegation) == EIP7702_PREFIX ? address(bytes20(delegation << 24)) : address(0);
}
合约验签:
abstract contract SignerERC7702 is AbstractSigner {
/**
* @dev Validates the signature using the EOA's address (i.e. `address(this)`).
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return address(this) == recovered && err == ECDSA.RecoverError.NoError;
}
}
OKX WalletCore
OKX
钱包升级升级 7702
后,和该钱包交互只需要执行 calldata
不为空的 transfer
交易, calldata 自动匹配代理合约的函数执行功能函数
代理合约主要存在三种类型交易:
executeFromSelf
只能 EOA
地址自己执行,在 transfer_To_Self
交易的同时附加上 calldata
,底层逻辑会自动匹配合约函数执行
modifier onlySelf() {
if (msg.sender != address(this)) revert Errors.NotFromSelf();
_;
}
function executeFromSelf(Call[] calldata calls) external onlySelf {
_batchCall(calls);
}
executeWithValidator
任意地址拿着有效签名都可以发送交易,其中签名的 Validator
可以是 EOA
本身或者是 EOA
指定的白名单地址
这个函数就允许外部地址代付 Gas
,任意外部地址只要拿着有效签名就能执行升级 7702_EOA
钱包的批量操作
升级后的 EOA
地址会成为一个 Storage
合约的 Owner
Storage
内部存储自增的nonce
保证签名单次使用Storage
中能够管理Validator
白名单
在发送交易时,根据 Validator 类型有不同的验签流程:
- Validator == address(1),
- 表示
Validator
是EOA Self
,要求signer == EOA
- 表示
- Validator 是其余白名单地址时,要求签名满足 Validator 合约的内部逻辑
try IValidator(validator).validate(typedDataHash, validationData)
- 如果
Validator
被设置成纯EOA
地址的话,底层call
调用将永远会返回true
,也就是将会绕过签名校验的流程 - 这就需要
EOA-7702
谨慎设置validator
白名单
function executeWithValidator(
Call[] calldata calls,
address validator,
bytes calldata validationData
) external onlyValidator(calls, validator, validationData) {
_batchCall(calls);
}
executeFromExecutor
在 executeWithValidator
基础功能上继续扩展数据结构,增加 Session
结构体限制:
struct Session {
uint256 id;
address executor;
address validator;
uint256 validUntil;
uint256 validAfter;
bytes preHook;
bytes postHook;
bytes signature;
}
- 限制交易只能由
Executor
执行 - 整个
Session
结构和批量转账数据必须通过Validator
的验签流程 - 交易还能设置有效期、
Hook
功能校验
function validateSession(Session calldata session) public view {
// Check executor authorization
if (msg.sender != session.executor) revert Errors.InvalidExecutor();
// Check time bounds
if (
session.validAfter > block.timestamp ||
block.timestamp > session.validUntil
) revert Errors.InvalidSession();
// Check invalidSessionId & validValidator in storage
getMainStorage().validateSession(session.id, session.validator);
// Validate signature
bytes32 hash = getSessionTypedHash(session);
bool isValid = WalletCoreLib.validate(
session.validator,
hash,
session.signature
);
if (!isValid) revert Errors.InvalidSignature();
}
Preference
https://github.com/okx/wallet-core
https://ethereum.org/roadmap/pectra/7702
https://eips.ethereum.org/EIPS/eip-7702
https://etherscan.io/tx/0xebc6f503d3c7cf9560308c8efef69a6c4a993a29224d7a4a4f13ca98d6ae0621