Solidity脚本

介绍

Solidity 脚本是一种使用 Solidity 以声明方式部署合约的方法,而不是使用限制更多且用户友好度较低的 forge create

Solidity 脚本就像您在使用 Hardhat 等工具时编写的脚本; Solidity 脚本的不同之处在于它们是用 Solidity 而不是 JavaScript 编写的,并且它们在快速的 Foundry EVM 后端上运行,该后端提供试运行功能。

设置

让我们尝试使用 solidity 脚本部署在 solmate 教程中制作的 NFT 合约。 首先,我们需要通过以下方式创建一个新的 Foundry 项目:

forge init solidity-scripting

由于 solmate 教程中的 NFT 合约继承了 solmate 和 OpenZeppelin 合约,我们必须通过运行以下命令将它们安装为依赖项:

# Enter the project
cd solidity-scripting

# Install Solmate and OpenZeppelin contracts as dependencies
forge install transmissions11/solmate Openzeppelin/openzeppelin-contracts

接下来,我们必须删除 src 文件夹中的 Counter.sol 文件并创建另一个名为 NFT.sol 的文件。 你可以通过运行来做到这一点:

rm src/Counter.sol test/Counter.t.sol && touch src/NFT.sol && ls src

set up commands

完成后,你应该打开你喜欢的代码编辑器并将下面的代码复制到NFT.sol 文件中。

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.10;

import "solmate/tokens/ERC721.sol";
import "openzeppelin-contracts/utils/Strings.sol";
import "openzeppelin-contracts/access/Ownable.sol";

error MintPriceNotPaid();
error MaxSupply();
error NonExistentTokenURI();
error WithdrawTransfer();

contract NFT is ERC721, Ownable {

    using Strings for uint256;
    string public baseURI;
    uint256 public currentTokenId;
    uint256 public constant TOTAL_SUPPLY = 10_000;
    uint256 public constant MINT_PRICE = 0.08 ether;

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseURI
    ) ERC721(_name, _symbol) {
        baseURI = _baseURI;
    }

    function mintTo(address recipient) public payable returns (uint256) {
        if (msg.value != MINT_PRICE) {
            revert MintPriceNotPaid();
        }
        uint256 newTokenId = ++currentTokenId;
        if (newTokenId > TOTAL_SUPPLY) {
            revert MaxSupply();
        }
        _safeMint(recipient, newTokenId);
        return newTokenId;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        if (ownerOf(tokenId) == address(0)) {
            revert NonExistentTokenURI();
        }
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString()))
                : "";
    }

    function withdrawPayments(address payable payee) external onlyOwner {
        uint256 balance = address(this).balance;
        (bool transferTx, ) = payee.call{value: balance}("");
        if (!transferTx) {
            revert WithdrawTransfer();
        }
    }
}

现在,让我们尝试编译我们的合约以确保一切正常。

forge build

如果您的输出看起来像这样,则合同已成功编译。 compile successful

部署我们的合约

我们将把“NFT”合约部署到 Goerli 测试网,但为此我们需要稍微配置 Foundry,通过设置 Goerli RPC URL 之类的东西,这是一个由 Goerli Eth 资助的账户的私钥 ,以及用于验证 NFT 合约的 Etherscan 密钥。

💡 注意:您可以在 此处 获得一些 Goerli 测试网 ETH。

环境配置

完成所有这些后,创建一个 .env 文件并添加变量。 Foundry 会自动加载项目目录中的 .env 文件。

.env 文件应遵循以下格式:

GOERLI_RPC_URL=
PRIVATE_KEY=
ETHERSCAN_API_KEY=

我们现在需要编辑 foundry.toml 文件。 项目的根目录中应该已经有一个。

将以下行添加到文件末尾:

[rpc_endpoints]
goerli = "${GOERLI_RPC_URL}"

[etherscan]
goerli = { key = "${ETHERSCAN_API_KEY}" }

This creates a RPC alias for Goerli and loads the Etherscan API key.

编写脚本

接下来,我们必须创建一个文件夹并将其命名为script,并在其中创建一个名为NFT.s.sol的文件。 这是我们将创建部署脚本本身的地方。

NFT.s.sol 的内容应该是这样的:

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

import "forge-std/Script.sol";
import "../src/NFT.sol";

contract MyScript is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        NFT nft = new NFT("NFT_tutorial", "TUT", "baseUri");

        vm.stopBroadcast();
    }
}

现在让我们通读代码并弄清楚它的实际含义和作用。

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

请记住,即使它是一个脚本,它仍然像智能合约一样工作,但从未部署过,所以就像任何其他用 Solidity 编写的智能合约一样,必须指定pragma version

import "forge-std/Script.sol";
import "../src/NFT.sol";

就像我们在编写测试时可能会导入 Forge Std 来获取测试实用程序一样,Forge Std 也提供了一些我们在这里导入的脚本实用程序。

下一行只是导入NFT合约。

contract MyScript is Script {

我们创建一个名为 MyScript 的合约,它从 Forge Std 继承了 Script

 function run() external {

默认情况下,脚本是通过调用名为run的函数(我们的入口点)来执行的。

uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

这会从我们的 .env 文件中加载私钥。 注意:.env 文件中公开私钥并将它们加载到程序中时必须小心。 这仅建议与非特权部署者一起使用或用于本地/测试设置。 对于生产设置,请查看 Foundry 支持的各种钱包选项

vm.startBroadcast(deployerPrivateKey);

这是一个特殊的Cheatcode,用于记录我们的主脚本合约进行的调用和合约创建。 我们传递 deployerPrivateKey 以指示它使用该密钥来签署交易。 稍后,我们将广播这些交易以部署我们的 NFT 合约。

 NFT nft = new NFT("NFT_tutorial", "TUT", "baseUri");

在这里,我们只是创建我们的 NFT 合约。 因为我们在这行之前调用了 vm.startBroadcast(),合约创建将被 Forge 记录下来,并且如前所述,我们可以广播交易以在链上部署合约。 默认情况下,广播事务日志将存储在“广播”目录中。 您可以通过在 foundry.toml 文件中设置 broadcast 来更改日志位置。

现在您已经了解了脚本智能合约的功能,让我们运行它。

您应该已经将我们之前提到的变量添加到 .env 中,以便下一部分工作。

在项目运行的根目录:

# To load the variables in the .env file
source .env

# To deploy and verify our contract
forge script script/NFT.s.sol:MyScript --rpc-url $GOERLI_RPC_URL --broadcast --verify -vvvv

Forge 将运行我们的脚本并为我们广播交易——这可能需要一些时间,因为 Forge 还将等待交易收据。 大约一分钟后,您应该会看到类似这样的内容:

contract verified

这确认您已成功将 NFT 合约部署到 Goerli 测试网,并已在 Etherscan 上对其进行了验证,所有这些都通过一个命令完成。

本地部署

您可以通过将端口配置为 fork-url 来部署到本地测试网 Anvil。

在这里,我们在帐户方面有两种选择。 我们可以在没有任何标志的情况下启动 anvil,并使用提供的私钥之一。 或者,我们可以传递一个助记符给 anvil 来使用。

使用 Anvil 的默认帐户

首先,启动 Anvil:

anvil

使用 Anvil 提供给您的私钥更新您的 .env 文件。

然后运行以下脚本:

forge script script/NFT.s.sol:MyScript --fork-url http://localhost:8545 --broadcast

使用自定义助记符

将以下行添加到您的 .env 文件并使用您的助记符完成它:

MNEMONIC=

预计我们之前设置的PRIVATE_KEY环境变量是这个助记词中的前10个账户之一。

使用自定义助记符启动 Anvil:

source .env

anvil --m $MNEMONIC

然后运行以下脚本:

forge script script/NFT.s.sol:MyScript --fork-url http://localhost:8545 --broadcast

💡 注意:可以在 此处 找到本教程的完整实现,要进一步阅读有关 solidity 脚本的信息,您可以查看“forge script” 参考