Golom contest - arcoun's results

An NFT marketplace that offers the lowest industry fee, a publicly available order-book along with analytical tools.

General Information

Platform: Code4rena

Start Date: 26/07/2022

Pot Size: $75,000 USDC

Total HM: 29

Participants: 179

Period: 6 days

Judge: LSDan

Total Solo HM: 6

Id: 148

League: ETH

Golom

Findings Distribution

Researcher Performance

Rank: 17/179

Findings: 8

Award: $690.97

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

26.7695 USDC - $26.77

Labels

bug
duplicate
3 (High Risk)
upgraded by judge

External Links

Judge has assessed an item in Issue #744 as High risk. The relevant finding follows:

  1. _writeCheckpoint will fail to insert an initial checkpoint https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L101

Due to solidity 0.8 overflow/underflow protection, accessing checkpoints[toTokenId][nCheckpoints - 1] will throw if nCheckpoints == 0.

As it is not possible to insert the initial checkpoint, _writeCheckpoint will always fail. This issue currently prevents adding a delegation.

#0 - dmvt

2022-10-24T14:24:11Z

Duplicate of #630

Awards

26.7695 USDC - $26.77

Labels

bug
duplicate
3 (High Risk)

External Links

Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89

Vulnerability details

Impact

In the delegate function in VoteEscrowDelegation.sol, there is no verification that a token has been already delegated to an other token.

A malicious user can create several veNFT tokens by splitting and locking his GOL tokens. By delegating each token to the other ones, he can increase his voting power and make a (malicious) proposal to be accepted.

I have rated this finding as Medium because the governance contract is out-of-scope. But voter fraud can be used to gain ownership, update contract settings, transfer available funds, ... This can be High if the impact on the governance contract can be used.

Note: The whole delegation functionnality is currently unusable because the first checkpoint can never be inserted due to an overflow when getting oldCheckpoint in _writeCheckpoint(). A Low-impact finding is related to this bug. This finding assumes that this issue is fixed.

Proof of Concept

  • A malicious user has 1000 GOL tokens
  • He lock all of them, using 10 create_lock() calls, with 100 GOL tokens each time.
  • He now owns 10 veNFT tokens
  • He delegate each veNFT token to the 10 veNFT tokens, (100 delegate() calls necessary)
  • He now have 10 times more voting power than he should normally have

Update the number of veNFT tokens to create to have as much voting power as necessary to make a proposal be accepted.

A check must be added to prevent a token to be delegated several times, to different tokens.

#0 - KenzoAgada

2022-08-02T12:09:47Z

Duplicate of #169

Findings Information

🌟 Selected for report: kenzo

Also found by: 0xA5DF, 0xpiglet, 0xsanson, Bahurum, IllIllI, arcoun

Labels

bug
duplicate
3 (High Risk)

Awards

550.3388 USDC - $550.34

External Links

Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L79-L80 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L213-L214

Vulnerability details

Impact

Delegations are tracked using checkpoints stored in the contract's storage.

When delegate() and removeDelegation() are used to add a new checkpoint, the previous checkpoint is also modified because the modification is done on Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1] which is the storage of the previous checkpoint, not a temporary Checkpoint in memory.

By modifying the previous checkpoint, a user can add a delegation after a proposal has been submited and have the related delegation votes taken into account.

I have rated this finding as Medium because it allows voter fraud. The impact can be High on the governance contract, but it is out-of-scope.

Note: The whole delegation functionnality is currently unusable because the first checkpoint can never be inserted due to an overflow when getting oldCheckpoint in _writeCheckpoint(). A Low-impact finding is related to this bug. This finding assumes that this issue is fixed.

Proof of Concept

  • Have a veNFT token with at least one delegated token (so there is at least one related checkpoint)
  • A voting proposal is submited
  • Other veNFT tokens are delegated to the first one, using delegate(), but after the proposal has been submited
  • The getPriorVotes() function, called with the proposal timestamp will also return the delegated votes, although they have been delegated after.

Use a temporary array stored in memory or modify delegatedTokenIds after the new checkpoint has been inserted.

#0 - KenzoAgada

2022-08-02T07:58:12Z

Duplicate of #81

Judge has assessed an item in Issue #744 as Medium risk. The relevant finding follows:

  1. Usage of deprecated transfer() in payEther() https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L154

The original transfer used to send eth uses a fixed stipend 2300 gas. This was used to prevent reentrancy. However this limit interaction with others contracts that need more than that to process the transaction.

Related: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/.

In GolomTrader, ether paid to rewards distributor and exchanges will probably be paid to contracts. If one target throw on transfer(), the order will not be processed.

#0 - dmvt

2022-10-21T13:36:26Z

Duplicate of #343

Judge has assessed an item in Issue #744 as Medium risk. The relevant finding follows:

  1. Use safeTransferFrom() to transfer ERC721 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L236 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L301 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L361 For safety and consistency with ERC1155, ERC721 tokens transfered by GolomTrader should use the safeTransferFrom() function.

#0 - dmvt

2022-10-21T13:37:43Z

Duplicate of #342

Findings Information

Labels

bug
duplicate
2 (Med Risk)

Awards

47.2456 USDC - $47.25

External Links

Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowCore.sol#L604

Vulnerability details

Impact

The safeTransferFrom() function is described in EIP-721 as a function to safely transfer NFTs. As described in EIP-721 and in the NatSpec of the method in VoteEscrowCore.sol, the method must throw if the onERC721Received() function does not return the expected bytes4 signature.

The implementation of safeTransferFrom() in VoteEscrowCore.sol does not verify the returned value.

A user may accidentally transfer his NTF to a contract which does not support EIP-721 and permanently lose his asset, although this would have been avoided if the bytes4 signature was verified.

Proof of Concept

  • A user wants to transfer his veNFT to an other address
  • He calls safeTransferFrom because transferFrom is discouraged (https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowCore.sol#L106) as no verification is done on the target.
  • The to parameter is set to a contract which do not support EIP-721
  • The onERC721Received() call on the destination targets does not throw because the fallback implementation may (for example) handle the call without error.
  • His veNTF token is transfered and is now lost

The bytes4 data, returned by onERC721Received, must be verified against the expected signature.

Note that the expected signature is not bytes4(keccak256("onERC721Received(address,address,uint,bytes)")) as stated in:

The correct signature is bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")) (ie. uint256 instead of uint)

#0 - KenzoAgada

2022-08-04T02:01:28Z

Duplicate of #577

Awards

4.5163 USDC - $4.52

Labels

bug
duplicate
2 (Med Risk)
disagree with severity

External Links

Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L216-L267 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L382-L401

Vulnerability details

Impact

The GolomTrader contract does not check if there are remaining ether after an order has been executed. The remaining ether will be permanently lost as it is not automatically returned and there is no function to withdraw ether from the contract.

In fillAsk, remaining ether can be due to:

In _settleBalances, remaining ether can be due to:

During the handling of an order, the sent ether must be tracked and the remaining ether must be sent back:

  • to the calling user in fillAsk
  • to the signer in fillBid and fillCriteriaBid

A withdraw() method can also be added for the owner of the contract. This is a good practice, especially when fallback and receive are left available, as it is the case here.

#0 - KenzoAgada

2022-08-04T02:49:39Z

fillAsk is duplicate of #75 settleBalances needs to be determined later if considered separate issue

#1 - dmvt

2022-10-12T15:43:57Z

I view this as the same issue ultimately, as did the warden since they only submitted one version. Duplicate of #75

Low Risk Issues

1. _writeCheckpoint will fail to insert an initial checkpoint

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L101

Due to solidity 0.8 overflow/underflow protection, accessing checkpoints[toTokenId][nCheckpoints - 1] will throw if nCheckpoints == 0.

As it is not possible to insert the initial checkpoint, _writeCheckpoint will always fail. This issue currently prevents adding a delegation.

2. Updating a delegation checkpoint in the same block will not be saved in storage

When updating a checkpoint already available for the current block, the modification is done on a memory structure, not on the storage. These modifications are lost after the call is finished.

3. It is not possible to remove a delegation, except for the same token

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L210-L216

The removeDelegation() function can only be used to remove a delegation of a token on itself. But there is no function to remove a delegation of a token to an other one.

There is a commented removeDelegationByOwner() function in the VoteEscrowDelegation.sol file but it is invalid because it removes the wrong element from delegatedTokenIds (delegatedTokenId instead of ownerTokenId).

4. xxxxRewards() functions should accept a range of epoch

The 3 functions traderRewards(), exchangeRewards() and stakerRewards() will loop over each epoch (1 day), which will consume more and more ressources each day.

The stakerRewards() will also return an array of epoch elements which will reach 960 bytes after only a month, and 11.4kB after a year.

These functions should accept a range of epoch to limit resource consumption and avoid running out of gas.

5. delegate() and removeDelegation() do not allow token operator

The VoteEscrowCore contract supports setting operator addresses on a token through approve() and setApprovalForAll().

For consistency with other operations on tokens, the delegate() and removeDelegation() should use _isApprovedOrOwner(msg.sender, tokenIf) instead of checking ownerOf(tokenId) == msg.sender

6. validateOrder() should check if order is filled/cancelled before checking for expiration

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L182-L192

If an order has been filled or cancelled, the validateOrder() will return state 1 (deadline reached) once the order expiration timestamp has been reached, although this is not the expected state.

7. Usage of deprecated transfer() in payEther()

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L154

The original transfer used to send eth uses a fixed stipend 2300 gas. This was used to prevent reentrancy. However this limit interaction with others contracts that need more than that to process the transaction.

Related: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/.

In GolomTrader, ether paid to rewards distributor and exchanges will probably be paid to contracts. If one target throw on transfer(), the order will not be processed.

Non-critical Issues

1. Use openzeppelin implementation for common functions

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/TokenUriHelper.sol#L5-L63

Base64 library already available (same code) in @openzeppelin/contracts/utils/Base64.sol

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/TokenUriHelper.sol#L128-L149

toString already available (same code) in @openzeppelin/contracts/utils/Strings.sol

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L405-L440

MerkleProof library is available in @openzeppelin/contracts/utils/cryptography/MerkleProof.sol and is already used by the GolomAirdrop contract.

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowCore.sol#L355-L364

ReentrancyGard is available in @openzeppelin/contracts/security/ReentrancyGuard.sol.

2. Use safeTransferFrom() to transfer ERC721

For safety and consistency with ERC1155, ERC721 tokens transfered by GolomTrader should use the safeTransferFrom() function.

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