Skip to content

Commit

Permalink
feat: add ERC20 as gas fee (#15)
Browse files Browse the repository at this point in the history
* add ERC20 as fee

* add feeToken to ERC20 as fee tutorial

* add more description

* address comments

* Update README.md

---------

Co-authored-by: Qi Zhou <[email protected]>
  • Loading branch information
qizhou and Qi Zhou authored Oct 31, 2024
1 parent 808a3ac commit b7d1494
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
39 changes: 39 additions & 0 deletions chapter1/contracts/src/ERC20Fee.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
77 changes: 77 additions & 0 deletions chapter1/erc20-fee/README.md
Original file line number Diff line number Diff line change
@@ -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=<ERC20 addr>
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="<enter-contract-address>"
```

- 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
```

0 comments on commit b7d1494

Please sign in to comment.