【问题标题】:How to test the Solidity fallback() function via Hardhat?如何通过 Hardhat 测试 Solidity fallback() 函数?
【发布时间】:2022-11-10 14:49:04
【问题描述】:

我有一个 Solidity 智能合约 Demo,我正在 Hardhat 开发它并在 RSK Testnet 上进行测试。

//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Demo {
    event Error(string);
    fallback() external {
      emit Error("call of a non-existent function");
    }
}

我想确保调用fallback 函数并发出事件Error。为此,我试图在智能合约上调用nonExistentFunction

const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('Demo', () => {
  let deployer;
  let demoContract;
    
  before(async () => {
    [deployer] = await ethers.getSigners();
    const factory = await ethers.getContractFactory('Demo');
    demoContract = await factory.deploy().then((res) => res.deployed());
  });
    
  it('should invoke the fallback function', async () => {
    const tx = demoContract.nonExistentFunction();
    await expect(tx)
      .to.emit(demoContract, 'Error')
      .withArgs('call of a non-existent function');
  });
});

然而,Hardhat 甚至在它实际连接到 RSK 上的智能合约之前就抛出了 TypeError

  Demo
    1) should invoke the fallback function


  0 passing (555ms)
  1 failing

  1) Demo
       should invoke the fallback function:
     TypeError: demoContract.nonExistentFunction is not a function
      at Context.<anonymous> (test/Demo.js:13:29)
      at processImmediate (internal/timers.js:461:21)

我怎样才能超越 Hardhat/Ethers.js 并最终能够调用不存在的函数,从而在智能合约中调用 fallback 函数?

作为参考,这是我的hardhat.config.js

require('@nomiclabs/hardhat-waffle');
const { mnemonic } = require('./.secret.json');

module.exports = {
  solidity: '0.8.4',
  networks: {
    hardhat: {},
    rsktestnet: {
      chainId: 31,
      url: 'https://public-node.testnet.rsk.co/',
      accounts: {
        mnemonic,
        path: "m/44'/60'/0'/0",
      },
    },
  },
  mocha: {
    timeout: 600000,
  },
};

【问题讨论】:

  • 使用修改后的 ABI 初始化 ethers.Contract 实例,该 ABI 包含实际合约中不存在的函数签名。这样你就可以编写这个测试了。

标签: solidity ethers.js hardhat rsk


【解决方案1】:

您可以使用注入不存在的函数签名的方法 进入智能合约对象(ethers.Contract)。 创建一个函数签名对于nonExistentFunction

const nonExistentFuncSignature =
  'nonExistentFunction(uint256,uint256)';

请注意,参数列表不应包含空格, 并且仅包含参数类型(无参数名称)。

然后实例化一个新的智能合约对象。

执行此操作时,您需要修改 Demo 的 ABI 这样它就包含了这个额外的函数签名。:

const fakeDemoContract = new ethers.Contract(
  demoContract.address,
  [
    ...demoContract.interface.fragments,
    `function ${nonExistentFuncSignature}`,
  ],
  deployer,
);

请注意,部署的合约与以前相同 - 它确实不是包含这个新功能。 然而,与此智能合约交互的客户端 - 在这种情况下的测试 - 做思考智能合约现在有这个功能。

此时,您将能够运行原始测试, 稍作修改:

const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
await expect(tx)
  .to.emit(demoContract, 'Error')
  .withArgs('call of a non-existent function');

全面测试:

it('should invoke the fallback function', async () => {
    const nonExistentFuncSignature = 'nonExistentFunc(uint256,uint256)';
    const fakeDemoContract = new ethers.Contract(
      demoContract.address,
      [
        ...demoContract.interface.fragments,
        `function ${nonExistentFuncSignature}`,
      ],
      deployer,
    );
    const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
    await expect(tx)
      .to.emit(demoContract, 'Error')
      .withArgs('call of a non-existent function');
 });

测试结果:

  Demo
    ✔ should invoke the fallback function (77933ms)


  1 passing (2m)

【讨论】:

    【解决方案2】:

    执行函数的事务在 data 字段中的(ABI 编码)输入参数之后包含函数 selector

    当事务data 字段以不匹配任何现有函数的选择器开始时,fallback() 函数将被执行。例如一个空的选择器。

    所以你可以在合约地址生成一个交易to,其中data字段为空,调用fallback()函数。

    it('should invoke the fallback function', async () => {
        const tx = deployer.sendTransaction({
            to: demoContract.address,
            data: "0x",
        });
        await expect(tx)
            .to.emit(demoContract, 'Error')
            .withArgs('call of a non-existent function');
    });
    

    注意:如果您还声明了 receive() 函数,则在数据字段为空的情况下,它优先于 fallback()。但是,fallback() 仍然会为每个非空的不匹配选择器执行,而 receive() 仅在选择器为空时调用。

    【讨论】:

    • 谢谢,我尝试了你的想法,但实际上,如果我将 receive 函数添加到 s/c,它会拦截 fallback 调用。我想有更好的解决方案
    • @AleksShenshin 如果要在合约中同时声明 fallback()receive() 时调用 fallback() 函数,则可以传递一个不会转换为任何函数选择器的非空 data 值。例如:0x1234
    【解决方案3】:

    以下合同和测试代码有效:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.16;
    import 'hardhat/console.sol';
    
    contract Contract8 {
        event myEvent(string name);
        fallback() external {
            emit myEvent('fallback-function-works-yoo');
        }
    }
    
    it('receive function, receive payable function, fallback function, ', async () => {
        const Contract8 = await ethers.getContractFactory('Contract8')
        const contract8 = await Contract8.deploy()
        await contract8.deployed()
    
        const tx = contract8.signer.sendTransaction({
            to: contract8.address,
            data: '0x',
        })
    
        // Test fallback function
        await expect(tx).to.emit(contract8, 'myEvent').withArgs('fallback-function-works-yoo')
    })
    

    【讨论】:

      猜你喜欢
      • 2023-01-17
      • 2022-08-08
      • 2021-09-04
      • 2022-12-02
      • 1970-01-01
      • 2022-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多