Cudos contest - GimelSec's results

Decentralised cloud computing for Web3.

General Information

Platform: Code4rena

Start Date: 03/05/2022

Pot Size: $75,000 USDC

Total HM: 6

Participants: 55

Period: 7 days

Judge: Albert Chon

Total Solo HM: 2

Id: 116

League: COSMOS

Cudos

Findings Distribution

Researcher Performance

Rank: 15/55

Findings: 3

Award: $871.71

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: wuwe1

Also found by: Dravee, GermanKuber, GimelSec, WatchPug, cccz, defsec, dipp, jah, reassor

Labels

bug
duplicate
2 (Med Risk)

Awards

502.4722 USDC - $502.47

External Links

Lines of code

https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L595-L609

Vulnerability details

Impact

Some ERC tokens have a fee on each transfer. The protocol doesn’t handle the fee when transferring this kind of ERC20 tokens, leading to the inconsistent amount of token actually received in the contract. Validators on the Cudos will mint more tokens to the destination address than it received in sendToCosmos().

Proof of Concept

In sendToCosmos, _amount is used in SendToCosmosEvent. However, if _tokenContract collects a fee on every transfer. The actual amount of token received is not equal to _amount

https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L595-L609

function sendToCosmos( address _tokenContract, bytes32 _destination, uint256 _amount ) public nonReentrant { IERC20(_tokenContract).safeTransferFrom(msg.sender, address(this), _amount); state_lastEventNonce = state_lastEventNonce.add(1); emit SendToCosmosEvent( _tokenContract, msg.sender, _destination, _amount, state_lastEventNonce ); }

Tools Used

vim

Use balanceAfter - balanceBefore rather than using amount directly:

function retrieveTokens(address sender, uint256 amount) public { uint256 balanceBefore = deflationaryToken.balanceOf(address(this)); deflationaryToken.transferFrom(sender, address(this), amount); uint256 balanceAfter = deflationaryToken.balanceOf(address(this)); amount = balanceAfter.sub(balanceBefore); totalTokenTransferred += amount; }

Token Transfer Calculation Accuracy in this article: https://blog.chain.link/defi-security-best-practices/

#0 - mlukanova

2022-05-11T11:09:49Z

Duplicate of #3

Awards

302.6663 USDC - $302.67

Labels

bug
QA (Quality Assurance)
sponsor disputed

External Links

Lines of code

https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L337 https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L406 https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L510

Vulnerability details

Impact

updateValset operation doesn't set a timeout, leading to the signed newCheckpoint (new Valset) being used indefinitely.

Proof of Concept

It doesn't check the operation timeout in the updateValset function, so the new Valset can be delayed and change the current Valset abruptly at any time.

Additionally, submitBatch and submitLogicCall function check the checkpoint:

require( makeCheckpoint(_currentValset, state_gravityId) == state_lastValsetCheckpoint, "Supplied current validators and powers do not match checkpoint." );

An attacker can hold the newCheckpoint and call updateValset abruptly to change validators list to deny all valid operations, which has been signed by previous _currentValset.validators.

Tools Used

vim

Use timeout in updateValset:

function updateValset( // The new version of the validator set ValsetArgs memory _newValset, // The current validators that approve the change ValsetArgs memory _currentValset, // These are arrays of the parts of the current validator's signatures uint8[] memory _v, bytes32[] memory _r, bytes32[] memory _s, ) public nonReentrant { // CHECKS + require(block.number < _newValset.updateTimeout, "Timed out"); ...

#0 - V-Staykov

2022-05-11T13:40:57Z

The update valset function is being called by all the orchestrators and basically the first of them to pass is being stored. So if a validator decides to hold a updateValset transaction he doesn't do any harm oher than making other validators incur the fees of the transaction. Also each valsetupdate has an once which would prevent overiding an update that was sent after it, since its nonce would be higher.

#1 - albertchon

2022-05-18T22:25:44Z

Agreed with @V-Staykov

Awards

66.5732 USDC - $66.57

Labels

bug
G (Gas Optimization)

External Links

Save gas in for loops by unchecked arithmetic

The for loop has no overflow risk of i. Use an unchecked block to save gas.

Proof of Concept

Gravity.sol 128: for (uint256 i = 0; i < _users.length; i++) { 233: for (uint256 i = 0; i < _currentValidators.length; i++) { 263: for (uint256 i = 0; i < _newValset.validators.length; i++) { 453: for (uint256 i = 0; i < _amounts.length; i++) { 568: for (uint256 i = 0; i < _args.transferAmounts.length; i++) { 579: for (uint256 i = 0; i < _args.feeAmounts.length; i++) { 660: for (uint256 i = 0; i < _powers.length; i++) {

Recommendation

Use unchecked blocks to avoid overflow checks, or use ++i rather than i++ if you don't use unchecked blocks.

for (uint256 i = 0; i < length; ) { ... unchecked { ++i; } }

Use immutable to save gas

Proof of Concept

https://github.com/code-423n4/2022-05-cudos/blob/main/solidity/contracts/Gravity.sol#L59-L61

Recommendation

// These are set once at initialization bytes32 public immutable state_gravityId; uint256 public immutable state_powerThreshold;
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