Tigris Trade contest - 0x4non'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: 30/84

Findings: 4

Award: $403.84

QA:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0x4non

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

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor confirmed
M-02

Awards

78.4798 USDC - $78.48

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L117

Vulnerability details

Impact

Some tokens (like USDT) 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.

Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L117

    function claimGovFees() public {
        address[] memory assets = bondNFT.getAssets();

        for (uint i=0; i < assets.length; i++) {
            uint balanceBefore = IERC20(assets[i]).balanceOf(address(this));
            IGovNFT(govNFT).claim(assets[i]);
            uint balanceAfter = IERC20(assets[i]).balanceOf(address(this));
            IERC20(assets[i]).approve(address(bondNFT), type(uint256).max);// @audit this could fail always with some tokens, 
            bondNFT.distribute(assets[i], balanceAfter - balanceBefore);
        }
    }

Tools Used

manual revision

Add an approve(0) before approving;

function claimGovFees() public { address[] memory assets = bondNFT.getAssets(); for (uint i=0; i < assets.length; i++) { uint balanceBefore = IERC20(assets[i]).balanceOf(address(this)); IGovNFT(govNFT).claim(assets[i]); uint balanceAfter = IERC20(assets[i]).balanceOf(address(this)); IERC20(assets[i]).approve(address(bondNFT), 0); IERC20(assets[i]).approve(address(bondNFT), type(uint256).max); bondNFT.distribute(assets[i], balanceAfter - balanceBefore); } }

#0 - GalloDaSballo

2022-12-20T15:48:59Z

Making primary but most are similar

#1 - c4-judge

2022-12-20T15:49:08Z

GalloDaSballo marked the issue as primary issue

#2 - c4-sponsor

2022-12-23T06:13:41Z

TriHaz marked the issue as sponsor confirmed

#3 - GalloDaSballo

2023-01-13T18:58:27Z

The Warden has shown how, due to the function approving max multiple times, certain tokens, that only allow a non-zero allowance to be set starting from zero, could revert.

Because this depends on the token implementation, but there's a reasonable chance to believe that USDT will be used, I agree with Medium Severity.

#4 - c4-judge

2023-01-13T18:58:34Z

GalloDaSballo marked the issue as selected for report

#5 - c4-sponsor

2023-01-27T19:08:14Z

GainsGoblin marked the issue as sponsor acknowledged

#6 - GainsGoblin

2023-01-27T19:09:39Z

Since the purpose of the bonds is to lock tigAsset liquidity, only tigAsset tokens will be allowed to be locked, which don't have this issue.

#7 - c4-sponsor

2023-01-30T01:41:03Z

GainsGoblin marked the issue as sponsor confirmed

Findings Information

🌟 Selected for report: 0xA5DF

Also found by: 0x4non, 0xmuxyz, 8olidity, HollaDieWaldfee

Labels

bug
2 (Med Risk)
satisfactory
duplicate-356

Awards

165.6217 USDC - $165.62

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L245-L249

Vulnerability details

Impact

The function safeTransferMany its not doing a _safeTransfer its just using _transfer

Use of _transfer method for ERC721 is discouraged and recommended to use _safeTransfer whenever possible by OpenZeppelin.

This is because _transfer cannot check whether the receiving address know how to handle ERC721 tokens. If the recipient is a contract not aware of incoming NFTs, then the transferred NFT would be locked in the recipient forever.

The name of the function is safeTransferMany thats why its expected to do a _safeTransfer

Proof of Concept

Use safeTransferMany to transfer NFT to a contract and you will see that its no triggerin the expected callback hook on the receiver contract.

Tools Used

Manual revision

Use _safeTransfer instead of _transfer, take in consideration that _safeTransfer will trigger a callback to the receiver, so stick to check - effects - itterarion pattern

Also you havent implemented a _safeTransfer as you do on _transfer GovNFT.sol#L94-L101

#0 - c4-judge

2022-12-20T15:54:18Z

GalloDaSballo marked the issue as duplicate of #356

#1 - c4-judge

2023-01-22T17:46:41Z

GalloDaSballo marked the issue as satisfactory

Awards

13.7578 USDC - $13.76

Labels

bug
2 (Med Risk)
satisfactory
duplicate-533

External Links

Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L67 https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L49

Vulnerability details

Impact

If a token has more than 18 decimals the tokens will be imposible to use because it will always trigger a revert by underflow.

ERC20 can have more than 18 decimas can-an-erc-20-have-more-than-18-decimals

Proof of Concept

On https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L67

 _output = _amount/10**(18-IERC20Mintable(_token).decimals());

Simalr case on: https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L49

Tools Used

manual revision

On the listToken method add a check for decimals if is greater than 18 revert. You could also change the function and add a mapping that cache the computes (10**(18-IERC20Mintable(_token).decimals())) Example;

mapping(address => uint8) tokenMul; function listToken(address _token) external onlyOwner { require(!allowed[_token], "Already added"); tokenIndex[_token] = tokens.length; tokens.push(_token); allowed[_token] = true; uint8 _decimals = IERC20Mintable(_token).decimals(); tokenMul[_token] = (_decimals > 18) ? _decimals - 18 : 18 - _decimals; } // Then instead of // _output = _amount/10**(18-IERC20Mintable(_token).decimals()); // you do // _output = _amount/10**tokenMul[_token];

#0 - c4-judge

2022-12-20T15:43:30Z

GalloDaSballo marked the issue as duplicate of #533

#1 - c4-judge

2023-01-22T17:45:04Z

GalloDaSballo marked the issue as satisfactory

Findings Information

Labels

bug
grade-b
QA (Quality Assurance)
sponsor confirmed
Q-02

Awards

145.9808 USDC - $145.98

External Links

LOWS - NC - QA

Low

Lack of validation for _amount, _period & _owner on function createLock

On BondNFT.sol#L57-L61 consider adding a threashold to _amount,_period and add a check for address(0) to _owner

_safeMint is recommend instead of _mint

Currently, the contract uses the _mint() function to mint new tokens which is discouraged by the OpenZeppelin documentation because there is no check as to whether that token was received. It's recommended that _safeMint() is used when handling ERC721 tokens as this guarantees the end user has received them.

Lines affected;

Possible reentrancy (if minted is setup as expected with _safeMint)

On function initiateLimitOrder Trading.sol#L314-L349 you are not following the Checks Effects Interactions pattern after the mint you update the state on line 347, you can add a reentrancy guard or just simple move the mint after updating the state.

Missing Zero-address Validation

Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.

Consider adding explicit zero-address validation on input parameters of address type.

Files:

Missing threashold leaving to user/owner put any desired value even if not valid

QA

Avoid floating pragmas for non-library contracts.

While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.

A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.

It is recommended to pin to a concrete compiler version.

Affected lines; contracts/Position.sol:2:pragma solidity ^0.8.0; contracts/TradingExtension.sol:2:pragma solidity ^0.8.0; contracts/Forwarder.sol:2:pragma solidity ^0.8.0; contracts/Lock.sol:2:pragma solidity ^0.8.0; contracts/StableToken.sol:2:pragma solidity ^0.8.0; contracts/GovNFT.sol:2:pragma solidity ^0.8.0; contracts/PairsContract.sol:2:pragma solidity ^0.8.0; contracts/BondNFT.sol:2:pragma solidity ^0.8.0; contracts/Trading.sol:2:pragma solidity ^0.8.0; contracts/Referrals.sol:2:pragma solidity ^0.8.0; contracts/StableVault.sol:2:pragma solidity ^0.8.0;

Missing event emission on critical function

On Lock.sol

Be explicit declaring types

Be explicit declaring types, instead of uint use uint256, uint is an alias of uint256, but its preffer just to use uint256. Example (not only this line but the whole file); https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L10

Non Critical

Use named imports; import

Avoid magic numbers, its better use named constant

Instead of 1e9 try to replace it with a name constant to understand what is 1e9; PairsContract.sol#L161

Follow solidity best practice ordering contract elements

The best-practice layout for a contract should follow the following order: state variables, events, modifiers, constructor and functions. Function ordering helps readers identify which functions they can call and find constructor and fallback functions easier. Functions should be grouped according to their visibility and ordered as: constructor, receive function (if exists), fallback function (if exists), external, public, internal, private. Some constructs deviate from this recommended best-practice.

Inside each contract, library or interface, use the following order:

Type declarations State variables Events Modifiers Functions

Example 1, on Position.sol#L69-L83 your layout is;

Functions State variables Functions State variables Functions Modifiers

Example 2, on GovNFT.sol#L257-L281 your layout is;

State variables Events Functions State variables Functions

Example 3, on Trading.sol#L980-L1052, you have all events at the end of the contract layout.

Example 4, on PairsContract.sol#L194-L199, you have events at the end of the contract layout.

Separate interfaces in different files

Its a best practice to have one file per interface, you shoul move this interfaces into a separate file; StableVault.sol#L8-L25

Use 1 DAYS instead of calculating the 24 * 60 * 60

Use 1 DAYS instead of calculating the 24 * 60 * 60 Example BondNFT.sol#L10

More on units-and-global-variables.html#time-units

Use ether-units instead of raw number

Use 1000 gwei insteado of 1000000000000 Example 1 TradingExtension.sol#L26 Example 2 GovNFT.sol#L17

More on ether-units

public or external functions shouldnt start with underscore

On GovNFT.sol#L64 rename _bridgeMint to bridgeMint

Shadow variables owner

On _burn [GovNFT.sol#L76](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L76= variable of owner is shadowing owner() from ownable, consider rename internal variable owner to _owner to avoid collisions.

Lack of event emission on critical functions

Several functions update critical parameters that are missing event emission. These should be performed to ensure tracking of changes of such critical parameters.

Consider adding events to functions that change critical parameters.

Also while initing refererals on Referrals.sol#L71 function initRefs, Referrals.sol#L71 should emit ReferralCreated and Referrals.sol#L74 should emit Referred

#0 - GalloDaSballo

2022-12-27T22:16:17Z

Lack of validation for _amount, _period & _owner on function createLock

R

_safeMint is recommend instead of _mint

L

Possible reentrancy (if minted is setup as expected with _safeMint)

R

Missing Zero-address Validation

L

Missing threashold leaving to user/owner put any desired value even if not valid

L

Avoid floating pragmas for non-library contracts.

NC

Missing event emission on critical function

NC

Be explicit declaring types

NC

Avoid magic numbers, its better use named constant

R

Follow solidity best practice ordering contract elements

NC

Separate interfaces in different files

NC

Use 1 DAYS instead of calculating the 24 * 60 * 60

R

Use ether-units instead of raw number

R

public or external functions shouldnt start with underscore

R

Shadow variables owner

Disputed as it's not the variable but the function

Lack of event emission on critical functions

Already awarded

3L 6R 5NC

#1 - eugenioclrc

2022-12-31T04:00:10Z

Thanks for the feedback!

#2 - c4-sponsor

2023-01-05T19:11:48Z

GainsGoblin marked the issue as sponsor confirmed

#3 - GalloDaSballo

2023-01-22T21:12:54Z

1L from Dups

4L 6R 5NC

#4 - c4-judge

2023-01-23T08:47:45Z

GalloDaSballo marked the issue as grade-b

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