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