code-423n4 / 2022-02-hubble-findings

2 stars 2 forks source link

Gas Optimizations #34

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Gas Optimizations

Loop optimizations

Loops can be optimized in several ways. Let's take for example the loop in the _liquidateMaker function in ClearingHouse.

for (uint i = 0; i < amms.length; i++) {
    (,, uint dToken,,,,) = amms[i].makers(maker);
    // @todo put checks on slippage
    (int256 _realizedPnl, uint _quote) = amms[i].removeLiquidity(maker, dToken, 0, 0);
    realizedPnl += _realizedPnl;
    quoteAsset += _quote;
}

We can do multiple things here:

  1. Variables in solidity are already initialized to their default value, and initializing them to the same value actually costs more gas. So for example in the loop above, the code can be optimized using uint i; instead of uint i = 0;.
  2. Use ++i instead of i++ to save some gas spent in every iteration.
  3. Save the array length in a local variable before the loop instead of accessing it in every iteration.
  4. Save amms[i] in a local variable instead of accessing the i element of the array twice in every iteration. So after all these changes, the code will look something like this:
    uint length = amms.length;
    IAMM amm_i;
    for (uint i; i < length; ++i) {
    amm_i = amms[i];
    (,, uint dToken,,,,) = amm_i.makers(maker);
    // @todo put checks on slippage
    (int256 _realizedPnl, uint _quote) = amm_i.removeLiquidity(maker, dToken, 0, 0);
    realizedPnl += _realizedPnl;
    quoteAsset += _quote;
    }

    There are more loops in the code that can be optimized the same way, for example in the ClearingHouse, MarginAccount and the HubbleViewer contracts.

save return value of a function instead of calling it multiple times

in the _calcTwap function in the AMM contract there are multiple calls to the _blockTimestamp(), and it can be called once to save gas.

avoid accessing a mapping multiple times

in the _calcTwap function in the AMM contract, instead of accessing the reserveSnapshots[snapshotIndex] twice in the loop it can be accessed only once. old code:

currentSnapshot = reserveSnapshots[snapshotIndex];
currentPrice = reserveSnapshots[snapshotIndex].lastPrice;

new code:

currentSnapshot = reserveSnapshots[snapshotIndex];
currentPrice = currentSnapshot.lastPrice;

this can be done in some other places in the code, like the _increasePosition function for example, when the positions mapping is accessed twice. old code:

positions[trader].size += baseAssetQuantity; // -ve baseAssetQuantity will increase short position
positions[trader].openNotional += quoteAsset;

new code:

Position storage pos = positions[trader];
pos.size += baseAssetQuantity; // -ve baseAssetQuantity will increase short position
pos.openNotional += quoteAsset;

another place it can be done is in removeMargin in the MarginAccount when accessing margin[idx][trader] twice.

atvanguard commented 2 years ago

Good report, also duplicates.