When the vestingRate
is updated, it not only affects the vesting rate of future tokens, but also of past released tokens that haven't been minted yet, i.e., tokens since last call to mint()
.
The internal function _getMintAmount()
controls the exact amount of templeGold
tokens that can be minted at any given time. Tokens are released following a linear vesting formula: at any given time, the amount to be minted is proportional to the time since the last mint (block.timestamp - _lastMintTimestamp
) and the minting rate, (stored in the vestingFactor
storage variable):
The vestingFactor
is not immutable and can be updated by calling the setVestingFactor()
function (only callable by the contract owner):
However, when the vestingFactor
is updated, it affects the tokens that have been "released" according to the original vestingFactor
, but that haven't been minted yet, i.e., released tokens since the last call to mint()
. Another way to view the problem is to think about the following simple invariant:
Given that the vesting schedule is linear, tokens that have been already released cannot be taken back.
In that context, updating the vestingFactor
should only affect the vesting rate of future tokens, and not the tokens that have already been released. This is not the case, as it is demonstrated in the Proof of concept below.
The following test (written in Foundry) illustrates how setting a new vestingFactor
alters the output from getMintAmount()
, which are the tokens available to mint at any given time. Updating the vesting factor should not alter the tokens that have been released, and therefore should not alter the output of getMintAmount()
, yet it does.
To run the test, add it to the test contract TempleGoldTest
, in the test file: protocol/test/forge/templegold/TempleGold.t.sol
, and then run forge test --mt test_setVestingPeriodAltersGetMintAmount -vvv
.
When the vestingFactor
is updated, it changes retrospectively the tokens that have been released according to the original vesting rate, but have not been minted yet (tokens released since the last call to mint()
). In both cases, it will generate a step-wise change in the released schedule, instead of only a change in slope.
If the new vestingFactor
is greater than the original, this step would suddenly release extra tokens.
If the new vestingFactor
is lower than the original, it will effectively "unrelease already released templeGold tokens", reducing the rewards for next period in the TempleGoldStaking
and DaiGoldAuction
contracts.
Note: the impact is smaller the smaller the duration between block.timestamp
and lastMintTime
.
Manual review, foundry for the PoC
Several ways of mitigating the issue:
Not changing the vesting rate. Remove the function or never call it
Only changing the vesting rate right after calling mint()
, to minimize the impact of the miscalculation. To enforce this, add the following requirement:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.