1

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.

1 Answer 1

0

this error:

 Error: missing argument: passed to contract (count=2, expectedCount=4, code=MISSING_ARGUMENT, version=contracts/5.7.0)

It mean that the number of arguments passed to the contract is not the same as the number of arguments expected by the contract

at the line await treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("50"));

replace it with await treasury.connect(owner).deposit(token.address, ethers.utils.parseEther("50"), "0x", "0x");

then this error

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

mean that the gas estimation failed because the function call reverted, it look like it is due because the function call is being made to a non-contract account

in this test

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);
});

you also have to pass the userData and operatorData

await expect(
    treasury.connect(user1).deposit("0x0000000000000000000000000000000000000000", depositAmount, "0x", "0x", { value: depositAmount })
).to.not.be.reverted;
2
  • The first error is related to getBalance() function: 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)); } After following your advice I get this: ` getBalance should return updated balance after depositing more tokens: Error: VM Exception while processing transaction: reverted with reason string 'Treasury: amount mismatch' `
    – sphraax
    Apr 18 at 12:55
  • there is a mismatch between the amount of tokens that the deposit function expects and the actual amount of tokens being deposited
    – Saxtheowl
    Apr 18 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.