code-423n4 / 2022-02-tribe-turbo-findings

1 stars 0 forks source link

Gas Optimizations #47

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Gas Optimizations

inline some functions to save gas

In the ERC4626, there are multiple functions that their code can be inlined to save the gas spending on calling them.

function previewDeposit(uint256 amount) public view virtual returns (uint256) {
    uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
    return supply == 0 ? amount : amount.mulDivDown(supply, totalAssets());
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
    uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
    return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 amount) public view virtual returns (uint256) {
    uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
    return supply == 0 ? amount : amount.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
    uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
    return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}

For example, the previewDeposit function. It is used in the deposit function:

function deposit(uint256 amount, address to) public virtual returns (uint256 shares) {
    // Check for rounding error since we round down in previewDeposit.
    require((shares = previewDeposit(amount)) != 0, "ZERO_SHARES");
    // ...
}

Instead of calling that function, shares can be calculated by inlining the function, something like this:

function deposit(uint256 amount, address to) public virtual returns (uint256 shares) {
    // Check for rounding error since we round down in previewDeposit.
    shares = totalSupply; // saving totalSupply to save a SLOAD, and using shares variable to avoid creating a new variable.
    shares = (shares == 0 ? amount : amount.mulDivDown(shares, totalAssets()))
    require(shares != 0, "ZERO_SHARES");
    // ...
}

This can be sone to all of the other functions I attached before (previewMint, previewWithdraw, previewRedeem).

Save mapping values instead of accessing it twice

Save the getTotalFeiBoostedForVault[vault] in the slurp function of the TurboSafe instead of accessing it twice for reading. It will look something like this:

function slurp(ERC4626 vault) external nonReentrant requiresLocalOrMasterAuth {
    uint256 totalFeiBoostedForVault = getTotalFeiBoostedForVault[vault];
    require(totalFeiBoostedForVault != 0, "NO_FEI_BOOSTED");

    // ...

    unchecked {
        // Update the total Fei held in the Vault proportionately.
        // Cannot overflow because the total cannot be less than a single Vault.
        getTotalFeiBoostedForVault[vault] = totalFeiBoostedForVault + safeInterestAmount;
    }
    // ...
}
transmissions11 commented 2 years ago

not worth the extra complexity for the first suggestion, but agreed on caching that mapping read

GalloDaSballo commented 2 years ago

While impractical the first finding is valid and each jump avoided would save 8 gas, 32

Saving a SLOAD would save 100 gas at the cost of 6 for using a memory cache, 94

126