如何使用ApeWorX/ape创建一个质押资金池智能合约

  • QuickNode
  • 发布于 2024-03-03 14:28
  • 阅读 11

这篇指南详细介绍了如何使用ApeWorX框架在以太坊Sepolia测试网部署ERC4626合规的质押储蓄合约。文章深入讲解了创建和部署ERC20代币和铁定盈利的储蓄合约的整个过程,包括必要的依赖、代码实现以及如何进行智能合约测试和部署,适合有一定基础的智能合约开发者。

提示

虽然本指南演示了如何在以太坊 Sepolia 测试网中进行部署,但智能合约代码兼容任何基于 EVM 的区块链。欢迎根据其他网络(如 Base、Optimism、Avalanche 等)调整部署流程。请查看 QuickNode 支持的所有链 在这里

请注意,本指南中展示的代码仅供教育用途,并作为开发的起点。它尚未经过彻底测试以用于生产环境。如果你对 Solidity 版本感兴趣,请查看此 指南

概述

ApeWorX,简称“ape”,是一个强大且灵活的开发框架,用于在 EVM 兼容区块链上进行智能合约开发。在本指南中,我们将创建和部署一个符合 ERC4626 标准的质押库智能合约,该合约使用 Vyper 编写,并使用 ApeWorX 框架进行测试。

让我们开始吧!

你将需要的工具

依赖项 版本
Python ^3.9.2
pip 24.2
eth-ape 0.8.10

你将要做的事情

  • 了解 ApeWorX/ape
  • 使用 Ape 创建并部署 ERC-20 代币
  • 创建并部署一个 ERC-4626 的质押库合约,以质押 ERC-20 代币
  • 测试在质押库智能合约中存入和提取份额

什么是 Ape (ApeWorX)

Ape 是一个基于 Python 的开发框架和以太坊生态系统智能合约工具套件。它提供了一整套用于编写、测试和部署智能合约的工具。Ape 支持多种语言进行智能合约开发,包括 Solidity 和 Vyper。本指南将重点介绍使用 Vyper 创建智能合约,Vyper 是一种面向合约的 Python 风格的编程语言,旨在以安全性和审计的便利性为目标,优于 Solidity,因此对新开发者而言是一个极好的选择。

设置项目

首先,让我们设置我们的 QuickNode 端点,以便我们能够与以太坊网络进行通信。然后,我们将初始化一个 Ape 项目目录,并安装必要的依赖项。

创建一个 QuickNode RPC 端点

要与以太坊网络互动并部署本文所述的智能合约,我们需要一个 RPC 端点。请按照以下步骤使用 QuickNode 创建一个:

  1. QuickNode 注册一个账户
  2. 创建一个新的以太坊主网端点
  3. 将 HTTP 提供程序 URL 保留,供以后使用

初始化 Ape 项目

使用以下命令创建一个项目目录:

mkdir staking-vault-ape
cd staking-vault-ape

使用 ape 时,设置虚拟环境是一种良好的实践。使用以下命令设置一个虚拟环境:

python -m venv vyper-env

根据你的 Python 安装配置,你可能需要使用 python3

然后激活它:

source vyper-env/bin/activate

接下来,安装 Ape:

pip install eth-ape

然后,初始化一个新的 Ape 项目:

ape init

在系统提示项目名称时,输入“staking-vault-ape”。初始化后,你将看到以下目录结构:

├── contracts // 用于 Solidity 和 Vyper 智能合约
├── scripts // 部署和与智能合约互动的脚本
├── tests // 本地测试
├── vyper-env

然后,如果你尚未安装 Vyper,可以通过 ape 插件进行安装:

ape plugins install vyper

接下来,在代码编辑器中打开项目,并更新项目目录中的 ape-config.yaml 文件,以在 YOUR_QUICKNODE_RPC_URL 占位符中包含你的 QuickNode HTTP 提供程序 URL。

name: ape-staking-vault

node:
  ethereum:
    sepolia:
      uri: YOUR_QUICKNODE_RPC_URL

记得保存文件。

接下来,让我们配置将用于智能合约部署的账户。在终端中运行以下命令,并按照提示操作(准备好你的私钥):

ape accounts import my_account

my_account 是一个别名,可以根据自己的喜好进行修改。你也可以在 这里 查看其他账户选项方法。

系统会提示你输入私钥和密码。

在下一部分中,我们将设置创建、部署和测试质押奖励智能合约所需的智能合约和脚本。

创建 ERC-20 代币

我们来创建一个简单的 ERC-20 代币,用于质押。该代币命名为 StakingToken (ST),在合约部署时将铸造 1000000 代币至合约部署者。

创建一个新的文件 contracts/StakingToken.vy 并添加以下 Vyper 代码:

## @version 0.3.7

from vyper.interfaces import ERC20

implements: ERC20

## ERC20 代币元数据
NAME: constant(String[20]) = "StakingToken"
SYMBOL: constant(String[5]) = "ST"
DECIMALS: constant(uint8) = 18

## ERC20 状态变量
totalSupply: public(uint256)
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])

## 事件
event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    amount: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    amount: uint256

owner: public(address)
isMinter: public(HashMap[address, bool])
nonces: public(HashMap[address, uint256])
DOMAIN_SEPARATOR: public(bytes32)
DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
PERMIT_TYPE_HASH: constant(bytes32) = keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)')

@external
def __init__():
    self.owner = msg.sender
    self.totalSupply = 1000000
    self.balanceOf[msg.sender] = 1000000
    self.DOMAIN_SEPARATOR = keccak256(
        concat(
            DOMAIN_TYPE_HASH,
            keccak256(NAME),
            keccak256("1.0"),
            _abi_encode(chain.id, self)
        )
    )

@pure
@external
def name() -> String[20]:
    return NAME

@pure
@external
def symbol() -> String[5]:
    return SYMBOL

@pure
@external
def decimals() -> uint8:
    return DECIMALS

@external
def transfer(receiver: address, amount: uint256) -> bool:
    assert receiver not in [empty(address), self]

    self.balanceOf[msg.sender] -= amount
    self.balanceOf[receiver] += amount

    log Transfer(msg.sender, receiver, amount)
    return True

@external
def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
    assert receiver not in [empty(address), self]

    self.allowance[sender][msg.sender] -= amount
    self.balanceOf[sender] -= amount
    self.balanceOf[receiver] += amount

    log Transfer(sender, receiver, amount)
    return True

@external
def approve(spender: address, amount: uint256) -> bool:
    """
    @param spender 将代表所有者执行操作的地址。
    @param amount 要转移的代币数量。
    """
    self.allowance[msg.sender][spender] = amount

    log Approval(msg.sender, spender, amount)
    return True

@external
def burn(amount: uint256) -> bool:
    """
    @notice 从发送者的钱包中销毁提供的代币数量。
    @param amount 要销毁的代币数量。
    """
    self.balanceOf[msg.sender] -= amount
    self.totalSupply -= amount

    log Transfer(msg.sender, empty(address), amount)

    return True

@external
def mint(receiver: address, amount: uint256) -> bool:
    """
    @notice 铸造代币的函数
    @param receiver 接收铸造代币的地址。
    @param amount 要铸造的代币数量。
    @return 一个布尔值,指示操作是否成功。
    """
    assert msg.sender == self.owner or self.isMinter[msg.sender], "访问被拒绝。"
    assert receiver not in [empty(address), self]

    self.totalSupply += amount
    self.balanceOf[receiver] += amount

    log Transfer(empty(address), receiver, amount)

    return True

@external
def permit(owner: address, spender: address, amount: uint256, expiry: uint256, signature: Bytes[65]) -> bool:
    """
    @notice
        通过所有者的签名批准花费所有者的代币。
        请参见https://eips.ethereum.org/EIPS/eip-2612。
    @param owner 签署许可证的资金来源地址。
    @param spender 被允许花费资金的地址。
    @param amount 要花费的代币数量。
    @param expiry 超过此时间戳后许可证将不再有效。
    @param signature 有效 secp256k1 签名的许可证,由所有者编码为 r,s,v。
    @return 如果事务成功完成,则返回 True
    """
    assert owner != empty(address)  # dev: 无效的所有者
    assert expiry == 0 or expiry >= block.timestamp  # dev: 许可证过期
    nonce: uint256 = self.nonces[owner]
    digest: bytes32 = keccak256(
        concat(
            b'\x19\x01',
            self.DOMAIN_SEPARATOR,
            keccak256(
                _abi_encode(
                    PERMIT_TYPE_HASH,
                    owner,
                    spender,
                    amount,
                    nonce,
                    expiry,
                )
            )
        )
    )
    # 注意:签名按 r, s, v 打包
    r: uint256 = convert(slice(signature, 0, 32), uint256)
    s: uint256 = convert(slice(signature, 32, 32), uint256)
    v: uint256 = convert(slice(signature, 64, 1), uint256)
    assert ecrecover(digest, v, r, s) == owner  # dev: 签名无效
    self.allowance[owner][spender] = amount
    self.nonces[owner] = nonce + 1

    log Approval(owner, spender, amount)
    return True

上述代码是合规的 ERC-20 代币。你可以在 这里 查看 EIP 标准,或查看我们的指南以获取更多信息:如何创建和部署 ERC20 代币

创建 ERC4626 质押库合约

在本指南中,我们将实现一个符合 ERC4626 标准的质押库。ERC4626 标准化了代币化库的接口,使其更容易与其他 DeFi 协议进行交互和集成。我们的合约将允许用户存入一个 ERC20 代币(资产),并获得份额作为回报,代表他们在库中的权益。

现在,创建一个名为 contracts/StakingVault.vy 的新文件,并输入以下代码:

## @version 0.3.7
from vyper.interfaces import ERC20
from vyper.interfaces import ERC4626

implements: ERC20
implements: ERC4626

###### ERC20 ###

totalSupply: public(uint256)
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])

NAME: constant(String[10]) = "Test Vault"
SYMBOL: constant(String[5]) = "vTEST"
DECIMALS: constant(uint8) = 18

event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    amount: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    allowance: uint256

###### ERC4626 ###

asset: public(ERC20)

event Deposit:
    depositor: indexed(address)
    receiver: indexed(address)
    assets: uint256
    shares: uint256

event Withdraw:
    withdrawer: indexed(address)
    receiver: indexed(address)
    owner: indexed(address)
    assets: uint256
    shares: uint256

@external
def __init__(asset: ERC20):
    self.asset = asset

@view
@external
def name() -> String[10]:
    return NAME

@view
@external
def symbol() -> String[5]:
    return SYMBOL

@view
@external
def decimals() -> uint8:
    return DECIMALS

@external
def transfer(receiver: address, amount: uint256) -> bool:
    self.balanceOf[msg.sender] -= amount
    self.balanceOf[receiver] += amount
    log Transfer(msg.sender, receiver, amount)
    return True

@external
def approve(spender: address, amount: uint256) -> bool:
    self.allowance[msg.sender][spender] = amount
    log Approval(msg.sender, spender, amount)
    return True

@external
def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
    self.allowance[sender][msg.sender] -= amount
    self.balanceOf[sender] -= amount
    self.balanceOf[receiver] += amount
    log Transfer(sender, receiver, amount)
    return True

@view
@external
def totalAssets() -> uint256:
    return self.asset.balanceOf(self)

@view
@internal
def _convertToAssets(shareAmount: uint256) -> uint256:
    totalSupply: uint256 = self.totalSupply
    if totalSupply == 0:
        return 0

    # 注意:`shareAmount = 0` 是极为罕见的情况,不做优化
    # 注意:`totalAssets = 0` 是极为罕见的情况,不做优化
    return shareAmount * self.asset.balanceOf(self) / totalSupply

@view
@external
def convertToAssets(shareAmount: uint256) -> uint256:
    return self._convertToAssets(shareAmount)

@view
@internal
def _convertToShares(assetAmount: uint256) -> uint256:
    totalSupply: uint256 = self.totalSupply
    totalAssets: uint256 = self.asset.balanceOf(self)
    if totalAssets == 0 or totalSupply == 0:
        return assetAmount  # 1:1 价格

    # 注意:`assetAmount = 0` 是极为罕见的情况,不做优化
    return assetAmount * totalSupply / totalAssets

@view
@external
def convertToShares(assetAmount: uint256) -> uint256:
    return self._convertToShares(assetAmount)

@view
@external
def maxDeposit(owner: address) -> uint256:
    return MAX_UINT256

@view
@external
def previewDeposit(assets: uint256) -> uint256:
    return self._convertToShares(assets)

@external
def deposit(assets: uint256, receiver: address=msg.sender) -> uint256:
    shares: uint256 = self._convertToShares(assets)
    self.asset.transferFrom(msg.sender, self, assets)

    self.totalSupply += shares
    self.balanceOf[receiver] += shares
    log Transfer(empty(address), receiver, shares)
    log Deposit(msg.sender, receiver, assets, shares)
    return shares

@view
@external
def maxMint(owner: address) -> uint256:
    return MAX_UINT256

@view
@external
def previewMint(shares: uint256) -> uint256:
    assets: uint256 = self._convertToAssets(shares)

    # 注意:Vyper 会对 if 进行懒惰评估,因此大多数时候避免了 SLOAD
    if assets == 0 and self.asset.balanceOf(self) == 0:
        return shares  # 注意:如果尚未存入,则假设 1:1 价格

    return assets

@external
def mint(shares: uint256, receiver: address=msg.sender) -> uint256:
    assets: uint256 = self._convertToAssets(shares)

    if assets == 0 and self.asset.balanceOf(self) == 0:
        assets = shares  # 注意:如果尚未存入,则假设 1:1 价格

    self.asset.transferFrom(msg.sender, self, assets)

    self.totalSupply += shares
    self.balanceOf[receiver] += shares
    log Transfer(empty(address), receiver, shares)
    log Deposit(msg.sender, receiver, assets, shares)
    return assets

@view
@external
def maxWithdraw(owner: address) -> uint256:
    return MAX_UINT256  # 实际最大值为 `self.asset.balanceOf(self)`

@view
@external
def previewWithdraw(assets: uint256) -> uint256:
    shares: uint256 = self._convertToShares(assets)

    # 注意:Vyper 会对 if 进行懒惰评估,因此大多数时候避免了 SLOAD
    if shares == assets and self.totalSupply == 0:
        return 0  # 注意:无可赎回的部分

    return shares

@external
def withdraw(assets: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256:
    shares: uint256 = self._convertToShares(assets)

    # 注意:Vyper 会对 if 进行懒惰评估,因此大多数时候避免了 SLOAD
    if shares == assets and self.totalSupply == 0:
        raise  # 无可赎回的部分

    if owner != msg.sender:
        self.allowance[owner][msg.sender] -= shares

    self.totalSupply -= shares
    self.balanceOf[owner] -= shares

    self.asset.transfer(receiver, assets)
    log Transfer(owner, empty(address), shares)
    log Withdraw(msg.sender, receiver, owner, assets, shares)
    return shares

@view
@external
def maxRedeem(owner: address) -> uint256:
    return MAX_UINT256  # 实际最大值为 `self.totalSupply`

@view
@external
def previewRedeem(shares: uint256) -> uint256:
    return self._convertToAssets(shares)

@external
def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256:
    if owner != msg.sender:
        self.allowance[owner][msg.sender] -= shares

    assets: uint256 = self._convertToAssets(shares)
    self.totalSupply -= shares
    self.balanceOf[owner] -= shares

    self.asset.transfer(receiver, assets)
    log Transfer(owner, empty(address), shares)
    log Withdraw(msg.sender, receiver, owner, assets, shares)
    return assets

上述代码遵循 ERC4626 标准,同时也继承 ERC-20 标准。实现的函数包括:

  • asset(): 返回基础资产代币的地址
  • totalAssets(): 返回库中持有的基础资产的总量
  • convertToShares(assets): 计算给定数量的资产将铸造多少份额
  • convertToAssets(shares): 计算给定数量的份额将赎回多少资产
  • maxDeposit(receiver): 返回可以存入的最大资产数量
  • previewDeposit(assets): 模拟存入的资产将铸造多少份额
  • deposit(assets, receiver): 将资产存入库中并铸造份额给接收者
  • maxMint(receiver): 返回可以铸造的最大份额数量
  • previewMint(shares): 模拟铸造特定数量份额所需的资产数量
  • mint(shares, receiver): 将特定数量的份额铸造给接收者,并存入必要的资产
  • maxWithdraw(owner): 返回所有者可以提取的最大资产数量
  • previewWithdraw(assets): 模拟提取所需燃烧的份额数量
  • withdraw(assets, receiver, owner): 将特定数量的资产提取给接收者,燃烧必要的份额
  • maxRedeem(owner): 返回所有者可以赎回的最大份额数量
  • previewRedeem(shares): 模拟赎回特定数量份额所需提取的资产数量
  • redeem(shares, receiver, owner): 赎回特定数量的份额,从而将资产提取到接收者

欲了解有关此标准的更多信息,请查看 EIP 在这里。另外,你还可以查看我们的指南:如何在你的智能合约中使用 ERC-4626

在下一节中,我们将测试从库合约中存款和提取代币,然后再部署到像 Sepolia 这样的公共测试网。

测试存款和提取

为测试我们的 ERC4626 库合约,我们将创建一个使用 Ape 测试框架的 Python 脚本。这将使我们能够在本地测试环境中部署我们的合约,存入代币(质押)和提取代币(解除质押),确保我们的实现符合 ERC4626 标准。

在项目目录中创建一个名为 tests/test_vault.py 的新文件,并添加以下代码:

import pytest
from ape import accounts, project
from ape.exceptions import VirtualMachineError

@pytest.fixture(scope="module")
def owner(accounts):
    return accounts[0]

@pytest.fixture(scope="module")
def user1(accounts):
    return accounts[1]

@pytest.fixture(scope="module")
def user2(accounts):
    return accounts[2]

@pytest.fixture(scope="function")
def asset_token(owner, project):
    # 部署一个模拟的 ERC20 代币作为资产
    return owner.deploy(project.StakingToken)

@pytest.fixture(scope="function")
def staking_reward(owner, asset_token, project):
    # 部署 StakingVault 合约
    return owner.deploy(project.StakingVault, asset_token.address)

def test_initial_state(staking_reward, asset_token):
    assert staking_reward.name() == "Test Vault"
    assert staking_reward.symbol() == "vTEST"
    assert staking_reward.decimals() == 18
    assert staking_reward.totalSupply() == 0
    assert staking_reward.totalAssets() == 0
    assert staking_reward.asset() == asset_token.address

def test_deposit_and_withdraw(staking_reward, asset_token, owner, user1):
    # 为测试铸造一些代币给 user1
    asset_token.mint(user1, 1000, sender=owner)

    # 批准质押合约支出 user1 的代币
    asset_token.approve(staking_reward.address, 500, sender=user1)

    # 存入代币
    staking_reward.deposit(500, user1, sender=user1)

    assert staking_reward.balanceOf(user1) == 500
    assert asset_token.balanceOf(staking_reward.address) == 500

    # 提取代币
    staking_reward.withdraw(500, user1, sender=user1)

    assert staking_reward.balanceOf(user1) == 0
    assert asset_token.balanceOf(user1) == 1000

def test_conversion_functions(staking_reward, asset_token, owner, user1):
    # 为转换测试铸造和存入代币
    asset_token.mint(user1, 1000, sender=owner)
    asset_token.approve(staking_reward.address, 500, sender=user1)
    staking_reward.deposit(500, user1, sender=user1)

    # 测试 convertToAssets
    shares = staking_reward.convertToShares(100)
    assert shares == 100

    # 测试 convertToShares
    assets = staking_reward.convertToAssets(100)
    assert assets == 100

要运行这些测试,请在终端中使用以下命令:

ape test

你将看到类似如下的输出:

tests/test_staking.py ...                                                                                                                                      [100%]

============================================================================= 3 passed in 2.68s ==============================================================================

部署

现在我们的测试已完成。让我们将智能合约部署到测试网上。

scripts/deploy_token.py 文件中输入以下代码:

from ape import accounts, project, networks

def main():
    # 连接到 Sepolia 网络
    networks.parse_network_choice("ethereum:sepolia")  # 使用你在 ape-config.yaml 中定义的网络

    # 加载导入的账户
    account = accounts.load("my_account")
    print(f"使用账户: {account.address}")

    # 部署 StakingToken 合约
    staking_token = account.deploy(project.StakingToken)

    print(f"StakingToken 部署在: {staking_token.address}")

    return staking_token

if __name__ == "__main__":
    main()

使用以下终端命令进行部署:

ape run deploy_token --network ethereum:sepolia:node

在此过程中,你将被提示签署交易并输入你之前设置的账户密码。

然后,在 scripts/deploy_staking_vault.py 文件中输入以下代码:

from ape import accounts, project, networks, Contract

def main():
    # 使用我们的自定义配置连接到 Sepolia 网络
    networks.parse_network_choice("ethereum:sepolia")

    # 加载导入的账户
    account = accounts.load("my_account")
    print(f"使用账户: {account.address}")

    # 指定在初始化期间要使用的资产代币地址
    asset_token_address = "YOUR_ERC20_TOKEN_ADDRESS"  # 用你的实际部署地址替换

    # 加载现有资产代币合约
    asset_token = Contract(asset_token_address)
    print(f"Loaded AssetToken 在: {asset_token.address}")

    # 打印可用合约
    print("可用合约:", project.contracts)

    # 使用资产代币地址作为参数部署 StakingVault 合约
    vault = account.deploy(project.StakingVault, asset_token_address)
    print(f"StakingVault 库部署在: {vault.address}")

    return asset_token, vault

if __name__ == "__main__":
    main()

记得将 YOUR_ERC20_TOKEN_ADDRESS 替换为之前步骤中输出的代币地址。上述脚本部署了我们的 ERC4626 合规质押库,将接受我们之前部署的资产代币的存款。

使用以下终端命令进行部署:

ape run deploy_staking_vault --network ethereum:sepolia:node

你将再次被提示签署交易。一旦交易得到确认,你将看到类似如下的输出:

INFO: 连接到现有Geth节点 https://dry-omniscient-ensemble.ethereum-sepolia.quiknode.pro/[hidden].
使用账户: 0x894bC5FB859CFD53E7E59E97a5CE3bf89D84fa04
Loaded AssetToken 在: 0xCBF509F9E7251B6217A700EA7816Ce1a10894e48
可用合约: <Contracts $HOME/Documents/Code/guide-tests/con-232-guide-idea-how-to-create-smart-contracts-with-apeworxape/staking-vault-ape/contracts>
DynamicFeeTransaction:
  chainId: 11155111
  from: 0x894bC5FB859CFD53E7E59E97a5CE3bf89D84fa04
  gas: 930942
  nonce: 44
  value: 0
  data: 0x307836...653438
  type: 2
  maxFeePerGas: 10571472034
  maxPriorityFeePerGas: 420184473
  accessList: []

签字:  [y/N]: y
输入密码以解锁 'my_account' []:
保持 'my_account' 解锁吗? [y/N]: y
INFO: 提交到 https://sepolia.etherscan.io/tx/0xa3acdd62ba2d1f422663cdc617de541d19534cdbffd9f5dda6baf81eb602dc9c
确认 (2/2): 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:42<00:00, 21.45s/it]
INFO: 确认 0xa3acdd62ba2d1f422663cdc617de541d19534cdbffd9f5dda6baf81eb602dc9c (总费用支付 = 7006047733668594)
INFO: 确认 0xa3acdd62ba2d1f422663cdc617de541d19534cdbffd9f5dda6baf81eb602dc9c (总费用支付 = 7006047733668594)
SUCCESS: 合约 'StakingVault' 部署到: 0x73e78463952960200322D004dCb13602eB2F8468
StakingVault 库部署在: 0x73e78463952960200322D004dCb13602eB2F8468

就这样!你的合约现在已部署在公共测试网上。在下一节中,我们将介绍如何使这些合约开源。然而,在继续之前,请检查 Etherscan 以确保它们尚未开源(如果开源了,你可以跳过下一节)。

在 Etherscan 上验证

要使你的智能合约开源,可以在 Etherscan 等区块浏览器上验证。请按照以下步骤进行:

  1. 导航至你的智能合约 https://etherscan.io(你需要分别对两个合约进行此操作;即 StakingToken 和 StakingVault)
  2. 转到“合约”选项卡并单击“验证并发布”
  3. 编译器类型:Vyper
  4. 编译器版本:0.3.7 # 根据需要进行更改
  5. 无许可证

在下一页中,输入合约代码。对于 StakingToken,没有构造函数参数。

对于 StakingVault 合约,你需要编码构造函数参数。你可以在相同的项目目录中创建一个 encode.py 文件,包含以下代码。

from eth_abi import encode

## 来自你部署脚本输出的值
staking_token_address = "0x97a54ad6993f1fbcae9fa75357a81eb85160120a" # 更改为你的部署地址

reward_rate = 10**16

## 编码参数
encoded_args = encode(['address', 'uint256'], [staking_token_address, reward_rate])

## 转换为不带 '0x' 前缀的十六进制字符串
hex_encoded_args = encoded_args.hex()

print(hex_encoded_args)

然后执行该脚本:

python encode.py

输出结果将类似于(但合约地址应该是你部署的地址):

00000000000000000000000097a54ad6993f1fbcae9fa75357a81eb85160120a000000000000000000000000000000000000000000000000002386f26fc10000

将上述编码参数粘贴到部署 StakingVault 合约时的“构造函数参数 ABI 编码”字段中。验证合约后,你将在“合约”选项卡图标上看到一个勾号。

你已经成功部署了 ERC-20 代币以及 StakingVault 合约。为了挑战自己,请尝试做以下事情:

  1. 创建脚本在 “StakingVault” 合约上质押“StakingToken”
  2. 创建一个用户界面(UI)与质押合约进行交互
  3. 在公共测试网(我们仅在本地演示)上测试合约功能

最后的想法

在本指南中,我们探索了如何使用 ApeWorX 和 Vyper 创建质押库合约。我们覆盖了从设置开发环境到在 Sepolia 测试网部署和验证我们的合约的整个过程。

我们鼓励你尝试代码,添加新功能,最重要的是,继续构建!区块链生态系统需要像你这样富有创意的开发者来推动创新并创建下一代的 web3 应用程序。

请通过关注我们的 Twitter (@QuickNode) 或加入我们的 Discord 社区,了解最新的区块链开发工具和见解。我们期待看到你接下来将构建的内容!

我们 ❤️ 反馈!

告诉我们 如果你有任何反馈或对新主题的请求。我们很想听听你的想法。

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。