Golom contest - JohnSmith'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: 21/179

Findings: 6

Award: $554.22

🌟 Selected for report: 1

🚀 Solo Findings: 0

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/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L101

Vulnerability details

[M-02] _writeCheckpoint() and removeDelegation()revert because of underflow

Problem

When nCheckpoints is zero we get an undreflow error because we are trying to substract from nCheckpoints and since it is of type uint256, it can't be less than zero. https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L101

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
101:         Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];

213:         Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1];

PoC

It is a test, where we try to call delegate(uint256, uint256):

it('delegateVotesReverts', async () => {
            let tenGolomTokens = "10000000000000000000" //10 * 1e18
            let oneGolomTokens = "1000000000000000000" //1 * 1e18
            let user = accounts[6]
            let user2 = accounts[7]
            let userAddress = await user.getAddress()
            let user2Address = await user2.getAddress()
            let rewardDistributor1 = accounts[8]
            await golomToken.connect(governance).setMinter(await rewardDistributor1.getAddress());
            await golomToken.connect(governance).executeSetMinter();
            await golomToken.connect(rewardDistributor1).mint(userAddress, tenGolomTokens)
            await golomToken.connect(rewardDistributor1).mint(user2Address, tenGolomTokens)

            await golomToken.connect(user).approve(voteEscrow.address, oneGolomTokens)
            await golomToken.connect(user2).approve(voteEscrow.address, oneGolomTokens)

            let tx = await voteEscrow.connect(user).create_lock(oneGolomTokens, 1000000)
            let  rc = await tx.wait()
            let  event = rc.events?.find(event => event?.event === 'Transfer');
            let [from, to, tokenId] = event?.args || [];
            tx = await voteEscrow.connect(user2).create_lock(oneGolomTokens, 1000000)
            rc = await tx.wait()
            event = rc.events?.find(event => event?.event === 'Transfer');
            let [from2, to2, tokenId2] = event?.args || [];

            tx = await voteEscrow.connect(user2).delegate(tokenId2, tokenId)
            rc = await tx.wait()
        });

Full test code at https://gist.github.com/johnsmith1623/8f826454986492bbd5b6b6774807b397 We get an error:

Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block) at VoteEscrow._writeCheckpoint (contracts/vote-escrow/VoteEscrowDelegation.sol:101) at VoteEscrow.delegate (contracts/vote-escrow/VoteEscrowDelegation.sol:85)

Mitigation

Check if value is not zero before substracting from it.

#0 - KenzoAgada

2022-08-02T09:07:15Z

Duplicate of #630 (the issue with _writeCheckpoint()) The issue with removeDelegation - needs to be determined if separate issue. Warden hardly mentions it and didn't describe the full impact which another warden identified - undelegated tokens can not be transferred.

#1 - dmvt

2022-10-04T14:53:12Z

IMO removeDelegation is not possible as a result of not being able to delegate in the first place and so doesn't qualify as a separate issue.

Findings Information

🌟 Selected for report: async

Also found by: 0xpiglet, 0xsanson, DimitarDimitrov, Dravee, ElKu, IllIllI, JohnSmith, ak1, kenzo, scaraven

Labels

bug
duplicate
3 (High Risk)

Awards

189.5656 USDC - $189.57

External Links

Lines of code

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

Vulnerability details

[H-01] User can delegate same token multiple times in same block and get bigger voting weight

Attacker can delegate same tokenId many times in same block and have bigger voting weight

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
71:     function delegate(uint256 tokenId, uint256 toTokenId) external {
72:         require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
73:         require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');
74: 
75:         delegates[tokenId] = toTokenId;
76:         uint256 nCheckpoints = numCheckpoints[toTokenId];
77: 
78:         if (nCheckpoints > 0) {
79:             Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];
80:             checkpoint.delegatedTokenIds.push(tokenId);//@audit what if pushed multiple times same block
81:             _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds);

Function _writeCheckpoint(uint256,uint256,uint256[]) with its if check does not prevent anything

101:         Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
102:
103:         if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
104:             oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;//@audit does nothing

PoC

It is a test, where we delegate multiple times in one block delegate(uint256, uint256):

 describe("delegation", () => {
        it('delegateVotesAttack', async () => {
            let tenGolomTokens = "10000000000000000000" //10 * 1e18
            let oneGolomTokens = "1000000000000000000" //1 * 1e18
            let attacker = accounts[6]
            let attacker2 = accounts[7]
            let attackerAddress = await attacker.getAddress()
            let attacker2Address = await attacker2.getAddress()
            let rewardDistributor1 = accounts[8]

            await golomToken.connect(governance).setMinter(await rewardDistributor1.getAddress());
            await golomToken.connect(governance).executeSetMinter();
            await golomToken.connect(rewardDistributor1).mint(attackerAddress, tenGolomTokens)
            await golomToken.connect(rewardDistributor1).mint(attacker2Address, tenGolomTokens)
            
            await golomToken.connect(attacker).approve(voteEscrow.address, oneGolomTokens)
            await golomToken.connect(attacker2).approve(voteEscrow.address, oneGolomTokens)
            
            let tx = await voteEscrow.connect(attacker).create_lock(oneGolomTokens, 1000000)
            let  rc = await tx.wait()
            let  event = rc.events?.find(event => event?.event === 'Transfer');
            let [from, to, tokenId] = event?.args || [];
            tx = await voteEscrow.connect(attacker2).create_lock(oneGolomTokens, 1000000)
            rc = await tx.wait()
            event = rc.events?.find(event => event?.event === 'Transfer');
            let [from2, to2, tokenId2] = event?.args || [];

             voteEscrow.connect(attacker2).delegate(tokenId2, tokenId)
             voteEscrow.connect(attacker2).delegate(tokenId2, tokenId)
            tx = await voteEscrow.connect(attacker2).delegate(tokenId2, tokenId)
            rc = await tx.wait()

            let votes = await voteEscrow.connect(attacker).getVotes(tokenId)
            console.log("votes: " + votes)
            
        });

Full test code at https://gist.github.com/johnsmith1623/8f826454986492bbd5b6b6774807b397 You may have to fix underflow error before trying to test it. User should not be able to increase voting weight at same checkpoint in such way

Mitigation

Check if it is not there already before pushing it.

#0 - KenzoAgada

2022-08-02T10:55:18Z

Duplicate of #455 as the warden mentions "same block"

Lines of code

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

Vulnerability details

[M-03] Usage of deprecated transfer to send ETH

Usage of deprecated transfer function can revert.

Findings

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

File:	contracts/core/GolomTrader.sol 
154:             payable(payAddress).transfer(payAmt); // royalty transfer to royaltyaddress

Proof of Concept

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

A good article about that: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/.

Used call instead. For example

(bool success, ) = payable(payAddress).call{value: payAmt}(""); require(success, "Transfer failed.");

#0 - KenzoAgada

2022-08-03T14:04:39Z

Duplicate of #343

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/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L604

Vulnerability details

[M-05] Return value of IERC721Receiver(to).onERC721Received() is ignored

By standard "Return of other than the magic value MUST result in the transaction being reverted." But value is not schecked and there is no revert if wrong value returned. Other contract creators rely on this standard and can expect that caller will revert transaction if they don't return the magic value.

Findings

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
588: ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
589: ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.

604: try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4) {} catch (
605: 	bytes memory reason
606: ) {

Add check for returned value like this:

 try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
    return retval == IERC721Receiver.onERC721Received.selector;
 } catch (bytes memory reason) {

#0 - KenzoAgada

2022-08-04T02:01:34Z

Duplicate of #577

Low risk Issues

IssueInstances
1Timelock is too short3
2Zero-address checks are missing17
3Use of floating pragma1
4Events not emitted for important state changes11
5Return values of transfer()/transferFrom() not checked6
6Unused/empty receive() and fallback()functions4
7abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()1
8Use of assert() instead of require()13
9Shadowing State Variables2
10Hardcoded contract address1
11Misleading comment in function comments2
12_writeCheckpoint() stores array to memory1
13Dependencies are outdated1
14Add a timelock to critical functions1
15Set sane maximum for input parameter1
16Non-exploitable(yet) reentrancy1

[L-01] Timelock is too short

It is a good practice to give time for users to react and adjust to critical changes. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious Manager making a frontrunning/sandwich attack on the fees). Timelock should be at least three days to cover weekends. A lot of people do not work on weekends, and will not be able to react to changes in time.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L58

File:	contracts/governance/GolomToken.sol
58:     function setMinter(address _minter) external onlyOwner {
59:         pendingMinter = _minter;
60:         minterEnableDate = block.timestamp + 1 days;
61:     }

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L286

File:	contracts/rewards/RewardDistributor.sol
286:         traderEnableDate = block.timestamp + 1 days;

302:             voteEscrowEnableDate = block.timestamp + 1 days;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L415

File:	contracts/core/GolomTrader.sol 
449:             distributorEnableDate = block.timestamp + 1 days;

Add more time: *****EnableDate = block.timestamp + 3 days

[L-02] Zero-address checks are missing

Zero-address checks are a best practice for input validation of critical address parameters. While the codebase applies this in some cases, there are many places where this is missing in constructors and setters. Accidental use of zero-addresses may result in exceptions, burn fees/tokens, or force redeployment of contracts.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L29

File:	contracts/governance/GolomToken.sol
29:         _transferOwnership(_governance); // set the new owner

59:         pendingMinter = _minter;

67:             minter = pendingMinter;

70:             minter = pendingMinter;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L80

File:	contracts/rewards/RewardDistributor.sol
80:         weth = ERC20(_weth);
81:         trader = _trader;
82:         rewardToken = ERC20(_token);
83:         _transferOwnership(_governance); // set the new owner

287:         pendingTrader = _trader;

293:         trader = pendingTrader;

300:             ve = VE(pendingVoteEscrow);

303:             pendingVoteEscrow = _voteEscrow;

310:         ve = VE(pendingVoteEscrow);

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L94

File:	contracts/core/GolomTrader.sol 
94:         _transferOwnership(_governance);

446:             distributor = Distributor(_distributor);

448:             pendingDistributor = _distributor;

456:         distributor = Distributor(pendingDistributor);

Add zero-address checks, e.g.:

require(_trader != address(0), "Zero-address");

[L-03] Use of floating pragma

Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively. https://swcregistry.io/docs/SWC-103

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L2

File:	contracts/governance/GolomToken.sol
2: pragma solidity ^0.8.11;

Lock the pragma version to the same version as used in the other contracts and also consider known bugs (https://github.com/ethereum/solidity/releases) for the compiler version that is chosen.

Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile it locally.

[L-04] Events not emitted for important state changes

When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L59

File:	contracts/governance/GolomToken.sol
59:         pendingMinter = _minter;

67:             minter = pendingMinter;

70:             minter = pendingMinter;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L287

File:	contracts/rewards/RewardDistributor.sol
287:         pendingTrader = _trader;

293:         trader = pendingTrader;

300:             ve = VE(pendingVoteEscrow);

303:             pendingVoteEscrow = _voteEscrow;

310:         ve = VE(pendingVoteEscrow);

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L446

File:	contracts/core/GolomTrader.sol 
446:             distributor = Distributor(_distributor);

448:             pendingDistributor = _distributor;

456:         distributor = Distributor(pendingDistributor);

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
261:         MIN_VOTING_POWER_REQUIRED = _newMinVotingPower;

Emit events for state variable changes.

[L-05] Return values of transfer()/transferFrom() not checked

Not all IERC20 implementations revert() when there’s a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L151

File:	contracts/rewards/RewardDistributor.sol
151:         rewardToken.transfer(addr, reward);

165:         rewardToken.transfer(addr, reward);

208:         rewardToken.transfer(tokenowner, reward);
209:         weth.transfer(tokenowner, rewardEth);

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

File:	contracts/core/GolomTrader.sol 
154:             payable(payAddress).transfer(payAmt); // royalty transfer to royaltyaddress

382:         WETH.transferFrom(o.signer, address(this), o.totalAmt * amount);

Check return value

[L-06] Unused/empty receive() and fallback()functions

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L313

File:	contracts/rewards/RewardDistributor.sol
313:     fallback() external payable {}

315:     receive() external payable {}

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L459

File:	contracts/core/GolomTrader.sol 
459:     fallback() external payable {}

461:     receive() external payable {}

[L-07] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). "Unless there is a compelling reason, abi.encode should be preferred". If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L175

File:	contracts/core/GolomTrader.sol 
175:         bytes32 hash = keccak256(abi.encodePacked('\x19\x01', EIP712_DOMAIN_TYPEHASH, hashStruct));

Use abi.encode()

[L-08] Use of assert() instead of require()

Contracts use assert() instead of require() in multiple places. This causes a Panic error on failure and prevents the use of error strings. Prior to solc 0.8.0, assert() used the invalid opcode which used up all the remaining gas while require() used the revert opcode which refunded the gas and therefore the importance of using require() instead of assert() was greater. However, after 0.8.0, assert() uses revert opcode just like require() but creates a Panic(uint256) error instead of Error(string) created by require(). Nevertheless, Solidity’s documentation says:

“Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will cause a Panic.”

whereas

“The require function either creates an error without any data or an error of type Error(string). It should be used to ensure valid conditions that cannot be detected until execution time. This includes conditions on inputs or return values from calls to external contracts.”

Findings

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
493:         assert(idToOwner[_tokenId] == address(0));

506:         assert(idToOwner[_tokenId] == _from);

519:         assert(idToOwner[_tokenId] == _owner);

666:         assert(_operator != msg.sender);

679:         assert(_to != address(0));

861:             assert(IERC20(token).transferFrom(from, address(this), _value));

977:         assert(_isApprovedOrOwner(msg.sender, _tokenId));

981:         assert(_value > 0); // dev: need non-zero value

991:         assert(_isApprovedOrOwner(msg.sender, _tokenId));

1007:         assert(_isApprovedOrOwner(msg.sender, _tokenId));

1023:         assert(IERC20(token).transfer(msg.sender, value));

1110:         assert(_block <= block.number);

1206:         assert(_block <= block.number);

Use Custom Errors or at least use require() with informative error strings instead of assert().

[L-09] Shadowing State Variables

https://swcregistry.io/docs/SWC-119 In future it could go unnoticed and subsequently lead to security issues.

Findings

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
71:     function delegate(uint256 tokenId, uint256 toTokenId) external {

79:     Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];

116:     function _getCurrentDelegated(uint256 tokenId) internal view returns (uint256[] memory) {

168:     function getVotes(uint256 tokenId) external view returns (uint256) {

185:     function getPriorVotes(uint256 tokenId, uint256 blockNumber) public view returns (uint256) {

210:     function removeDelegation(uint256 tokenId) external {

213:     Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1];

tokenId and checkpoint are already defined in parent contract:

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
323:     uint256 internal tokenId;

915:     function checkpoint() external {

Rename the local variables that shadow another component.

[L-10] Hardcoded contract address

On different blockchains same address may contain contract with different logic.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L45

File:	contracts/core/GolomTrader.sol 
45:     ERC20 WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

Make it immutable and set it in constructor();

[L-11] Misleading comment in function comments

The function comments imply that if passed address is zero address, then function "throws". In reality it just returns 0 .

Findings

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
395:     /// @dev Returns the number of NFTs owned by `_owner`.
396:     ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
397:     /// @param _owner Address for whom to query the balance.
398:     function _balance(address _owner) internal view returns (uint256) {
399:         return ownerToNFTokenCount[_owner];
400:     }
401: 
402:     /// @dev Returns the number of NFTs owned by `_owner`.
403:     ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
404:     /// @param _owner Address for whom to query the balance.
405:     function balanceOf(address _owner) external view returns (uint256) {
406:         return _balance(_owner);
407:     }


588:     ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
589:     ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
					//@audit no check, does not throw if wrong value returned
604:             try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4) {} catch (
605:                 bytes memory reason
606:             ) {

Add zero address check and revert.

[L-12] _writeCheckpoint() stores array to memory

Array _delegatedTokenIds is stored to memory struct, not to storage, probably intended to store it in storage.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L94-L109

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
094:     function _writeCheckpoint(
095:         uint256 toTokenId,
096:         uint256 nCheckpoints,
097:         uint256[] memory _delegatedTokenIds
098:     ) internal {
099:         require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
100:         uint index = nCheckpoints == 0 ? 0 : nCheckpoints- 1;
101:         Checkpoint memory oldCheckpoint = checkpoints[toTokenId][index];
102: 
103:         if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
104:             oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;
105:         } else {
106:             checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds);
107:             numCheckpoints[toTokenId] = nCheckpoints + 1;
108:         }
109:     }

change oldCheckpoint from being memory variable to be storage

Checkpoint storage oldCheckpoint = checkpoints[toTokenId][index];

[L-13] Dependencies are outdated

Some libraries are outdated and contain various vulnerabilities npm audit shows 44 vulnerabilities (8 moderate, 35 high, 1 critical)

Findings

@openzeppelin/contracts 4.0.0 - 4.7.0 Severity: high OpenZeppelin Contracts's SignatureChecker may revert on invalid EIP-1271 signers - https://github.com/advisories/GHSA-4g63-c64m-25w9 OpenZeppelin Contracts's ERC165Checker may revert instead of returning false - https://github.com/advisories/GHSA-qh9x-gcfh-pcrw //@audit and many more in result report below

Update dependencies;

[L-14] Add a timelock to critical functions

It is a good practice to give time for users to react and adjust to critical changes. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious Manager making a frontrunning/sandwich attack on the fees).

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L260-L262

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
260:     function changeMinVotingPower(uint256 _newMinVotingPower) external onlyOwner {
261:         MIN_VOTING_POWER_REQUIRED = _newMinVotingPower;
262:     }

Add a timelock.

[L-15] Set sane maximum for input parameter

There should be an upper limit to minimum voting power

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L260-L262

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
260:     function changeMinVotingPower(uint256 _newMinVotingPower) external onlyOwner {
261:         MIN_VOTING_POWER_REQUIRED = _newMinVotingPower;
262:     }

Add a check for input parameter, so it can not be unreasonably high.

[L-16] Non-exploitable(yet) reentrancy

There is no reentrancy guard in RewardDistributor.addFee(address[2],uint256) function, and if the trader changed with reentrancy guard missing, it will cause exploitation of this function.

Findings

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L98

File:	contracts/rewards/RewardDistributor.sol
98:     function addFee(address[2] memory addr, uint256 fee) public onlyTrader {

Add reentrancy guard

Non-Critical Issues

IssueInstances
1Function is missing NatSpec34
2Contract/interface/library declaraiton is missing NatSpec8
3NatSpec is incomplete13
4Event is missing indexed fields4
5Remove commented out code1
6No need to explicitly initialize variables with default values35
7require()/revert() statements should have descriptive reason strings33
8Large multiples of ten should use scientific notation (e.g. 1e6) rather than decimal literals (e.g. 1000000000 or 10**18), for readability and obsolete calculations.2
9constants should be defined rather than using magic numbers26
10Numeric values having to do with time should use time units for readability3
11Use a more recent version of solidity6
12States/flags should use Enums rather than plainuint3
13File does not contain an SPDX Identifier1
14Incorrectly positioned comment2
15constant naming conventions not followed9
16Variable naming conventions not followed1
17Obsolete return; at the function end1

[N-01] Function is missing NatSpec

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L32-L40

File:	contracts/rewards/RewardDistributor.sol
32: function ownerOf(uint256 tokenId) external view returns (address owner);

34: function totalSupplyAtT(uint256 t) external view returns (uint256);

36: function balanceOfNFTAt(uint256 _tokenId, uint256 _t) external view returns (uint256);

38: function totalSupplyAt(uint256 _block) external view returns (uint256);

40: function balanceOfAtNFT(uint256 _tokenId, uint256 _block) external view returns (uint256);

74: constructor(

141: function traderClaim(address addr, uint256[] memory epochs) public {

155: function exchangeClaim(address addr, uint256[] memory epochs) public {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L112

File:	contracts/core/GolomTrader.sol 
112: function hashPayment(Payment calldata p) private pure returns (bytes32) {

123: function _hashOrder(Order calldata o) private pure returns (bytes32) {

127: function _hashOrderinternal(Order calldata o, uint256[2] memory extra) private pure returns (bytes32) {

151: function payEther(uint256 payAmt, address payAddress) internal {

312: function cancelOrder(Order calldata o) public nonReentrant {

323: function incrementNonce() external nonReentrant {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
13: function balanceOf(uint256) external view returns (uint256);

15: function balanceOfAtNFT(uint256, uint256) external view returns (uint256);

17: function ownerOf(uint256) external view returns (address);

52: constructor(address _token) {

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
445: function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool) {

571: function _isContract(address account) internal view returns (bool) {

868:     function setVoter(address _voter) external {

873:     function voting(uint256 _tokenId) external {

878:     function abstain(uint256 _tokenId) external {

883:     function attach(uint256 _tokenId) external {

888:     function detach(uint256 _tokenId) external {

893:     function merge(uint256 _from, uint256 _to) external {

910:     function block_number() external view returns (uint256) {

1093:     function balanceOfNFT(uint256 _tokenId) external view returns (uint256) {

1098:     function balanceOfNFTAt(uint256 _tokenId, uint256 _t) external view returns (uint256) {

1156:     function balanceOfAtNFT(uint256 _tokenId, uint256 _block) external view returns (uint256) {

1198:     function totalSupply() external view returns (uint256) {

1226:     function _burn(uint256 _tokenId) internal {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L66

File:	contracts/vote-escrow/TokenUriHelper.sol
66:     function _tokenURI(
67:         uint256 _tokenId,
68:         uint256 _balanceOf,
69:         uint256 _locked_end,
70:         uint256 _value
71:     ) public pure returns (string memory output) {

128:     function toString(uint256 value) internal pure returns (string memory) {

[N-02] Contract/interface/library declaraiton is missing NatSpec

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L31

File:	contracts/rewards/RewardDistributor.sol
31: interface VE {

43: contract RewardDistributor is Ownable {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L36

File:	contracts/core/GolomTrader.sol 
36: interface Distributor {

40: contract GolomTrader is Ownable, ReentrancyGuard {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
12: interface IVoteEscrow {

20: contract VoteEscrow is VoteEscrowCore, Ownable {

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
275: contract VoteEscrowCore is IERC721, IERC721Metadata {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L65

File:	contracts/vote-escrow/TokenUriHelper.sol
65: library TokenUriHelper {

[N-03] NatSpec is incomplete

Check @audit comments for details Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L328

File:	contracts/core/GolomTrader.sol 
					//@audit NatSpec tags @param tokenId, @param proof are missing
328:    /// @dev function to fill a signed order of ordertype 2 also has a payment param in case the taker wants
329:    ///      to send ether to that address on filling the order, Match an criteria order, ensuring that the supplied proof demonstrates inclusion of the tokenId in the associated merkle root, if root is 0 then any token can be used to fill the order
330:    /// @param o the Order struct to be filled must be orderType 2
331:    /// @param amount the amount of times the order is to be filled(useful for ERC1155)
332:    /// @param referrer referrer of the order
333:    /// @param p any extra payment that the taker of this order wanna send on succesful execution of order
334:    function fillCriteriaBid(
335:        Order calldata o,
336:        uint256 amount,
337:        uint256 tokenId,
338:        bytes32[] calldata proof,
339:        address referrer,
340:        Payment calldata p
341:    ) public nonReentrant {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
			//@audit NatSpec tags @param toTokenId, param nCheckpoints, @param _delegatedTokenIds are missing
91:    /**
92:     * @notice Writes the checkpoint to store current NFTs in the specific block
93:     */
94:    function _writeCheckpoint(
95:        uint256 toTokenId,
96:        uint256 nCheckpoints,
97:        uint256[] memory _delegatedTokenIds
98:    ) internal {

				//@audit NatSpec tags @param _from, @param _to, @param _tokenId, @param _sender are missing
227:    /// @dev Exeute transfer of a NFT.
228:    ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
229:    ///      address for this NFT. (NOTE: `msg.sender` not allowed in internal function so pass `_sender`.)
230:    ///      Throws if `_to` is the zero address.
231:    ///      Throws if `_from` is not the current owner.
232:    ///      Throws if `_tokenId` is not a valid NFT.
233:    function _transferFrom(
234:        address _from,
235:        address _to,
236:        uint256 _tokenId,
237:        address _sender
238:    ) internal override {

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
				//@audit NatSpec tags @param _owner, @param _tokenIndex, @return are missing
428:    /// @dev  Get token by index
429:    function tokenOfOwnerByIndex(address _owner, uint256 _tokenIndex) external view returns (uint256) {
430:        return ownerToNFTokenIdList[_owner][_tokenIndex];
431:    }
				
				//@audit NatSpec tags @param _to, @param _tokenId are missing
489:    /// @dev Add a NFT to a given address
490:    ///      Throws if `_tokenId` is owned by someone.
491:    function _addTokenTo(address _to, uint256 _tokenId) internal {

				//@audit NatSpec tags @param _from, @param _tokenId are missing
502:    /// @dev Remove a NFT from a given address
503:    ///      Throws if `_from` is not the current owner.
504:    function _removeTokenFrom(address _from, uint256 _tokenId) internal {

				//@audit NatSpec tags @param _owner, @param _tokenId are missing
515:    /// @dev Clear an approval of a given address
516:    ///      Throws if `_owner` is not the current owner.
517:    function _clearApproval(address _owner, uint256 _tokenId) internal {

					//@audit NatSpec tags @param _from, @param _to, @param _tokenId, @param _sender are missing
526:     /// @dev Exeute transfer of a NFT.
527:     ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
528:     ///      address for this NFT. (NOTE: `msg.sender` not allowed in internal function so pass `_sender`.)
529:     ///      Throws if `_to` is the zero address.
530:     ///      Throws if `_from` is not the current owner.
531:     ///      Throws if `_tokenId` is not a valid NFT.
532:     function _transferFrom(
533:         address _from,
534:         address _to,
535:         uint256 _tokenId,
536:         address _sender
537:     ) internal virtual {

				//@audit NatSpec tag @param _tokenId is missing
974:     /// @notice Deposit `_value` additional tokens for `_tokenId` without modifying the unlock time
975:     /// @param _value Amount of tokens to deposit and add to the lock
976:     function increase_amount(uint256 _tokenId, uint256 _value) external nonreentrant {

				//@audit NatSpec tag @param _tokenId is missing
988:     /// @notice Extend the unlock time for `_tokenId`
989:     /// @param _lock_duration New number of seconds until tokens unlock
990:     function increase_unlock_time(uint256 _tokenId, uint256 _lock_duration) external nonreentrant {

					//@audit NatSpec tag @param t is missing
1189:     /// @notice Calculate total voting power
1190:     /// @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
1191:     /// @return Total voting power
1192:     function totalSupplyAtT(uint256 t) public view returns (uint256) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L11

File:	contracts/vote-escrow/TokenUriHelper.sol
				//@audit NatSpec tag @param data is missing
11:     /// @notice Encodes some bytes to the base64 representation
12:     function encode(bytes memory data) internal pure returns (string memory) {

[N-04] Event is missing indexed fields

Each event should use three indexed fields if there are three or more fields Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L70

File:	contracts/rewards/RewardDistributor.sol
70:     event NewEpoch(uint256 indexed epochNo, uint256 tokenMinted, uint256 rewardStaker, uint256 previousEpochFee);

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
29:     event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
284:     event Deposit(
285:         address indexed provider,
286:         uint256 tokenId,
287:         uint256 value,
288:         uint256 indexed locktime,
289:         DepositType deposit_type,
290:         uint256 ts
291:     );
292:     event Withdraw(address indexed provider, uint256 tokenId, uint256 value, uint256 ts);

[N-05] Remove commented out code

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L171

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
218:     // /// @notice Remove delegation by user
219:     // function removeDelegationByOwner(uint256 delegatedTokenId, uint256 ownerTokenId) external {
220:     //     require(ownerOf(ownerTokenId) == msg.sender, 'VEDelegation: Not allowed');
221:     //     uint256 nCheckpoints = numCheckpoints[delegatedTokenId];
222:     //     Checkpoint storage checkpoint = checkpoints[delegatedTokenId][nCheckpoints - 1];
223:     //     removeElement(checkpoint.delegatedTokenIds, delegatedTokenId);
224:     //     _writeCheckpoint(ownerTokenId, nCheckpoints, checkpoint.delegatedTokenIds);
225:     // }

[N-06] No need to explicitly initialize variables with default values

If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address...). Explicitly initializing it with its default value is an anti-pattern. As an example: for (uint256 i = 0; i < numIterations; ++i) { should be replaced with for (uint256 i; i < numIterations; ++i) { Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L142

File:	contracts/rewards/RewardDistributor.sol
142:         uint256 reward = 0;
143:         for (uint256 index = 0; index < epochs.length; index++) {

156:         uint256 reward = 0;
157:         for (uint256 index = 0; index < epochs.length; index++) {

175:         uint256 reward = 0;
176:         uint256 rewardEth = 0;

180:         for (uint256 tindex = 0; tindex < tokenids.length; tindex++) {

183:             for (uint256 index = 0; index < epochs.length; index++) {

222:         uint256 reward = 0;
223:         uint256 rewardEth = 0;

226:         for (uint256 index = 0; index < epoch; index++) {

257:         uint256 reward = 0;
258:         for (uint256 index = 0; index < epoch; index++) {

272:         uint256 reward = 0;
273:         for (uint256 index = 0; index < epoch; index++) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L415

File:	contracts/core/GolomTrader.sol 
415:         for (uint256 i = 0; i < proof.length; i++) {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
147:         uint256 lower = 0;

170:         uint256 votes = 0;
171:         for (uint256 index = 0; index < delegated.length; index++) {

188:         uint256 votes = 0;
189:         for (uint256 index = 0; index < delegatednft.length; index++) {

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
697:         int128 old_dslope = 0;
698:         int128 new_dslope = 0;

735:         uint256 block_slope = 0; // dblock/dt

745:             for (uint256 i = 0; i < 255; ++i) {

749:                 int128 d_slope = 0;

1042:         uint256 _min = 0;

1044:         for (uint256 i = 0; i < 128; ++i) {

1113:         uint256 _min = 0;

1115:         for (uint256 i = 0; i < 128; ++i) {

1133:         uint256 d_block = 0;
1134:         uint256 d_t = 0;

1167:         for (uint256 i = 0; i < 255; ++i) {

1169:             int128 d_slope = 0;

1211:         uint256 dt = 0;

[N-07] require()/revert() statements should have descriptive reason strings

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L88

File:	contracts/rewards/RewardDistributor.sol
88:         require(msg.sender == trader);

144:             require(epochs[index] < epoch);

158:             require(epochs[index] < epoch);

217:         require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm'); //@audit Saruman | golom.io: "No hidden meaning just random string"

220:             require(msg.sender == o.reservedAddress);

285:         require(
286:             o.totalAmt * amount >
287:                 (o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt) * amount + p.paymentAmt
288:         ); 

291:             require(msg.sender == o.reservedAddress);

293:         require(o.orderType == 1);

295:         require(status == 3);
296:         require(amountRemaining >= amount);

313:         require(o.signer == msg.sender);

342:         require(o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt);

345:             require(msg.sender == o.reservedAddress);

347:         require(o.orderType == 2);
349:         require(status == 3);
350:         require(amountRemaining >= amount);

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
245:         require(_isApprovedOrOwner(_sender, _tokenId));

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
360:         require(_entered_state == _not_entered);

540:         require(_isApprovedOrOwner(_sender, _tokenId));

646:         require(owner != address(0));

648:         require(_approved != owner);

652:         require(senderIsOwner || senderIsApprovedForAll);

869:         require(msg.sender == voter);

874:         require(msg.sender == voter);

879:         require(msg.sender == voter);

884:         require(msg.sender == voter);

889:         require(msg.sender == voter);

895:         require(_from != _to);
896:         require(_isApprovedOrOwner(msg.sender, _from));
897:         require(_isApprovedOrOwner(msg.sender, _to));

927:         require(_value > 0); // dev: need non-zero value

944:         require(_value > 0); // dev: need non-zero value

[N-08] Large multiples of ten should use scientific notation (e.g. 1e6) rather than decimal literals (e.g. 1000000000 or 10**18), for readability and obsolete calculations.

There are 2 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L48

File:	contracts/rewards/RewardDistributor.sol
48:     uint256 constant dailyEmission = 600000 * 10**18;

100:    if (rewardToken.totalSupply() > 1000000000 * 10**18) {

[N-09] constants should be defined rather than using magic numbers

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L44

File:	contracts/governance/GolomToken.sol
44:         _mint(_airdrop, 150_000_000 * 1e18);

52:         _mint(_rewardDistributor, 62_500_000 * 1e18);

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L48

File:	contracts/rewards/RewardDistributor.sol
100:         if (rewardToken.totalSupply() > 1000000000 * 10**18) {

120:             rewardTrader[epoch] = ((tokenToEmit - stakerReward) * 67) / 100;
121:             rewardExchange[epoch] = ((tokenToEmit - stakerReward) * 33) / 100;

179:             return (0, hashStruct, 0);

183:             return (1, hashStruct, 0);

187:             return (2, hashStruct, 0);

191:             return (2, hashStruct, 0);

193:         return (3, hashStruct, o.tokenAmt - filled[hashStruct]);

212:             o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt + (o.totalAmt * 50) / 10000,

226:         require(status == 3, 'order not valid');

242:         payEther(((o.totalAmt * 50) / 10000) * amount, address(distributor));

254:                     (o.totalAmt * 50) /
255:                     10000 -

263:                 (o.totalAmt - (o.totalAmt * 50) / 10000 - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount,

269:         distributor.addFee([o.signer, o.exchange.paymentAddress], ((o.totalAmt * 50) / 10000) * amount);

381:         uint256 protocolfee = ((o.totalAmt * 50) / 10000) * amount;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
611:                         revert(add(32, reason), mload(reason))

745:             for (uint256 i = 0; i < 255; ++i) {

1044:         for (uint256 i = 0; i < 128; ++i) {

1115:         for (uint256 i = 0; i < 128; ++i) {

1167:         for (uint256 i = 0; i < 255; ++i) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L17

File:	contracts/vote-escrow/TokenUriHelper.sol
17:         uint256 encodedLen = 4 * ((len + 2) / 3);

20:         bytes memory result = new bytes(encodedLen + 32);

139:             temp /= 10;

145:             value /= 10;

[N-10] Numeric values having to do with time should use time units for readability

There are units for seconds, minutes, hours, days, and weeks Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L57

File:	contracts/rewards/RewardDistributor.sol
57:     uint256 constant secsInDay = 24 * 60 * 60; 

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
296:     uint256 internal constant MAXTIME = 4 * 365 * 86400;
297:     int128 internal constant iMAXTIME = 4 * 365 * 86400;

[N-11] Use a more recent version of solidity

Use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked(<str>,<str>) Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L2

File:	contracts/governance/GolomToken.sol
2: pragma solidity ^0.8.11;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L3

File:	contracts/core/GolomTrader.sol 
3: pragma solidity 0.8.11;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L3

File:	contracts/vote-escrow/TokenUriHelper.sol
3: pragma solidity 0.8.11;

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
2: pragma solidity 0.8.11;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
2: pragma solidity 0.8.11;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L2

File:	contracts/rewards/RewardDistributor.sol
2: pragma solidity 0.8.11;

[N-12] States/flags should use Enums rather than plainuint

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L51

File:	contracts/core/GolomTrader.sol 
51:         uint256 orderType; // 0 if selling nft for eth , 1 if offering weth for nft,2 if offering weth for collection with special criteria root

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
356:     uint8 internal constant _not_entered = 1;
357:     uint8 internal constant _entered = 2;

[N-13] File does not contain an SPDX Identifier

Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L1

File:	contracts/vote-escrow/TokenUriHelper.sol
1: /// [MIT License]

[N-14] Incorrectly positioned comment

Some comments are misplaced. Check @audit for details. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L152

File:	contracts/core/GolomTrader.sol 
152:         if (payAmt > 0) {
153:             // if royalty has to be paid  @audit comment should be above if check

342:         require(o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt);
343:         // require eth amt is sufficient  @audit this comment should be before require check above;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
538:         require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');
539:         // Check requirements  @audit this comment should be before require check above;

[N-15] constant naming conventions not followed

Constants should be written in uppercase characters separated by underscores. Constant names may also contain digits if appropriate, but not as the first character. Some constants do not follow it; Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L48

File:	contracts/rewards/RewardDistributor.sol
48:     uint256 constant dailyEmission = 600000 * 10**18;

57:     uint256 constant secsInDay = 24 * 60 * 60;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
297:     int128 internal constant iMAXTIME = 4 * 365 * 86400;

317:     string public constant name = 'veNFT';
318:     string public constant symbol = 'veNFT';
319:     string public constant version = '1.0.0';
320:     uint8 public constant decimals = 18;

356:     uint8 internal constant _not_entered = 1;
357:     uint8 internal constant _entered = 2;

[N-16] Variable naming conventions not followed

variable MIN_VOTING_POWER_REQUIRED should not be uppercased, it looks like it is a constant but it is not Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L50

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
50:     uint256 public MIN_VOTING_POWER_REQUIRED = 0;

261:    MIN_VOTING_POWER_REQUIRED = _newMinVotingPower;

[N-17] Obsolete return; at the function end

return; is obsolete at the end of a function Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L137

File:	contracts/rewards/RewardDistributor.sol
137:         return;
138:     }

IssueInstances
1++variable costs less gas than variable++12
2Access mappings directly rather than using accessor functions6
3Cheaper input valdiations should come before expensive operations3
4Help the optimizer by saving a storage variable's reference1
5The result of external function calls should be cached rather than re-calling the function4
6Amounts should be checked for 0 before calling a transfer10
7public functions to external8
8Using calldata instead of memory for read-only arguments in external functions saves gas4
9Default value initialization2
10Multiplication/division by 2 should use bit shifting3
11Splitting require() statements that use && saves gas4
12abi.encode() is less efficient than abi.encodePacked()4
13struct Order: can be more tighly packed1
14State variables can be packed into fewer storage slots8
15x += y costs more gas than x = x + y for state variables2
16internal and private functions only called once can be inlined to save gas10
17Unchecked arithmetic50
18Using bools for storage incurs overhead5
19Using > 0 costs more gas than != 0 when used on a uint in a require() and assert statement3
20Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead11
21Use custom errors rather than revert()/require() strings to save gas76
22Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate5
23Functions guaranteed to revert when called by normal users can be marked payable12
24Do not calculate constants4
25<array>.length should not be looked up in every loop of a for-loop8
26Copying struct to memory can be more expensive than just reading from storagexxx3
27require()/revert() strings longer than 32 bytes cost extra gas8
28Remove or replace unused variables2
29State variables only set in the constructor should be declared immutable3
30State variables with values known at compile time should be constants2
31State variables should be cached in stack variables rather than re-reading them from storage14
32Using private rather than public for constants, saves gas4
33Remove unreachable code1
34No need to evaluate all expressions to know if one of them is true2
35No need to read tokenId second time1
36last_point value rewritten right after initialization1
37Wasted gas on copying a struct1
38Remove duplicate code1
39No need for mapping supportedInterfaces to exist1
40Same calculation twice1
41Obsolete constants2
42Variable end can be of type uint1281

[G-01] ++variable costs less gas than variable++, especially when it’s used in for-loops (--variable/variable-- too)

Prefix increments are cheaper than postfix increments. Saves 6 gas PER LOOP There are 12 instances of this issue:

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L143

File: contracts/rewards/RewardDistributor.sol
143:	for (uint256 index = 0; index < epochs.length; index++) {

157:	for (uint256 index = 0; index < epochs.length; index++) {

180:	for (uint256 tindex = 0; tindex < tokenids.length; tindex++) {

183:	for (uint256 index = 0; index < epochs.length; index++) {

226:	for (uint256 index = 0; index < epoch; index++) {

258:	for (uint256 index = 0; index < epoch; index++) {

273:	for (uint256 index = 0; index < epoch; index++) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L415

File:	contracts/core/GolomTrader.sol 
415:	for (uint256 i = 0; i < proof.length; i++) {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
171:	for (uint256 index = 0; index < delegated.length; index++) {

189:	for (uint256 index = 0; index < delegatednft.length; index++) {

199:	for (uint256 i; i < _array.length; i++) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L138

File:	contracts/vote-escrow/TokenUriHelper.sol
138:	digits++;

Mitigation

Change i++ to ++i

[G-02] Access mappings directly rather than using accessor functions

Saves having to do two JUMP instructions, along with stack setup There are 6 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L72

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
72:		require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');

211:  require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
1229:	address owner = ownerOf(_tokenId);

406:	return _balance(_owner);

453:	uint256 current_count = _balance(_to);

464:	uint256 current_count = _balance(_from) - 1;

Mitigation

Istead of ownerOf(tokenId) use idToOwner[tokenId]

[G-03] Cheaper input valdiations should come before expensive operations

Check @audit comment for details There are 3 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L925

File:	contracts/vote-escrow/VoteEscrowCore.sol
925:        LockedBalance memory _locked = locked[_tokenId];
926:					//@audit this check should be before we read from storage
927:        require(_value > 0); // dev: need non-zero value

942:        uint256 unlock_time = ((block.timestamp + _lock_duration) / WEEK) * WEEK; // Locktime is rounded down to weeks
943:					//@audit this check should be before we do unlock_time evalutaion
944:        require(_value > 0); // dev: need non-zero value

977:        assert(_isApprovedOrOwner(msg.sender, _tokenId));
978:
979:        LockedBalance memory _locked = locked[_tokenId];
980:					//@audit this check should be first, before line 977 check
981:        assert(_value > 0); // dev: need non-zero value

[G-04] Help the optimizer by saving a storage variable's reference instead of repeatedly fetching it

To help the optimizer, declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. The effect can be quite significant. As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L138

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
138:        if (checkpoints[nftId][nCheckpoints - 1].fromBlock <= blockNumber) {
139:            return checkpoints[nftId][nCheckpoints - 1].delegatedTokenIds;

Declare storage variable and use it:

Checkpoint storage checkpoint = checkpoints[nftId][nCheckpoints - 1];
if (checkpoint.fromBlock <= blockNumber) {
    return checkpoint.delegatedTokenIds;
}

[G-05] The result of external function calls should be cached rather than re-calling the function

There is no need to call another contract functions multiple times to get same value, returned value should be cached in a variable; The instances below point to the second+ call of the function within a single function: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L100

File:	contracts/rewards/RewardDistributor.sol
100: if (rewardToken.totalSupply() > 1000000000 * 10**18) {

112:		uint256 tokenToEmit = (dailyEmission * (rewardToken.totalSupply() - rewardToken.balanceOf(address(ve)))) /
113:			rewardToken.totalSupply();
114:		uint256 stakerReward = (tokenToEmit * rewardToken.balanceOf(address(ve))) / rewardToken.totalSupply();

239:      (rewardStaker[index] * ve.balanceOfAtNFT(tokenid, epochBeginTime[index])) /
240:      ve.totalSupplyAt(epochBeginTime[index]);

244:       ve.balanceOfAtNFT(tokenid, epochBeginTime[index])) /
245:     ve.totalSupplyAt(epochBeginTime[index]);

rewardToken.totalSupply() should be cached. ve.balanceOfAtNFT(tokenid, epochBeginTime[index])) should be cached. ve.totalSupplyAt(epochBeginTime[index]) should be cached.

[G-06] Amounts should be checked for 0 before calling a transfer

Checking non-zero transfer values can avoid an expensive external call and save gas. While this is done at some places, it's not consistently done in the solution. I suggest adding a non-zero-value check here: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L151

File:	contracts/rewards/RewardDistributor.sol
151: rewardToken.transfer(addr, reward);

165: rewardToken.transfer(addr, reward);

208:  rewardToken.transfer(tokenowner, reward);

209:  weth.transfer(tokenowner, rewardEth);

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L238

File:	contracts/core/GolomTrader.sol 
238: ERC1155(o.collection).safeTransferFrom(o.signer, receiver, o.tokenId, amount, '');

304: nftcontract.safeTransferFrom(msg.sender, o.signer, o.tokenId, amount, '');

364: nftcontract.safeTransferFrom(msg.sender, o.signer, tokenId, amount, '');

382: WETH.transferFrom(o.signer, address(this), o.totalAmt * amount);

383: WETH.withdraw(o.totalAmt * amount);

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
1023: assert(IERC20(token).transfer(msg.sender, value));

[G-07] public functions to external

External call cost is less expensive than of public functions. Contracts are allowed to override their parents’ functions and change the visibility from external to public. The following functions could be set external to save gas and improve code quality: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L98

File:	contracts/rewards/RewardDistributor.sol
98:  function addFee(address[2] memory addr, uint256 fee) public onlyTrader {

141: function traderClaim(address addr, uint256[] memory epochs) public {

155: function exchangeClaim(address addr, uint256[] memory epochs) public {

172: function multiStakerClaim(uint256[] memory tokenids, uint256[] memory epochs) public {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L203

File:	contracts/core/GolomTrader.sol 
203: function fillAsk(
204:        Order calldata o,
205:        uint256 amount,
206:        address referrer,
207:        Payment calldata p,
208:        address receiver
209:    ) public payable nonReentrant {

279: function fillBid(
280:        Order calldata o,
281:        uint256 amount,
282:        address referrer,
283:        Payment calldata p
284:    ) public nonReentrant {

312: function cancelOrder(Order calldata o) public nonReentrant {

334: function fillCriteriaBid(
335:        Order calldata o,
336:        uint256 amount,
337:        uint256 tokenId,
338:        bytes32[] calldata proof,
339:        address referrer,
340:        Payment calldata p
341:    ) public nonReentrant {

[G-08] Using calldata instead of memory for read-only arguments in external functions saves gas

If you choose to make suggested above public functions as external, to continue gas optimizaton we can use calldata function arguments instead of memory. When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Structs have the same overhead as an array of length one. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L98

File:	contracts/rewards/RewardDistributor.sol
98:  function addFee(address[2] memory addr, uint256 fee) public onlyTrader {

141: function traderClaim(address addr, uint256[] memory epochs) public {

155: function exchangeClaim(address addr, uint256[] memory epochs) public {

172: function multiStakerClaim(uint256[] memory tokenids, uint256[] memory epochs) public {

[G-09] Default value initialization

Problem

If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). Explicitly initializing it with its default value is an anti-pattern and wastes gas. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L45

File:	contracts/rewards/RewardDistributor.sol
45:  uint256 public epoch = 0;

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
uint256 public MIN_VOTING_POWER_REQUIRED = 0;

Proof of Concept

In case of RewardDistributor hardhat-gas-reporter results before fix:

Deployments
Contractminmaxavg
GolomTrader237950723795432379537
after fix:
Deployments
-:-::-::-:
Contractminmaxavg
GolomTrader237723323772692377263

Mitigation

Remove explicit initialization for default values.

[G-10] Multiplication/division by 2 should use bit shifting

<x> * 2 is equivalent to <x> << 1 and <x> / 2 is the same as <x> >> 1. The MUL and DIV opcodes cost 5 gas, whereas SHL and SHR only cost 3 gas

There are 3 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L150

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
150: uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
1049: uint256 _mid = (_min + _max + 1) / 2;
1120: uint256 _mid = (_min + _max + 1) / 2;

[G-11] Splitting require() statements that use && saves gas

Instead of using the && operator in a single require statement to check multiple conditions, I suggest using multiple require statements with 1 condition per require statement (saving 3 gas per &). See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper

There are 4 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L239

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
239: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
538: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');
894: require(attachments[_from] == 0 && !voted[_from], 'attached');
1008: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

[G-12] abi.encode() is less efficient than abi.encodePacked()

Consider changing it if possible. There are 4 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L102

File:	contracts/core/GolomTrader.sol 
102:  abi.encode(	
115:	abi.encode(		 
130:	abi.encode(
414:  bytes32 computedHash = keccak256(abi.encode(leaf));

[G-13] struct Order: can be more tighly packed

Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings before: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L47

File:	contracts/core/GolomTrader.sol 
47:     struct Order {
        address collection; // NFT contract address
        uint256 tokenId; // order for which tokenId of the collection
        address signer; // maker of order address
        uint256 orderType; // 0 if selling nft for eth , 1 if offering weth for nft,2 if offering weth for collection with special criteria root
        uint256 totalAmt; // price value of the trade // total amt maker is willing to give up per unit of amount
        Payment exchange; // payment agreed by maker of the order to pay on succesful filling of trade this amt is subtracted from totalamt
        Payment prePayment; // another payment , can be used for royalty, facilating trades
        bool isERC721; // standard of the collection , if 721 then true , if 1155 then false
        uint256 tokenAmt; // token amt useful if standard is 1155 if >1 means whole order can be filled tokenAmt times
        uint256 refererrAmt; // amt to pay to the address that helps in filling your order
        bytes32 root; // A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order.
        address reservedAddress; // if not address(0) , only this address can fill the order
        uint256 nonce; // nonce of order usefull for cancelling in bulk
        uint256 deadline; // timestamp till order is valid epoch timestamp in secs
        uint8 v;
        bytes32 r;
        bytes32 s;
    }
Methods
ContractMethodminmaxavg#calls
GolomTraderfillAsk2381672419742414257
GolomTradersetDistributor46281704494743221
Deployments
Contractavg
GolomTrader2029204

after:

    struct Order {
        address collection; // NFT contract address
        uint256 tokenId; // order for which tokenId of the collection
        address signer; // maker of order address
        uint256 orderType; // 0 if selling nft for eth , 1 if offering weth for nft,2 if offering weth for collection with special criteria root
        uint256 totalAmt; // price value of the trade // total amt maker is willing to give up per unit of amount
        Payment exchange; // payment agreed by maker of the order to pay on succesful filling of trade this amt is subtracted from totalamt
        Payment prePayment; // another payment , can be used for royalty, facilating trades
        uint256 tokenAmt; // token amt useful if standard is 1155 if >1 means whole order can be filled tokenAmt times
        uint256 refererrAmt; // amt to pay to the address that helps in filling your order
        bytes32 root; // A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order.
        address reservedAddress; // if not address(0) , only this address can fill the order
        uint256 nonce; // nonce of order usefull for cancelling in bulk
        uint256 deadline; // timestamp till order is valid epoch timestamp in secs
        bool isERC721; // standard of the collection , if 721 then true , if 1155 then false
        uint8 v;
        bytes32 r;
        bytes32 s;
    }
Methods
ContractMethodminmaxavg#calls
GolomTraderfillAsk2381532419482413947
GolomTradersetDistributor46259704274741021
Deployments
Contractavg
GolomTrader2013782

[G-14] State variables can be packed into fewer storage slots

These state variables can be packed together to use less storage slots: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L297

File:	contracts/vote-escrow/VoteEscrowCore.sol
297: int128 internal constant iMAXTIME = 4 * 365 * 86400;

320: uint8 public constant decimals = 18;

347: bytes4 internal constant ERC165_INTERFACE_ID = 0x01ffc9a7;
350: bytes4 internal constant ERC721_INTERFACE_ID = 0x80ac58cd;
353: bytes4 internal constant ERC721_METADATA_INTERFACE_ID = 0x5b5e139f;

356: uint8 internal constant _not_entered = 1;
357: uint8 internal constant _entered = 2;
358: uint8 internal _entered_state = 1;

[G-15] x += y costs more gas than x = x + y for state variables

x += y costs more than x = x + y same as x -= y Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L499

File:	contracts/vote-escrow/VoteEscrowCore.sol
499: ownerToNFTokenCount[_to] += 1;

512: ownerToNFTokenCount[_from] -= 1;

Mitigation

Replace x += y and x -= y with x = x + y and x = x - y.

[G-16] internal and private functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 10 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L116

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
116: function _getCurrentDelegated(uint256 tokenId) internal view returns (uint256[] memory) 
{
129: function _getPriorDelegated(uint256 nftId, uint256 blockNumber) internal view returns (uint256[] memory) {

198: function removeElement(uint256[] storage _array, uint256 _element) internal {

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
452: function _addTokenToOwnerList(address _to, uint256 _tokenId) internal {

462: function _removeTokenFromOwnerList(address _from, uint256 _tokenId) internal {

571: function _isContract(address account) internal view returns (bool) {

677: function _mint(address _to, uint256 _tokenId) internal returns (bool) {

1107: function _balanceOfAtNFT(uint256 _tokenId, uint256 _block) internal view returns (uint256) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L123

File:	contracts/governance/GolomToken.sol
123: function _hashOrder(Order calldata o) private pure returns (bytes32) {

127: function _hashOrderinternal(Order calldata o, uint256[2] memory extra) private pure returns (bytes32) {

[G-17] Unchecked arithmetic

The default “checked” behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected. if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L60

File:	contracts/governance/GolomToken.sol
minterEnableDate = block.timestamp + 1 days;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L106

File:	contracts/rewards/RewardDistributor.sol
106: if (block.timestamp > startTime + (epoch) * secsInDay) {

112:  uint256 tokenToEmit = (dailyEmission * (rewardToken.totalSupply() - rewardToken.balanceOf(address(ve)))) /
113:    rewardToken.totalSupply();
114:  uint256 stakerReward = (tokenToEmit * rewardToken.balanceOf(address(ve))) / rewardToken.totalSupply();

118: epoch = epoch + 1;

143: for (uint256 index = 0; index < epochs.length; index++) {

157: for (uint256 index = 0; index < epochs.length; index++) {

180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) {

183: for (uint256 index = 0; index < epochs.length; index++) {

258: for (uint256 index = 0; index < epoch; index++) {

273: for (uint256 index = 0; index < epoch; index++) {

286: traderEnableDate = block.timestamp + 1 days;

302: voteEscrowEnableDate = block.timestamp + 1 days;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L324

File:	contracts/core/GolomTrader.sol 
324: uint256 newNonce = ++nonces[msg.sender];

415: for (uint256 i = 0; i < proof.length; i++) {

449: distributorEnableDate = block.timestamp + 1 days;

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
107: numCheckpoints[toTokenId] = nCheckpoints + 1;

119: return nCheckpoints > 0 ? checkpoints[tokenId][nCheckpoints - 1].delegatedTokenIds : myArray;

138:    if (checkpoints[nftId][nCheckpoints - 1].fromBlock <= blockNumber) {
139:        return checkpoints[nftId][nCheckpoints - 1].delegatedTokenIds;

148: uint256 upper = nCheckpoints - 1;

150: uint256 center = upper - (upper - lower) / 2;

157: upper = center - 1;

171: for (uint256 index = 0; index < delegated.length; index++) {
172: 	    votes = votes + this.balanceOfNFT(delegated[index]);

189:    for (uint256 index = 0; index < delegatednft.length; index++) {
190:        votes = votes + this.balanceOfAtNFT(delegatednft[index], blockNumber);

199: for (uint256 i; i < _array.length; i++) {

201: _array[i] = _array[_array.length - 1];

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
499: ownerToNFTokenCount[_to] += 1;

512: ownerToNFTokenCount[_from] -= 1;

745: for (uint256 i = 0; i < 255; ++i) {

748: t_i += WEEK;

768: _epoch += 1;

994: uint256 unlock_time = ((block.timestamp + _lock_duration) / WEEK) * WEEK; // Locktime is rounded down to weeks

999: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');

1044: for (uint256 i = 0; i < 128; ++i) {

1049: uint256 _mid = (_min + _max + 1) / 2;

1053: _max = _mid - 1;

1115: for (uint256 i = 0; i < 128; ++i) {

1120: uint256 _mid = (_min + _max + 1) / 2;

1124: _max = _mid - 1;

1167: for (uint256 i = 0; i < 255; ++i) {

1213:  Point memory point_next = point_history[target_epoch + 1];

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/TokenUriHelper.sol#L138

File:	contracts/vote-escrow/TokenUriHelper.sol
138:            digits++;
139:            temp /= 10;

143:            digits -= 1;
144:            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
145:            value /= 10;

Mitigation

Place the arithmetic operations in an unchecked block

for (uint i; i < length;) { ... unchecked{ ++i; } }

[G-18] Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past

There are 5 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L20

File:	contracts/governance/GolomToken.sol
20:    bool public isAirdropMinted;
21:    bool public isGenesisRewardMinted;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
314: mapping(uint256 => bool) public voted;

341: mapping(address => mapping(address => bool)) internal ownerToOperators;

344: mapping(bytes4 => bool) internal supportedInterfaces;

[G-19] Using > 0 costs more gas than != 0 when used on a uint in a require() and assert statement

> 0 is less efficient than != 0 for unsigned integers. != 0 costs less gas compared to > 0 for unsigned integers in require and assert statements with the optimizer enabled (6 gas) There are 3 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L927

File:	contracts/vote-escrow/VoteEscrowCore.sol
927: require(_value > 0); // dev: need non-zero value

944: require(_value > 0); // dev: need non-zero value

981: assert(_value > 0); // dev: need non-zero value

Mitigation

Replace > 0 with != 0 Or update soldity compiler to >=0.8.13

[G-20] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. It is only beneficial to use reduced-size arguments if you are dealing with storage values because the compiler will pack multiple elements into one storage slot, and thus, combine multiple reads or writes into a single operation. When dealing with function arguments or memory values, there is no inherent benefit because the compiler does not pack these values.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L271

File:	contracts/vote-escrow/VoteEscrowCore.sol
271: int128 amount; //@audit no storage slot saved

297: int128 internal constant iMAXTIME = 4 * 365 * 86400;

311: mapping(uint256 => int128) public slope_changes; // time -> signed slope change

320: uint8 public constant decimals = 18;

356:  uint8 internal constant _not_entered = 1;
357:	uint8 internal constant _entered = 2;
358:  uint8 internal _entered_state = 1;

697:  int128 old_dslope = 0;
698:  int128 new_dslope = 0;

749:  int128 d_slope = 0;

1169: int128 d_slope = 0;

[G-21] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also saves deployment gas.

There are 76 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L24

File:	contracts/governance/GolomToken.sol
24: require(msg.sender == minter, 'GolomToken: only reward distributor can enable');

43: require(!isAirdropMinted, 'already minted');

51: require(!isGenesisRewardMinted, 'already minted');

69: require(minterEnableDate <= block.timestamp, 'GolomToken: wait for timelock');

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L88

File:	contracts/rewards/RewardDistributor.sol
88: require(msg.sender == trader);

144: require(epochs[index] < epoch);

158: require(epochs[index] < epoch);

173: require(address(ve) != address(0), ' VE not added yet');

181: require(tokenowner == ve.ownerOf(tokenids[tindex]), 'Can only claim for a single Address together');

184: require(epochs[index] < epoch, 'cant claim for future epochs');
185: require(claimed[tokenids[tindex]][epochs[index]] == 0, 'cant claim if already claimed');

220: require(address(ve) != address(0), ' VE not added yet');

292: require(traderEnableDate <= block.timestamp, 'RewardDistributor: time not over yet');

309: require(voteEscrowEnableDate <= block.timestamp, 'RewardDistributor: time not over yet');

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L177

File:	contracts/core/GolomTrader.sol 
177: require(signaturesigner == o.signer, 'invalid signature');

211:  require(
212:    o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt + (o.totalAmt * 50) / 10000,
213:    'amt not matching'
214:  );

217: require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm');

220: require(msg.sender == o.reservedAddress);

222: require(o.orderType == 0, 'invalid orderType');

226: require(status == 3, 'order not valid');
227: require(amountRemaining >= amount, 'order already filled');

235: require(amount == 1, 'only 1 erc721 at 1 time');

285: require(
286:   o.totalAmt * amount >
287:    (o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt) * amount + p.paymentAmt

291:  require(msg.sender == o.reservedAddress);

293: require(o.orderType == 1);

295:  require(status == 3);
296:  require(amountRemaining >= amount);

299: require(amount == 1, 'only 1 erc721 at 1 time');

313: require(o.signer == msg.sender);

342: require(o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt);

345: require(msg.sender == o.reservedAddress);

347: require(o.orderType == 2);

349: require(status == 3);
350: require(amountRemaining >= amount);

359: require(amount == 1, 'only 1 erc721 at 1 time');

455: require(distributorEnableDate <= block.timestamp, 'not allowed');

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
72: require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
73: require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');

99: require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');

130: require(blockNumber < block.number, 'VEDelegation: not yet determined');

186: require(blockNumber < block.number, 'VEDelegation: not yet determined');

211: require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');

239: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

245: require(_isApprovedOrOwner(_sender, _tokenId));

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
360: require(_entered_state == _not_entered);

538: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

540: require(_isApprovedOrOwner(_sender, _tokenId));

646: require(owner != address(0));

648: require(_approved != owner);

652: require(senderIsOwner || senderIsApprovedForAll);

869: require(msg.sender == voter);

874: require(msg.sender == voter);

879: require(msg.sender == voter);

884: require(msg.sender == voter);

889: require(msg.sender == voter);

894: require(attachments[_from] == 0 && !voted[_from], 'attached');
895: require(_from != _to);
896: require(_isApprovedOrOwner(msg.sender, _from));
897: require(_isApprovedOrOwner(msg.sender, _to));

927: require(_value > 0); // dev: need non-zero value
928: require(_locked.amount > 0, 'No existing lock found');
929: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');

944: require(_value > 0); // dev: need non-zero value
945: require(unlock_time > block.timestamp, 'Can only lock until time in the future');
946: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');

982: require(_locked.amount > 0, 'No existing lock found');
983: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');

996: require(_locked.end > block.timestamp, 'Lock expired');
997: require(_locked.amount > 0, 'Nothing is locked');
998: require(unlock_time > _locked.end, 'Can only increase lock duration');
999: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');

1008: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

1011: require(block.timestamp >= _locked.end, "The lock didn't expire");

1082: require(idToOwner[_tokenId] != address(0), 'Query for nonexistent token');

1227: require(_isApprovedOrOwner(msg.sender, _tokenId), 'caller is not owner nor approved');

Mitigation

Custom errors are defined using the error statement Replace require statements with custom errors.

[G-22] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 5 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L58

File:	contracts/rewards/RewardDistributor.sol
58:    mapping(address => mapping(uint256 => uint256)) public feesTrader; // fees accumulated by address of trader per epoch
59:    mapping(address => mapping(uint256 => uint256)) public feesExchange; // fees accumulated by exchange of trader per epoch

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
331: /// @dev Mapping from owner address to count of his tokens.
332: mapping(address => uint256) internal ownerToNFTokenCount;

334: /// @dev Mapping from owner address to mapping of index to tokenIds
335: mapping(address => mapping(uint256 => uint256)) internal ownerToNFTokenIdList;

340: /// @dev Mapping from owner address to mapping of operator addresses.
341: mapping(address => mapping(address => bool)) internal ownerToOperators;

[G-23] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 12 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L36

File:	contracts/governance/GolomToken.sol
36: function mint(address _account, uint256 _amount) external onlyMinter {

42: function mintAirdrop(address _airdrop) external onlyOwner {

50: function mintGenesisReward(address _rewardDistributor) external onlyOwner {

58: function setMinter(address _minter) external onlyOwner {

65: function executeSetMinter() external onlyOwner {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L285

File:	contracts/rewards/RewardDistributor.sol
285: function changeTrader(address _trader) external onlyOwner {

291: function executeChangeTrader() external onlyOwner {

298: function addVoteEscrow(address _voteEscrow) external onlyOwner {

308: function executeAddVoteEscrow() external onlyOwner {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L444

File:	contracts/core/GolomTrader.sol 
444: function setDistributor(address _distributor) external onlyOwner {

454: function executeSetDistributor() external onlyOwner {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
260: function changeMinVotingPower(uint256 _newMinVotingPower) external onlyOwner {

[G-24] Do not calculate constants

Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L48

File:	contracts/rewards/RewardDistributor.sol
48: uint256 constant dailyEmission = 600000 * 10**18;

57: uint256 constant secsInDay = 24 * 60 * 60;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
296: uint256 internal constant MAXTIME = 4 * 365 * 86400;
297: int128 internal constant iMAXTIME = 4 * 365 * 86400;

[G-25] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L143

File:	contracts/rewards/RewardDistributor.sol
143: for (uint256 index = 0; index < epochs.length; index++) {

157: for (uint256 index = 0; index < epochs.length; index++) {

180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) {

183: for (uint256 index = 0; index < epochs.length; index++) {

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L415

File:	contracts/core/GolomTrader.sol 
415: for (uint256 i = 0; i < proof.length; i++) {

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
171: for (uint256 index = 0; index < delegated.length; index++) {

189: for (uint256 index = 0; index < delegatednft.length; index++) {

199: for (uint256 i; i < _array.length; i++) {

[G-26] Copying struct to memory can be more expensive than just reading from storage

I suggest using the storage keyword instead of the memory one, as the copy in memory is wasting some MSTOREs and MLOADs. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L925

File:	contracts/vote-escrow/VoteEscrowCore.sol
1083: LockedBalance memory _locked = locked[_tokenId];

1136: Point memory point_1 = point_history[_epoch + 1];

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
101: Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];

should use storage, because each struct field is read only once

[G-27] require()/revert() strings longer than 32 bytes cost extra gas

Each extra chunk of byetes past the original 32 iincurs an MSTORE which costs 3 gas There are 8 instances of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L24

File:	contracts/governance/GolomToken.sol
24: require(msg.sender == minter, 'GolomToken: only reward distributor can enable');

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L181

File:	contracts/rewards/RewardDistributor.sol
181:  require(tokenowner == ve.ownerOf(tokenids[tindex]), 'Can only claim for a single Address together');

292: require(traderEnableDate <= block.timestamp, 'RewardDistributor: time not over yet');

309: require(voteEscrowEnableDate <= block.timestamp, 'RewardDistributor: time not over yet');

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

File:	contracts/vote-escrow/VoteEscrowDelegation.sol
73: require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
929: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');

945: require(unlock_time > block.timestamp, 'Can only lock until time in the future');

983: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');

[G-28] Remove or replace unused variables

Remove or replace unused variables to save deployment gas. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L4

File:	contracts/vote-escrow/VoteEscrowCore.sol
319:    string public constant version = '1.0.0';
320:    uint8 public constant decimals = 18;

[G-29] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas). If getters are still desired, '_' can be added to the variable name and the getter can be added manually

There is 3 instance of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L143

File:	contracts/rewards/RewardDistributor.sol
68: ERC20 public rewardToken;
69: ERC20 public weth;

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
300: address public token;

[G-30] State variables with values known at compile time should be constants

Variables with values known at compile time and that do not change at runtime should be declared as constant There is 2 instance of this issue: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L46

File:	contracts/rewards/RewardDistributor.sol
46: uint256 public startTime; // timestamp at which the contracts need to be activated

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L45

File:	contracts/core/GolomTrader.sol 
45: ERC20 WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

[G-31] State variables should be cached in stack variables rather than re-reading them from storage

Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read. Less obvious fixes/optimizations include having local storage variables of mappings within state variable mappings or mappings within state variable structs, having local storage variables of structs within mappings, having local memory caches of state variable structs, or having local caches of state variable contracts/addresses. Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L98-L138 rewardToken accessed 7 times; epoch accessed 15 times; ve accessed 2 times; epochTotalFee[epoch] accessed 2 times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L144 epoch accessed N times, each loop iteration, where N = epochs.length

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L158 epoch accessed N times, each loop iteration, where N = epochs.length

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L172-L210 ve accessed 8 times epochBeginTime[epochs[index]] accessed 4 times epoch accessed N times, each loop iteration, where N = epochs.length

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L242-L269 distributor accessed two times;

small PoC:

copy distributor to stack variable: Distributor _distributor = distributor ; use _distributor instead of distributor; hardhat-gas-reporter results before fix:

Methods
ContractMethodminmaxavg#calls
GolomTraderfillAsk2381532419482414017
after fix:
Methods
-:-:-:-:-:-:
ContractMethodminmaxavg#calls
GolomTraderfillAsk2380522418472413007

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L382-L383 WETH accessed two times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L384-L402 distributor accessed two times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L138-L139 checkpoints[nftId][nCheckpoints - 1] accessed two times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L213-L214 checkpoint.delegatedTokenIds accessed two times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L644-L650 idToOwner[_tokenId] accessed two times;

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L948-L949 tokenId accessed two times;

[G-32] Using private rather than public for constants, saves gas

If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L317

File:	contracts/vote-escrow/VoteEscrowCore.sol
317:     string public constant name = 'veNFT';
318:    string public constant symbol = 'veNFT';
319:    string public constant version = '1.0.0';
320:    uint8 public constant decimals = 18;

[G-33] Remove unreachable code

There is a peace of code that does not need to exist: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L177

File:	contracts/core/GolomTrader.sol 
177:        require(signaturesigner == o.signer, 'invalid signature');
178:        if (signaturesigner != o.signer) {
179:            return (0, hashStruct, 0); //@audit unreachable code
180:        }

Check inside if is pointless, because signaturesigner != o.signer can not be true, when if it is, then transaction is already reverted because of line 177. hardhat-gas-reporter shows deployment gas difference from 2013842 to 2001108.

[G-34] No need to evaluate all expressions to know if one of them is true

When we have a code expressionA || expressionB if expressionA is true then expressionB will not be evaluated and gas saved; Instances include: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L650

File:	contracts/vote-escrow/VoteEscrowCore.sol
650: bool senderIsOwner = (idToOwner[_tokenId] == msg.sender);
651: bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
652: require(senderIsOwner || senderIsApprovedForAll);

Variables bool senderIsOwner and bool senderIsApprovedForAll just add more work, it should be:

 require((idToOwner[_tokenId] == msg.sender) || (ownerToOperators[owner])[msg.sender];

so if (idToOwner[_tokenId] == msg.sender) is true we will not do SLOAD to get (ownerToOperators[owner])[msg.sender]; saving runtime and deployment gas;

same thing here: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L439

File:	contracts/vote-escrow/VoteEscrowCore.sol
439:        bool spenderIsOwner = owner == _spender;
440:        bool spenderIsApproved = _spender == idToApprovals[_tokenId];
441:        bool spenderIsApprovedForAll = (ownerToOperators[owner])[_spender];
442:        return spenderIsOwner || spenderIsApproved || spenderIsApprovedForAll;

But this one is a view function, so only deployment gas saved.

[G-35] No need to read tokenId second time

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
948:        ++tokenId;
949:        uint256 _tokenId = tokenId;

Mitigation

Change it to uint256 _tokenId = ++tokenId; and 92 gas is saved this way

[G-36] last_point value rewritten right after initialization

If _epoch > 0, then last_point is rewritten, and initialization on L726 becomes waste of gas; https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#726

File:	contracts/vote-escrow/VoteEscrowCore.sol
726:        Point memory last_point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});
727:        if (_epoch > 0) {
728:            last_point = point_history[_epoch];
729:        }

Mitigation

Point memory last_point = _epoch > 0 ? last_point = point_history[_epoch] 
																				: Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});

[G-37] Wasted gas on copying a struct

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

File:	contracts/vote-escrow/VoteEscrowCore.sol
840: LockedBalance memory _locked = locked_balance;

1164:     function _supply_at(Point memory point, uint256 t) internal view returns (uint256) {
1165:         Point memory last_point = point;

locked_balance is of same type and never used after this. Same thing with point; There is no need in copy of it, but gas for making a copy is spent;

[G-38] Remove duplicate code

There is a peace of code that is duplicated in bo blocks of if else https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#467

467:    if (current_count == current_index) {
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_count] = 0;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[_tokenId] = 0;
        } else {
            uint256 lastTokenId = ownerToNFTokenIdList[_from][current_count];

            // Add
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_index] = lastTokenId;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[lastTokenId] = current_index;

            // Delete
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_count] = 0;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[_tokenId] = 0;
        }

To reduce deployment gas should be:

	if (current_count != current_index) {
			uint256 lastTokenId = ownerToNFTokenIdList[_from][current_count];

			// Add
			// update ownerToNFTokenIdList
			ownerToNFTokenIdList[_from][current_index] = lastTokenId;
			// update tokenToOwnerIndex
			tokenToOwnerIndex[lastTokenId] = current_index;
	} 
	// update ownerToNFTokenIdList
	ownerToNFTokenIdList[_from][current_count] = 0;
	// update tokenToOwnerIndex
	tokenToOwnerIndex[_tokenId] = 0;

[G-39] No need for mapping supportedInterfaces to exist

This mapping takes some space and every time supportsInterface(bytes4) is called we do SLOAD to get data from this mapping. https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L368

File:	contracts/vote-escrow/VoteEscrowCore.sol
368:     function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
369:         return supportedInterfaces[_interfaceID];
370:     }

I suggest we remove the mapping and change the function supportsInterface(bytes4) to:

368:     function supportsInterface(bytes4 _interfaceID) external virtual view returns (bool) {
369:         return _interfaceID == ERC165_INTERFACE_ID ||
											_interfaceID == ERC721_INTERFACE_ID ||
											_interfaceID == ERC721_METADATA_INTERFACE_ID
370:     }

This way we don't need the mapping supportedInterfaces and no SLOADs done; If inherited contracts support other interfaces or don't support these just override this function and add/remove conditions;

[G-40] Same calculation twice

supply_before - value calculated twice, also it is checked arithmetic operation, so cheaper to store result in a stack variable than calculate it twice; https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L1015-L1029

File:	contracts/vote-escrow/VoteEscrowCore.sol
1015:         uint256 supply_before = supply;
1016:         supply = supply_before - value;

1029:         emit Supply(supply_before, supply_before - value);

We could store result to stack variable and use it instead:

1016:         uint256 supply_now = supply = supply_before - value;

1029:         emit Supply(supply_before, supply_now);

[G-41] Obsolete constants

These values can be accessed via kewords, there is no benefit in having these constants: https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L57

File:	contracts/rewards/RewardDistributor.sol
57:     uint256 constant secsInDay = 24 * 60 * 60; 

We can use 1 days instead of secsInDay https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L4

File:	contracts/vote-escrow/VoteEscrowCore.sol
295:     uint256 internal constant WEEK = 1 weeks;

We can use 1 weeks instead of WEEK

[G-42] Variable end can be of type uint128

Variable end can be of type uint128 to take less place and save one storage slot. A lot of protocols even use uint64 for dates to reduce gas usage. https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L270-L273

File:	contracts/vote-escrow/VoteEscrowCore.sol
270: struct LockedBalance {
271:     int128 amount;
272:     uint256 end; //@audit can be uint128 to save storage slot
273: }

it can be

270: struct LockedBalance {
271:     int128 amount;
272:     uint128 end;
273: }
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