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

Use a Waffle mock contract as a callee of a method in a unit test

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

示例:

// 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();
  }
}
// B.test.ts
// going to simplify some unimportant stuff here
describe('doSomething', () => {
  it('should fulfil when called by contract A', async () => {
    const mockA = await deployMockContract('A'); // Waffle
    const b = await bFactory.deploy();

    const signerFromMockA = b.provider.getSigner(mockA.address);
    const bAsMockA = b.connect(signerFromMockA);

    await expect(bAsMockA.doSomething()).to.have.eventually.fulfilled;
  })
  it('should reject when called by other address', async () => {
    const mockA = await deployMockContract('A'); // Waffle
    const b = await bFactory.deploy();

    const [, nonOwner] = ethers.getSigners();
    const bAsNonOwner = b.connect(nonOwner);

    await expect(bAsNonOwner.doSomething()).to.have.eventually.rejected;
  })
})

我希望这能通过,但我收到“未知帐户”AssertionError,表明联系方法只能由以太生成的签名者调用。

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

您可以使用 impersonating an account in hardhat.

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

幸运的是,Hardhat 让您可以模拟帐户(this 回答很好地解释了模拟的概念)。 按照您的示例,要模拟 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;
  })
});

当然,模拟账户只能在 Hardhat 网络中使用。