本文提供了一份详尽的指南,介绍了如何使用 Dapp Tools 开发、测试和部署一个基本的Escrow智能合约。内容涵盖了所需的系统环境、工具安装、合约创建及测试,最后讲解了如何在 Sepolia 测试网上进行部署和交互,适合希望提升以太坊开发技能的读者。
info
请注意,dapp.tools 目前不再积极开发,这可能会导致兼容性问题或破坏性更改。Foundry 作为一个良好维护的替代方案,为寻求持续支持和更新的用户提供服务。请查看我们相关的 Foundry 指南 这里。
准备好提升你的以太坊开发技能了吗?在本指南中,我们将深入探讨 Dapp Tools 以构建、测试和部署一个基本的 Escrow 智能合约。我们将探索 Dapp Tools 的高级功能,这些功能可以简化你的开发工作流程。开始吧!
依赖项 | 版本 |
---|---|
nix | 2.24.9 |
dapp | 0.35.0 |
seth | 0.12.0 |
你需要一个 API 端点来与以太坊网络通信。你可以使用公共节点或部署并管理自己的基础设施;不过,如果你希望获得 8 倍的响应速度,可以将这些重担留给我们。请在 这里 注册一个免费账户。
登录后,点击 创建端点,然后选择 Ethereum Sepolia 测试网链。
创建端点后,复制 HTTP Provider URL 链接并保持备用,因为你在开发 Escrow 智能合约时将需要它。
在将我们的 Escrow 合约部署到 Sepolia 测试网之前,我们需要确保有足够的 Sepolia 测试网 ETH 来支付 gas 费用。 Multi-Chain QuickNode Faucet 提供了一个轻松获取测试 ETH 的方式。获取你的 Sepolia 测试网 ETH 的步骤如下:
注意:在以太坊主网络上使用 EVM faucet 时,主网余额要求为 0.001 ETH。你还可以通过推特或者登录你的 QuickNode 账户来获得额外奖励!
收到你的 Sepolia 测试网 ETH 后,你就可以开始设置项目文件夹。
Dapp Tools 是 Dapp Hub 为以太坊智能合约开发创建的一组命令行工具。它帮助开发者更高效地构建、测试和部署智能合约。
Dapp Tools 的主要组件包括:
Dapp Tools 还包括一些有用的智能合约库:
文档概述可以在 这里 找到。
这些工具协同工作,为开发人员更有效地创建和测试智能合约提供帮助。Dapp Tools 提供了基于属性的测试、形式验证和 gas 优化等功能,这可以导致更可靠的智能合约。
接下来,让我们开始设置我们的 Dapp Tools 项目。
info
在安装 Dapp Tools 之前,请确保你已安装 Nix。Nix 是一个为类 Unix 系统设计的包管理器和构建系统,帮助使包管理更加可靠和可重现。
首先,让我们安装 Dapp Tools。打开你的终端并运行:
nix profile install github:dapphub/dapptools#{dapp,ethsign,hevm,seth} --extra-experimental-features nix-command --extra-experimental-features flakes
你应该看到 Dapp Tools 的版本号显示。
让我们为我们的 Escrow 合约创建一个新的 Dapp 项目:
mkdir escrow-contract
cd escrow-contract
dapp init
接下来,让我们创建我们的 Escrow 合约。在代码编辑器中打开 src/EscrowContract.sol
文件并添加以下代码:
pragma solidity ^0.8.6;
contract Escrow {
address public admin;
mapping(uint256 => address) public participantIds;
mapping(address => uint256) public participantIdsByAddress;
mapping(address => uint256) public balances;
mapping(address => bool) public whitelist;
uint256 public participantCount;
event Deposit(address indexed participant, uint256 amount);
event Distribution(uint256 indexed participantId, address indexed participant, uint256 amount);
event Whitelisted(address indexed participant, uint256 participantId);
event RemovedFromWhitelist(address indexed participant);
event BatchWhitelisted(uint256 count);
event EmergencyWithdraw(address indexed admin, uint256 amount);
constructor() {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can perform this action");
_;
}
modifier onlyWhitelisted() {
require(whitelist[msg.sender], "Address not whitelisted");
_;
}
function addParticipants(address[] memory _participants) public onlyAdmin {
for (uint i = 0; i < _participants.length; i++) {
if (!whitelist[_participants[i]]) {
participantCount++;
whitelist[_participants[i]] = true;
participantIds[participantCount] = _participants[i];
participantIdsByAddress[_participants[i]] = participantCount;
emit Whitelisted(_participants[i], participantCount);
}
}
emit BatchWhitelisted(_participants.length);
}
function removeFromWhitelist(address _participant) public onlyAdmin {
require(whitelist[_participant], "Address not whitelisted");
whitelist[_participant] = false;
emit RemovedFromWhitelist(_participant);
}
function depositFunds() public payable onlyWhitelisted {
require(msg.value > 0, "Deposit amount must be greater than 0");
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function distribute(uint256 _participantId, uint256 _amount) public onlyAdmin {
address payable participant = payable(participantIds[_participantId]);
require(participant != address(0), "Invalid participant ID");
require(_amount > 0, "Distribution amount must be greater than 0");
require(_amount <= address(this).balance, "Insufficient contract balance");
(bool success, ) = participant.call{value: _amount}("");
require(success, "Transfer failed");
emit Distribution(_participantId, participant, _amount);
}
function emergencyWithdraw() public onlyAdmin {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
(bool success, ) = payable(admin).call{value: balance}("");
require(success, "Transfer failed");
emit EmergencyWithdraw(admin, balance);
}
function getParticipant(uint256 _participantId) public view returns (address) {
return participantIds[_participantId];
}
function getParticipantId(address _participant) public view returns (uint256) {
return participantIdsByAddress[_participant];
}
function getBalance(address _participant) public view returns (uint256) {
return balances[_participant];
}
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
}
这个 Escrow 合约提供了以下核心功能:
Dapp Tools 使用 ds-test
库来编写测试。让我们为我们的 Escrow 合约创建测试。在文件 src/EscrowContract.t.sol
中输入以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
import "ds-test/test.sol";
import "./EscrowContract.sol";
contract EscrowTest is DSTest {
Escrow escrow;
address admin;
address alice = address(0x1);
address bob = address(0x2);
address charlie = address(0x3);
function setUp() public {
admin = address(this);
escrow = new Escrow();
}
function testAddParticipants() public {
address[] memory addresses = new address[](3);
addresses[0] = alice;
addresses[1] = bob;
addresses[2] = charlie;
escrow.addParticipants(addresses);
assertTrue(escrow.whitelist(alice));
assertTrue(escrow.whitelist(bob));
assertTrue(escrow.whitelist(charlie));
assertEq(escrow.getParticipantId(alice), 1);
assertEq(escrow.getParticipantId(bob), 2);
assertEq(escrow.getParticipantId(charlie), 3);
}
function testDeposit() public {
address[] memory addresses = new address[](1);
addresses[0] = address(this);
escrow.addParticipants(addresses);
escrow.depositFunds{value: 1 ether}();
assertEq(escrow.getBalance(address(this)), 1 ether);
assertEq(escrow.getContractBalance(), 1 ether);
}
function testFailDepositNotWhitelisted() public {
escrow.depositFunds{value: 1 ether}();
}
function testDistribute() public {
address[] memory addresses = new address[](1);
addresses[0] = alice;
escrow.addParticipants(addresses);
addresses[0] = address(this);
escrow.addParticipants(addresses);
escrow.depositFunds{value: 2 ether}();
uint256 initialBalance = alice.balance;
escrow.distribute(1, 1 ether);
assertEq(alice.balance, initialBalance + 1 ether);
assertEq(escrow.getContractBalance(), 1 ether);
}
function testFailDistributeInvalidId() public {
escrow.distribute(999, 1 ether);
}
function testEmergencyWithdraw() public {
address[] memory addresses = new address[](1);
addresses[0] = address(this);
escrow.addParticipants(addresses);
escrow.depositFunds{value: 5 ether}();
uint256 initialBalance = address(this).balance;
escrow.emergencyWithdraw();
assertEq(address(this).balance, initialBalance + 5 ether);
assertEq(escrow.getContractBalance(), 0);
}
receive() external payable {}
}
此测试文件包括以下测试用例:
在执行测试之前,让我们确认我们的智能合约可以成功编译,使用以下命令:
dapp build
如果编译成功,你将看到以下内容:
+ dapp clean
+ rm -rf out
要运行测试,请执行:
dapp test
你将看到以下输出:
dapp test
Running 6 tests for src/EscrowContract.t.sol:EscrowTest
[PASS] testEmergencyWithdraw() (gas: 138756)
[PASS] testFailDistributeInvalidId() (gas: 4451)
[PASS] testFailDepositNotWhitelisted() (gas: 10122)
[PASS] testDistribute() (gas: 241956)
[PASS] testDeposit() (gas: 130555)
[PASS] testAddParticipants() (gas: 249315)
接下来,让我们将 Escrow 合约部署到 Sepolia 测试网。
要将我们的 Escrow 合约部署到 Sepolia,我们将使用 dapp create。首先,让我们设置我们的环境。在项目根目录中创建一个 ~/.sethrc
文件并填写以下值:
export ETH_RPC_URL=YOUR_RPC_URL
export ETH_FROM=YOUR_WALLET_ADDRESS
export ETH_GAS=2000000
export ETH_PRIO_FEE=$(seth --to-wei 0.1 gwei)
请记住在
ETH_RPC_URL
和ETH_FROM
中填写正确的值。对于其他 gas 值,根据当前市场情况进行调整。
接下来,让我们设置要使用的钱包以部署 Escrow 合约。在项目根目录的终端中运行以下命令:
ethsign import
系统将提示你输入私钥和一个密码。
现在,让我们构建并部署合约:
dapp build
dapp create src/EscrowContract.sol:Escrow
Dapp Tools 将提示你输入密钥库密码,然后部署合约。它将输出交易哈希和已部署合约地址。
Ethereum account passphrase (not echoed): seth-send: Published transaction with 6528 bytes of calldata.
seth-send: 0x7a5918711c0c0bbc18da3ac57e664ac48a0b1102fa0301f01ac71d31ef1b5df6
seth-send: Waiting for transaction receipt..........................
seth-send: Transaction included in block 6808530.
0xc9e5ba29E85f5F39333A3B2DF238fff40fcA36D0
接下来,让我们介绍如何与已部署的 Escrow 合约进行交互。
我们可以使用 seth
(Dapp Tools 的一部分)与我们已部署的合约进行交互。首先,让我们为已部署合约地址设置一个全局值。在你的终端窗口中运行以下命令,并输入你的 Escrow 合约地址:
CONTRACT_ADDRESS=YOUR_DEPLOYED_ESCROW_CONTRACT_ADDRESS
以下是一些示例命令:
要将参与者添加到白名单(作为管理员):
seth send $CONTRACT_ADDRESS "addParticipants(address[])" "[0xb84c..., 0x894c...]"
作为白名单参与者存入 0.01 ETH:
seth send $CONTRACT_ADDRESS "depositFunds()" --value $(seth --to-wei 0.01 eth)
注意,你需要在
.sethrc
中重新配置FROM_ADDRESS
为参与者地址。
作为管理员将 0.01 ETH 分配给参与者:
seth send $CONTRACT_ADDRESS "distribute(uint256,uint256)" 1 $(seth --to-wei 0.01 eth)
注意,分配函数需要一个参与者 ID(从 1 开始)。
获取参与者的余额:
seth call $CONTRACT_ADDRESS "getBalance(address)(uint256)" "PARTICIPANT_ADDRESS"
记得在调用具有 onlyAdmin
或 onlyWhitelisted
修饰符的函数时使用正确的地址(管理员或参与者)。
Dapp Tools 为以太坊开发提供了一套强大的实用工具。通过利用其测试、部署和交互能力,你可以简化智能合约开发过程,创建更强大、经过良好测试的合约。
我们希望看到你正在创建的内容!在 Discord 或推特上与我们分享你的项目。如果你对本指南有任何反馈或问题,我们很乐意听到你的声音!
让我们知道 如果你有任何反馈或新主题请求。我们期待你的反馈。
- 原文链接: quicknode.com/guides/eth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!