code-423n4 / 2022-05-cally-findings

2 stars 0 forks source link

Gas Optimizations #304

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Usage of storage instead of memory can save gas

When calling Vault memory vault = _vaults[vaultId]; all the struct is read from storage to memory. If we are interested in only some values we can use Vault storage vault = _vaults[vaultId]; which saves a reference to storage, meaning that SLOADs happen later only for the value we access.

These are all instances where it can save gas (see @audit comments for diff)

getPremium

    function getPremium(uint256 vaultId) public view returns (uint256 premium) {
        Vault storage vault = _vaults[vaultId];  //@audit from memory to storage
        return premiumOptions[vault.premiumIndex];
    }

buyOption

    function buyOption(uint256 vaultId) external payable returns (uint256 optionId) {
        Vault storage vault = _vaults[vaultId];  //@audit from memory to storage

        // vaultId should always be odd
        require(vaultId % 2 != 0, "Not vault type");

        // check vault exists
        require(ownerOf(vaultId) != address(0), "Vault does not exist");

        // check that the vault still has the NFTs as collateral
        require(vault.isExercised == false, "Vault already exercised");

        // check that the vault is not in withdrawing state
        require(vault.isWithdrawing == false, "Vault is being withdrawn");

        // check enough eth was sent to cover premium
        uint256 premium = getPremium(vaultId);
        require(msg.value >= premium, "Incorrect ETH amount sent");

        // check option associated with the vault has expired
        uint32 auctionStartTimestamp = vault.currentExpiration;
        require(block.timestamp >= auctionStartTimestamp, "Auction not started");

        // set new currentStrike
        //@audit also updates storage
        vault.currentStrike = getDutchAuctionStrike(
            strikeOptions[vault.dutchAuctionStartingStrikeIndex],
            vault.currentExpiration + AUCTION_DURATION,
            vault.dutchAuctionReserveStrike
        );

        // set new expiration
        //@audit also updates storage
        vault.currentExpiration = uint32(block.timestamp) + (vault.durationDays * 1 days);

        //@audit no needed - already updated
        // update the vault with the new option expiration and strike
        //_vaults[vaultId] = vault;

        // force transfer the vault's associated option from old owner to new owner
        // option id for a respective vault is always vaultId + 1
        optionId = vaultId + 1;
        _forceTransfer(msg.sender, optionId);

        // increment vault beneficiary's unclaimed premiums
        address beneficiary = getVaultBeneficiary(vaultId);
        ethBalance[beneficiary] += msg.value;

        emit BoughtOption(optionId, msg.sender, vault.token);
    }

exercise

    function exercise(uint256 optionId) external payable {
        // optionId should always be even
        require(optionId % 2 == 0, "Not option type");

        // check owner
        require(msg.sender == ownerOf(optionId), "You are not the owner");

        uint256 vaultId = optionId - 1;
        Vault storage vault = _vaults[vaultId];  //@audit from memory to storage

        // check option hasn't expired
        require(block.timestamp < vault.currentExpiration, "Option has expired");

        // check correct ETH amount was sent to pay the strike
        require(msg.value == vault.currentStrike, "Incorrect ETH sent for strike");

        // burn the option token
        _burn(optionId);

        // mark the vault as exercised
        vault.isExercised = true;
        //_vaults[vaultId] = vault;  //@audit no needed - already updated

        // collect protocol fee
        uint256 fee = 0;
        if (feeRate > 0) {
            fee = (msg.value * feeRate) / 1e18;
            protocolUnclaimedFees += fee;
        }

        // increment vault beneficiary's ETH balance
        ethBalance[getVaultBeneficiary(vaultId)] += msg.value - fee;

        emit ExercisedOption(optionId, msg.sender);

        // transfer the NFTs or ERC20s to the exerciser
        vault.tokenType == TokenType.ERC721
            ? ERC721(vault.token).transferFrom(address(this), msg.sender, vault.tokenIdOrAmount)
            : ERC20(vault.token).safeTransfer(msg.sender, vault.tokenIdOrAmount);
    }

withdraw

L325

Vault storage vault = _vaults[vaultId];  //@audit from memory to storage