TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of validation, which allow a malicious actor to `DOS` the the `auctionStarter`'s TX of the DaiGoldAuction#`startAuction()`

Summary

Due to lack of validation, a malicious actor can DOS the auctionStarter's TX of the DaiGoldAuction#startAuction() by repeadedly calling the DaiGoldAuction#distributeGold() to increase the _totalDistributed to the amount, which is close to the max supply of the TGLD is 1 billion (MAX_SUPPLY = 1_000_000_000 ether) before the auction get started.

Vulnerability Details

Within the TempleGold contract, the _totalDistributed would be defined as a global variable like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L55

/// @notice Total distribtued to track total supply
uint256 private _totalDistributed;

Within the TempleGold contract, the MAX_SUPPLY would be defined as 1_000_000_000 ether (1 billion) like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L47

/// @notice 1B max supply
uint256 public constant MAX_SUPPLY = 1_000_000_000 ether; // 1B

Within the DaiGoldAuction#distributeGold(), the DaiGoldAuction#_distributeGold() would be called like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/DaiGoldAuction.sol#L300

/**
* @notice Mint and distribute TGOLD
*/
function distributeGold() external {
_distributeGold(); ///<------------- @audit
}

Within the DaiGoldAuction#_distributeGold(), the TempleGold#mint() would be called like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/DaiGoldAuction.sol#L305

function _distributeGold() private {
/// @dev no op silent fail if nothing to distribute
templeGold.mint(); ///<------------- @audit
}

Within the TempleGold#mint(), the mintAmount would be validated via the TempleGold#_canDistribute().
Then, the TempleGold#_distribute() would be called like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L151
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L155

function mint() external override onlyArbitrum {
...
DistributionParams storage distributionParamsCache = distributionParams;
...
uint256 mintAmount = _getMintAmount(vestingFactorCache);
/// @dev no op silently
if (!_canDistribute(mintAmount)) { return; } ///<------------- @audit
...
_distribute(distributionParamsCache, mintAmount); ///<------------- @audit
}

Within the TempleGold#_distribute(), the _totalDistributed would be incremented by adding the given mintAmount like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L244

function _distribute(DistributionParams storage params, uint256 mintAmount) private {
...
_totalDistributed += mintAmount; ///<------------- @audit

Within the DaiGoldAuction#startAuction(), the DaiGoldAuction#_distributeGold() would be called like this:
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/DaiGoldAuction.sol#L113

function startAuction() external override {
...
_distributeGold(); ///<------------- @audit
...

The DaiGoldAuction#distributeGold() above is supposed to be called by only privileged-user.
However, within the DaiGoldAuction#distributeGold(), there is no validation heck whether or not the caller (msg.sender) of the DaiGoldAuction#distribute() is a privileged-user.

This allow anyone to call the DaiGoldAuction#distributeGold() to mint TGLD to the both contracts (TempleGoldStaking and the DaiGoldAuction) and increase the nextAuctionGoldAmount as much as the user want.

If the malicious actor would repeadedly call the DaiGoldAuction#distributeGold() before the auction get started, the _totalDistributed can be increased to the amount, which is close to the max supply of the TGLD is 1 billion (MAX_SUPPLY = 1_000_000_000 ether),

After the malicious action above, if an auctionStarter would call the DaiGoldAuction#startAuction(), it would be validated whether or not the sum of the _totalDistributed and the newly minted-amount (mintAmount) is equal to the MAX_SUPPLY (_totalDistributed + mintAmount == MAX_SUPPLY) in the TempleGold#_canDistribute(), which is called inside the TempleGold#mint() via the DaiGoldAuction#startAuction().
However, in the case above, since the _totalDistributed would be close to the max supply of the TGLD is 1 billion (MAX_SUPPLY = 1_000_000_000 ether), the sum of the _totalDistributed and the mintAmount can be equal to the MAX_SUPPLY (_totalDistributed + mintAmount == MAX_SUPPLY) or larger than the MAX_SUPPLY (_totalDistributed + mintAmount > MAX_SUPPLY).
Hence, in the case above, false will be returned from the TempleGold#_canDistribute():

function _canDistribute(uint256 mintAmount) private view returns (bool) {
return mintAmount != 0 && _totalDistributed + mintAmount == MAX_SUPPLY ? true : mintAmount >= MINIMUM_MINT;
}

Since the TX can be returned if false would be returned from the TempleGold#_canDistribute() in the TempleGold#mint(), the malicious actor would be successful to DOS the auctionStarter's TX of the DaiGoldAuction#startAuction():
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol/contracts/templegold/TempleGold.sol#L151

function mint() external override onlyArbitrum {
...
if (!_canDistribute(mintAmount)) { return; }

Thus, the malicious actor can DOS the auctionStarter's TX of the DaiGoldAuction#startAuction() by repeadedly calling the DaiGoldAuction#distributeGold() to increase the _totalDistributed to the amount, which is close to the max supply of the TGLD is 1 billion (MAX_SUPPLY = 1_000_000_000 ether) before the auction get started.

Attack scenario

Let's say Alice is an auctionStarter and Bob is a malicious actor:

  • 1/ Now, the auction is not started.

  • 2/ Bob would repeadedly call the DaiGoldAuction#distributeGold().

    • By repeadedly doing so, eventually, the _totalDistributed would be increased to 999_999_100 ether, which is close to the max supply of the TGLD (MAX_SUPPLY = 1_000_000_000 ether).

  • 3/ Alice would call the DaiGoldAuction#startAuction() to start an auction.

    • At this point, within the DaiGoldAuction#startAuction(), the DaiGoldAuction#_distributeGold() would internally be called.

      • Let's say 900 ether of TGLD would newly be minted (900 mintAmount) at this point.

      • In this case, the _totalDistributed + mintAmount = 999_999_100 ether + 900 ether = 1_000_000_000 ether (MAX_SUPPLY). In other words, this is _totalDistributed + mintAmount == MAX_SUPPLY.

      • Hence, the TempleGold#_canDistribute() will return false in the TempleGold#mint(), which is called via the DaiGoldAuction#startAuction().

function _canDistribute(uint256 mintAmount) private view returns (bool) {
return mintAmount != 0 && _totalDistributed + mintAmount == MAX_SUPPLY ? true : mintAmount >= MINIMUM_MINT;
}
function mint() external override onlyArbitrum {
...
if (!_canDistribute(mintAmount)) { return; }

Impact

A malicious actor can DOS the auctionStarter's TX of the DaiGoldAuction#startAuction() by repeadedly calling the DaiGoldAuction#distributeGold() to increase the _totalDistributed to the amount, which is close to the max supply of the TGLD is 1 billion (MAX_SUPPLY = 1_000_000_000 ether) before the auction get started.

Tools Used

  • Foundry

Recommendations

Within the DaiGoldAuction#distributeGold(), consider adding an access control modifier (i.e. onlyElevatedAccess) - so that only privileged-user can call the DaiGoldAuction#distributeGold() like this:

+ function distributeGold() onlyElevatedAccess {
- function distributeGold() external {
_distributeGold();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.