solidity-用abi调用智能合约的方法详解

  1. solidity用abi调用智能合约的方法详解
  2. 什么是abi
  3. 调用合约方法(拆解)
  4. 调用合约方法(encodeWithSignature)

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 方法获取方法签名的编码
6b2fabfb-4cbd-41e0-915b-dc19a684d6f9-image.png

再用 CallFoo 的 getParamsBytes 方法获取参数的编码,我们把入参设置为1000
379a489b-bde2-4a34-878a-fcc6912462fe-image.png

有了这两个参数之后,把两者进行拼接,拼接的结果是0x2fbebd3800000000000000000000000000000000000000000000000000000000000003e8

注意把入参编码的0x开头删掉,然后放到callFoo中进行调用,同时地址需要填入Call合约的地址,结果如下,可以看到已经正常调用。
b8b36cac-52a9-4af5-8956-f468c9f65120-image.png

Call合约中的x已经被设置为了1000
ae148659-5db4-4c09-9226-cfdc24148f8b-image.png

调用合约方法(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 方法实际上就是把上面两个步骤合成了一个,方便用户使用。
9ab92a09-9079-4d39-b58b-86ff3851fa0a-image.png


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com