solidity用abi调用智能合约的方法详解
首先来看看调用一个智能合约的方法需要哪些东西,按照我们以往的经验,我们要调用一个方法,首先我们需要知道该方法的名称,以及需要传入的参数,还有该方法是在哪里调用的。
调用智能合约的方法也不例外, 我们同样需要该智能合约有哪些方法和该方法需要的参数,以及智能合约的地址,在强类型的语言中,同时我们还需要知道参数的类型。
什么是abi
那我们去哪知道一个智能合约到底有哪些方法呢,这时我们就需要一个叫abi的东西,abi描述了一个智能合约的各个信息,以及描述了该合约有哪些方法,方法的可见性等信息。
以下面的一个简单的合约举例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Callled {
uint256 public x;
function foo(uint256 _x) external returns (string memory) {
x = _x;
return "foo called";
}
}
我们对其进行编译,那么在项目的artifacts
目录下会自动为我们生成合约的描述文件,里面就包含了abi的信息,abi是一个数组,描述了有哪些方法,需要注意的是,编译过后,如果变量是外部可见的,那么会自动给我们生成一个访问变量的方法,也会被记录到abi中。比如下面的 x。
每个abi中input描述了入参的名称和类型,是个数组,表示多个,而output则描述了返回参数的名称和类型,如果不具名返回,则name为空。
{
"_format": "hh-sol-artifact-1",
"contractName": "Callled",
"sourceName": "contracts/call.sol",
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "_x",
"type": "uint256"
}
],
"name": "foo",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "x",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b5061024f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630c55699c1461003b5780632fbebd3814610059575b600080fd5b610043610089565b60405161005091906100ee565b60405180910390f35b610073600480360381019061006e919061013a565b61008f565b60405161008091906101f7565b60405180910390f35b60005481565b6060816000819055506040518060400160405280600a81526020017f666f6f2063616c6c6564000000000000000000000000000000000000000000008152509050919050565b6000819050919050565b6100e8816100d5565b82525050565b600060208201905061010360008301846100df565b92915050565b600080fd5b610117816100d5565b811461012257600080fd5b50565b6000813590506101348161010e565b92915050565b6000602082840312156101505761014f610109565b5b600061015e84828501610125565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156101a1578082015181840152602081019050610186565b60008484015250505050565b6000601f19601f8301169050919050565b60006101c982610167565b6101d38185610172565b93506101e3818560208601610183565b6101ec816101ad565b840191505092915050565b6000602082019050818103600083015261021181846101be565b90509291505056fea2646970667358221220bd1204a1c1c8297de8b2d64fa78f44fc6c9030f68b4b89951ad9b3a38c6f131664736f6c63430008120033",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80630c55699c1461003b5780632fbebd3814610059575b600080fd5b610043610089565b60405161005091906100ee565b60405180910390f35b610073600480360381019061006e919061013a565b61008f565b60405161008091906101f7565b60405180910390f35b60005481565b6060816000819055506040518060400160405280600a81526020017f666f6f2063616c6c6564000000000000000000000000000000000000000000008152509050919050565b6000819050919050565b6100e8816100d5565b82525050565b600060208201905061010360008301846100df565b92915050565b600080fd5b610117816100d5565b811461012257600080fd5b50565b6000813590506101348161010e565b92915050565b6000602082840312156101505761014f610109565b5b600061015e84828501610125565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156101a1578082015181840152602081019050610186565b60008484015250505050565b6000601f19601f8301169050919050565b60006101c982610167565b6101d38185610172565b93506101e3818560208601610183565b6101ec816101ad565b840191505092915050565b6000602082019050818103600083015261021181846101be565b90509291505056fea2646970667358221220bd1204a1c1c8297de8b2d64fa78f44fc6c9030f68b4b89951ad9b3a38c6f131664736f6c63430008120033",
"linkReferences": {},
"deployedLinkReferences": {}
}
有了这些信息,我们就可以写另外一个合约,来调用这个合约的abi,
调用合约方法(拆解)
以下是拆解做法:
首先我们通过getFunctionByte4来获取函数名,以及参数的类型,这是固定写法,注意都是有顺序要求的,先用keccak256进行编码,再截取前4个字节。这个等于就是方法的签名。如果调用合约的方法没有入参,例如foo()
,那么call方法里可以直接传bytes4(keccak256("foo()"))
然后我们再通过abi.encode获取参数的编码,有多少个参数依次再后面追加即可。
拿到之后我们把这两个参数进行拼接,得到calldata,就可以使用Call合约的地址进行调用了。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CallFoo {
function getFunctionByte4() public pure returns (bytes4) {
return bytes4(keccak256("foo(uint256)"));
}
function getParamsBytes(uint256 _num) public pure returns (bytes memory) {
return abi.encode(_num);
}
function callFoo(
address _address,
bytes calldata _data
) public returns (bool) {
(bool success, ) = _address.call(_data);
return success;
}
}
实操一下,先同时部署两个合约,我这里直接使用remix部署,先用 CallFoo 的 getFunctionByte4 方法获取方法签名的编码
再用 CallFoo 的 getParamsBytes 方法获取参数的编码,我们把入参设置为1000
有了这两个参数之后,把两者进行拼接,拼接的结果是0x2fbebd3800000000000000000000000000000000000000000000000000000000000003e8
注意把入参编码的0x
开头删掉,然后放到callFoo中进行调用,同时地址需要填入Call合约的地址,结果如下,可以看到已经正常调用。
Call合约中的x已经被设置为了1000
调用合约方法(encodeWithSignature)
当然我们不可能每调用一次都手动去进行数据的拼接,那么我们此时就可以使用abi的内置方法 encodeWithSignature , 帮我们同时做这两件事
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CallFoo {
// function getFunctionByte4() public pure returns (bytes4) {
// return bytes4(keccak256("foo(uint256)"));
// }
// function getParamsBytes(uint256 _num) public pure returns (bytes memory) {
// return abi.encode(_num);
// }
// function callFoo(
// address _address,
// bytes calldata _data
// ) public payable returns (bool) {
// (bool success, ) = _address.call{value: msg.value}(_data);
// return success;
// }
function test(uint256 _num) public pure returns (bytes memory) {
return abi.encodeWithSignature("foo(uint256)", _num);
}
function Call(address _address, uint256 _num) external {
(bool success, ) = _address.call(
abi.encodeWithSignature("foo(uint256)", _num)
);
require(success, "call fail");
}
}
我们来试一试,我们把num也设置成1000,重新部署合约测试,也是完全没有问题的,此时 Call 合约中的 x 已经被设置为了1000,而在我们test方法中获取到的结果,和我们手动拼出来的结果是一模一样的,由此可见,abi.encodeWithSignature 方法实际上就是把上面两个步骤合成了一个,方便用户使用。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com