Cally contest - BondiPestControl'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: 10/100

Findings: 6

Award: $2,626.28

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: WatchPug

Also found by: BondiPestControl, IllIllI

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

2072.9482 USDC - $2,072.95

External Links

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/1849f9ee12434038aa80753266ce6a2f2b082c59/contracts/src/Cally.sol#L200

Vulnerability details

Impact

There are no checks to ensure the the vault.token contract exists when creating a vault.

Token address are deterministic in the EVM and can be known ahead of time. As a result it is possible for a user to call createVault() with token set to a ERC20 contract that has not yet been deployed but will be deployed in the future.

Now if a user calls createVault() before a new ERC20 token has been created it will credit the vault with these tokens. The cause is from ERC20(token).safeTransferFrom() which will succeed if token has zero bytecode at the address.

The later when the ERC20 token is deployed and another user calls createVault() with this token, the attacker is able to call withdraw() and there will be no tokens left in the contract for the genuine vault.

Proof of Concept

There are no checks before calling safeTransferFrom to ensure vault.token has bytecode.

ERC20(vault.token).safeTransferFrom(msg.sender, address(this), vault.tokenIdOrAmount);

Use the extcodesize EVM opcode to ensure that there is bytecode at the vault.token address during createVault() otherwise revert the transaction.

#0 - outdoteth

2022-05-15T21:02:11Z

options can be sold on for erc20s that have not been deployed yet: https://github.com/code-423n4/2022-05-cally-findings/issues/225

Awards

8.1655 USDC - $8.17

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

External Links

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/1849f9ee12434038aa80753266ce6a2f2b082c59/contracts/src/Cally.sol#L119-L121 https://github.com/code-423n4/2022-05-cally/blob/1849f9ee12434038aa80753266ce6a2f2b082c59/contracts/src/Cally.sol#L289

Vulnerability details

Impact

It is possible for the owner of the contract to set the feeRate equal to or above 100%.

The impact of setting the contract above 100% (1e18) is that the fee which is msg.value * feeRate / 1e18 may be greater than the msg.value. There will be an overflow in line #289 causing all calls to exercise() to fail.

ethBalance[getVaultBeneficiary(vaultId)] += msg.value - fee;

Furthermore, the owner may perform a rug pull attack by setting the feeRate = 1e18 (exactly 100%). This would allow the owner to exercise any options without paying any funds. That is due to the fact that the entire strike amount will be considered fees and attributed to protocolUnclaimedFees which the owner may later withdraw. The owner would then also receive the token which was held in the vault.

Proof of Concept

There are no restrictions on the function setFee().

    function setFee(uint256 feeRate_) external onlyOwner {
        feeRate = feeRate_;
    }

The owner may then claim all fees and receive tokens in exercise(), the only cost to the owner would by the premium amount when they purchase the option from the auction.

        // 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);

Consider setting an reasonable upper limit of the feeRate such as 5% in setFee().

Furthermore, consider have vaults lock in the feeRate when they first createVault(). This would prevent the owner manipulating the value when a user is trying to exercise the option.

#0 - outdoteth

2022-05-15T18:57:58Z

Awards

31.6149 USDC - $31.61

Labels

bug
2 (Med Risk)
resolved
sponsor confirmed

External Links

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/1849f9ee12434038aa80753266ce6a2f2b082c59/contracts/src/Cally.sol#L223-L224

Vulnerability details

Impact

It is possible for a user purchasing an option to accidentally overpay the premium during buyOption().

Any excess funds paid for in excess of the premium will be transferred to the vault creator.

The premium is fixed at the time the vault is first created by vault.premiumIndex. Hence there is no need to allow users to overpay since there will be no benefit.

Proof of Concept

buyOption() allows msg.value > premium

        uint256 premium = getPremium(vaultId);
        require(msg.value >= premium, "Incorrect ETH amount sent");

Consider modifying the check such that the msg.value is exactly equal to the premuim. e.g.

        uint256 premium = getPremium(vaultId);
        require(msg.value == premium, "Incorrect ETH amount sent");

#0 - outdoteth

2022-05-17T17:25:57Z

#1 - HardlyDifficult

2022-05-20T22:20:45Z

Agree with 2 (Medium) for this. The issue doesn't really open the door for an attack, except for maybe via a malicious frontend. But it could potentially leak value in terms of over compensating the vault creator.

#2 - HickupHH3

2022-06-08T06:11:30Z

QA report #182 should have its issue bumped up and marked as a duplicate IMO

Findings Information

🌟 Selected for report: hickuphh3

Also found by: BondiPestControl, GimelSec, VAD37, sseefried

Labels

bug
duplicate
2 (Med Risk)
disagree with severity

Awards

447.7568 USDC - $447.76

External Links

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/1849f9ee12434038aa80753266ce6a2f2b082c59/contracts/src/Cally.sol#L238

Vulnerability details

Impact

There is a potential overflow in the function buyOption() which will revert if triggered.

The overflow occurs when durationDays >= 195. As a result it is impossible for any user to call buyOption() if the duration exceeds this value.

The vault creator will still be able to withdraw through withdraw()

Proof of Concept

The following line may cause an overflow due to (vault.durationDays * 1 days).

        vault.currentExpiration = uint32(block.timestamp) + (vault.durationDays * 1 days);

The issue occurs since vault.durationDays is of type uint8 and 1 days is of type uint24 (solidity defaults to the lowest size that the value fits).

As a result vault.durationDays * 1 days are treated as uint24 * uint24.

1 days = 86,400

and

195 * 86,400 = 16,848,000

Since,

2**24 = 16,777,216

there is an overflow for the uint24. Overflows in solidity 0.8.13 will cause the transaction to revert.

Since vault.durationDays cannot be changes all calls to buyOption() for this token ID will fail if durationDays >= 195

Consider updating the multiplication such that durationDays is first cast to uint32. This will prevent an overflow since the max value of durationDays = 255 multiplied by 1 days is less than 2**32 and so will not overflow a uint32.

e.g.

vault.currentExpiration = uint32(block.timestamp) + (uint32(vault.durationDays) * 1 days);

#0 - outdoteth

2022-05-15T11:04:48Z

This should be bumped to a high risk issue - a user can unintentionally create vaults that would never have been able to have been filled and result in them losing funds; in terms of gas prices at 100 gwei (which it frequently was a few months ago), 140k gas is not a tiny amount.

#1 - outdoteth

2022-05-15T17:22:32Z

Awards

10.8874 USDC - $10.89

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

External Links

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/main/contracts/src/Cally.sol#L200 https://github.com/code-423n4/2022-05-cally/blob/main/contracts/src/Cally.sol#L296

Vulnerability details

Impact

When a user creates a ERC20 vault by calling createVault(), tokens are transferred from the caller to the contract equivalent tokenIdOrAmount. If the token contract used charges a fee for the transfer, the transfer initiated by an exercised option will ultimately fail.

As a result, users could set up a honey pot where they create a vault using a fee-on-transfer token, sell options on this vault with whatever strike price and premium and with certainty keep their tokens and the premium amount as an exercise of this option will fail. The vault owner will always profit from this trade and the option buyer will always lose the premium they paid.

Because this leads to a loss of funds by an honest option buyer, I believe high severity is deserving.

Consider snapshotting the before and after balance for all ERC20 transfers in and out of the contract. This will ensure the balance for each vault is strictly correct, regardless if a vault is created upon a fee-on-transfer token.

#0 - outdoteth

2022-05-15T17:15:29Z

Lines of code

https://github.com/code-423n4/2022-05-cally/blob/main/contracts/src/Cally.sol#L198-L200

Vulnerability details

Impact

While the Cally.sol contract is compatible with standard ERC20 and ERC721 tokens, it is not able to handle popular non-standard ERC721 tokens such as cryptopunks. The typical transferFrom() call will fail as a result, and these users will likely opt to use another protocol which does support options on their specific NFT.

NFTX protocol has implemented a way to handle the transfer of both standard and non-standard ERC721 tokens. The relevant implementation can be found here. The solution provided also utilises OpenZeppelin's safeTransferFrom() function on most transfers as this ensures the function reverts on failure.

#0 - outdoteth

2022-05-15T13:43:59Z

Technically correct but we have no intention to support these tokens. users can use those tokens by getting a wrapped version of them that conforms to the erc721 spec

#1 - HardlyDifficult

2022-05-22T15:23:08Z

This is a common issue when working with NFTs. Wrappers, native support, or simply not supporting these non-standard tokens are reasonable courses. Lowing to 1 (Low) and converting into a QA report for the warden.

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