Tigris Trade contest - cccz's results

A multi-chain decentralized leveraged exchange featuring instant settlement and guaranteed price execution on 30+ pairs.

General Information

Platform: Code4rena

Start Date: 09/12/2022

Pot Size: $90,500 USDC

Total HM: 35

Participants: 84

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 12

Id: 192

League: ETH

Tigris Trade

Findings Distribution

Researcher Performance

Rank: 25/84

Findings: 5

Award: $618.80

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-23

Awards

162.9965 USDC - $163.00

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Lock.sol#L84-L92

Vulnerability details

Impact

When the user deposits assets into the Lock, in lock(), totalLocked is correctly updated.

    function lock(
        address _asset,
        uint _amount,
        uint _period
    ) public {
        require(_period <= maxPeriod, "MAX PERIOD");
        require(_period >= minPeriod, "MIN PERIOD");
        require(allowedAssets[_asset], "!asset");

        claimGovFees();

        IERC20(_asset).transferFrom(msg.sender, address(this), _amount);
        totalLocked[_asset] += _amount;
        
        bondNFT.createLock( _asset, _amount, _period, msg.sender);
    }

But this is not done in extendLock which makes totalLocked incorrect.

    function extendLock(
        uint _id,
        uint _amount,
        uint _period
    ) public {
        address _asset = claim(_id);
        IERC20(_asset).transferFrom(msg.sender, address(this), _amount);
        bondNFT.extendLock(_id, _asset, _amount, _period, msg.sender);
    }

This will cause the release function to overflow when calculating totalLocked, thus preventing the user from withdrawing the asset

    function release(
        uint _id
    ) public {
        claimGovFees();
        (uint amount, uint lockAmount, address asset, address _owner) = bondNFT.release(_id, msg.sender);
        totalLocked[asset] -= lockAmount;
        IERC20(asset).transfer(_owner, amount);
    }

Consider the following scenario: User A calls Lock.lock to deposit 100 tokens for one month, where totalLocked = 100. And then calls Lock.extendLock to deposit 100 tokens again, where totalLocked == 100 since Lock.extendLock does not update totalLocked. After one month, user A calls Lock.release to withdraw the tokens, because lockAmount == 200, totalLocked - lockAmount overflows and the function fails, thus the tokens cannot be withdrawn

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Lock.sol#L84-L92 https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Lock.sol#L98-L105

Tools Used

None

    function extendLock(
        uint _id,
        uint _amount,
        uint _period
    ) public {
        address _asset = claim(_id);
        IERC20(_asset).transferFrom(msg.sender, address(this), _amount);
+      totalLocked[_asset] += _amount;
        bondNFT.extendLock(_id, _asset, _amount, _period, msg.sender);
    }

#0 - c4-judge

2022-12-21T15:02:34Z

GalloDaSballo marked the issue as duplicate of #23

#1 - c4-judge

2023-01-22T17:38:07Z

GalloDaSballo marked the issue as satisfactory

Findings Information

🌟 Selected for report: 0x4non

Also found by: 0xNazgul, Deivitto, __141345__, cccz, eierina, imare, kwhuo68, rvierdiiev

Labels

bug
2 (Med Risk)
satisfactory
duplicate-104

Awards

60.3691 USDC - $60.37

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Trading.sol#L643-L653

Vulnerability details

Impact

Some tokens (like USDT on Ethereum) do not work when changing the allowance from an existing non-zero allowance value. They must first be approved by zero and then the actual allowance must be approved.

In _handleDeposit, if _marginAsset is USDT, this will cause _handleDeposit to get stuck

function _handleDeposit(address _tigAsset, address _marginAsset, uint256 _margin, address _stableVault, ERC20PermitData calldata _permitData, address _trader) internal { IStable tigAsset = IStable(_tigAsset); if (_tigAsset != _marginAsset) { if (_permitData.usePermit) { ERC20Permit(_marginAsset).permit(_trader, address(this), _permitData.amount, _permitData.deadline, _permitData.v, _permitData.r, _permitData.s); } uint256 _balBefore = tigAsset.balanceOf(address(this)); uint _marginDecMultiplier = 10**(18-ExtendedIERC20(_marginAsset).decimals()); IERC20(_marginAsset).transferFrom(_trader, address(this), _margin/_marginDecMultiplier); IERC20(_marginAsset).approve(_stableVault, type(uint).max); IStableVault(_stableVault).deposit(_marginAsset, _margin/_marginDecMultiplier);

Considering that tigris is a multi-chain application, it should be adapted to USDT on ethereum

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Trading.sol#L643-L653

Tools Used

None

    function _handleDeposit(address _tigAsset, address _marginAsset, uint256 _margin, address _stableVault, ERC20PermitData calldata _permitData, address _trader) internal {
        IStable tigAsset = IStable(_tigAsset);
        if (_tigAsset != _marginAsset) {
            if (_permitData.usePermit) {
                ERC20Permit(_marginAsset).permit(_trader, address(this), _permitData.amount, _permitData.deadline, _permitData.v, _permitData.r, _permitData.s);
            }
            uint256 _balBefore = tigAsset.balanceOf(address(this));
            uint _marginDecMultiplier = 10**(18-ExtendedIERC20(_marginAsset).decimals());
            IERC20(_marginAsset).transferFrom(_trader, address(this), _margin/_marginDecMultiplier);
+          IERC20(_marginAsset).approve(_stableVault, 0);
            IERC20(_marginAsset).approve(_stableVault, type(uint).max);
            IStableVault(_stableVault).deposit(_marginAsset, _margin/_marginDecMultiplier);

#0 - c4-judge

2022-12-20T15:49:38Z

GalloDaSballo marked the issue as duplicate of #104

#1 - c4-judge

2023-01-22T17:45:48Z

GalloDaSballo marked the issue as satisfactory

Findings Information

🌟 Selected for report: cccz

Also found by: 0xbepresent, Madalad, unforgiven

Labels

bug
2 (Med Risk)
judge review requested
primary issue
selected for report
sponsor confirmed
M-08

Awards

299.0391 USDC - $299.04

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/GovNFT.sol#L19-L20

Vulnerability details

Impact

In GovNFT, setMaxBridge function is provided to set maxBridge, but this variable is not used, literally it should be used to limit the number of GovNFTs crossing chain, but it doesn't work in GovNFT.

    uint256 public maxBridge = 20;
...
    function setMaxBridge(uint256 _max) external onlyOwner {
        maxBridge = _max;
    }

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/GovNFT.sol#L19-L20 https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/GovNFT.sol#L311-L313

Tools Used

None

Consider applying the maxBridge variable

#0 - GalloDaSballo

2022-12-20T01:50:38Z

R

#1 - c4-judge

2022-12-20T01:50:43Z

GalloDaSballo changed the severity to QA (Quality Assurance)

#2 - GainsGoblin

2023-01-05T19:55:04Z

I disagree with the severity downgrade.

#3 - c4-sponsor

2023-01-05T19:55:13Z

GainsGoblin requested judge review

#4 - GalloDaSballo

2023-01-22T17:27:11Z

@GainsGoblin Thank you for flagging.

I have checked the LayerZero Library and it seems like it will not revert when relaying a tx that is too expensive.

For this reason I agree with you and will raise Severity to Medium.

The Warden has shown how, an unused variable, which was meant to cap the amount of tokens bridged per call, could cause a DOS.

These types of DOS could only be fixed via Governance Operations, and could create further issues, for this reason I agree with Medium Seveirty

#5 - Simon-Busch

2023-01-23T08:26:52Z

Reverted back to M and set as Primary issue as requested by @GalloDaSballo

#6 - c4-judge

2023-01-23T09:10:17Z

GalloDaSballo marked the issue as selected for report

#8 - c4-sponsor

2023-02-13T21:52:49Z

GainsGoblin marked the issue as sponsor confirmed

Awards

0.5736 USDC - $0.57

Labels

bug
2 (Med Risk)
partial-50
duplicate-377

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/StableToken.sol#L13-L22

Vulnerability details

Impact

Using the burnFrom() function of stableToken, minter can burn an arbitrary amount of tokens from any address.

    function burnFrom(
        address account,
        uint256 amount
    ) 
        public 
        virtual 
        onlyMinter() 
    {
        _burn(account, amount);
    }

This is not necessary, the StableToken should be transferred from the user to the contract first and then called StableToken.burnFrom(address(this), amount).

If the private key of the owner of the StableToken is compromised, an attacker can set the minter and burn any user's StableToken


    function setMinter(
        address _address,
        bool _status
    ) 
        public
        onlyOwner()
    {
        isMinter[_address] = _status;
    }

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/StableToken.sol#L13-L22 https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/StableToken.sol#L38-L46

Tools Used

None

Update burnFrom function for only user can burn his tokens.

#0 - GalloDaSballo

2022-12-23T17:52:44Z

While the warden focused on burning, technically the admin can mint and rug the vault, I'll award that, but may apply a penalty as they missed an even bigger economical attack

#1 - c4-judge

2022-12-23T18:09:04Z

GalloDaSballo marked the issue as duplicate of #383

#2 - c4-judge

2022-12-23T18:09:12Z

GalloDaSballo marked the issue as partial-50

#3 - c4-judge

2023-01-15T14:04:00Z

GalloDaSballo marked the issue as duplicate of #377

#4 - c4-judge

2023-01-22T17:34:15Z

GalloDaSballo marked the issue as satisfactory

#5 - c4-judge

2023-01-22T17:34:36Z

GalloDaSballo marked the issue as partial-50

Findings Information

🌟 Selected for report: rbserver

Also found by: HE1M, KingNFT, bin2chen, cccz, stealthyz, unforgiven

Labels

bug
2 (Med Risk)
satisfactory
duplicate-649

Awards

95.824 USDC - $95.82

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Trading.sol#L749

Vulnerability details

Impact

When Trading._handleOpenFees calls gov.distribute, gov transfers the _tigAsset from Trading to gov, which requires Trading approve _tigAsset to gov first.

        gov.distribute(_tigAsset, IStable(_tigAsset).balanceOf(address(this)));
...
    function distribute(address _tigAsset, uint _amount) external {
        if (assets.length == 0 || assets[assetsIndex[_tigAsset]] == address(0) || totalSupply() == 0 || !_allowedAsset[_tigAsset]) return;
        try IERC20(_tigAsset).transferFrom(_msgSender(), address(this), _amount) {
            accRewardsPerNFT[_tigAsset] += _amount/totalSupply();
        } catch {
            return;
        }
    }

Since the default openFees is 0, there is no trouble in the test, but if the openFees are changed, the system will not be stuck due to the try-catch in gov.distribute, but it will cause the fees to be locked in Trading and not distributed to the GovNFT holder.

    Fees public openFees = Fees(
        0,
        0,
        0,
        0
    );
...
    function setFees(bool _open, uint _daoFees, uint _burnFees, uint _referralFees, uint _botFees, uint _percent) external onlyOwner {
        unchecked {
            require(_daoFees >= _botFees+_referralFees*2);
            if (_open) {
                openFees.daoFees = _daoFees;
                openFees.burnFees = _burnFees;
                openFees.referralFees = _referralFees;
                openFees.botFees = _botFees;
            } else {

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/Trading.sol#L749 https://github.com/code-423n4/2022-12-tigris/blob/496e1974ee3838be8759e7b4096dbee1b8795593/contracts/GovNFT.sol#L287-L289

Tools Used

None

approve _tigAsset to gov before calling gov.distribute in Trading._handleOpenFees

#0 - c4-judge

2022-12-21T15:12:37Z

GalloDaSballo marked the issue as duplicate of #324

#1 - c4-judge

2022-12-22T01:09:48Z

GalloDaSballo marked the issue as duplicate of #256

#2 - c4-judge

2022-12-22T01:11:34Z

GalloDaSballo marked the issue as duplicate of #649

#3 - c4-judge

2023-01-22T17:53:26Z

GalloDaSballo marked the issue as satisfactory

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