Tapioca DAO - xuwinnie's results

The first ever Omnichain money market, powered by LayerZero.

General Information

Platform: Code4rena

Start Date: 05/07/2023

Pot Size: $390,000 USDC

Total HM: 136

Participants: 132

Period: about 1 month

Judge: LSDan

Total Solo HM: 56

Id: 261

League: ETH

Tapioca DAO

Findings Distribution

Researcher Performance

Rank: 15/132

Findings: 12

Award: $4,564.37

🌟 Selected for report: 2

🚀 Solo Findings: 2

Findings Information

Awards

20.4247 USDC - $20.42

Labels

bug
3 (High Risk)
satisfactory
duplicate-1567

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLendingCommon.sol#L23-L25

Vulnerability details

Impact

The modifier allowedBorrow only checks that share is less than allowance, but both amount and share can be specified when calling addCollateral. As a result, attacker can call function addCollateral with a zero share and a nonzero amount to bypass the check.

Proof of Concept

function addCollateral( address from, address to, bool skim, uint256 amount, uint256 share ) public notPaused allowedBorrow(from, share) { _addCollateral(from, to, skim, amount, share); } function _allowedBorrow(address from, uint share) internal { if (from != msg.sender) { if (allowanceBorrow[from][msg.sender] < share) { revert NotApproved(from, msg.sender); } allowanceBorrow[from][msg.sender] -= share; } } function _addCollateral( address from, address to, bool skim, uint256 amount, uint256 share ) internal { if (share == 0) { share = yieldBox.toShare(collateralId, amount, false); } userCollateralShare[to] += share; uint256 oldTotalCollateralShare = totalCollateralShare; totalCollateralShare = oldTotalCollateralShare + share; _addTokens( from, to, collateralId, share, oldTotalCollateralShare, skim ); emit LogAddCollateral(skim ? address(yieldBox) : from, to, share); }

When a innocent user tries to add collateral, they need to first approve or permit Singularity to spend YieldBox asset. If attacker detects such transactions, he can immediately call addCollateral with {from: user, to: attacker, skim: false, amount: _amount, share: 0} to steal the user's collateral by adding collateral for himself.

The attack also applies to any other users who have approved Singularity to spend YieldBox collateral.

Tools Used

Manual

function addCollateral( address from, address to, bool skim, uint256 amount, uint256 share ) public notPaused { if (share == 0) { share = yieldBox.toShare(collateralId, amount, false); } _allowedBorrow(from, share) _addCollateral(from, to, skim, 0, share); }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-05T02:53:07Z

minhquanym marked the issue as duplicate of #55

#1 - c4-judge

2023-09-12T17:31:43Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: peakbolt

Also found by: Kaysoft, carrotsmuggler, cergyk, windhustler, xuwinnie

Labels

bug
3 (High Risk)
satisfactory
duplicate-1163

Awards

254.4518 USDC - $254.45

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L195-L200

Vulnerability details

Impact

When a cross chain tx fails, bridged asset should be directly transfered to receiver. However, an extra revert is made after the transfer. As a result, the user will not be able to get the asset back by any means.

(bool success, bytes memory reason) = module.delegatecall( abi.encodeWithSelector( this.leverageDownInternal.selector, amount, swapData, externalData, lzData, leverageFor ) ); if (!success) { if (balanceAfter - balanceBefore >= amount) { IERC20(address(this)).safeTransfer(leverageFor, amount); } revert(_getRevertMsg(reason)); //forward revert because it's handled by the main executor }

Proof of Concept

A cross chain request can fail due to various reasons, such as high slippage, wrong parameters. When the request involves debiting and crediting USDO(TOFT), if the module execution fails, we need to directly transfer the balance to user. However, in current implementation, an extra revert is made after the transfer, so even if user retries, the tx will still always revert, which means the asset is permanently locked.

Tools Used

Manual

if (!success) { if (balanceAfter - balanceBefore >= amount) { IERC20(address(this)).safeTransfer(leverageFor, amount); } else { revert(_getRevertMsg(reason)); //forward revert because it's handled by the main executor } }

Assessed type

Context

#0 - c4-pre-sort

2023-08-07T04:57:03Z

minhquanym marked the issue as duplicate of #1410

#1 - c4-judge

2023-09-18T16:31:30Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: carrotsmuggler

Also found by: xuwinnie

Labels

bug
3 (High Risk)
partial-50
sponsor confirmed
duplicate-1034

Awards

581.7372 USDC - $581.74

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L212

Vulnerability details

Impact

When calling _unwrap in function leverageDownInternal, msg.sender is actually LayerZero Endpoint as context remains the same during delegate call. As a result, the _unwrap will fail.

Proof of Concept

else if (packetType == PT_LEVERAGE_MARKET_DOWN) { _executeOnDestination( Module.Leverage, abi.encodeWithSelector( BaseTOFTLeverageModule.leverageDown.selector, leverageModule, _srcChainId, _srcAddress, _nonce, _payload ), _srcChainId, _srcAddress, _nonce, _payload ); } function _executeModule( Module _module, bytes memory _data, bool _forwardRevert ) private returns (bool success, bytes memory returnData) { success = true; address module = _extractModule(_module); (success, returnData) = module.delegatecall(_data); if (!success && !_forwardRevert) { revert(_getRevertMsg(returnData)); } } function leverageDown( address module, uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload ) public { ( , , uint256 amount, IUSDOBase.ILeverageSwapData memory swapData, IUSDOBase.ILeverageExternalContractsData memory externalData, IUSDOBase.ILeverageLZData memory lzData, address leverageFor ) = abi.decode( _payload, ( uint16, bytes32, uint256, IUSDOBase.ILeverageSwapData, IUSDOBase.ILeverageExternalContractsData, IUSDOBase.ILeverageLZData, address ) ); uint256 balanceBefore = balanceOf(address(this)); bool credited = creditedPackets[_srcChainId][_srcAddress][_nonce]; if (!credited) { _creditTo(_srcChainId, address(this), amount); creditedPackets[_srcChainId][_srcAddress][_nonce] = true; } uint256 balanceAfter = balanceOf(address(this)); (bool success, bytes memory reason) = module.delegatecall( abi.encodeWithSelector( this.leverageDownInternal.selector, amount, swapData, externalData, lzData, leverageFor ) ); if (!success) { if (balanceAfter - balanceBefore >= amount) { IERC20(address(this)).safeTransfer(leverageFor, amount); } revert(_getRevertMsg(reason)); //forward revert because it's handled by the main executor } emit ReceiveFromChain(_srcChainId, leverageFor, amount); } function leverageDownInternal( uint256 amount, IUSDOBase.ILeverageSwapData memory swapData, IUSDOBase.ILeverageExternalContractsData memory externalData, IUSDOBase.ILeverageLZData memory lzData, address leverageFor ) public payable { _unwrap(address(this), amount); // ...... }

As above, leverageDownInternal is delegate called by leverageDown and leverageDown is delegate called by _executeModule, so msg.sender in _unwrap will be LayerZero Endpoint, who calls lzReceive. As a result, the _unwrap will revert since the Endpoint has no token to burn.

function _unwrap(address _toAddress, uint256 _amount) private { _burn(msg.sender, _amount); if (erc20 == address(0)) { _safeTransferETH(_toAddress, _amount); } else { IERC20(erc20).safeTransfer(_toAddress, _amount); } }

Tools Used

Manual

Call unwrap externally

address(this).unwrap(address(this), amount);

Assessed type

call/delegatecall

#0 - c4-pre-sort

2023-08-07T05:11:00Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-08-18T17:04:52Z

0xRektora marked the issue as disagree with severity

#2 - c4-sponsor

2023-08-18T17:05:48Z

0xRektora marked the issue as sponsor confirmed

#3 - c4-sponsor

2023-09-19T21:17:41Z

0xRektora marked the issue as agree with severity

#4 - c4-judge

2023-09-26T16:25:28Z

dmvt marked issue #1034 as primary and marked this issue as a duplicate of 1034

#5 - c4-judge

2023-09-26T16:25:41Z

dmvt marked the issue as partial-50

Findings Information

🌟 Selected for report: carrotsmuggler

Also found by: 0x73696d616f, peakbolt, xuwinnie

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-1032

Awards

471.2071 USDC - $471.21

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTStrategyModule.sol#L89-L120

Vulnerability details

Impact

Cross chain request to retrieveFromStrategy can be frontrunned. Attacker can take advantage of the fact that input data amount and share can both be specified, and use two seperate transactions to steal user's asset.

Proof of Concept

function retrieveFromStrategy( address _from, uint256 amount, uint256 share, uint256 assetId, uint16 lzDstChainId, address zroPaymentAddress, bytes memory airdropAdapterParam ) external payable { require(amount > 0, "TOFT_0"); bytes32 toAddress = LzLib.addressToBytes32(msg.sender); bytes memory lzPayload = abi.encode( PT_YB_RETRIEVE_STRAT, LzLib.addressToBytes32(_from), toAddress, amount, share, assetId, zroPaymentAddress ); _lzSend( lzDstChainId, lzPayload, payable(msg.sender), zroPaymentAddress, airdropAdapterParam, msg.value ); emit SendToChain(lzDstChainId, msg.sender, toAddress, amount); } function strategyWithdraw( uint16 _srcChainId, bytes memory _payload ) public { ( , bytes32 from, , uint256 _amount, uint256 _share, uint256 _assetId, address _zroPaymentAddress ) = abi.decode( _payload, (uint16, bytes32, bytes32, uint256, uint256, uint256, address) ); address _from = LzLib.bytes32ToAddress(from); _retrieveFromYieldBox(_assetId, _amount, _share, _from, address(this)); _debitFrom( address(this), lzEndpoint.getChainId(), LzLib.addressToBytes32(address(this)), _amount ); bytes memory lzSendBackPayload = _encodeSendPayload( from, _ld2sd(_amount) ); _lzSend( _srcChainId, lzSendBackPayload, payable(this), _zroPaymentAddress, "", address(this).balance ); }

When a user initiates a cross chain request to retrieveFromStrategy, they need to sign a EIP712 permit to approve YieldBox for TOFT contract. Attacker can take the signature and frontrun the tx with two seperate transactions.

  1. Call retrieveFromStrategy with {from: user, amount: 1 wei, share: user's balance, approvals:user's signature}. All of the user's share will be withdrawn but _debitFrom only burns 1 wei of TOFT. The extra TOFT will stay in the contract.
  2. Call retrieveFromStrategy with {from: attacker, amount: TOFT's balance, share: 1 wei}. Only 1 wei of share needs to be withdrawn from attacker's yieldbox balance and attacker can receive all the detained (in step 1) TOFT.
function _withdrawFungible( Asset storage asset, uint256 assetId, address from, address to, uint256 amount, uint256 share ) internal returns (uint256 amountOut, uint256 shareOut) { // Effects uint256 totalAmount = _tokenBalanceOf(asset); if (share == 0) { // value of the share paid could be lower than the amount paid due to rounding, in that case, add a share (Always round up) share = amount._toShares(totalSupply[assetId], totalAmount, true); } else { // amount may be lower than the value of share due to rounding, that's ok amount = share._toAmount(totalSupply[assetId], totalAmount, false); }

Tools Used

Manual

Only allow specifying amount.

function retrieveFromStrategy( address _from, uint256 amount, -- uint256 share, uint256 assetId, uint16 lzDstChainId, address zroPaymentAddress, bytes memory airdropAdapterParam )

Assessed type

Context

#0 - c4-pre-sort

2023-08-09T07:25:23Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-09-01T16:51:04Z

0xRektora (sponsor) confirmed

#2 - 0xRektora

2023-09-01T16:51:38Z

#3 - c4-judge

2023-09-29T19:21:09Z

dmvt marked the issue as duplicate of #1173

#4 - c4-judge

2023-09-29T20:51:13Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: xuwinnie

Labels

bug
2 (Med Risk)
downgraded by judge
primary issue
selected for report
sponsor confirmed
M-15

Awards

1008.3444 USDC - $1,008.34

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L111-L146

Vulnerability details

Impact

multiHopSell and multiHopBuy can be frontrunned with high slippage tolerance. User may experince loss from a sandwich attack.

Proof of Concept

When a user initiates a cross chain request to multiHopSell, they need to sign a EIP712 permit to approve borrow share for TOFT contract. Attacker can take the signature and frontrun the tx with same data except a lower swapData.amountOutMin.

Action flow is multiHopSell(local) -> leverageDown(local) -> lend(remote)

function leverageDownInternal( uint256 amount, IUSDOBase.ILeverageSwapData memory swapData, IUSDOBase.ILeverageExternalContractsData memory externalData, IUSDOBase.ILeverageLZData memory lzData, address leverageFor ) public payable { _unwrap(address(this), amount); //swap to USDO IERC20(erc20).approve(externalData.swapper, amount); ISwapper.SwapData memory _swapperData = ISwapper(externalData.swapper) .buildSwapData(erc20, swapData.tokenOut, amount, 0, false, false); (uint256 amountOut, ) = ISwapper(externalData.swapper).swap( _swapperData, swapData.amountOutMin, address(this), swapData.data );

As a result, user may experince loss from a sandwich attack. The attack applies to multiHopBuy in a similar way.

Tools Used

Manual

Set a maximum slippage.

Assessed type

Uniswap

#0 - c4-pre-sort

2023-08-09T08:06:26Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-08-28T15:43:31Z

0xRektora marked the issue as sponsor confirmed

#2 - c4-judge

2023-09-19T11:56:08Z

dmvt marked the issue as selected for report

#3 - c4-judge

2023-10-02T11:40:44Z

dmvt changed the severity to 2 (Med Risk)

#4 - dmvt

2023-10-02T11:41:55Z

This attack requires external conditions. Downgrading to medium.

Findings Information

🌟 Selected for report: xuwinnie

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor confirmed
edited-by-warden
M-18

Awards

1008.3444 USDC - $1,008.34

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTStrategyModule.sol#L89-L120

Vulnerability details

Impact

User could be forced to withdraw more amount than desired when calling retrieveFromStrategy, because they can not specify the amount of yieldbox balance to permit.

Proof of Concept

function retrieveFromStrategy( address _from, uint256 amount, uint256 share, uint256 assetId, uint16 lzDstChainId, address zroPaymentAddress, bytes memory airdropAdapterParam ) external payable { require(amount > 0, "TOFT_0"); bytes32 toAddress = LzLib.addressToBytes32(msg.sender); bytes memory lzPayload = abi.encode( PT_YB_RETRIEVE_STRAT, LzLib.addressToBytes32(_from), toAddress, amount, share, assetId, zroPaymentAddress ); _lzSend( lzDstChainId, lzPayload, payable(msg.sender), zroPaymentAddress, airdropAdapterParam, msg.value ); emit SendToChain(lzDstChainId, msg.sender, toAddress, amount); }

When a user initiates a cross chain request to retrieveFromStrategy, they need to sign a EIP712 permit to approve YieldBox for TOFT contract. Attacker can take the signature and frontrun the tx with {from: user, amount: larger than user wishes, approvals:user's signature} to force the user to withdraw more amount than desired. If user forgets to revoke their approvals, the attack could also happen. Attacker can withdraw the user's yieldbox balance without being noticed.

Note: I believe it is a mistake that ICommonData.IApproval[] calldata approvals is missing at retrieveFromStrategy input param. Even though this is intended, the issue still exists as user still needs to approve YieldBox for TOFT contract in other way.

Tools Used

Manual

Add more refined allowance control for YieldBox. (just like erc20)

Assessed type

Context

#0 - c4-pre-sort

2023-08-09T07:28:13Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-08-25T18:03:30Z

0xRektora marked the issue as sponsor confirmed

#2 - c4-judge

2023-09-30T14:39:30Z

dmvt marked the issue as selected for report

Findings Information

🌟 Selected for report: peakbolt

Also found by: xuwinnie

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor confirmed
duplicate-1209

Awards

349.0423 USDC - $349.04

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTStrategyModule.sol#L208-L213

Vulnerability details

Impact

In function strategyWithdraw, _debitFrom will fail because msg.sender(Endpoint) has no allowance. We should directly _burn TOFT here.

Proof of Concept

function strategyWithdraw( uint16 _srcChainId, bytes memory _payload ) public { ( , bytes32 from, , uint256 _amount, uint256 _share, uint256 _assetId, address _zroPaymentAddress ) = abi.decode( _payload, (uint16, bytes32, bytes32, uint256, uint256, uint256, address) ); address _from = LzLib.bytes32ToAddress(from); _retrieveFromYieldBox(_assetId, _amount, _share, _from, address(this)); _debitFrom( address(this), lzEndpoint.getChainId(), LzLib.addressToBytes32(address(this)), _amount ); function _debitFrom(address _from, uint16, bytes32, uint _amount) internal virtual override returns (uint) { address spender = _msgSender(); if (_from != spender) _spendAllowance(_from, spender, _amount); _burn(_from, _amount); return _amount; }

strategyWithdraw is delegate called by function _nonblockingLzReceive, so msg.sender is LayerZero Endpoint. However, the Endpoint has not been approved, so _debitFrom(address(this),...) will revert.

Tools Used

Manual

Replace

_debitFrom( address(this), lzEndpoint.getChainId(), LzLib.addressToBytes32(address(this)), _amount );

with

_burn(address(this), _amount);

Assessed type

call/delegatecall

#0 - c4-pre-sort

2023-08-07T08:16:15Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-08-20T15:34:28Z

0xRektora marked the issue as sponsor disputed

#2 - c4-sponsor

2023-08-20T15:35:55Z

0xRektora marked the issue as sponsor confirmed

#3 - c4-judge

2023-09-27T12:17:32Z

dmvt marked the issue as satisfactory

#4 - c4-judge

2023-09-27T12:17:35Z

dmvt marked issue #1209 as primary and marked this issue as a duplicate of 1209

#5 - c4-judge

2023-09-27T12:17:42Z

dmvt changed the severity to 2 (Med Risk)

Findings Information

🌟 Selected for report: 0xRobocop

Also found by: xuwinnie

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-700

Awards

349.0423 USDC - $349.04

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLendingCommon.sol#L66-L69

Vulnerability details

Impact

When comparing total borrow with borrow cap, totalBorrow.base is mistakenly used instead of totalBorrow.elastic, this will make actual borrow cap higher than expected.

Proof of Concept

function _borrow( address from, address to, uint256 amount ) internal returns (uint256 part, uint256 share) { uint256 feeAmount = (amount * borrowOpeningFee) / FEE_PRECISION; // A flat % fee is charged for any borrow (totalBorrow, part) = totalBorrow.add(amount + feeAmount, true); require( totalBorrowCap == 0 || totalBorrow.base <= totalBorrowCap, "SGL: borrow cap reached" );

The borrow cap is meant to set a limit for the total debt. When interest accures, totalBorrow.elastic increases while totalBorrow.base remains the same. If we need to limit the actual debt amount, we should use totalBorrow.elastic.

Tools Used

Manual

require( totalBorrowCap == 0 || totalBorrow.elastic <= totalBorrowCap, "SGL: borrow cap reached" );

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-07T04:13:58Z

minhquanym marked the issue as duplicate of #700

#1 - c4-judge

2023-09-26T15:05:03Z

dmvt changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-09-26T15:05:11Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: rokinot

Also found by: c7e7eff, cergyk, xuwinnie

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-187

Awards

141.3621 USDC - $141.36

External Links

Lines of code

https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/options/TapiocaOptionBroker.sol#L239-L258

Vulnerability details

Impact

Malicious user can manipulate twAML by locking tOLP/TAP for a excessively long period of time. Current mechanism fails to re-adjust the cumulative to a reasonable range. As a result, user will only have two choices, lock for 7 days to get 5% discount or lock permanently to get 50% discount.

Proof of Concept

// Participate in twAMl voting bool hasVotingPower = lock.amount >= computeMinWeight(pool.totalDeposited, MIN_WEIGHT_FACTOR); if (hasVotingPower) { pool.totalParticipants++; // Save participation pool.averageMagnitude = (pool.averageMagnitude + magnitude) / pool.totalParticipants; // compute new average magnitude // Compute and save new cumulative divergenceForce = lock.lockDuration > pool.cumulative; if (divergenceForce) { pool.cumulative += pool.averageMagnitude; } else { if (pool.cumulative > pool.averageMagnitude) { pool.cumulative -= pool.averageMagnitude; } else { pool.cumulative = 0; } }

Suppose currently cumulative is 20 months and totalParticipants is 1000. Alice is a crazy supporter of Tapioca. She decides to lock her SGL position for 1000000 years to earn maximum rewards. Her magnitude would be 1000000 years, and averageMagnitude would be 1000 years. Then, cumulative would become 1000 years.

Afterwards, normal users will find that they only have two choices. The first choice is to lock 7 days and get a minimum discount (now cumulative is 1000 years, so locking 7 days and locking 7 years receive almost the same discount); the second choice is to lock 2000 years or more (nearly permanently) to get a maximum discount. Angry users wait to see if cumulative will go down after some time.

Bob tries to drive cumulative down again. The best way is to lock for 999 years, magnitude would be 414 years, and cumulative would be driven down for 0.414 years. So Bob will at least need to repeat this process (lock nearly permanently) for 2000 times. The cost is high and no normal user is incentivized to do so (locking for 999 years but still unable to get max discount). Most user will choose to lock for 7 days but the averageMagnitude (amount that cumulative will be driven down) is ignorable comparing to 1000 years. As a result, twAML can no longer function properly.

Note: There is no loss for attacker as they will keep receiving oTAP with maximum discount.

Tools Used

Manual

Set an upper limit for lock duration.

Assessed type

Timing

#0 - c4-pre-sort

2023-08-08T21:01:31Z

minhquanym marked the issue as duplicate of #884

#1 - c4-judge

2023-09-27T20:03:14Z

dmvt changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-09-27T20:03:29Z

dmvt marked the issue as duplicate of #187

#3 - c4-judge

2023-09-27T20:03:53Z

dmvt 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