如何使用 ethers.js 从智能合约函数中获取返回数据?

How to get the returned data from a smart contract function using ethers.js?

我正在尝试使用 ethers.js 从智能合约中获取函数。该函数检索之前登录的用户的信息(在其他函数的帮助下)。这是函数片段。

function getUser(address _userAddress)
        public
        onlyAuthCaller
        returns (
            string memory name,
            string memory info,
            string memory role,
        )
    {
        User memory tmpData = userDetails[_userAddress];
        return (
            tmpData.name,
            tmpData.info,
            tmpData.role
        );
    }

使用 React,我正在渲染一个按钮来获取用户信息,如下所示:

const GetUser = () => {
    const askUser = async () => {
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const account = await window.ethereum.request({
            method: "eth_requestAccounts",
        });
        const signer = provider.getSigner();
        const erc20 = new ethers.Contract(
            ContractAddress,
            ContractABI.abi,
            signer
        );

        try {
            const user = await erc20.getUser(account[0]);
            console.log(user);
        } catch (error) {
            console.log("ERROR AT GETTING USER: ", error);
        }
    };
    return (
        <div>
            <Button type="submit" variant="contained" onClick={askUser}>
                GET USER
            </Button>
        </div>
    );
};

我想知道为什么我没有得到智能合约 getUser 函数的 return 结果,我预计在等待该函数后 const user 的信息。而不是 const user,我有交易元数据,如下所示:

{hash: '0x24818569ec29d328b66f58736750a420a5a3bd8e28a72a6a0f72fd8ba5e088d8', type: 2, accessList: null, blockHash: null, blockNumber: null, …}
accessList: null
blockHash: null
blockNumber: null
chainId: 0
confirmations: 0
creates: null
data: "0x6f77926b00000000000000000000000086b2b772014a87730928c7e54f4762d2c09ea4e5"
from: "0x86b2b772014A87730928c7e54F4762d2c09eA4e5"
gasLimit: BigNumber {_hex: '0xd15f', _isBigNumber: true}
gasPrice: BigNumber {_hex: '0x73a20d0c', _isBigNumber: true}
hash: "0x24818569ec29d328b66f58736750a420a5a3bd8e28a72a6a0f72fd8ba5e088d8"
maxFeePerGas: BigNumber {_hex: '0x73a20d0c', _isBigNumber: true}
maxPriorityFeePerGas: BigNumber {_hex: '0x73a20d00', _isBigNumber: true}
nonce: 5
r: "0x6a8fed76397e03a2fc564d18e1ec12abdf39a38fbe825df990f744bb50fc4a8b"
s: "0x66e9b4513047b65aac724dc6fb07d069967f6ca6fd8cd5fe85f6dbe495864765"
to: "0x9719E9dC77A7eDD3825844c77a68c896d4a7BB2b"
transactionIndex: null
type: 2
v: 0
value: BigNumber {_hex: '0x00', _isBigNumber: true}
wait: confirmations => {…}
length: 1
name: ""
arguments: (…)
caller: (…)
[[FunctionLocation]]: index.ts:336
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[4]
[[Prototype]]: Object

当我在 Remix IDE 上尝试我的合约功能时,一切都按预期工作。例如,在 Remix 我得到这个答案,其中函数检索的数据位于 decoded output.

status  true Transaction mined and execution succeed
transaction hash    0x206af46a0f8e6bcc04ae632c85da005c901d8fc82f650e8d40a445f6988adcc2
from    0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to  SupplychainUser.getUser(address) 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B
gas 61639 gas
transaction cost    53599 gas 
execution cost  53599 gas 
input   0x6f7...35cb2
decoded input   {
    "address _userAddress": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"
}
decoded output  {
    "0": "string: name Carl Bertz",
    "1": "string: info 0987654321",
    "2": "string: role processor",
}
logs    []
val 0 wei

我想要同样的东西,但是使用 React,那么我怎样才能从合同 getUser 函数中获得 returned 数据?

您想在合同中使用 callStatic,例如

await erc20.callStatic.getUser(account[0])

所以你 call 函数,而不是 运行 一个 send 执行交易(消耗 gas)

这是为了回答其中一条评论中的一个问题:EVM 支持多种“调用”,您可以在此处的 Yul 文档中查看 (https://docs.soliditylang.org/en/latest/yul.html#evm-dialect)。静态调用告诉 EVM 期望状态更改指令不会是 运行(即强制调用“视图”函数)。其他调用类型与其他类型的调用相关(例如,delegatecall() 是支持代理的核心)。

call(g, a, v, in, insize, out, outsize)

call contract at address a with input mem[in…(in+insize)) providing g gas and v wei and output area mem[out…(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success See more


callcode(g, a, v, in, insize, out, outsize)

identical to call but only use the code from a and stay in the context of the current contract otherwise See more


delegatecall(g, a, in, insize, out, outsize)

identical to callcode but also keep caller and callvalue See more


staticcall(g, a, in, insize, out, outsize)

identical to call(g, a, 0, in, insize, out, outsize) but do not allow state modifications See more

您在调用 getUser 时获取交易元数据的原因是因为 getUser 函数不是 view 函数。 不是 view 函数会导致区块链为该特定函数调用创建交易,该交易必须由区块链验证,并且在您执行 getUser 函数时不可用。

对于这种情况,推荐的方法是使用事件,即发出一个包含您需要的信息的事件,然后在反应端监听该事件。

考虑将 getUser 函数设为 view 函数,因为它不会更改合约的状态:

function getUser(address _userAddress)
        public
        view
        onlyAuthCaller
        returns (
            string memory name,
            string memory info,
            string memory role,
        )
    {
        User memory tmpData = userDetails[_userAddress];
        return (
            tmpData.name,
            tmpData.info,
            tmpData.role
        );
    }