跳到主要内容

Ethers极简入门: 6. 部署合约

我最近在重新学ethers.js,巩固一下细节,也写一个WTF Ethers极简入门,供小白们使用。

推特@0xAA_Science

WTF Academy社群: 官网 wtf.academy | WTF Solidity教程 | discord | 微信群申请

所有代码和教程开源在github: github.com/WTFAcademy/WTFEthers


这一讲,我们将介绍ethers.js中的合约工厂ContractFactory类型,并利用它部署合约。

部署智能合约

在以太坊上,智能合约的部署是一种特殊的交易:将编译智能合约得到的字节码发送到0地址。如果这个合约的构造函数有参数的话,需要利用abi.encode将参数编码为字节码,然后附在在合约字节码的尾部一起发送。对于ABI编码的介绍见WTF Solidity极简教程第27讲 ABI编码

合约工厂

ethers.js创造了合约工厂ContractFactory类型,方便开发者部署合约。你可以利用合约abi,编译得到的字节码bytecode和签名者变量signer来创建合约工厂实例,为部署合约做准备。

const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);

注意:如果合约的构造函数有参数,那么在abi中必须包含构造函数。

在创建好合约工厂实例之后,可以调用它的deploy函数并传入合约构造函数的参数args来部署并获得合约实例:

const contract = await contractFactory.deploy(args)

你可以利用下面两种命令,等待合约部署在链上确认,然后再进行交互。

await contractERC20.deployed()
//或者 await contract.deployTransaction.wait()

例子:部署ERC20代币合约

ERC20标准代币合约的介绍见WTF Solidity极简教程第31讲 ERC20

  1. 创建providerwallet变量。

    import { ethers } from "ethers";

    // 利用Infura的rpc节点连接以太坊网络
    const INFURA_ID = '184d4c5ec78243c290d151d3f1a10f1d'
    // 连接Rinkeby测试网
    const provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${INFURA_ID}`)

    // 利用私钥和provider创建wallet对象
    const privateKey = '0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b'
    const wallet = new ethers.Wallet(privateKey, provider)
  2. 准备ERC20合约的字节码和ABI。因为ERC20的构造函数含有参数,因此我们必须把它包含在ABI中。合约的字节码可以从remix的部署面板中点击Bytecode按钮,把它复制下来,其中"object"字段对应的数据就是字节码。如果部署在链上的合约,你可以在etherscan的Contract页面的Contract Creation Code中找到。

    // ERC20的人类可读abi
    const abiERC20 = [
    "constructor(string memory name_, string memory symbol_)",
    "function name() view returns (string)",
    "function symbol() view returns (string)",
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint)",
    "function transfer(address to, uint256 amount) external returns (bool)",
    "function mint(uint amount) external",
    ];
    // 填入合约字节码,在remix中,你可以在两个地方找到Bytecode
    // 1. 部署面板的Bytecode按钮
    // 2. 文件面板artifact文件夹下与合约同名的json文件中
    // 里面"object"字段对应的数据就是Bytecode,挺长的,608060起始
    // "object": "608060405260646000553480156100...
    const bytecodeERC20 = ""

    Remix中获取字节码

  3. 创建合约工厂ContractFactory实例。

    const factoryERC20 = new ethers.ContractFactory(abiERC20, bytecodeERC20, wallet);
  4. 调用工厂合约的deploy()函数并填入构造函数的参数(代币名称和代号),部署ERC20代币合约并获得合约实例。你可以利用:

    • contract.address获取合约地址,
    • contract.deployTransaction获取部署详情,
    • contractERC20.deployed()等待合约部署在链上确认。
    // 1. 利用contractFactory部署ERC20代币合约
    console.log("\n1. 利用contractFactory部署ERC20代币合约")
    // 部署合约,填入constructor的参数
    const contractERC20 = await factoryERC20.deploy("WTF Token", "WTF")
    console.log(`合约地址: ${contractERC20.address}`);
    console.log("部署合约的交易详情")
    console.log(contractERC20.deployTransaction)
    console.log("\n等待合约部署上链")
    await contractERC20.deployed()
    console.log("合约已上链")

    部署合约

  5. 在合约上链后,调用name()symbol()函数打印代币名称和代号,然后调用mint()函数给自己铸造10,000枚代币。

    // 打印合约的name()和symbol(),然后调用mint()函数,给自己地址mint 10,000代币
    console.log("\n2. 调用mint()函数,给自己地址mint 10,000代币")
    console.log(`合约名称: ${await contractERC20.name()}`)
    console.log(`合约代号: ${await contractERC20.symbol()}`)
    let tx = await contractERC20.mint("10000")
    console.log("等待交易上链")
    await tx.wait()
    console.log(`mint后地址中代币余额: ${await contractERC20.balanceOf(wallet.address)}`)
    console.log(`代币总供给: ${await contractERC20.totalSupply()}`)

    铸造代币

  6. 调用transfer()函数,给V神转账1,000枚代币。

    // 3. 调用transfer()函数,给V神转账1000代币
    console.log("\n3. 调用transfer()函数,给V神转账1,000代币")
    tx = await contractERC20.transfer("vitalik.eth", "1000")
    console.log("等待交易上链")
    await tx.wait()
    console.log(`V神钱包中的代币余额: ${await contractERC20.balanceOf("vitalik.eth")}`)

    转账

总结

这一讲我们介绍了ether.js中的合约工厂ContractFactory类型,利用它部署了一个ERC20代币合约,并给V神转账了1,000枚代币。