Skip to content
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

feat: add ERC20 as gas fee #15

Merged
merged 5 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
```