原文在这里

在线代码编辑器

部署和调用智能合约

这份合同部署在孟买测试网上。在这个实时代码编辑器中,你会找到以下内容:

在代码编辑器中,你会找到:

  • ContractCounter.sol:solidity编写的合约,其中包括:
    • uint256 number:合同的状态变量。
    • increase():这个函数会将number变量增加1。
    • getNumber():这个函数会返回number变量的当前值。
  • counterABI.json:ContractCounter.sol的ABI。
  • counterBytecode.json:ContractCounter.sol编译后的字节码。
  • main.js:在这里你会找到3个函数:
    • deploy():使用ABI和字节码部署智能合同的示例脚本。
    • getNumber():调用智能合同的读取函数getNumber()的示例脚本。
    • increase():与智能合同的increase()函数交互的示例脚本。
  • 合约地址:

    0xB9433C87349134892f6C9a9E342Ed6adce39F8dF

import './style.css';
import { Web3 } from 'web3';
import ABI from './counterABI.json';
import BYTECODE from './counterBytecode.json';

//initialize mumbai provider
const provider = new Web3('https://rpc.ankr.com/polygon_mumbai');

async function deploy() {
  //initialize a wallet(with funds)
  const wallet = provider.eth.wallet.add('YOUR_PRIVATE_KEY');

  //initialize contract
  const myContract = new provider.eth.Contract(ABI);

  //create contract deployer
  const deployer = myContract.deploy({
    data: '0x' + BYTECODE, //bytecode must start with 0x
    arguments: [7], //starting number for the constructor in the contract
  });

  //send transaction to the network
  const txReceipt = await deployer.send({ from: wallet[0].address });

  //print deployed contract address
  console.log(txReceipt.options.address);
}

//deploy();

async function getNumber() {
  //initialize contract
  const address = '0xB9433C87349134892f6C9a9E342Ed6adce39F8dF';
  const myContract = new provider.eth.Contract(ABI, address);

  //make call
  const result = await myContract.methods.getNumber().call();

  //print result of current counter
  document.querySelector('#app').innerHTML = `
  Current counter is: ${result} <br>`;
}

getNumber();

async function increase() {
  //initialize a wallet(with funds)
  const wallet = provider.eth.wallet.add('YOUR_PRIVATE_KEY');

  //initialize contract
  const address = '0xB9433C87349134892f6C9a9E342Ed6adce39F8dF';
  const myContract = new provider.eth.Contract(ABI, address);

  //send transaction to the network
  const txReceipt = await myContract.methods
    .increase() //name of the function you are calling in the contract
    .send({ from: wallet[0].address });

  //show tx hash
  console.log(txReceipt.transactionHash);
}

//increase();

合约类

INFO: 这份指南假设你已经有一些基础知识。如果你刚刚开始,建议你首先查看这个教程:部署和交互智能合同

Contract类是web3-eth-contract包导出的主要对象。它也在web3包中可用。

导入Contract类

要使用Contract类,你需要从两个包中的一个导入它:直接从web3-eth-contract包导入,或者从web3包导入。

下面是使用这两个包的示例:

// Importing from web3-eth-contract package
import { Contract } from 'web3-eth-contract';
const contract = new Contract(...);

// Importing from the main web3 package
import { Contract } from 'web3';
const contract = new Contract(...);

// Importing from the main web3 package from inside `web3.eth` namespace
import { Web3 } from 'web3';
const web3 = new Web3('http://127.0.0.1:8545');
const contract = new web3.eth.Contract(...);

// to set the provider for the contract instance:
contract.setProvider('http://127.0.0.1:7545');

对比Contractweb3.eth.Contract

除了上述列出的方式,还有一种创建合约对象的方法。那就是通过访问Web3对象的实例。这个Web3的实例通常被称为web3

实际上,web3.eth.Contract通常是你通过一个已经设置了提供者并可能已经有自定义配置的web3实例来访问该类的方式。

TIP

  1. 如果你想减小应用程序的大小,而不是导入整个Web3模块,那么导入web3-eth-contract模块可能会有益。
  2. 注意Web3.eth.Contractweb3instance.eth.Contract(通常被命名为web3.eth.Contract)之间的区别是,Web3W大写)用于访问在命名空间中组织的对象。而通常被命名为web3web3instance,是用来访问这个实例的属性。

示例:

import { Contract } from 'web3-eth-contract';

// instantiating Contract directly with provider URL from Contract package
// alternatively, you can instantiate the Contract without a provider and set it later using contract.setProvider()
const abi = [{...}];
const address = '0x...';
const contract = new Contract(abi, address { provider: 'http://127.0.0.1:8545' }); 

// the provider can be set like this if not provided at the constructor:
contract.setProvider('http://127.0.0.1:7545');

// using Contract from a web3 instance
const web3 = new Web3('http://localhost:8545');
const contract = new web3.eth.Contract(abi, address);
// no need to pass the provider to this contract instance.
// because it will have the same provider of the web3 instance.

构造参数

当你实例化一个合约时,主要提供一个或两个参数,有时是3个参数:

  1. ABI(应用程序二进制接口):ABI告诉合约如何格式化调用和交易,以便合约能够理解它们。

    提示 如果你不知道如何获取合约ABI,我们建议你查看#步骤4:部署和交互智能合约教程的第4步。并查看指南:从JSON工件推断合约类型

  2. (可选)合约地址:你的合约部署的以太坊地址。如果合约还未部署,不要传递第二个参数或将其设为未定义。
  3. (可选)合约选项:你可以作为第三个参数提供合约选项。
const abi = [{...}]; /* obtained ABI as an array */;
const address = '0x...'; // Deployed address of the contract

const myContract = new Contract(abi, address, {
  defaultGasPrice: '20000000000', // default gas price in wei, 20 gwei in this case
  defaultGas: 5000000, // provide the gas limit for transactions
  //...other optional properties
});

合约属性和方法

Contract类配备了一系列用于合约交互的属性和方法。我们鼓励你在合约API文档部分查看它们。


属性包括

  • config:合约实例的配置集,其默认值与web3对象实例的值相同。但是,它允许为特定的合约实例使用不同的配置。所以,在大多数情况下,你会使用web3.eth.Contract并保持父上下文(来自web3实例)的配置。除非有些事情你需要为特定的合约实例单独处理。

以下是如何在合约实例上设置特定配置变量的值的示例:

import {Web3} from 'web3';

// Set up a connection to a testnet or Ethereum network
const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545')); //or new Web3('http://127.0.0.1:8545')

// Create a new contract object using the ABI and bytecode
const abi = [{...}]
const myContract = new web3.eth.Contract(abi);
console.log(myContract.config.handleRevert); //false
    
// This will set `handleRevert` to `true` only on `myContract` instance:
myContract.handleRevert = true; // same as: myContract.config.handleRevert
console.log(myContract.config.handleRevert); //true

API文档中有更多关于config属性的信息

  • options:合约实例的选项集。这些选项可以作为构造函数的第三个参数传递。并且也可以稍后通过contractInstance.options访问。
myContract.options = {
    address: '0x1234567890123456789012345678901234567891',
    from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe',
    gasPrice: '10000000000000',
    gas: 1000000
}

// If the smart contract is not deployed yet, the property `address` will be filled automatically after deployment succeed.
// If the smart contract is already deployed, you can set the `address`:
myContract.options.address = '0x1234567890123456789012345678901234567891';
// this is the same as the second parameter in the constructor:
// new Contract(abi, `address`); 

// set default from address
myContract.options.from = '0x1234567890123456789012345678901234567891'; 
// set default gas price in wei
myContract.options.gasPrice = '20000000000000';
// set the gas limit
myContract.options.gas = 5000000;
    
// you can also use this to update the ABI of the contract
myContract.options.jsonInterface = [{...}]; // ABI
// this is the same as the first parameter in the Contract constructor:
// new Contract(`abi`, address)
  • methods:一个映射你的合约方法的对象,便于调用。这个属性根据传递的ABI提供强类型的方法。以下是如何使用它:
// note that the bellow METHOD_NAME and METHOD_PARAMETERS are 
// according to the early provided ABI.
// And TypeScript intellisense will help you with.

// to call a method by sending a transaction 
contract.methods.METHOD_NAME(METHOD_PARAMETERS).send();
// you need to specify the account (from) that will be used to sign and send the transaction
contract.methods.METHOD_NAME(METHOD_PARAMETERS).send({from: '0x...'});

// to call a view or pure method that does not send a transaction
contract.methods.METHOD_NAME(METHOD_PARAMETERS).call();
  • events:一个映射你的合约事件的对象,允许你订阅它们。

以下是如何使用它的示例:

//If you want to filter events, create `options`:
const options: ContractEventOptions = {
    // the following means all events where `myNumber` is `12` or `13`
    filter: myNumber: [12,13];
    // you can specify the block from where you like to start
    // listing to events
    fromBlock: 'earliest';
    
    // You can also manually set the topics for the event filter.
    // If given the filter property and event signature, 
    // (topic[0]) will not be set automatically.
    // Each topic can also be a nested array of topics that behaves 
    // as `or` operation between the given nested topics.
    topics?: ['0x617cf8a4400dd7963ed519ebe655a16e8da1282bb8fea36a21f634af912f54ab'];
}

// if you would like to not filter, don't pass `options`.
const event = await myContract.events.MyEvent(options);

event.on('data', (data) => {
    console.log(data)
});
event.on('error', (err: Error) => {
    console.log(err);
});

要订阅所有事件,可以使用allEvents

const event = await myContract.events.allEvents(options);

方法包括

  • 部署:部署一个新的合约实例。
// this will give you the accounts from the connected provider
// For example, if you are using MetaMask, it will be the account available.
const providersAccounts = await web3.eth.getAccounts();
const defaultAccount = providersAccounts[0];
console.log('deployer account:', defaultAccount);

// NOTE: If you want to manually unlock an account with a private key, you can use wallet.add(privateKey).
// however, exercise caution and ensure the security of your private keys.

// this is how to obtain the deployer function,
// so you can estimate its needed gas and deploy it.
const contractDeployer = myContract.deploy({
    data: bytecode, // prefix the bytecode with '0x' if it is note already
    arguments: [1], // provide the parameters in an array; in this case, it's the number `1`.
});

// optionally, estimate the gas that will be used for development and log it
const gas = await contractDeployer.estimateGas({
    from: defaultAccount,
});
console.log('estimated gas:', gas);

// Deploy the contract to the Ganache network
const tx = await contractDeployer.send({
    from: defaultAccount,
    gas,
    gasPrice: 10000000000,
});
console.log('Contract deployed at address: ' + tx.options.address);

TIP 如果你不知道如何获取合约字节码,我们建议你查看部署和交互智能合约教程的第四步。

  • getPastEvents:获取此合约的过去事件。它与events属性不同,它返回过去事件的数组,而不是像使用events属性时那样允许订阅它们。更多信息请查阅API文档
  • setProvider:这允许你为合约实例设置特定的提供者。如本指南前面所强调的,如果你从web3-eth-contract导入Contract对象,然后你需要在没有web3上下文可以读取提供者的情况下设置提供者,这将特别方便。
// Importing from web3-eth-contract package
import { Contract } from 'web3-eth-contract';
const contract = new Contract(...);

// to set the provider for the contract instance
contract.setProvider('yourProvider');

ABI和字节码

ABI

ABI是智能合约的应用程序二进制接口(ABI)。它定义了智能合约中可用的方法和变量,我们可以使用这些方法和变量与该智能合约进行交互。

例如,对于以下的solidity代码:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;

contract MyContract {
    uint256 public myNumber;

    constructor(uint256 _myNumber) {
        myNumber = _myNumber;
    }

    function setMyNumber(uint256 _myNumber) public {
        myNumber = _myNumber;
    }
}

它的ABI会是:

const abi = [
    {
        inputs: [{ internalType: 'uint256', name: '_myNumber', type: 'uint256' }],
        stateMutability: 'nonpayable',
        type: 'constructor',
    },
    {
        inputs: [],
        name: 'myNumber',
        outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
        stateMutability: 'view',
        type: 'function',
    },
    {
        inputs: [{ internalType: 'uint256', name: '_myNumber', type: 'uint256' }],
        name: 'setMyNumber',
        outputs: [],
        stateMutability: 'nonpayable',
        type: 'function',
    },
] as const

字节码

字节码是Solidity代码编译的结果。字节码通常是紧凑的数字代码、常量和其他信息。其中每个指令步骤都是一个被称为“操作码”的操作,通常长度为一字节(八位)。这就是它们被称为“字节码”的原因——一字节的操作码。并且这种字节码通常表示为类似于以下的长十六进制字符串:

const bytecode = '0x60806040523480156100115760006000fd5b50604051610224380380610224...'

INFO 如前面章节中的提示所述:如果你不知道如何获取合约的ABI和字节码,我们建议你查看部署和交互智能合约教程的第四步。

我是否总是需要合约的字节码?

简单来说,只有当你需要自己部署智能合约时,你才需要字节码。以下是对此的更多详细说明。

基本上,对于每个合约实例,有两种情况。第一种情况是你想要部署一个智能合约。在这种情况下,你需要提供这个智能合约的字节码。

import {Contract} from 'web3-eth-contract';

const myContract = new Contract(abi, undefined, options);
// if there is no options to be passed you can write:
const myContract = new Contract(abi);

await myContract.deploy({
    data: '0x' + bytecode,

    // the smart contract constructor arguments in an array
    arguments: [arg1, arg2],
}).send({
    from: someAccount,
    ...
});

// the contract address will be filled automatically here after deployment:
myContract.options.address

另一种情况是,当你想要与已经部署的智能合约进行交互时。在这种情况下,你需要提供已经部署的智能合约的地址。

import {Contract} from 'web3-eth-contract';

const myContract = new Contract(abi, smartContractAddress, options);
// if there is no options to be passed you can write:
const myContract = new Contract(abi, smartContractAddress);

我是否总是需要合约的ABI?

答案是肯定的,你需要ABI,特别是如果你想享受TypeScript的智能提示,我们强烈建议这样做。ABI会告诉你的开发环境关于合约的结构,从而提供改进的代码建议和类型检查。

如果你选择不提供ABI,你将无法正确地与合约的方法进行交互,也将无法享受智能提示的支持。

const myContract = new Contract(abi, address || undefined, options);
// remember that address can be empty if the contract is not deployed yet.
// or you can set the address to directly interact with the contract.

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特