While trying to pass tests for two solidity functions, I get stuck on two tests:
23 passing (4s)
2 failing
1) Treasury
getBalance
should return updated balance after depositing more tokens:
Error: missing argument: passed to contract (count=2, expectedCount=4, code=MISSING_ARGUMENT, version=contracts/5.7.0)
at Logger.makeError (node_modules/@ethersproject/logger/src.ts/index.ts:269:28)
at Logger.throwError (node_modules/@ethersproject/logger/src.ts/index.ts:281:20)
at Logger.checkArgumentCount (node_modules/@ethersproject/logger/src.ts/index.ts:340:18)
at /home/brsh/WebstormProjects/dsb/node_modules/@ethersproject/contracts/src.ts/index.ts:187:12
at step (node_modules/@ethersproject/contracts/lib/index.js:48:23)
at Object.next (node_modules/@ethersproject/contracts/lib/index.js:29:53)
at /home/brsh/WebstormProjects/dsb/node_modules/@ethersproject/contracts/lib/index.js:23:71
at new Promise (<anonymous>)
at __awaiter (node_modules/@ethersproject/contracts/lib/index.js:19:12)
at populateTransaction (node_modules/@ethersproject/contracts/lib/index.js:152:12)
2) Treasury
deposit
"before each" hook for "should deposit ETH successfully":
Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="Transaction reverted: function call to a non-contract account", method="estimateGas", transaction={"from":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","data":"0x60806040523480156200001157600080fd5b50604051620024fb380380620024fb8339810160
The second issue appeared after I implemented the ERC777 feature in the deposit function. It looks like this:
function deposit(
address tokenAddress,
uint256 amount,
bytes calldata userData,
bytes calldata operatorData
) external payable nonReentrant {
require(amount > 0, "Treasury: Deposit amount must be greater than zero");
require(msg.value == amount, "Treasury: amount mismatch");
require(!isLocked, "Treasury: Contract is locked");
if (tokenAddress == ETH_ADDRESS) { // ETH
emit DepositETH(tokenAddress, msg.sender, amount);
} else {
IERC165 tokenContract = IERC165(tokenAddress);
if (isERC777(tokenAddress)) {
IERC777(tokenAddress).send(address(this), amount, abi.encode(userData, operatorData));
emit DepositERC777(tokenAddress, msg.sender, amount);
} else if (tokenContract.supportsInterface(type(IERC20).interfaceId)) {
require(allowedTokens[tokenAddress], "Treasury: Token not allowed");
SafeERC20.safeTransferFrom(IERC20(tokenAddress), msg.sender, address(this), amount);
emit DepositERC20(tokenAddress, msg.sender, amount);
} else {
revert("Treasury: Unsupported token type");
}
}
}
Here is the SampleERC77:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import "./ITreasury.sol";
contract SampleERC777 is ERC777, IERC777Recipient {
constructor(
uint256 initialSupply,
address[] memory defaultOperators
) ERC777("Sample ERC777", "S777", defaultOperators) {
_mint(msg.sender, initialSupply, "", "");
}
function tokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata operatorData
) external override {
require(to != address(0), "SampleERC777: zero address not allowed");
if (ITreasury(to).isTreasuryContract()) {
ITreasury(to).deposit(operator, from, amount, userData, operatorData);
}
}
}
and here are all the failing tests:
// getBalance tests
describe("getBalance", () => {
beforeEach(async () => {
const Token = await ethers.getContractFactory("SampleERC20");
token = await Token.deploy();
await token.deployed();
await token.mint(treasury.address, ethers.utils.parseEther("100"));
});
it("should return updated balance after depositing more tokens", async function () {
await treasury.connect(tokenManager).addAllowedToken(token.address);
await token.mint(owner.address, ethers.utils.parseEther("50"));
await token.connect(owner).approve(treasury.address, ethers.utils.parseEther("50"));
await treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("50"));
const balance = await treasury.getBalance(token.address);
expect(balance).to.equal(ethers.utils.parseEther("150"));
});
});
// deposit tests
describe("deposit", function () {
let Treasury, treasury, SampleERC20, sampleERC20, SampleERC777, sampleERC777, owner, tokenManager, user1, user2;
beforeEach(async function () {
// Get the ContractFactory and Signers here.
[owner, tokenManager, user1, user2] = await ethers.getSigners();
const initialSupply = ethers.utils.parseUnits("1000");
Treasury = await ethers.getContractFactory("Treasury");
treasury = await Treasury.deploy();
await treasury.deployed();
SampleERC777 = await ethers.getContractFactory("SampleERC777");
sampleERC777 = await SampleERC777.deploy(initialSupply, [treasury.address]);
await sampleERC777.deployed();
// Grant token manager role
await treasury.grantRole(await treasury.TOKEN_MANAGER_ROLE(), tokenManager.address);
// Add ERC20 and ERC777 tokens to allowed tokens
await treasury.addAllowedToken(sampleERC20.address);
await treasury.addAllowedToken(sampleERC777.address);
// Mint some tokens for user1 and user2
await sampleERC20.connect(owner).mint(user1.address, ethers.utils.parseEther("100"));
await sampleERC20.connect(owner).mint(user2.address, ethers.utils.parseEther("100"));
await sampleERC777.connect(owner).mint(user1.address, ethers.utils.parseEther("100"), "0x", "0x");
await sampleERC777.connect(owner).mint(user2.address, ethers.utils.parseEther("100"), "0x", "0x");
});
it("should deposit ETH successfully", async function () {
const depositAmount = ethers.utils.parseEther("1");
await expect(
treasury.connect(user1).deposit("0x0000000000000000000000000000000000000000", depositAmount, { value: depositAmount })
).to.not.be.reverted;
expect(await ethers.provider.getBalance(treasury.address)).to.equal(depositAmount);
});
it("should deposit ERC20 tokens successfully", async function () {
const depositAmount = ethers.utils.parseEther("10");
await sampleERC20.connect(user1).approve(treasury.address, depositAmount);
await treasury.connect(user1).deposit(sampleERC20.address, depositAmount);
expect(await sampleERC20.balanceOf(treasury.address)).to.equal(depositAmount);
});
it("should deposit ERC777 tokens successfully", async () => {
const depositAmount = ethers.utils.parseUnits("100", 18);
await sampleERC777.connect(user1).send(treasury.address, depositAmount, "0x");
const user1TreasuryBalance = await treasury.connect(user1).balanceOf(user1.address);
expect(user1TreasuryBalance).to.equal(depositAmount);
});
it("should emit Deposit event on successful deposit", async () => {
const amount = ethers.utils.parseUnits("1000", 18);
await token.connect(owner).approve(treasury.address, amount);
await token.connect(owner).transfer(user1.address, amount);
await expect(treasury.connect(user1).deposit(token.address, amount))
.to.emit(treasury, "Deposit")
.withArgs(user1.address, token.address, amount);
});
it("should revert when trying to deposit non-allowed token", async function () {
const anotherTokenFactory = await ethers.getContractFactory("SampleERC20");
const anotherToken = await anotherTokenFactory.deploy();
await anotherToken.deployed();
await anotherToken.mint(owner.address, ethers.utils.parseEther("1000"));
await anotherToken.connect(owner).approve(treasury.address, ethers.utils.parseEther("500"));
await expect(treasury.connect(owner).deposit(anotherToken.address, ethers.utils.parseEther("500"))).to.be.revertedWith(
"Treasury: Token not allowed"
);
});
it("should revert when token transfer amount exceeds balance", async () => {
const treasuryBalanceBefore = await erc20Token.balanceOf(treasury.address);
await expectRevert(
treasury.deposit(erc20Token.address, treasuryBalanceBefore.add(new BN("1")), { from: user1 }),
"ERC20: transfer amount exceeds balance"
);
});
it("should revert when not enough tokens are approved for transfer", async function () {
const sender = owner;
const amountToTransfer = ethers.utils.parseEther("100");
const amountToApprove = ethers.utils.parseEther("50"); // Less than the amountToTransfer
await token.connect(sender).approve(treasury.address, amountToApprove);
await expect(treasury.connect(sender).deposit(token.address, amountToTransfer))
.to.be.revertedWith("SafeERC20: low-level call failed"); // Since SafeERC20 wraps the error message
});
it("should revert when token transfer fails", async function () {
await token.connect(owner).approve(treasury.address, ethers.utils.parseEther("500"));
await expect(treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("1500"))).to.be.revertedWith(
"ERC20: transfer amount exceeds balance"
);
});
it("should revert when trying to deposit a zero amount", async function () {
await token.connect(owner).approve(treasury.address, ethers.utils.parseEther("500"));
await expect(treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("0"))).to.be.revertedWith(
"Treasury: Deposit amount must be greater than zero"
);
});
it("should revert when trying to deposit an amount greater than approved", async function () {
await token.connect(owner).approve(treasury.address, ethers.utils.parseEther("400"));
await expect(treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("500"))).to.be.revertedWith(
"ERC20: transfer amount exceeds allowance"
);
});
it("should correctly update the treasury balance after multiple deposits", async function () {
// Before depositing tokens in the treasury, approve the treasury to spend tokens on behalf of the owner
await token.connect(owner).approve(treasury.address, ethers.utils.parseEther("1000"));
await treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("300"));
await treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("200"));
const balance = await treasury.getBalance(token.address);
expect(balance).to.equal(ethers.utils.parseEther("500"));
});
});
In the deposit function, only the "should revert when trying to deposit a zero amount" and the "should revert when trying to deposit non-allowed token" work, the "should deposit ETH successfully" was working as well and after I added the ERC777 part, it failed. Here is more relevant parts on the contract:
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(TOKEN_MANAGER_ROLE, msg.sender);
_grantRole(NFT_MANAGER_ROLE, msg.sender);
_grantRole(LOCK_MANAGER_ROLE, msg.sender);
_grantRole(WITHDRAWER_ROLE, msg.sender);
// Add the project token to the allowedTokens mapping
allowedTokens[projectToken] = true;
allowedTokens[ETH_ADDRESS] = true;
}
function isERC777(address tokenAddress) private view returns (bool) {
bytes memory payload = abi.encodeWithSignature("granularity()");
(bool success, bytes memory returnData) = tokenAddress.staticcall(payload);
return success && (returnData.length == 32);
}
function getBalance(address token) external view returns (uint256) {
require(allowedTokens[token], "Treasury: Token not allowed");
if (token == address(0)) {
return address(this).balance;
}
return IERC20(token).balanceOf(address(this));
}
I'm stuck for a long time on the deposit tests and I don't know further, I tried different approaches but they lead to the same errors, it might be an issue in the functions that I'm not able to see... Maybe I didn't implement ERC777 properly.