# 使用 Foundry 部署 ERC20 合约

{% hint style="info" %}
请在GitHub参阅完整的[foundry-erc20](https://github.com/berachain/guides/tree/main/apps/foundry-erc20)代码库。
{% endhint %}

本节将展示如何使用[Foundry](https://getfoundry.sh/)创建新的Solidity合约，配置Berachain网络详细信息，将合约部署到Berachain，以及验证合约。

### 先决条件

开始之前，请确保你的本地设备上满足以下条件：

* 参考[Foundry安装文档](https://book.getfoundry.sh/getting-started/installation)安装该软件。

### 创建ERC20合约代码设置

首先，为ERC20合约创建一个新的文件夹：

```bash
mkdir create-erc20-contract-using-foundry;
cd create-erc20-contract-using-foundry;
```

然后，运行以下代码，创建由Foundry定义的初始ERC20合约模板：

```bash
# FROM: ./create-erc20-contract-using-foundry

forge init; # forge init --force; # if there is already an existing .git repository associated

# [Expected Output]:
# ...
# Resolving deltas: 100% (129/129), done.
#     Installed forge-std v1.7.1
#     Initialized forge project
```

如果模板创建成功，会显示以下代码结构：

```bash
# FROM: ./create-erc20-contract-using-foundry
.
├── README.md
├── foundry.toml
├── lib
│   └── forge-std
├── script
│   └── Counter.s.sol
├── src
│   └── Counter.sol
└── test
    └── Counter.t.sol
```

现在，所有代码已设置完成，运行以下代码，安装来自[OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts)的ERC20合约所需的依赖项：

```bash
# FROM: ./create-erc20-contract-using-foundry

forge install OpenZeppelin/openzeppelin-contracts;
# If existing git setup run:
# forge install OpenZeppelin/openzeppelin-contracts --no-commit;

# [Expected Output]:
# ...
# Resolving deltas: 100% (129/129), done.
#     Installed openzeppelin-contracts v5.0.0
```

### 创建ERC20合约

开始之前，请将现有的`src/Counter.sol`转换为新的`BingBongToken.sol`，并将代码替换为以下 Solidity代码：

```bash
# FROM: ./create-erc20-contract-using-foundry

mv src/Counter.sol src/BingBongToken.sol;
```

**文件位置**：`./src/BingBongToken.sol`

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract BingBongToken is ERC20 {
    /**
     * @dev Init constructor for setting token name and symbol
     */
    constructor(string memory name_, string memory symbol_, uint256 mintedTokens_) ERC20(name_, symbol_) {
        _mint(msg.sender, mintedTokens_);
    }
}
```

运行以下代码，测试编译是否正确：

```bash
# FROM: ./create-erc20-contract-using-foundry

forge compile;

# [Expected Error Output]:
# [⠊] Compiling...
# [⠒] Unable to resolve imports:
#       "../src/Counter.sol" in "/path/to/create-erc20-contract-using-foundry/test/Counter.t.sol"
#  ...
```

如果出现上方显示的`Expected Error Output`，原因是引用了一个不存在的文件。为了解决这个问题，需要将其重命名为`BingBongToken.t.sol`，并替换一些占位符代码：

```bash
# FROM: ./create-erc20-contract-using-foundry

mv test/Counter.t.sol test/BingBongToken.t.sol;
```

**文件位置**：`./test/BingBongToken.t.sol`

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {BingBongToken} from "../src/BingBongToken.sol";

contract BingBongTokenTest is Test {

}
```

现在，运行`forge compile`时，应该显示以下结果：

```bash
# FROM: ./create-erc20-contract-using-foundry

forge compile;

# [Expected Output]:
# [⠢] Compiling...
# [⠰] Compiling 27 files with 0.8.21
# [⠃] Solc 0.8.21 finished in 6.25s
# Compiler run successful!
```

### 测试ERC20合约

使用重命名的`BingBongToken.t.sol`文件，添加以下测试代码，该代码覆盖广泛的ERC20合约测试。

请检查每项测试，以便更加了解如何应对并成功处理各种情况。

**文件位置**：`./test/BingBongToken.t.sol`

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console2, stdError} from "forge-std/Test.sol";
import {BingBongToken} from "../src/BingBongToken.sol";

contract BingBongTokenTest is Test {
    // Variables
    BingBongToken public token;
    address supplyOwnerAddress = makeAddr("BerachainWalletUser"); // 0xE3284cB941608AA9E65F7EDdbb50c461D936622f
    address randomWalletAddress = makeAddr("GiveMeTokens"); // 0x187A660c372Fa04D09C1A71f2927911e62e98a89
    address anotherWalletAddress = makeAddr("AnotherAddress"); // 0x0F3B9cC98eef350B12D5b7a338D8B76c2F9a92CC
    error ERC20InvalidReceiver(address receiver);

    // Initial Read Tests
    // ========================================================
    /**
     * @dev Initial contract setup
     */
    function setUp() public {
        vm.prank(supplyOwnerAddress);
        token = new BingBongToken("BingBong Token", "BBT", 10000);
    }

    /**
     * @dev Test initiatted token name
     */
    function test_name() public {
        assertEq(token.name(), "BingBong Token");
    }

    /**
     * @dev Test initiatted token symbol
     */
    function test_symbol() public {
        assertEq(token.symbol(), "BBT");
    }

    /**
     * @dev Test default decimals
     */
    function test_decimals() public {
        assertEq(token.decimals(), 18);
    }

    /**
     * @dev Test initial total token supply
     */
    function test_totalSupply() public {
        assertEq(token.totalSupply(), 10000);
    }

    /**
     * @dev Test initial random account balance
     */
    function test_balanceOfAddress0() public {
        assertEq(token.balanceOf(address(0)), 0);
    }

    /**
     * @dev Test account balance of original deployer
     */
    function test_balanceOfAddressSupplyOwner() public {
        assertEq(token.balanceOf(supplyOwnerAddress), 10000);
    }

    /**
     * @dev Test Revert transfer to sender as 0x0
     */
    function test_transferRevertInvalidSender() public {
        vm.prank(address(0));
        vm.expectRevert(abi.encodeWithSignature("ERC20InvalidSender(address)", address(0)));
        token.transfer(randomWalletAddress, 100);
    }

    /**
     * @dev Test Revert transfer to receiver as 0x0
     */
    function test_transferRevertInvalidReceiver() public {
        vm.prank(supplyOwnerAddress);
        vm.expectRevert(abi.encodeWithSignature("ERC20InvalidReceiver(address)", address(0)));
        token.transfer(address(0), 100);
    }

    /**
     * @dev Test Revert transfer to sender with insufficient balance
     */
    function test_transferRevertInsufficientBalance() public {
        vm.prank(randomWalletAddress);
        // NOTE: Make sure to keep this string for `encodeWithSignature` free of spaces for the string (" ")
        vm.expectRevert(abi.encodeWithSignature("ERC20InsufficientBalance(address,uint256,uint256)", randomWalletAddress, 0, 100));
        token.transfer(supplyOwnerAddress, 100);
    }

    /**
     * @dev Test transfer to receiver from sender with sufficient balance
     */
    function test_transfer() public {
        vm.prank(supplyOwnerAddress);
        assertEq(token.transfer(randomWalletAddress, 100), true);
        assertEq(token.balanceOf(randomWalletAddress), 100);
        assertEq(token.balanceOf(supplyOwnerAddress), 10000 - 100);
    }

    /**
     * @dev Test allowance of random address for supplyOwner
     */
    function test_allowance() public {
        assertEq(token.allowance(supplyOwnerAddress, randomWalletAddress), 0);
    }

    /**
     * @dev Test Revert approve of owner as 0x0
     */
    function test_approveRevertInvalidApprover() public {
        vm.prank(address(0));
        vm.expectRevert(abi.encodeWithSignature("ERC20InvalidApprover(address)", address(0)));
        token.approve(randomWalletAddress, 100);
    }

    /**
     * @dev Test Revert approve of spender as 0x0
     */
    function test_approveRevertInvalidSpender() public {
        vm.prank(supplyOwnerAddress);
        vm.expectRevert(abi.encodeWithSignature("ERC20InvalidSpender(address)", address(0)));
        token.approve(address(0), 100);
    }

    /**
     * @dev Test approve of spender for 0 and 50
     */
    function test_approve() public {
        vm.prank(supplyOwnerAddress);
        assertEq(token.approve(randomWalletAddress, 0), true);
        assertEq(token.approve(randomWalletAddress, 50), true);
    }

    /**
     * @dev Test Revert transferFrom of spender with 0 approveed
     */
    function test_transferFromRevertInsufficientAllowanceFor0x0() public {
        vm.prank(supplyOwnerAddress);
        vm.expectRevert(abi.encodeWithSignature("ERC20InsufficientAllowance(address,uint256,uint256)", supplyOwnerAddress, 0, 100));
        token.transferFrom(randomWalletAddress, address(0), 100);
    }

    /**
     * @dev Test Revert transferFrom of spender transferring to 0x0
     */
    function test_transferFromRevertInvalidReceiver() public {
        // Setup
        vm.prank(supplyOwnerAddress);
        token.approve(randomWalletAddress, 30);

        // Test
        vm.prank(randomWalletAddress);
        vm.expectRevert(abi.encodeWithSignature("ERC20InvalidReceiver(address)", address(0)));
        token.transferFrom(supplyOwnerAddress, address(0), 30);
    }

    /**
     * @dev Test Revert transferFrom of spender transferring 50/30 approved
     */
    function test_transferFromRevertInsufficientAllowance() public {
        // Setup
        vm.prank(supplyOwnerAddress);
        token.approve(randomWalletAddress, 30);

        // Test
        vm.prank(randomWalletAddress);
        vm.expectRevert(abi.encodeWithSignature("ERC20InsufficientAllowance(address,uint256,uint256)", randomWalletAddress, 30, 50));
        token.transferFrom(supplyOwnerAddress, anotherWalletAddress, 50);
    }

    /**
     * @dev Test transferFrom of spender 10/30 approved
     */
    function test_transferFrom() public {
        // Setup
        vm.prank(supplyOwnerAddress);
        token.approve(randomWalletAddress, 30);

        // Test
        vm.prank(randomWalletAddress);
        assertEq(token.transferFrom(supplyOwnerAddress, anotherWalletAddress, 10), true);
        assertEq(token.balanceOf(anotherWalletAddress), 10);
        assertEq(token.balanceOf(supplyOwnerAddress), 10000 - 10);
        assertEq(token.allowance(supplyOwnerAddress, randomWalletAddress), 30 - 10);
    }
}
```

接下来，编译代码，运行测试，检查各项测试是否通过。

```bash
# FROM: ./create-erc20-contract-using-foundry

forge test -vvv; # v stands for verbose and multiple vvv allow for more details for tests

# [Expected Output]:
# [⠰] Compiling...
# No files changed, compilation skipped
#
# Running 18 tests for test/BingBongToken.t.sol:BingBongTokenTest
# [PASS] test_allowance() (gas: 12341)
# [PASS] test_approve() (gas: 42814)
# [PASS] test_approveRevertInvalidApprover() (gas: 11685)
# [PASS] test_approveRevertInvalidSpender() (gas: 11737)
# [PASS] test_balanceOfAddress0() (gas: 7810)
# [PASS] test_balanceOfAddressSupplyOwner() (gas: 9893)
# [PASS] test_decimals() (gas: 5481)
# [PASS] test_name() (gas: 9541)
# [PASS] test_symbol() (gas: 9650)
# [PASS] test_totalSupply() (gas: 7546)
# [PASS] test_transfer() (gas: 44880)
# [PASS] test_transferFrom() (gas: 75384)
# [PASS] test_transferFromRevertInsufficientAllowance() (gas: 42626)
# [PASS] test_transferFromRevertInsufficientAllowanceFor0x0() (gas: 16597)
# [PASS] test_transferFromRevertInvalidReceiver() (gas: 28334)
# [PASS] test_transferRevertInsufficientBalance() (gas: 16477)
# [PASS] test_transferRevertInvalidReceiver() (gas: 11796)
# [PASS] test_transferRevertInvalidSender() (gas: 11746)
# Test result: ok. 18 passed; 0 failed; 0 skipped; finished in 2.07ms
#
# Ran 1 test suites: 18 tests passed, 0 failed, 0 skipped (18 total tests)
```

### 为Berachain合约部署Foundry

现在，代码创建和测试都已完成，接下来创建部署`BingBongToken.sol`文件所需的脚本。为此，需要将`Course.s.sol`脚本文件重命名为`BingBongToken.s.sol`：

```bash
# FROM: ./create-erc20-contract-using-foundry

mv script/Counter.s.sol script/BingBongToken.s.sol;
```

然后，添加以下代码以替换现有代码，用于导入钱包私钥和部署合约。

**文件位置**：`./script/BingBongToken.s.sol`

```bash
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";
import "../src/BingBongToken.sol";

contract BingBongTokenScript is Script {
    /**
     * @dev Relevant source part starts here and spans across multiple lines
     */
    function setUp() public {
    }

    /**
     * @dev Main deployment script
     */
    function run() public {
        // Setup
        uint256 deployerPrivateKey = vm.envUint("WALLET_PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        // Deploy
        BingBongToken bbt = new BingBongToken("BingBongToken", "BBT", 5678);

        // Verify + End
        console2.log(bbt.totalSupply());
        vm.stopBroadcast();
    }
}
```

为了验证合约是否能够正确执行，可以通过运行`anvil`在本地节点上测试。请备份并保存私钥。

#### 终端 1：

```bash
# FROM: ./create-erc20-contract-using-foundry

anvil;

# [Expected Output]:
#
#
#                              _   _
#                             (_) | |
#       __ _   _ __   __   __  _  | |
#      / _` | | '_ \  \ \ / / | | | |
#     | (_| | | | | |  \ V /  | | | |
#      \__,_| |_| |_|   \_/   |_| |_|
#
#     0.2.0 (f5b9c02 2023-10-28T00:16:04.060987000Z)
#     https://github.com/foundry-rs/foundry
#
# Available Accounts
# ==================
#
# (0) "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" (10000.000000000000000000 ETH)
# ...
#
# Private Keys
# ==================
#
# (0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
# ...
```

使用上方代码中提供的私钥`Private Key`，替换`.env` 文件中的`WALLET_PRIVATE_KEY`。

**文件位置**：`./.env`

```bash
WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```

#### 终端 2：

在另一个终端窗口中，运行以下代码，将合约部署到本地节点RPC：

```bash
# FROM ./create-erc20-contract-using-foundry

forge script script/BingBongToken.s.sol --fork-url http://localhost:8545 --broadcast;

# [Expected Output]:
# Compiler run successful!
# Script ran successfully.
#
# == Logs ==
#   5678
# ...
# ✅  [Success]Hash: 0xc2b647051d11d8dbd88d131ff268ada417caa27e423747497b624cc3e9c75db8
# Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
# Block: 1
# ...
```

显示以上结果，部署成功！最后，务必使用`ctrl + c`停止**终端 1**中的`anvil`服务。

### 部署ERC20合约

{% hint style="info" %}
部署之前，确保你的钱包里有足够的`$BERA`代币`，`以支付部署合约所需的费用，并确认已替换`.env`文件中的`WALLET_PRIVATE_KEY`。
{% endhint %}

使用本地节点配置，部署到Berachain测试网的流程均相似，但需要指定不同的RPC URL端点：

```bash
# FROM ./create-erc20-contract-using-foundry

forge script script/BingBongToken.s.sol --rpc-url https://bartio.rpc.berachain.com/ --broadcast;

# [Expected Output]:
# Compiler run successful!
# Script ran successfully.
#
# == Logs ==
#   5678
# ...
# ✅  [Success]Hash: 0x69aeb8ee5084c44cce00cae2fda3563bd10efb9c8c663ec7b6a6929d6d48a50e
# Contract Address: 0x01870EC5C7656723b31a884259537B183FE15Fa7
# Block: 68764
# ...
```

### 验证ERC20合约

{% hint style="info" %}
目前，在`v0.2.0`版本的forge中，合约验证存在一些问题，可能导致合约验证无法进行，尝试运行以下代码，应该有助于验证合约：
{% endhint %}

```bash
# FROM ./create-erc20-contract-using-foundry

forge verify-contract 0xYOUR_DEPLOYED_CONTRACT_ADDRESS BingBongToken \
    --etherscan-api-key=xxxxx \
    --watch \
    --constructor-args $(cast abi-encode "constructor(string,string,uint256)" "BingBongToken" "BBT" 5678) \
    --retries=2 \
    --verifier-url=https://api.routescan.io/v2/network/testnet/evm/80084/etherscan/api/;
```

### 完整代码库

本节完整代码库，可在[Github - Berachain Guides](https://github.com/berachain/guides/) - [foundry-erc20](https://github.com/berachain/guides/tree/main/apps/foundry-erc20)中查看。

{% embed url="<https://github.com/berachain/guides/tree/main/apps/foundry-erc20>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.docszh.com/berachain-docs/developers/developer-guides/create-erc20-contract-using-foundry.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
