Cally contest - rotcivegaf's results

Earn yield on your NFTs or tokens via covered call vaults.

General Information

Platform: Code4rena

Start Date: 10/05/2022

Pot Size: $50,000 USDC

Total HM: 13

Participants: 100

Period: 5 days

Judge: HardlyDifficult

Total Solo HM: 1

Id: 122

League: ETH

Cally

Findings Distribution

Researcher Performance

Rank: 42/100

Findings: 1

Award: $89.40

🌟 Selected for report: 0

🚀 Solo Findings: 0

Gas reports

Contests: Cally

Autor: Rotcivegaf

Scope:

Contracts:

discord Code4rena, Cally-may10 channel

G-01) Decrease the gas usage on Vault struct

Cally.sol: Use uint128 in Vault struct to save:

struct Vault {
    uint256 tokenIdOrAmount;
    address token;
    uint8 premiumIndex;
    uint8 durationDays;
    uint8 dutchAuctionStartingStrikeIndex;
    uint32 currentExpiration;
    bool isExercised;
    bool isWithdrawing;
    TokenType tokenType;
    uint128 currentStrike;             // 16 bytes
    uint128 dutchAuctionReserveStrike; // 16 bytes
}

The dutchAuctionReserveStrike and currentStrike should be lower than all elements of strikeOptions array and the highest element its 6765 ether, this enters in a uint80 and we have a uint128

  • For check currentStrike look in getDutchAuctionStrike function

Saves the gas(2209 according to my tests) of storage a bytes32 every time how creates a Vault

G-02) Dynamic storage array to static storage array

CallyNft.sol: For premiumOptions and strikeOptions use static storage array of uint256 and assign the values in the constructor

...
contract Cally is CallyNft, ReentrancyGuard, Ownable {
        ...
        uint256[] public premiumOptions;
        uint256[] public strikeOptions;

        constructor () {
            premiumOptions[0] = 0.01 ether;
            premiumOptions[1] = 0.025 ether;
            premiumOptions[2] = 0.05 ether;
            premiumOptions[3] = 0.075 ether;
            premiumOptions[4] = 0.1 ether;
            premiumOptions[5] = 0.25 ether;
            premiumOptions[6] = 0.5 ether;
            premiumOptions[7] = 0.75 ether;
            premiumOptions[8] = 1.0 ether;
            premiumOptions[9] = 2.5 ether;
            premiumOptions[10] = 5.0 ether;
            premiumOptions[11] = 7.5 ether;
            premiumOptions[12] = 10 ether;
            premiumOptions[13] = 25 ether;
            premiumOptions[14] = 50 ether;
            premiumOptions[15] = 75 ether;
            premiumOptions[16] = 100 ether;

            strikeOptions[0] = 1 ether;
            strikeOptions[1] = 2 ether;
            strikeOptions[2] = 3 ether;
            strikeOptions[3] = 5 ether;
            strikeOptions[4] = 8 ether;
            strikeOptions[5] = 13 ether;
            strikeOptions[6] = 21 ether;
            strikeOptions[7] = 34 ether;
            strikeOptions[8] = 55 ether;
            strikeOptions[9] = 89 ether;
            strikeOptions[10] = 144 ether;
            strikeOptions[11] = 233 ether;
            strikeOptions[12] = 377 ether;
            strikeOptions[13] = 610 ether;
            strikeOptions[14] = 987 ether;
            strikeOptions[15] = 1597 ether;
            strikeOptions[16] = 2584 ether;
            strikeOptions[17] = 4181 ether;
            strikeOptions[18] = 6765 ether;
        }
        ...

This saves gas(55195 according to my tests) in the deploy of the contract Also saves 2100 gas for each .length consult and 2203 gas for each consult to the array

G-03) No need to initialize variables with default values

In solidity all variables initialize in 0, address(0), false, etc.

Cally.sol: Remove initialize in the uint256 public feeRate and uint256 public protocolUnclaimedFees variables

This save gas(2258 according to my tests) for each initialize in the deploy of the contract

G-04) Require not necessary

CallyNft.sol:

  • L15, L42: The requires are not necessary, only the Cally contract call this functions and allways use msg.sender as to
  • L16: The require is not necessary, the ERC721 identifier its controlled by the vaultIndex var in the Cally contract, and vaultIndex is only changed on the createVault function

G-05) ownerOf() to _ownerOf

CallyNft.sol: In the lines 214, 263, 307, 323, 354 use directly _ownerOf(vaultId), ownerOf function add an unnecessary require Also in L382 because if the vaultId not exists the currentBeneficiary will be the address(0)

G-06) Use unchecked for operations not expected to overflow

CallyNft.sol:

  • L188: vaultIndex += 2;
  • L231 to L235:
vault.currentStrike = getDutchAuctionStrike(
    strikeOptions[vault.dutchAuctionStartingStrikeIndex],
    vault.currentExpiration + AUCTION_DURATION,
    vault.dutchAuctionReserveStrike
);
  • L238: vault.currentExpiration = uint32(block.timestamp) + (vault.durationDays * 1 days);
  • L245: optionId = vaultId + 1;
  • L250: ethBalance[beneficiary] += msg.value;
  • L283 to L286:
if (feeRate > 0) {
    fee = (msg.value * feeRate) / 1e18;
    protocolUnclaimedFees += fee;
}
  • L289: ethBalance[getVaultBeneficiary(vaultId)] += msg.value - fee;
  • L417 to L422: all in getDutchAuctionStrike function
unchecked {
    <LINE/BLOCK>
}

These calculations cannot overflow

G-07) Do harvest if ethBalance it's greater than 0

function harvest() public returns (uint256 amount) {
    amount = ethBalance[msg.sender];
    if (amount > 0) {
        // reset premiums
        ethBalance[msg.sender] = 0;

        emit Harvested(msg.sender, amount);

        // transfer premiums to owner
        payable(msg.sender).safeTransferETH(amount);
    }
}

G-07) != 0 to > 0 for uint in require() statements

Cally.sol, L170: require(durationDays > 0, "durationDays too small"); to require(durationDays != 0, "durationDays too small");

This save gas(6 according to my tests) for each require

#0 - outdoteth

2022-05-16T20:16:04Z

high quality report

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter