Solidity 脚本
介绍
Solidity 脚本是一种使用 Solidity 以声明方式部署合约的方法,而不是使用限制更多且用户友好度较低的 forge create
。
Solidity 脚本就像你在使用 Hardhat 等工具时编写的脚本; Solidity 脚本的不同之处在于它们是用 Solidity 而不是 JavaScript 编写的,并且它们在快速的 Foundry EVM 后端上运行,该后端提供试运行功能。
高级概述
forge script
不以同步方式工作。首先,它会从脚本中收集所有交易,然后才会广播它们。它基本上可以分为 4 个阶段:
- 本地模拟 - 合同脚本在本地 evm 中运行。如果提供了 rpc/fork url,它将在该上下文中执行脚本。从
vm.broadcast
和/或vm.startBroadcast
进行的任何外部调用(非静态,非内部)都将被追加到列表中。 - 链上模拟 - 可选。如果提供了 rpc/fork url,则它将按顺序执行上一个阶段收集的所有交易。
- 广播 - 可选。如果提供了
--broadcast
标志,并且前几个阶段已成功,它将广播在步骤1
收集的交易,并在步骤2
模拟。 - 验证 - 可选。如果提供了
--verify
标志,有一个 API 密钥,并且前几个阶段已成功,它将尝试验证合同(例如 etherscan)。
考虑到这个流程,重要的是要意识到,那些行为可能受外部状态/参与者影响的交易,其结果可能与步骤 2
模拟的结果不同。例如,前置交易。
设置
让我们尝试使用 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@v5.0.1
接下来,我们必须删除 src
文件夹中的 Counter.sol
文件并创建另一个名为 NFT.sol
的文件。 为此,你可以运行:
rm src/Counter.sol test/Counter.t.sol script/Counter.s.sol && touch src/NFT.sol && ls src
完成后,你应该打开你喜欢的代码编辑器并将下面的代码复制到 NFT.sol
文件中。
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.10;
import {ERC721} from "solmate/tokens/ERC721.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.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) Ownable(msg.sender) {
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
如果你的输出看起来像这样,则合约已成功编译。
部署我们的合约
我们将把 NFT
合约部署到 Sepolia 测试网,但为此我们需要对 Foundry 进行一些配置,比如设置 Sepolia RPC URL、一个有 Sepolia Eth 资金的账户的私钥,以及用于验证 NFT 合约的 Etherscan 密钥。
💡 注意:你可以在这里获取一些 Sepolia 测试网 ETH。
环境配置
完成所有这些后,创建一个 .env
文件并添加变量。 Foundry 会自动加载项目目录中的 .env
文件。
.env
文件应遵循以下格式:
SEPOLIA_RPC_URL=
PRIVATE_KEY=
ETHERSCAN_API_KEY=
我们现在需要编辑 foundry.toml
文件。 项目的根目录中应该已经有一个。
将以下行添加到文件末尾:
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }
这将为 Sepolia 测试网创建一个 RPC 别名 并加载 Etherscan 的 API 密钥。
编写脚本
接下来,我们必须创建一个文件夹并将其命名为 script
,并在其中创建一个名为 NFT.s.sol
的文件。 这是我们将创建部署脚本本身的地方。
NFT.s.sol
的内容应该是这样的:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script} from "forge-std/Script.sol";
import {NFT} from "../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 {Script} from "forge-std/Script.sol";
import {NFT} from "../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);
这是一个特殊的作弊码,用于记录我们的主脚本合约进行的调用和合约创建。 我们传递 deployerPrivateKey
以指示它使用该密钥来签署交易。 稍后,我们将广播这些交易以部署我们的 NFT 合约。
NFT nft = new NFT("NFT_tutorial", "TUT", "baseUri");
在这里,我们刚刚创建了我们的 NFT 合约。因为我们在这行代码之前调用了 vm.startBroadcast()
,所以合约的创建将被 Forge 记录下来,如前所述,我们可以将交易广播以在链上部署合约。广播交易日志将默认存储在 broadcast
目录中。你可以通过在 foundry.toml
文件中设置 broadcast
来更改日志的位置。
广播发送者的确定顺序如下:
- 如果提供了
--sender
参数,则使用该地址。 - 如果仅设置了一个签名者(例如私钥、硬件钱包、密钥库),则使用该签名者。
- 否则,将尝试使用 Foundry 默认的发送者 (
0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38
)。
现在你已经了解了智能合约脚本的功能,让我们运行它。
你应该已经将我们之前提到的变量添加到 .env 中,以便下一部分工作。
在项目运行的根目录:
# To load the variables in the .env file
source .env
# To deploy and verify our contract
forge script --chain sepolia script/NFT.s.sol:MyScript --rpc-url $SEPOLIA_RPC_URL --broadcast --verify -vvvv
Forge 将运行我们的脚本并为我们广播交易——这可能需要一些时间,因为 Forge 还将等待交易收据。 大约一分钟后,你应该会看到类似这样的内容:
这确认你已成功将 NFT
合约部署到 Sepolia 测试网,并已在 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
参考。