From b7d1494d4dfe6e302fb3bb6b48cddd0b2caeddc6 Mon Sep 17 00:00:00 2001 From: Qi Zhou Date: Fri, 1 Nov 2024 05:02:14 +0800 Subject: [PATCH] feat: add ERC20 as gas fee (#15) * add ERC20 as fee * add feeToken to ERC20 as fee tutorial * add more description * address comments * Update README.md --------- Co-authored-by: Qi Zhou --- README.md | 1 + chapter1/contracts/src/ERC20Fee.sol | 39 +++++++++++++++ chapter1/erc20-fee/README.md | 77 +++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 chapter1/contracts/src/ERC20Fee.sol create mode 100644 chapter1/erc20-fee/README.md diff --git a/README.md b/README.md index 98ff2eb..23cd3eb 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This repository provides a step-by-step walk through for builders interested in - [Delegate an account to a p256 key](./chapter1/delegate-p256/): Describes how EIP-7702+EIP-7212 provide the ability to sign a message with a P256 key - [BLS Multisig](./chapter1/bls-multisig/): In-depth walk-through how to implement a Multisig based on BLS signatures verified through precompiles from EIP-2537 - [EOF](./chapter1/eof/): Instructions on how to deploy and inspect contracts in the new EOF format +- [ERC20 Fee](./chapter1/erc20-fee/): Describes how EIP-7702 provides the ability to pay ERC20 as gas fee to the gas sponsor. ### Build & Test diff --git a/chapter1/contracts/src/ERC20Fee.sol b/chapter1/contracts/src/ERC20Fee.sol new file mode 100644 index 0000000..4f3903b --- /dev/null +++ b/chapter1/contracts/src/ERC20Fee.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +interface IERC20 { + function transfer(address _to, uint256 _value) external returns (bool success); +} + +contract TestERC20 { + mapping (address => uint256) public balance; + + function transfer(address _to, uint256 _value) public returns (bool success) { + balance[msg.sender] -= _value; + balance[_to] += _value; + return true; + } + + function mint(address _to, uint256 _value) public { + balance[_to] += _value; + } +} + +/// @notice Contract designed for being delegated to by EOAs to authorize an ERC20 transfer with ERC20 as fee. +contract ERC20Fee { + + /// @notice Internal nonce used for replay protection, must be tracked and included into prehashed message. + uint256 public nonce; + + /// @notice Main entrypoint to send tx. + function execute(address to, bytes memory data, uint256 value, IERC20 feeToken, uint256 fee, uint8 v, bytes32 r, bytes32 s) public { + bytes32 digest = keccak256(abi.encode(nonce++, to, data, value, feeToken, fee)); + address addr = ecrecover(digest, v, r, s); + + require(addr == address(this)); + + (bool success,) = to.call{value: value}(data); + require(success, "call failed"); + require(feeToken.transfer(msg.sender, fee)); + } +} \ No newline at end of file diff --git a/chapter1/erc20-fee/README.md b/chapter1/erc20-fee/README.md new file mode 100644 index 0000000..546ef2f --- /dev/null +++ b/chapter1/erc20-fee/README.md @@ -0,0 +1,77 @@ +# Simple 7702 demo to pay gas fee using ERC20 + +This example demonstrates how EIP-7702 allows Alice to authorize a smart contract to execute an ERC20 transfer and pay fee in ERC20 to Bob, who sponsors the gas fees for a seamless experience. + +## Steps involved + +- Start local anvil node with Odyssey features enabled + +```bash +anvil --odyssey +``` + +- Anvil comes with pre-funded developer accounts which we can use for the example going forward + +```bash +# using anvil dev accounts +export ALICE_ADDRESS="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +export ALICE_PK="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" +export BOB_PK="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" +export BOB_ADDRESS="0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" +export CHARLES_ADDRESS="0x90F79bf6EB2c4f870365E785982E1f101E93b906" +``` + +- Deploy ERC20 and mint tokens +```bash +forge create TestERC20 --private-key $BOB_PK +export ERC20= +cast send $ERC20 'mint(address,uint256)' $ALICE_ADDRESS 10000000000000000000 --private-key $BOB_PK # 10E9 tokens +cast call $ERC20 'balance(address)' $ALICE_ADDRESS +``` + +- We need to deploy a contract which verifies the user signature and execute ERC20 transfers.: + +```bash +forge create ERC20Fee --private-key $BOB_PK + +export ERC20_FEE="" +``` + +- Alice (delegator) can sign a message which will delegate all calls to her address to the bytecode of smart contract we've just deployed. + +First, let's verify that we don't have a smart contract yet associated to Alice's account, if that's the case the command below should return a `0x` response: + +```bash +$ cast code $ALICE_ADDRESS +0x +``` + + +- Alice can sign an EIP-7702 authorization using `cast wallet sign-auth` as follows: + +```bash +SIGNED_AUTH=$(cast wallet sign-auth $ERC20_FEE --private-key $ALICE_PK) +``` + +- Alice can sign an off-chain data to authorize anyone to send ERC20 on behave of Alice in exchange of ERC20 fee + +```bash +ERC20_TRANSFER_CALLDATA=$(cast calldata 'transfer(address,uint256)' $CHARLES_ADDRESS 1000000000000000000) +SIGNED=$(cast wallet sign --no-hash $(cast keccak256 $(cast abi-encode 'f(uint256,address,bytes,uint256,address,uint256)' 0 $ERC20 $ERC20_TRANSFER_CALLDATA 0 $ERC20 1000)) --private-key $ALICE_PK) +V=$(echo $SIGNED | cut -b 1-2,131-132) +R=$(echo $SIGNED | cut -b 1-66) +S=$(echo $SIGNED | cut -b 1-2,67-130) +``` + +- Bob (delegate) relays the transaction on Alice's behalf using his own private key and thereby paying gas fee from his account and get the ERC20 fee: + +```bash +cast send $ALICE_ADDRESS "execute(address,bytes,uint256,address,uint256,uint8,bytes32,bytes32)" $ERC20 $ERC20_TRANSFER_CALLDATA 0 $ERC20 1000 $V $R $S --private-key $BOB_PK --auth $SIGNED_AUTH +``` + +- Bob will receive the ERC20 token as the fee, and Charles will receive the ERC20 token +```bash +cast call $ERC20 "balance(address)" $BOB_ADDRESS +cast call $ERC20 "balance(address)" $CHARLES_ADDRESS +``` +