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
Rank: 30/84
Findings: 4
Award: $403.84
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: 0x4non
Also found by: 0xNazgul, Deivitto, __141345__, cccz, eierina, imare, kwhuo68, rvierdiiev
78.4798 USDC - $78.48
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L117
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.
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); } }
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
#8 - GainsGoblin
2023-01-30T01:41:12Z
🌟 Selected for report: 0xA5DF
Also found by: 0x4non, 0xmuxyz, 8olidity, HollaDieWaldfee
165.6217 USDC - $165.62
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L245-L249
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
Use safeTransferMany
to transfer NFT to a contract and you will see that its no triggerin the expected callback hook on the receiver contract.
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
🌟 Selected for report: yjrwkk
Also found by: 0x4non, 0xDecorativePineapple, 0xdeadbeef0x, Avci, Critical, Deivitto, Dinesh11G, Englave, Tointer, ak1, chaduke, izhelyazkov, pwnforce, rbserver, rvierdiiev, unforgiven
13.7578 USDC - $13.76
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
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
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
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
🌟 Selected for report: brgltd
Also found by: 0x4non, 0xNazgul, 0xSmartContract, Aymen0909, Deivitto, IllIllI, chrisdior4, hansfriese, joestakey, rbserver, unforgiven
145.9808 USDC - $145.98
_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;
_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.
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:
_blockDelay
can be any value type(uint256).max
_maxWinPercent
can be any value type(uint256).max
_range
can be any value type(uint256).max
_maxLeverage
, _feeMultiplier
, _baseFundingRate
can be any value_minLeverage
can be greater than _maxLeverage
! and _minLeverage
, _maxLeverage
can be any valueWhile 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;
On Lock.sol
editAsset
sendNFTs
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
Use named imports; import
Instead of 1e9
try to replace it with a name constant to understand what is 1e9
;
PairsContract.sol#L161
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.
Its a best practice to have one file per interface, you shoul move this interfaces into a separate file; StableVault.sol#L8-L25
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
ether-units
instead of raw numberUse 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 underscoreOn GovNFT.sol#L64 rename _bridgeMint
to bridgeMint
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.
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
_amount
, _period
& _owner
on function createLock
R
_safeMint
is recommend instead of _mint
L
_safeMint
)R
L
L
NC
NC
NC
R
NC
NC
1 DAYS
instead of calculating the 24 * 60 * 60
R
ether-units
instead of raw numberR
public
or external
functions shouldnt start with underscoreR
owner
Disputed as it's not the variable but the function
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