Problem: I want to test that a method is only callable by another contract.
Example:
// 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;
})
})
I expect this to pass, but I get "unknown account" AssertionError, suggesting that the contact methods can be only called by the ethers generated signers.
(Please, ignore the constructors' circular dependency on each other. That's not the point of this example.)
You can use impersonating an account in hardhat.
Basically, smart contract addresses are not able to sign transactions by themselves as any other EOA would do it, in other words, they are unable to use eth_sendTransaction
.
Fortunately, Hardhat let's you impersonate accounts (this answer explains very well the concept of impersonating).
Following your example, for impersonating the mockA
contract you need to do:
// 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;
})
});
Of course, impersonating accounts only works in Hardhat network.