使用 Waffle 模拟合约作为单元测试中方法的被调用者

Dzu*_*ray 4 testing ethers.js hardhat

问题:我想测试一个方法只能由另一个合约调用。

例子:

// B.sol
contract B {
  A _a;
  uint256 i;

  constructor(A a) {
    _a = a;
    i = 0;
  }
  function doSomething() external {
    require(address(_a) == msg.sender);
    i += 1;
  }
}

// A.sol
contract A {
  B _b;
  constructor(B b) {
    _b = b;
  }
  function callB() external {
    _b.doSomething();
  }
}
Run Code Online (Sandbox Code Playgroud)
// B.sol
contract B {
  A _a;
  uint256 i;

  constructor(A a) {
    _a = a;
    i = 0;
  }
  function doSomething() external {
    require(address(_a) == msg.sender);
    i += 1;
  }
}

// A.sol
contract A {
  B _b;
  constructor(B b) {
    _b = b;
  }
  function callB() external {
    _b.doSomething();
  }
}
Run Code Online (Sandbox Code Playgroud)

我预计这会通过,但我收到“未知帐户”断言错误,表明联系方法只能由以太生成的签名者调用。

(请忽略构造函数之间的循环依赖。这不是本示例的重点。)

Dav*_*aga 7

您可以在 Hardhat 中使用模拟帐户

基本上,智能合约地址无法像任何其他 EOA 那样自行签署交易,换句话说,它们无法使用eth_sendTransaction.

幸运的是,Hardhat 让您可以模拟帐户(这个答案很好地解释了模拟的概念)。按照您的示例,为了模拟mockA合同,您需要执行以下操作:

// more imports...
import { ethers, network } from 'hardhat';

describe('doSomething', () => {
  it('should fulfil when called by contract A', async () => {
    const mockA = await deployMockContract('A'); // Waffle
    
    // impersonate mockA address
    await network.provider.request({
      method: "hardhat_impersonateAccount",
      params: [mockA.address],
    });
     
    // get MockA signer as an impersonated account
    // that is able to sign txs using `eth_sendTransaction`
    const mockAAsSigner = ethers.getSigner(mockA.address);
    
    const b = await bFactory.deploy();
    
    // connect you contract with mockAAsSigner;
    const bAsMockA = b.connect(mockAAsSigner);
    
    // Call bAsMockA contract acting as mockAAsSigner
    await expect(bAsMockA.doSomething()).to.have.eventually.fulfilled;
  })
});
Run Code Online (Sandbox Code Playgroud)

当然,模拟账户仅适用于 Hardhat 网络。