Description
This issue has been noticed by the Compound team before when forking the mainnet, and can be reproduced using the tests in this commit.
Steps to Reproduce
- Clone the above repository at the specified commit
- Run
npm install
- Compile contracts and run tests with
npx oz compile && npx mocha --exit --timeout 0 test/endaoment-test.js
- The tests will fail with the following console output
$ npx oz compile && npx mocha --exit --timeout 0 test/endaoment-test.js
Nothing to compile, all contracts are up to date.
Endaoment
✓ should see the deployed Endaoment contract
1) should allow a membership proposal & vote
1 passing (39s)
1 failing
1) Endaoment
should allow a membership proposal & vote:
Error: Returned error: VM Exception while processing transaction: revert Endaoment::processProposal - token transfer to guild bank failed -- Reason given: re-entered.
at PromiEvent (node_modules/@truffle/contract/lib/promievent.js:9:30)
at TruffleContract.processProposal (node_modules/@truffle/contract/lib/execute.js:169:26)
at Context.it (test/endaoment-test.js:61:29)
at process._tickCallback (internal/process/next_tick.js:68:7)
Explanation
In the file endaoment-test.js
we are failing in the test called "should allow a membership proposal & vote" at the line await this.instance.processProposal(0, {from: summoner});
This line calls the function processProposal()
in Endaoment.sol
. The failure occurs in the following section of that function:
// transfer tokens to guild bank
require(
guildBank.deposit(proposal.tokenTribute),
"Endaoment::processProposal - token transfer to guild bank failed"
);
This line is calling the deposit()
function in GuildBank.sol
. This function is shown below for convenience:
function deposit(uint256 amount) public onlyOwner returns (bool) {
bool transferSuccess = approvedToken.transferFrom(msg.sender, address(this), amount);
if (!transferSuccess) {
return false;
}
uint256 mintCode = cToken.mint(amount);
if (0 == mintCode) {
return false;
}
emit Deposit(amount);
return true;
}
Now, the statement if (0 == mintCode)
is incorrect, and really should be if (0 != mintCode)
. However, this mistake in the code means the function should return false and therefore the contract should error solely with the error message "Endaoment::processProposal - token transfer to guild bank failed".
Instead, you can see the cToken error message of "re-entered" is included. This is from the nonReentrant
modifier of cTokens, but because there is no reentrancy here this should not have been triggered.