Skip to content

Latest commit

 

History

History
33 lines (18 loc) · 2.5 KB

M-01.md

File metadata and controls

33 lines (18 loc) · 2.5 KB

Unbounded gas usage in staked S1 and S2 positions may lead to DoS

Impact

Contract state for staked positions from S1 and S2 citizens are represented in part using arrays, which are iterated in different functions that are part of the NeoTokyoStaker contract. Given these arrays are unbounded, traversing these may eventually lead to a denial of service issue due to an unbounded usage of gas.

getStakerPositions

https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L710-L752

This function iterates both _stakerS1Position and _stakerS2Position, and copies all the values from the items to calculate a summary of the positions of a particular staker.

getPoolReward

https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L1264-L1396

Iterates _stakerS1Position or _stakerS2Position based on the asset type to calculate the sum of all the points from the stake positions. For each element in the array, the function will fetch the full corresponding stake struct from storage and copy it to memory (lines 1281 and 1290).

_withdrawS1Citizen and _withdrawS2Citizen:

https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L1459-L1529

https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L1534-L1592

These withdraw actions loop over the corresponding positions array to find the index of the item that needs to be removed, and eventually swap it for the last element and pop the array.

Recommendation

A different strategy can be applied based on the case,

  • getStakerPositions: since this is an external view, using a pagination with an offset and a length should be enough. This will also need a way to know the length in advance, which should be exposed with another function (i.e. getStakerPositionsLength).
  • getPoolReward: instead of calculating the sum on the fly, have a precalculated counter by staker and asset type that is updated whenever the user stakes or unstakes.
  • _withdrawS1Citizen and _withdrawS2Citizen: have an additional mapping that tracks the position of each citizenId in each array. For example, mapping(uint256 => uint256) _stakerS1PositionIndicies that associates the citizenId (mapping key) to the index of the corresponding position in the _stakerS1Position array. Removing an item can then be implemented by querying this mapping to find the element that needs to be removed from the array, without looping.