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: 25/84
Findings: 5
Award: $618.80
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: HollaDieWaldfee
Also found by: 0xbepresent, 0xsomeone, Ruhum, ali_shehab, cccz, csanuragjain, kaliberpoziomka8552, rvierdiiev, sha256yan
162.9965 USDC - $163.00
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
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
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
🌟 Selected for report: 0x4non
Also found by: 0xNazgul, Deivitto, __141345__, cccz, eierina, imare, kwhuo68, rvierdiiev
60.3691 USDC - $60.37
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
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
🌟 Selected for report: cccz
Also found by: 0xbepresent, Madalad, unforgiven
299.0391 USDC - $299.04
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; }
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
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
#7 - GainsGoblin
2023-01-29T00:00:54Z
#8 - c4-sponsor
2023-02-13T21:52:49Z
GainsGoblin marked the issue as sponsor confirmed
🌟 Selected for report: 0xA5DF
Also found by: 0xA5DF, 0xNazgul, 0xSmartContract, 0xbepresent, 0xdeadbeef0x, 8olidity, Englave, Faith, HE1M, JohnnyTime, Madalad, Mukund, Ruhum, SmartSek, __141345__, aviggiano, carlitox477, cccz, chaduke, francoHacker, gz627, gzeon, hansfriese, hihen, imare, jadezti, kwhuo68, ladboy233, orion, peanuts, philogy, rbserver, wait, yjrwkk
0.5736 USDC - $0.57
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; }
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
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
95.824 USDC - $95.82
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 {
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
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