原文在这里。
在线代码编辑器
部署和调用智能合约
这份合同部署在孟买测试网上。在这个实时代码编辑器中,你会找到以下内容:
在代码编辑器中,你会找到:
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');
对比Contract
和web3.eth.Contract
除了上述列出的方式,还有一种创建合约对象的方法。那就是通过访问Web3
对象的实例。这个Web3
的实例通常被称为web3
。
实际上,web3.eth.Contract
通常是你通过一个已经设置了提供者并可能已经有自定义配置的web3实例来访问该类的方式。
TIP
- 如果你想减小应用程序的大小,而不是导入整个
Web3
模块,那么导入web3-eth-contract
模块可能会有益。- 注意
Web3.eth.Contract
和web3instance.eth.Contract
(通常被命名为web3.eth.Contract
)之间的区别是,Web3
(W
大写)用于访问在命名空间中组织的对象。而通常被命名为web3
的web3instance
,是用来访问这个实例的属性。
示例:
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个参数:
- ABI(应用程序二进制接口):ABI告诉合约如何格式化调用和交易,以便合约能够理解它们。
提示 如果你不知道如何获取合约ABI,我们建议你查看#步骤4:部署和交互智能合约教程的第4步。并查看指南:从JSON工件推断合约类型。
- (可选)合约地址:你的合约部署的以太坊地址。如果合约还未部署,不要传递第二个参数或将其设为未定义。
- (可选)合约选项:你可以作为第三个参数提供合约选项。
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: 恋水无意
腾讯云开发者社区:孟斯特