Platform: Code4rena
Start Date: 15/12/2022
Pot Size: $128,000 USDC
Total HM: 28
Participants: 111
Period: 19 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 194
League: ETH
Rank: 54/111
Findings: 2
Award: $144.53
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gz627
Also found by: AkshaySrivastav, Allarious, Czar102, HE1M, HollaDieWaldfee, KmanOfficial, adriro, ast3ros, betweenETHlines, bin2chen, brgltd, cccz, chaduke, hihen, imare, mookimgo, neumo, nogo, peanuts, unforgiven
21.713 USDC - $21.71
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L190-L194 https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L209-L216
The functions ProtocolDAO.registerContract()
and ProtocolDAO.upgradeExistingContract()
won't check if the new address being passed equals the address of a contract already registered. The impact can be unintendedly overriding existing contracts.
Assume there's a contract with name first
- address 0x1
and another contract with name second
- address 0x2
. If ProtocolDAO.upgradeExistingContract()
gets called to update the contract with the name second
passing a new address with the value 0x1
, the contract with name first
will be replaced - calling a contract name by passing the address 0x1
won't return the contract with name first
.
The following POC illustrates this scenario. The POC can be executed inside ProtocolDAO.t.sol
.
function testOverrideExistingContract() public { address firstContractAddr = address(1); string memory firstContractName = "first"; address secondContractAddr = address(2); string memory secondContractName = "second"; vm.startPrank(guardian); dao.registerContract(firstContractAddr, firstContractName); dao.registerContract(secondContractAddr, secondContractName); assertEq(store.getString(keccak256(abi.encodePacked("contract.name", firstContractAddr))), firstContractName); assertEq(store.getString(keccak256(abi.encodePacked("contract.name", secondContractAddr))), secondContractName); // Update the second contract to have a new name and a new address. // Note that it's the same address as the first contract string memory newSecondContractName = "new second"; dao.upgradeExistingContract(firstContractAddr, newSecondContractName, secondContractAddr); // Querying the name by the address will return the second contract name when // passing the first contract address. assertEq(store.getString(keccak256(abi.encodePacked("contract.name", firstContractAddr))), newSecondContractName); }
Add a check for already registered contracts when calling the functions to register and update contracts, e.g. when calling ProtocolDAO.registerContract()
or ProtocolDAO.upgradeExistingContract()
, the protocol should check if the new address being passed is not currently being used by an existing contract inside the protocol.
#0 - c4-judge
2023-01-09T10:05:08Z
GalloDaSballo marked the issue as duplicate of #742
#1 - c4-judge
2023-02-08T08:23:57Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: IllIllI
Also found by: 0xSmartContract, AkshaySrivastav, HollaDieWaldfee, RaymondFam, Rolezn, ast3ros, betweenETHlines, brgltd, chaduke, codeislight, cozzetti, lukris02, pauliax, sces60107
122.8177 USDC - $122.82
If Oracle.OneInch
gets configured with address zero, failure to immediately reset the value can result in unexpected behavior for the project.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Oracle.sol#L28-L30
Since the funds can be sent to any recipientAddress
, it can be a rugpull vector.
Specify a list of addresses that can receive the funds, or implement a timelock functionality to give DAO members extra time in case of large transfers.
Solmate won't check for contract existence. This can lead to silent failures while execute safe transfer functions.
If Vault.depositToken()
is called with an allowed input token address and a large amount
, passing a non existing ERC20 address would still compute the amount in tokenBalances
, potentially affecting token accounting.
This by itself woudn't be an issue (creating a balance for a non existing token). However it's possible for tokens to reuse the same addresses on different networks. An attacker could use this to set traps in the protocol.
depositToken()
with a large amount
.tokenBalances
for this token without having spent anything initially.Note that for this issue to occur in practice, it needs some strong hipoteticals, since the methods to approve and deposit tokens are permissioned and can only be executed by the guardian or registered contracts.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L128
Inside Vault.depositToken()
or Vault.addAllowedToken()
, consider adding a check for contract existence, e.g. checking if address(token).code.length
is bigger than zero.
Adding evens for parameter changes would facilitade offchain monitoring.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L204
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L209
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L190
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L198
Downcasting block.timestamp will not be an issue for a long time. However, the project could use a bigger value than 32 bits, to improve the resilience of the protocol. See this item marked as a finding for a previous code4rena QA report.
Consider adding a __gap[]
storage variable to allow for new storage variables in later versions.
See this link for a description of this storage variable issue. Please, refer to this example for an implementation reference.
Consider adding a revert statement when the new price
and the new timestamp
equal the existing price
and timestamp
. This can avoid emitting unnecessarely events when receiving stale values.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Oracle.sol#L57-L68
Consider making external calls after state changes to follow the checks effects interactions pattern.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L127-L130
Some contract are missing test coverage, e.g. ProtocolDAO
has 60.78% for number of lines and 62.26% for branches. Increasing the coverage can help increase the code quality and it's important for smart contract systems.
Optimizations are being actively developed and are not considered safe. Check the following audit recommending agaist deployments with the optimizer enabled (item 3 in the audit document).
If possible, consider measuring and documenting the tradeoffs when enabling the optimizer.
The original ERC20 approve function is susceptible race condition which can result in a front-running attack. More information here.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ClaimNodeOp.sol#L105
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Staking.sol#L342
On the two declarations calling ggp.approve
, consider implementing a functionality like increaseAllowance
from OpenZeppelin.
TODOs should be resolved before deployment.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Staking.sol#L203
As described on the solidity documentation. "The assert function creates an error of type Panic(uint256). … Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix.
Repeated validation statements can be converted into a functions modifier to improve code reusability
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L47-L50
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L62-L65
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L85-L88
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L113-L116
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L142-L145
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L171-L174
Named imports are being used on most contracts, except for when importing BaseAbstract.sol
and Base.sol
. The project still compiles when replacing import "./BaseAbstract.sol"
with import {BaseAbstract} from "./BaseAbstract.sol"
.
If there's any reason why BaseContract.sol
and Base.sol
are not named imports, consider adding a comment on the codebase. Otherwise, consider using named imports.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Base.sol#L4
Consider using only one approach throughout the codebase. E.g. only address(0)
instead of address(0x0)
.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/BaseAbstract.sol#L92
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/BaseAbstract.sol#L73
The solidity documentation recommends placing view functions on the bottom of a grouping.
Consider moving getGuardian()
bellow confirmGuardian()
.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Storage.sol#L51
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Storage.sol#L56
Another recommendation is to move private functions bellow the constructor and bellow external/public functions. Consider moving the private functions in MinipoolManager
bellow the constructor and bellow external/public functions.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L152
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L177
MinipoolManager.slash()
is a private function only called by MinipoolManager.recordStakingEnd()
. The values nodeID
, owner
and avaxLiquidStakerAmt
don't need to be recomputed inside MinipoolManager.recordStakingEnd()
since they are already computed inside MinipoolManager.slash()
.
nodeID
is an argument of MinipoolManager.recordStakingEnd()
- it will be used to get the minipool index, and the values for owner
and avaxLiquidStakerAmt
are computed from the storage inside MinipoolManager.recordStakingEnd()
logic.
Consider passing nodeID
, owner
and avaxLiquidStakerAmt
as arguments from MinipoolManager.recordStakingEnd()
to MinipoolManager.slash()
.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L386
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L399
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L405
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L674
The return statement on RewardsPool.getInflationAmt()
can be removed.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/RewardsPool.sol#L66
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/RewardsPool.sol#L77
Also, for MinipoolManager.getMinipoolByNodeID()
, the returned named variable is not being used.
Furthermore, throughout the codebase, some functions return named variables, others return explicit values.
Following function returns an explicit value.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MultisigManager.sol#L102
Following function return returns named variables.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MultisigManager.sol#L109
Consider adopting the same approach throughout the codebase to improve the explicitness and readability of the code.
The declaration using SafeTransferLib for address;
for Vault.sol
can be removed. Safe transfer functions are only being used for ERC20 instances on this contract.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L21
Consider addding NATSPEC on all external/public functions to improve documentation.
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L204
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L208
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Vault.sol#L156
For some functions, the arguments are defined leading underscores.
While other don't follow this pattern.
Consider using the same approach throughout the codebase.
Consider moving the logic on L356-358 above the initial function declaration on L354.
The solidity documentation recommends a maximum of 120 characters.
Consider adding a limit of 120 characters or less to prevent large lines.
#0 - GalloDaSballo
2023-01-24T12:27:38Z
L
Invalid per Scope
Invalid per Scope
NC
L
Disputing for token as it's a child contract
R
L
R
Disputing without evidence (with evidence can even be a High Severity)
Invalid, the allowance and it's usage are atomical
NC
R
R
NC
NC
NC
R
R
NC
NC
NC
NC
R
NC
#1 - GalloDaSballo
2023-02-03T16:25:35Z
3L 7R 10NC
#2 - c4-judge
2023-02-03T22:09:56Z
GalloDaSballo marked the issue as grade-b