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
Rank: 10/179
Findings: 9
Award: $1,582.32
π Selected for report: 1
π Solo Findings: 0
189.5656 USDC - $189.57
Users can be blocked by other users, from being able to delegate
The _writeCheckpoint()
function looks up the existing checkpoint and attempts to update its delegation array if one exists (line 104):
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #1 94 function _writeCheckpoint( 95 uint256 toTokenId, 96 uint256 nCheckpoints, 97 uint256[] memory _delegatedTokenIds 98 ) internal { 99 require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more'); 100 101 Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1]; 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: }
However, because the oldCheckpoint
variable is a memory
variable and not a storage
variable, the assignment of _delegatedTokenIds
does not actually get assigned to contract storage, and only lives in the memory
variable. This means the first user to delegate to a specific token in a block, is the only one able to actually have the delegation applied.
If the malicious user is a miner, that user can monitor the mempool for checkpoint updates and do a sandwich attack, where the order of transactions is:
removeDelegation()
to reclaim their votesCode inspection
Use checkpoints[toTokenId][nCheckpoints]
rather than oldCheckpoint.delegatedTokenIds
in the assignment on line 104
#0 - KenzoAgada
2022-08-02T08:14:33Z
Duplicate of #455
550.3388 USDC - $550.34
Queries for historical voting power will have the wrong answer, and any DAO/reward decisions made based off it it will be wrong
removeDelegation()
is marked as external
, but is called as if it were public
by using the this
address:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #1 233 function _transferFrom( 234 address _from, 235 address _to, 236 uint256 _tokenId, 237 address _sender 238 ) internal override { 239 require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached'); 240 241 // remove the delegation 242: this.removeDelegation(_tokenId);
removeDelegation()
fetches the current checkpoints, removes the delegated token (if it can find it), and then writes a new checkpoint. However, because it uses a storage
variable for the lookup, and then passes it to removeElement()
, which also takes in a storage variable, any changes made to the array are immediately applied to the contract's state variable, meaning that the old checkpoint is having its entry being removed, and and then a new checkpoint is written with a copy of the same storage array:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #2 210 function removeDelegation(uint256 tokenId) external { 211 require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); 212 uint256 nCheckpoints = numCheckpoints[tokenId]; 213 Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1]; 214 removeElement(checkpoint.delegatedTokenIds, tokenId); 215 _writeCheckpoint(tokenId, nCheckpoints, checkpoint.delegatedTokenIds); 216: }
In other words, the token is removed from the old checkpoint, the old checkpoint is saved, and a new checkpoint is added, which has the same values as the old checkpoint. This means that any prior delegations are always removed. This happens any time _transferFrom()
is called, which is any time the token is transferred.
Code inspection
Use memory
for the checkpoint
variable, as well as the argument to removeElement()
, so that a copy of the state array is made and operated upon
#0 - KenzoAgada
2022-08-02T08:07:32Z
Duplicate of #81 where it is considered together with the same issue in delegate
- up to judge to decide whether they are separate or not
#1 - dmvt
2022-10-12T15:26:41Z
I agree that this is a duplicate of #81
93.2805 USDC - $93.28
Delegation, and thus vote tracking, accounting breaks as soon as anyone delegates to a different address than the owner address. Broken accounting means invalid vote tallies, leading to the wrong outcomes for all voting decisions
delegate()
sets up delegation by fetching the checkpoints of the token to which a token is being delegated, and adds the delegated token to that checkpoint on lines 80 and 84:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #1 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); 81 _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds); 82 } else { 83 uint256[] memory array = new uint256[](1); 84 array[0] = tokenId; 85 _writeCheckpoint(toTokenId, nCheckpoints, array); 86 } 87 88 emit DelegateChanged(tokenId, toTokenId, msg.sender); 89: }
Note that above, delgate()
assigns tokenId
to toTokenId
's checkpoints. removeDelegation()
on the other hand, removes tokenId
from its own checkpoints:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #2 208 /// @notice Remove delegation 209 /// @param tokenId TokenId of which delegation needs to be removed 210 function removeDelegation(uint256 tokenId) external { 211 require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); 212 uint256 nCheckpoints = numCheckpoints[tokenId]; 213 Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1]; 214 removeElement(checkpoint.delegatedTokenIds, tokenId); 215 _writeCheckpoint(tokenId, nCheckpoints, checkpoint.delegatedTokenIds); 216: }
If tokenId
wasn't delegated to itself, then the call to removeElement()
will not fail, and a new checkpoint will be written with the token still being delegated to its old location:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #3 198 function removeElement(uint256[] storage _array, uint256 _element) internal { 199 for (uint256 i; i < _array.length; i++) { 200 if (_array[i] == _element) { 201 _array[i] = _array[_array.length - 1]; 202 _array.pop(); 203 break; 204 } 205 } 206: }
Code inspection
Use a storage mapping
for tracking which toTokenId
the token is currently delegated to, and remove from that token's checkpoints
#0 - KenzoAgada
2022-08-02T08:23:16Z
Duplicate of #751
π Selected for report: cloudjunky
Also found by: 0x1f8b, 0x4non, 0x52, 0xDjango, 0xHarry, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 8olidity, Bnke0x0, CertoraInc, Chom, Deivitto, Dravee, GalloDaSballo, GimelSec, IllIllI, Jmaxmanblue, JohnSmith, Jujic, Kenshin, Krow10, Lambda, MEP, Noah3o6, RedOneN, Ruhum, StErMi, StyxRave, TomJ, Treasure-Seeker, TrungOre, _Adam, __141345__, arcoun, asutorufos, bardamu, bearonbike, bin2chen, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, cccz, codexploder, cryptonue, cryptphi, cthulhu_cult, dharma09, dipp, djxploit, durianSausage, ellahi, giovannidisiena, hansfriese, horsefacts, hyh, immeas, indijanc, jayjonah8, jayphbee, joestakey, kenzo, kyteg, ladboy233, minhquanym, navinavu, obront, oyc_109, peritoflores, rbserver, reassor, rokinot, rotcivegaf, saian, scaraven, shenwilly, simon135, sseefried, teddav, zzzitron
0.0037 USDC - $0.00
Judge has assessed an item in Issue #796 as Medium risk. The relevant finding follows:
[Lβ01] Use of transferFrom()
rather than safeTransferFrom()
for NFTs in will lead to the loss of NFTs
The EIP-721 standard says the following about transferFrom()
:
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE /// THEY MAY BE PERMANENTLY LOST /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
https://github.com/ethereum/EIPs/blob/78e2c297611f5e92b6a5112819ab71f74041ff25/EIPS/eip-721.md?plain=1#L103-L113
Code must use the safeTransferFrom()
flavor if it hasn't otherwise verified that the receiving address can handle it
There is 1 instance of this issue:
File: contracts/core/GolomTrader.sol 236: ERC721(o.collection).transferFrom(o.signer, receiver, o.tokenId);
#0 - dmvt
2022-10-21T14:39:16Z
Duplicate of #343
π Selected for report: sseefried
Also found by: 0x4non, IllIllI, Jmaxmanblue, JohnSmith, Lambda, arcoun, berndartmueller, cccz, csanuragjain, minhquanym, rbserver, rotcivegaf
47.2456 USDC - $47.25
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L677-L684 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L607
Tokens may be lost when given to non-EOA accounts
The code has the following warning for safeTransferFrom()
:
File: /contracts/vote-escrow/VoteEscrowCore.sol #1 83 /** 84 * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 85: * are aware of the ERC721 protocol to prevent tokens from being forever locked.
The minting code, which creates the locks, does not do this check:
File: /contracts/vote-escrow/VoteEscrowCore.sol #2 677 function _mint(address _to, uint256 _tokenId) internal returns (bool) { 678 // Throws if `_to` is zero address 679 assert(_to != address(0)); 680 // Add NFT. Throws if `_tokenId` is owned by someone 681 _addTokenTo(_to, _tokenId); 682 emit Transfer(address(0), _to, _tokenId); 683 return true; 684: }
As a separate issue, once a lock is already minted, if it's transfered to a contract, the code does call onERC721Received()
:
File: /contracts/vote-escrow/VoteEscrowCore.sol #3 594 function safeTransferFrom( 595 address _from, 596 address _to, 597 uint256 _tokenId, 598 bytes memory _data 599 ) public { 600 _transferFrom(_from, _to, _tokenId, msg.sender); 601 602 if (_isContract(_to)) { 603 // Throws if transfer destination is a contract which does not implement 'onERC721Received' 604 try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4) {} catch ( 605 bytes memory reason 606 ) { 607 if (reason.length == 0) { 608 revert('ERC721: transfer to non ERC721Receiver implementer'); 609 } else { 610 assembly { 611 revert(add(32, reason), mload(reason)) 612 } 613 } 614 } 615 } 616: }
While the transfer function does in fact execute onERC721Received()
, it doesn't actually do a check of the bytes4
variable - it only checks for a non-zero length. The ERC721 standard says that for the function call to be valid, it must return the bytes4
function selector, otherwise it's invalid. If a user of the escrow system uses a contract that is attempting to explicitly reject NFTs by returning zero in its onERC721Received()
function, the VotingEscrowCore
will interpret that response as success and will transfer the NFT, potentially locking it forever. If the lock is minted to a contract, no checks are done, and the NFT can be locked forever.
Code inspection
Call onERC721Received()
in _mint()
and ensure that the return value equals IERC721Receiver.onERC721Received.selector
in both _mint()
and safeTransferFrom()
#0 - KenzoAgada
2022-08-04T02:09:40Z
The onERC721Received issue is duplicate of #577 The minting issue is duplicate of #618 This and #618 are identical issues, looks like both copy pasted from Velodrome contest, possibly other contests as well
#1 - dmvt
2022-10-14T16:37:32Z
Duplicate of #577
π Selected for report: horsefacts
Also found by: GalloDaSballo, IllIllI, berndartmueller, csanuragjain, hansfriese, kenzo, minhquanym, rotcivegaf
104.014 USDC - $104.01
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L1026 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L906
Approved users are unable to call merge()
or withdraw()
, breaking the basic fuctionality of the protocol
withdraw()
checks that the user is either the owner or the approver using the _isApprovedOrOwner()
function on line 1007, and later calls _burn()
on line 1026:
File: /contracts/vote-escrow/VoteEscrowCore.sol #1 1004 /// @notice Withdraw all tokens for `_tokenId` 1005 /// @dev Only possible if the lock has expired 1006 function withdraw(uint256 _tokenId) external nonreentrant { 1007 assert(_isApprovedOrOwner(msg.sender, _tokenId)); 1008 require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached'); 1009 1010 LockedBalance memory _locked = locked[_tokenId]; 1011 require(block.timestamp >= _locked.end, "The lock didn't expire"); 1012 uint256 value = uint256(int256(_locked.amount)); 1013 1014 locked[_tokenId] = LockedBalance(0, 0); 1015 uint256 supply_before = supply; 1016 supply = supply_before - value; 1017 1018 // old_locked can have either expired <= timestamp or zero end 1019 // _locked has only 0 end 1020 // Both can have >= 0 amount 1021 _checkpoint(_tokenId, _locked, LockedBalance(0, 0)); 1022 1023 assert(IERC20(token).transfer(msg.sender, value)); 1024 1025 // Burn the NFT 1026 _burn(_tokenId); 1027 1028 emit Withdraw(msg.sender, _tokenId, value, block.timestamp); 1029 emit Supply(supply_before, supply_before - value); 1030: }
_burn()
correctly calls _isApprovedOrOwner()
, but when it does the transfer, it uses msg.sender
on line 1234:
File: /contracts/vote-escrow/VoteEscrowCore.sol #2 1226 function _burn(uint256 _tokenId) internal { 1227 require(_isApprovedOrOwner(msg.sender, _tokenId), 'caller is not owner nor approved'); 1228 1229 address owner = ownerOf(_tokenId); 1230 1231 // Clear approval 1232 approve(address(0), _tokenId); 1233 // Remove token 1234 _removeTokenFrom(msg.sender, _tokenId); 1235 emit Transfer(owner, address(0), _tokenId); 1236: }
_removeTokenFrom()
will only work for the owner of the token:
File: /contracts/vote-escrow/VoteEscrowCore.sol #3 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 { 505 // Throws if `_from` is not the current owner 506: assert(idToOwner[_tokenId] == _from);
merge()
has the same _burn()
issue on line 906:
File: /contracts/vote-escrow/VoteEscrowCore.sol #4 893 function merge(uint256 _from, uint256 _to) external { 894 require(attachments[_from] == 0 && !voted[_from], 'attached'); 895 require(_from != _to); 896 require(_isApprovedOrOwner(msg.sender, _from)); 897 require(_isApprovedOrOwner(msg.sender, _to)); 898 899 LockedBalance memory _locked0 = locked[_from]; 900 LockedBalance memory _locked1 = locked[_to]; 901 uint256 value0 = uint256(int256(_locked0.amount)); 902 uint256 end = _locked0.end >= _locked1.end ? _locked0.end : _locked1.end; 903 904 locked[_from] = LockedBalance(0, 0); 905 _checkpoint(_from, _locked0, LockedBalance(0, 0)); 906: _burn(_from);
Code inspection
In _burn()
, change the argument to _removeTokenFrom()
from msg.sender
to owner
#0 - KenzoAgada
2022-08-02T06:04:31Z
Duplicate of #858
π Selected for report: 0x52
Also found by: IllIllI, berndartmueller, kenzo, rotcivegaf
285.3609 USDC - $285.36
Users can get unlimited votes which leads to them:
Users are required to call delegate()
to set up the initial token delegation. Thereafter, the overridden version of _transferFrom()
removes the delegation on line 242:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol #1 233 function _transferFrom( 234 address _from, 235 address _to, 236 uint256 _tokenId, 237 address _sender 238 ) internal override { 239 require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached'); 240 241 // remove the delegation 242 this.removeDelegation(_tokenId); 243 244 // Check requirements 245 require(_isApprovedOrOwner(_sender, _tokenId)); 246 // Clear approval. Throws if `_from` is not the current owner 247 _clearApproval(_from, _tokenId); 248 // Remove NFT. Throws if `_tokenId` is not a valid NFT 249 _removeTokenFrom(_from, _tokenId); 250 // Add NFT 251 _addTokenTo(_to, _tokenId); 252 // Set the block of ownership transfer (for Flash NFT protection) 253 ownership_change[_tokenId] = block.number; 254 // Log the transfer 255 emit Transfer(_from, _to, _tokenId); 256: }
But _burn()
is not overriden, and doesn't alter delegation:
File: /contracts/vote-escrow/VoteEscrowCore.sol #2 1226 function _burn(uint256 _tokenId) internal { 1227 require(_isApprovedOrOwner(msg.sender, _tokenId), 'caller is not owner nor approved'); 1228 1229 address owner = ownerOf(_tokenId); 1230 1231 // Clear approval 1232 approve(address(0), _tokenId); 1233 // Remove token 1234 _removeTokenFrom(msg.sender, _tokenId); 1235 emit Transfer(owner, address(0), _tokenId); 1236: }
_burn()
is called by both merge()
and withdraw()
, so to get free votes, an attacker can deposit a token, lock it, wait for the lock to expire, withdraw()
to or mege()
to another address, and repeat.
Code inspection
Override _burn()
and have the new version call removeDelegation()
#0 - KenzoAgada
2022-08-02T12:11:43Z
Duplicate of #59
π Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0x52, 0xA5DF, 0xDjango, 0xLovesleep, 0xNazgul, 0xNineDec, 0xSmartContract, 0xackermann, 0xc0ffEE, 0xf15ers, 0xmatt, 0xsanson, 0xsolstars, 8olidity, AuditsAreUS, Bahurum, Bnke0x0, CRYP70, CertoraInc, Ch_301, Chom, CryptoMartian, Deivitto, DevABDee, Dravee, ElKu, Franfran, Funen, GalloDaSballo, GimelSec, GiveMeTestEther, Green, JC, Jmaxmanblue, JohnSmith, Jujic, Junnon, Kenshin, Krow10, Kumpa, Lambda, MEP, Maxime, MiloTruck, Mohandes, NoamYakov, Picodes, RedOneN, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, Soosh, StErMi, StyxRave, Tadashi, TomJ, Treasure-Seeker, TrungOre, Waze, _Adam, __141345__, ajtra, ak1, apostle0x01, arcoun, asutorufos, async, benbaessler, berndartmueller, bin2chen, brgltd, c3phas, cRat1st0s, carlitox477, chatch, codetilda, codexploder, cryptonue, cryptphi, csanuragjain, cthulhu_cult, delfin454000, dipp, dirk_y, djxploit, ellahi, exd0tpy, fatherOfBlocks, giovannidisiena, hansfriese, horsefacts, hyh, idkwhatimdoing, indijanc, jayfromthe13th, jayphbee, joestakey, kenzo, kyteg, lucacez, luckypanda, mics, minhquanym, obront, oyc_109, pedr02b2, rajatbeladiya, rbserver, reassor, robee, rokinot, rotcivegaf, sach1r0, saian, saneryee, sashik_eth, scaraven, shenwilly, simon135, sseefried, supernova, teddav, ych18, zuhaibmohd, zzzitron
217.8492 USDC - $217.85
Issue | Instances | |
---|---|---|
[Lβ01] | Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs | 1 |
[Lβ02] | Don't use payable.transfer() /payable.send() | 1 |
[Lβ03] | Unused/empty receive() /fallback() function | 4 |
[Lβ04] | require() should be used instead of assert() | 13 |
[Lβ05] | Self-delegation is not automatic | 1 |
[Lβ06] | Function may run out of gas | 1 |
[Lβ07] | Vulnerable to cross-chain replay attacks due to static DOMAIN_SEPARATOR /domainSeparator | 1 |
[Lβ08] | Wrong comment | 1 |
[Lβ09] | Missing checks for address(0x0) when assigning values to address state variables | 7 |
[Lβ10] | Only a billion checkpoints available | 1 |
Total: 31 instances over 10 issues
Issue | Instances | |
---|---|---|
[Nβ01] | Consider addings checks for signature malleability | 1 |
[Nβ02] | ecrecover() signature validity not checked | 1 |
[Nβ03] | Boilerplate not replaced | 2 |
[Nβ04] | Remove commented out code | 1 |
[Nβ05] | Invalid/extraneous/optional function definitions in interface | 4 |
[Nβ06] | Remove include for hardhat's console | 1 |
[Nβ07] | Contract implements interface without extending the interface | 1 |
[Nβ08] | require() /revert() statements should have descriptive reason strings | 31 |
[Nβ09] | public functions not called by the contract should be declared external instead | 13 |
[Nβ10] | Non-assembly method available | 2 |
[Nβ11] | constant s should be defined rather than using magic numbers | 49 |
[Nβ12] | Numeric values having to do with time should use time units for readability | 5 |
[Nβ13] | Large multiples of ten should use scientific notation (e.g. 1e6 ) rather than decimal literals (e.g. 1000000 ), for readability | 2 |
[Nβ14] | Missing event and or timelock for critical parameter change | 3 |
[Nβ15] | Use a more recent version of solidity | 2 |
[Nβ16] | Use scientific notation (e.g. 1e18 ) rather than exponentiation (e.g. 10**18 ) | 2 |
[Nβ17] | Inconsistent spacing in comments | 2 |
[Nβ18] | Lines are too long | 8 |
[Nβ19] | Inconsistent method of specifying a floating pragma | 1 |
[Nβ20] | Variable names that consist of all capital letters should be reserved for constant /immutable variables | 2 |
[Nβ21] | Non-library/interface files should use fixed compiler versions, not floating ones | 1 |
[Nβ22] | Typos | 24 |
[Nβ23] | File does not contain an SPDX Identifier | 1 |
[Nβ24] | NatSpec is incomplete | 19 |
[Nβ25] | Event is missing indexed fields | 7 |
[Nβ26] | Duplicated require() /revert() checks should be refactored to a modifier or function | 14 |
Total: 199 instances over 26 issues
transferFrom()
rather than safeTransferFrom()
for NFTs in will lead to the loss of NFTsThe EIP-721 standard says the following about transferFrom()
:
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE /// THEY MAY BE PERMANENTLY LOST /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
https://github.com/ethereum/EIPs/blob/78e2c297611f5e92b6a5112819ab71f74041ff25/EIPS/eip-721.md?plain=1#L103-L113
Code must use the safeTransferFrom()
flavor if it hasn't otherwise verified that the receiving address can handle it
There is 1 instance of this issue:
File: contracts/core/GolomTrader.sol 236: ERC721(o.collection).transferFrom(o.signer, receiver, o.tokenId);
payable.transfer()
/payable.send()
The use of payable.transfer()
is heavily frowned upon because it can lead to the locking of funds. The transfer()
call requires that the recipient is either an EOA account, or is a contract that has a payable
callback. For the contract case, the transfer()
call only provides 2300 gas for the contract to complete its operations. This means the following cases can cause the transfer to fail:
payable
callbackpayable
callback spends more than 2300 gas (which is only enough to emit something)Address.sendValue()
insteadThere is 1 instance of this issue:
File: contracts/core/GolomTrader.sol 154: payable(payAddress).transfer(payAmt); // royalty transfer to royaltyaddress
receive()
/fallback()
functionIf the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth))
)
There are 4 instances of this issue:
File: contracts/core/GolomTrader.sol 459: fallback() external payable {} 461: receive() external payable {}
File: contracts/rewards/RewardDistributor.sol 313: fallback() external payable {} 315: receive() external payable {}
require()
should be used instead of assert()
Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()
/revert()
do. assert()
should be avoided even past solidity version 0.8.0 as its documentation states that "The assert function creates an error of type Panic(uint256). ... 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".
There are 13 instances of this issue:
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);
Unlike some of the other functions, _mint()
isn't overridden to call delegate()
, which means the user may forget to do so and will miss out
There is 1 instance of this issue:
File: /contracts/vote-escrow/VoteEscrowCore.sol 677 function _mint(address _to, uint256 _tokenId) internal returns (bool) { 678 // Throws if `_to` is zero address 679 assert(_to != address(0)); 680 // Add NFT. Throws if `_tokenId` is owned by someone 681 _addTokenTo(_to, _tokenId); 682 emit Transfer(address(0), _to, _tokenId); 683 return true; 684: }
Once the number of epochs grow to a large number, the array allocated will be large, and the number of iterations calling external functions on ve
will also be large, leading to the function running out of gas
There is 1 instance of this issue:
File: /contracts/rewards/RewardDistributor.sol 215 function stakerRewards(uint256 tokenid) public view returns ( 216 uint256, 217 uint256, 218 uint256[] memory 219 ){ 220 require(address(ve) != address(0), ' VE not added yet'); 221 222 uint256 reward = 0; 223 uint256 rewardEth = 0; 224 uint256[] memory unclaimedepochs = new uint256[](epoch); 225 // for each epoch 226: for (uint256 index = 0; index < epoch; index++) {
DOMAIN_SEPARATOR
/domainSeparator
See this issue from a prior contest for details
There is 1 instance of this issue:
File: /contracts/core/GolomTrader.sol 101 EIP712_DOMAIN_TYPEHASH = keccak256( 102 abi.encode( 103 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), 104 keccak256(bytes('GOLOM.IO')), 105 keccak256(bytes('1')), 106 chainId, 107 address(this) 108 ) 109: );
The function description and return values are incorrectly copied from another function
There is 1 instance of this issue:
File: /contracts/rewards/RewardDistributor.sol 252 /// @dev returns unclaimed rewards of an NFT, returns (unclaimed golom rewards, unclaimed eth rewards, unclaimed epochs) 253 /// @param addr the nft id to claim rewards for all ids in the list must belong to 1 address 254 function traderRewards(address addr) public view returns ( 255 uint256 256: ){
address(0x0)
when assigning values to address
state variablesThere are 7 instances of this issue:
File: contracts/core/GolomTrader.sol 448: pendingDistributor = _distributor;
File: contracts/governance/GolomToken.sol 59: pendingMinter = _minter;
File: contracts/rewards/RewardDistributor.sol 81: trader = _trader; 287: pendingTrader = _trader; 303: pendingVoteEscrow = _voteEscrow;
File: contracts/vote-escrow/VoteEscrowCore.sol 870: voter = _voter;
File: contracts/vote-escrow/VoteEscrowDelegation.sol 53: token = _token;
A user can only have a billion checkpoints which, if the user is a DAO, may cause issues down the line, especially if the last checkpoint involved delegating and can thereafter not be undone
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol 535: mapping(uint => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
Use OpenZeppelin's ECDSA
contract rather than calling ecrecover()
directly
There is 1 instance of this issue:
File: /contracts/core/GolomTrader.sol 176 address signaturesigner = ecrecover(hash, o.v, o.r, o.s); 177 require(signaturesigner == o.signer, 'invalid signature'); 178 if (signaturesigner != o.signer) { 179 return (0, hashStruct, 0); 180: }
ecrecover()
signature validity not checkedecrecover()
returns the zero address if the signature is invalid. If the signer provided is also zero, then all incorrect signatures will be allowed
There is 1 instance of this issue:
File: /contracts/core/GolomTrader.sol 176 address signaturesigner = ecrecover(hash, o.v, o.r, o.s); 177 require(signaturesigner == o.signer, 'invalid signature'); 178 if (signaturesigner != o.signer) { 179 return (0, hashStruct, 0); 180: }
There are 2 instances of this issue:
File: /contracts/governance/GolomToken.sol 5: /// @notice Explain to an end user what this does
File: /contracts/vote-escrow/VoteEscrowDelegation.sol 68: /// @notice Explain to an end user what this does
There is 1 instance of this issue:
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: // }
There are 4 instances of this issue:
File: contracts/core/GolomTrader.sol /// @audit withdraw(uint256) isn't defined with those arguments in the standard ERC20 definition 33: function withdraw(uint256 wad) external;
File: contracts/rewards/RewardDistributor.sol /// @audit mint(address,uint256) isn't defined with those arguments in the standard ERC20 definition 24: function mint(address account, uint256 amount) external; /// @audit balanceOfNFTAt(uint256,uint256) isn't defined with those arguments in the standard ERC20 definition 26: function balanceOfNFTAt(uint256 _tokenId, uint256 _t) external view returns (uint256); /// @audit deposit() isn't defined with those arguments in the standard ERC20 definition 28: function deposit() external payable;
include
for hardhat's consoleThere is 1 instance of this issue:
File: contracts/rewards/RewardDistributor.sol 9: import 'hardhat/console.sol';
Not extending the interface may lead to the wrong function signature being used, leading to unexpected behavior. If the interface is in fact being implemented, use the override
keyword to indicate that fact
There is 1 instance of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit IERC721Enumerable.tokenOfOwnerByIndex() 275: contract VoteEscrowCore is IERC721, IERC721Metadata {
require()
/revert()
statements should have descriptive reason stringsThere are 31 instances of this issue:
File: contracts/core/GolomTrader.sol 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: ); // cause bidder eth is paying for seller payment p , dont take anything extra from seller 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);
File: contracts/rewards/RewardDistributor.sol 88: require(msg.sender == trader); 144: require(epochs[index] < epoch); 158: require(epochs[index] < epoch);
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
File: contracts/vote-escrow/VoteEscrowDelegation.sol 245: require(_isApprovedOrOwner(_sender, _tokenId));
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
There are 13 instances of this issue:
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 {
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 { 215 function stakerRewards(uint256 tokenid) public view returns ( 216 uint256, 217 uint256, 218: uint256[] memory 254 function traderRewards(address addr) public view returns ( 255: uint256 269 function exchangeRewards(address addr) public view returns ( 270: uint256
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) {
File: contracts/vote-escrow/VoteEscrowDelegation.sol 185: function getPriorVotes(uint256 tokenId, uint256 blockNumber) public view returns (uint256) {
assembly{ id := chainid() }
=> uint256 id = block.chainid
, assembly { size := extcodesize() }
=> uint256 size = address().code.length
There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary
There are 2 instances of this issue:
File: contracts/core/GolomTrader.sol 98: chainId := chainid()
File: contracts/vote-escrow/VoteEscrowCore.sol 577: size := extcodesize(account)
constant
s should be defined rather than using magic numbersEven assembly can benefit from using readable constants instead of hex/numeric literals
There are 49 instances of this issue:
File: contracts/core/GolomTrader.sol /// @audit 50 /// @audit 10000 212: o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt + (o.totalAmt * 50) / 10000, /// @audit 3 226: require(status == 3, 'order not valid'); /// @audit 50 /// @audit 10000 242: payEther(((o.totalAmt * 50) / 10000) * amount, address(distributor)); /// @audit 50 254: (o.totalAmt * 50) / /// @audit 10000 255: 10000 - /// @audit 50 /// @audit 10000 263: (o.totalAmt - (o.totalAmt * 50) / 10000 - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount, /// @audit 50 /// @audit 10000 269: distributor.addFee([o.signer, o.exchange.paymentAddress], ((o.totalAmt * 50) / 10000) * amount); /// @audit 3 295: require(status == 3); /// @audit 3 349: require(status == 3); /// @audit 50 /// @audit 10000 381: uint256 protocolfee = ((o.totalAmt * 50) / 10000) * amount; /// @audit 0x00 436: mstore(0x00, a) /// @audit 0x20 437: mstore(0x20, b) /// @audit 0x00 /// @audit 0x40 438: value := keccak256(0x00, 0x40)
File: contracts/governance/GolomToken.sol /// @audit 150_000_000 /// @audit 1e18 44: _mint(_airdrop, 150_000_000 * 1e18); /// @audit 62_500_000 /// @audit 1e18 52: _mint(_rewardDistributor, 62_500_000 * 1e18);
File: contracts/rewards/RewardDistributor.sol /// @audit 1659211200 84: startTime = 1659211200; /// @audit 1000000000 /// @audit 18 100: if (rewardToken.totalSupply() > 1000000000 * 10**18) { /// @audit 67 /// @audit 100 120: rewardTrader[epoch] = ((tokenToEmit - stakerReward) * 67) / 100; /// @audit 33 /// @audit 100 121: rewardExchange[epoch] = ((tokenToEmit - stakerReward) * 33) / 100;
File: contracts/vote-escrow/TokenUriHelper.sol /// @audit 4 /// @audit 3 17: uint256 encodedLen = 4 * ((len + 2) / 3); /// @audit 32 20: bytes memory result = new bytes(encodedLen + 32); /// @audit 0xffffff 34: let input := and(mload(add(data, i)), 0xffffff) /// @audit 0x3F 36: let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) /// @audit 0x3F /// @audit 0xFF 38: out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) /// @audit 0x3F /// @audit 0xFF 40: out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) /// @audit 0x3F /// @audit 0xFF 42: out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) /// @audit 0x3d3d 52: mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) /// @audit 0x3d 55: mstore(sub(resultPtr, 1), shl(248, 0x3d)) /// @audit 48 144: buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit 255 745: for (uint256 i = 0; i < 255; ++i) { /// @audit 128 1044: for (uint256 i = 0; i < 128; ++i) { /// @audit 128 1115: for (uint256 i = 0; i < 128; ++i) { /// @audit 255 1167: for (uint256 i = 0; i < 255; ++i) {
File: contracts/vote-escrow/VoteEscrowDelegation.sol /// @audit 500 99: require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used
There are 5 instances of this issue:
File: contracts/rewards/RewardDistributor.sol /// @audit 600000 48: uint256 constant dailyEmission = 600000 * 10**18; /// @audit 60 /// @audit 60 57: uint256 constant secsInDay = 24 * 60 * 60;
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit 86400 296: uint256 internal constant MAXTIME = 4 * 365 * 86400; /// @audit 86400 297: int128 internal constant iMAXTIME = 4 * 365 * 86400;
1e6
) rather than decimal literals (e.g. 1000000
), for readabilityThere are 2 instances of this issue:
File: contracts/rewards/RewardDistributor.sol 100: if (rewardToken.totalSupply() > 1000000000 * 10**18) {
File: contracts/vote-escrow/VoteEscrowCore.sol 308: mapping(uint256 => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
Events help non-contract tools to track changes, and events prevent users from being surprised by changes
There are 3 instances of this issue:
File: contracts/core/GolomTrader.sol 444 function setDistributor(address _distributor) external onlyOwner { 445 if (address(distributor) == address(0)) { 446 distributor = Distributor(_distributor); 447 } else { 448 pendingDistributor = _distributor; 449 distributorEnableDate = block.timestamp + 1 days; 450 } 451: }
File: contracts/governance/GolomToken.sol 58 function setMinter(address _minter) external onlyOwner { 59 pendingMinter = _minter; 60 minterEnableDate = block.timestamp + 1 days; 61: }
File: contracts/vote-escrow/VoteEscrowCore.sol 868 function setVoter(address _voter) external { 869 require(msg.sender == voter); 870 voter = _voter; 871: }
Use a solidity version of at least 0.8.12 to get string.concat()
to be used instead of abi.encodePacked(<str>,<str>)
There are 2 instances of this issue:
File: contracts/core/GolomTrader.sol 3: pragma solidity 0.8.11;
File: contracts/vote-escrow/TokenUriHelper.sol 3: pragma solidity 0.8.11;
1e18
) rather than exponentiation (e.g. 10**18
)While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist
There are 2 instances of this issue:
File: contracts/rewards/RewardDistributor.sol 48: uint256 constant dailyEmission = 600000 * 10**18; 100: if (rewardToken.totalSupply() > 1000000000 * 10**18) {
Some lines use // x
and some use //x
. The instances below point out the usages that don't follow the majority, within each file
There are 2 instances of this issue:
File: contracts/core/GolomTrader.sol 181: //deadline
File: contracts/rewards/RewardDistributor.sol 99: //console.log(block.timestamp,epoch,fee);
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length
There are 8 instances of this issue:
File: contracts/core/GolomTrader.sol 132: 'order(address collection,uint256 tokenId,address signer,uint256 orderType,uint256 totalAmt,payment exchange,payment prePayment,bool isERC721,uint256 tokenAmt,uint256 refererrAmt,bytes32 root,address reservedAddress,uint256 nonce,uint256 deadline)payment(uint256 paymentAmt,address paymentAddress)' 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
File: contracts/rewards/RewardDistributor.sol 126: epochTotalFee[0] = address(this).balance; // staking and trading rewards start at epoch 1, for epoch 0 all contract ETH balance is converted to staker rewards rewards.
File: contracts/vote-escrow/TokenUriHelper.sol 72: output = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="468" viewBox="0 0 512 468" fill="none"><g clip-path="url(#clip0_2_190)"><rect width="512" height="468" rx="40" fill="#232323"/><g filter="url(#filter0_f_2_190)"><ellipse cx="256.5" cy="-132" rx="164" ry="381.5" transform="rotate(-90 256.5 -132)" fill="#FF8982"/></g>'; 78: '</text><text y="318px" x="54px" fill="white" font-family="Lexend Deca, sans-serif" font-weight="400" font-size="24px" fill-opacity="0.64">Locked Till</text>' 86: '</text><text y="248px" x="54px" fill="white" font-family="Lexend Deca, sans-serif" font-weight="400" font-size="24px" fill-opacity="0.64">Voting Power</text>' 94: '</text><text y="391px" x="54px" fill="white" font-family="Lexend Deca, sans-serif" font-weight="400" font-size="24px" fill-opacity="0.64">Value</text>' 102: '</text><mask id="mask0_2_190" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="399" y="73" width="58" height="58"><path d="M456.543 102.091C456.543 117.884 443.74 130.686 427.948 130.686C412.155 130.686 399.352 117.884 399.352 102.091C399.352 86.2981 412.155 73.4955 427.948 73.4955C443.74 73.4955 456.543 86.2981 456.543 102.091ZM405.91 102.091C405.91 114.262 415.777 124.128 427.948 124.128C440.118 124.128 449.985 114.262 449.985 102.091C449.985 89.9201 440.118 80.0537 427.948 80.0537C415.777 80.0537 405.91 89.9201 405.91 102.091Z" fill="#C4C4C4"/></mask><g mask="url(#mask0_2_190)"><path d="M458.138 73.4955H396.962V133.076H458.138V73.4955Z" fill="#FD7A7A"/><path d="M396.962 76.7614H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 80.266H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 83.7708H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 87.2754H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 90.7802H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 94.2848H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 97.7897H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 101.294H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 104.799H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 108.304H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 111.808H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 115.313H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 118.818H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 122.323H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 125.827H458.138" stroke="black" stroke-width="0.328567"/><path d="M396.962 129.332H458.138" stroke="black" stroke-width="0.328567"/></g><mask id="mask1_2_190" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="399" y="73" width="58" height="58"><path d="M456.543 102.091C456.543 117.884 443.74 130.686 427.948 130.686C412.155 130.686 399.352 117.884 399.352 102.091C399.352 86.2981 412.155 73.4955 427.948 73.4955C443.74 73.4955 456.543 86.2981 456.543 102.091ZM405.91 102.091C405.91 114.262 415.777 124.128 427.948 124.128C440.118 124.128 449.985 114.262 449.985 102.091C449.985 89.9201 440.118 80.0537 427.948 80.0537C415.777 80.0537 405.91 89.9201 405.91 102.091Z" fill="white"/></mask><g mask="url(#mask1_2_190)"><path d="M456.543 102.091C456.543 117.884 443.74 130.686 427.948 130.686C412.155 130.686 399.352 117.884 399.352 102.091C399.352 86.2981 412.155 73.4955 427.948 73.4955C443.74 73.4955 456.543 86.2981 456.543 102.091ZM405.91 102.091C405.91 114.262 415.777 124.128 427.948 124.128C440.118 124.128 449.985 114.262 449.985 102.091C449.985 89.9201 440.118 80.0537 427.948 80.0537C415.777 80.0537 405.91 89.9201 405.91 102.091Z" stroke="black" stroke-width="0.876179"/><path d="M422.132 130.606H428.026" stroke="black" stroke-width="0.109522"/><path d="M422.132 73.5752H428.026" stroke="black" stroke-width="0.109522"/></g><mask id="mask2_2_190" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="393" y="73" width="58" height="58"><path d="M450.648 102.091C450.648 117.884 437.845 130.686 422.053 130.686C406.26 130.686 393.457 117.884 393.457 102.091C393.457 86.2981 406.26 73.4955 422.053 73.4955C437.845 73.4955 450.648 86.2981 450.648 102.091ZM400.015 102.091C400.015 114.262 409.882 124.128 422.053 124.128C434.223 124.128 444.09 114.262 444.09 102.091C444.09 89.9201 434.223 80.0537 422.053 80.0537C409.882 80.0537 400.015 89.9201 400.015 102.091Z" fill="white"/></mask><g mask="url(#mask2_2_190)"><path d="M450.648 102.091C450.648 117.884 437.845 130.686 422.053 130.686C406.26 130.686 393.457 117.884 393.457 102.091C393.457 86.2981 406.26 73.4955 422.053 73.4955C437.845 73.4955 450.648 86.2981 450.648 102.091ZM400.015 102.091C400.015 114.262 409.882 124.128 422.053 124.128C434.223 124.128 444.09 114.262 444.09 102.091C444.09 89.9201 434.223 80.0537 422.053 80.0537C409.882 80.0537 400.015 89.9201 400.015 102.091Z" fill="white" stroke="black" stroke-width="0.876179"/></g></g><defs><filter id="filter0_f_2_190" x="-381" y="-552" width="1275" height="840" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="128" result="effect1_foregroundBlur_2_190"/></filter><clipPath id="clip0_2_190"><rect width="512" height="468" rx="40" fill="white"/></clipPath></defs></svg>'
Some files use >=
, some use ^
. The instances below are examples of the method that has the fewest instances for a specific version. Note that using >=
without also specifying <=
will lead to failures to compile, or external project incompatability, when the major version changes and there are breaking-changes, so ^
should be preferred regardless of the instance counts
There is 1 instance of this issue:
File: contracts/governance/GolomToken.sol 2: pragma solidity ^0.8.11;
constant
/immutable
variablesIf the variable needs to be different based on which class it comes from, a view
/pure
function should be used instead (e.g. like this).
There are 2 instances of this issue:
File: contracts/core/GolomTrader.sol 45: ERC20 WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
File: contracts/vote-escrow/VoteEscrowDelegation.sol 50: uint256 public MIN_VOTING_POWER_REQUIRED = 0;
There is 1 instance of this issue:
File: contracts/governance/GolomToken.sol 2: pragma solidity ^0.8.11;
There are 24 instances of this issue:
File: contracts/core/GolomTrader.sol /// @audit succesful 53: Payment exchange; // payment agreed by maker of the order to pay on succesful filling of trade this amt is subtracted from totalamt /// @audit facilating 54: Payment prePayment; // another payment , can be used for royalty, facilating trades /// @audit usefull 60: uint256 nonce; // nonce of order usefull for cancelling in bulk /// @audit succesful 201: /// @param p any extra payment that the taker of this order wanna send on succesful execution of order /// @audit succesful 278: /// @param p any extra payment that the taker of this order wanna send on succesful execution of order /// @audit succesful 333: /// @param p any extra payment that the taker of this order wanna send on succesful execution of order /// @audit succesfully 370: /// @dev function to settle balances when a bid is filled succesfully /// @audit succesful 374: /// @param p any extra payment that the taker of this order wanna send on succesful execution of order
File: contracts/rewards/RewardDistributor.sol /// @audit epoc 61: mapping(uint256 => uint256) public rewardTrader; // reward minted each epoc for trader /// @audit epoc /// @audit exhange 62: mapping(uint256 => uint256) public rewardExchange; // reward minted each epoc for exhange /// @audit epoc 63: mapping(uint256 => uint256) public rewardLP; // reward minted each epoc for LP /// @audit epoc 64: mapping(uint256 => uint256) public rewardStaker; // reward minted each epoc for stakers /// @audit upto 66: mapping(uint256 => uint256) public claimedUpto; // epoch upto which tokenid has claimed /// @audit upto 67: mapping(uint256 => mapping(uint256 => uint256)) public claimed; // epoch upto which tokenid has claimed /// @audit facilated 95: /// @dev Add fees contributed by the Seller of nft and the exchange/frontend that facilated the trade /// @audit atleast 107: // this assumes atleast 1 trade is done daily?????? /// @audit begiining /// @audit begining 111: // emissions is decided by epoch begiining locked/circulating , and amount each nft gets also decided at epoch begining /// @audit facilated 154: // allows exchange that facilated the nft trades to claim there previous epoch rewards
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit blocktimes 267: * and per block could be fairly bad b/c Ethereum changes blocktimes. /// @audit Exeute 526: /// @dev Exeute transfer of a NFT. /// @audit Pevious 688: /// @param old_locked Pevious locked amount / end lock time for the user
File: contracts/vote-escrow/VoteEscrowDelegation.sol /// @audit Exeute 227: /// @dev Exeute transfer of a NFT.
There is 1 instance of this issue:
File: contracts/vote-escrow/TokenUriHelper.sol 0: /// [MIT License]
There are 19 instances of this issue:
File: contracts/core/GolomTrader.sol /// @audit Missing: '@return' 162 /// OrderStatus = 3 , valid order 163 /// @param o the Order struct to be validated 164 function validateOrder(Order calldata o) 165 public 166 view 167 returns ( 168 uint256, 169 bytes32, 170: uint256 /// @audit Missing: '@param tokenId' /// @audit Missing: '@param proof' 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 {
File: contracts/rewards/RewardDistributor.sol /// @audit Missing: '@return' 213 /// @dev returns unclaimed rewards of an NFT, returns (unclaimed golom rewards, unclaimed eth rewards, unclaimed epochs) 214 /// @param tokenid the nft id to claim rewards for all ids in the list must belong to 1 address 215 function stakerRewards(uint256 tokenid) public view returns ( 216 uint256, 217 uint256, 218: uint256[] memory /// @audit Missing: '@return' 252 /// @dev returns unclaimed rewards of an NFT, returns (unclaimed golom rewards, unclaimed eth rewards, unclaimed epochs) 253 /// @param addr the nft id to claim rewards for all ids in the list must belong to 1 address 254 function traderRewards(address addr) public view returns ( 255: uint256 /// @audit Missing: '@return' 267 /// @dev returns unclaimed golom rewards of a trader 268 /// @param addr the nft id to claim rewards for all ids in the list must belong to 1 address 269 function exchangeRewards(address addr) public view returns ( 270: uint256
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit Missing: '@return' 366 /// @dev Interface identification is specified in ERC-165. 367 /// @param _interfaceID Id of the interface 368: function supportsInterface(bytes4 _interfaceID) external view returns (bool) { /// @audit Missing: '@return' 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) { /// @audit Missing: '@return' 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) { /// @audit Missing: '@return' 409 /// @dev Returns the address of the owner of the NFT. 410 /// @param _tokenId The identifier for an NFT. 411: function ownerOf(uint256 _tokenId) public view returns (address) { /// @audit Missing: '@return' 415 /// @dev Get the approved address for a single NFT. 416 /// @param _tokenId ID of the NFT to query the approval of. 417: function getApproved(uint256 _tokenId) external view returns (address) { /// @audit Missing: '@return' 422 /// @param _owner The address that owns the NFTs. 423 /// @param _operator The address that acts on behalf of the owner. 424: function isApprovedForAll(address _owner, address _operator) external view returns (bool) { /// @audit Missing: '@return' 935 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 936 /// @param _to Address to deposit 937 function _create_lock( 938 uint256 _value, 939 uint256 _lock_duration, 940 address _to 941: ) internal returns (uint256) { /// @audit Missing: '@return' 957 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 958 /// @param _to Address to deposit 959 function create_lock_for( 960 uint256 _value, 961 uint256 _lock_duration, 962 address _to 963: ) external nonreentrant returns (uint256) { /// @audit Missing: '@return' 968 /// @param _value Amount to deposit 969 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 970: function create_lock(uint256 _value, uint256 _lock_duration) external nonreentrant returns (uint256) { /// @audit Missing: '@param _tokenId' 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 Missing: '@param _tokenId' 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 Missing: '@return' 1079 /// @dev Returns current token URI metadata 1080 /// @param _tokenId Token ID to fetch URI for. 1081: function tokenURI(uint256 _tokenId) external view returns (string memory) { /// @audit Missing: '@param t' 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) {
indexed
fieldsIndex event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (threefields). Each event
should use three indexed
fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
There are 7 instances of this issue:
File: contracts/core/GolomTrader.sol 79: event NonceIncremented(address indexed maker, uint256 newNonce);
File: contracts/rewards/RewardDistributor.sol 70: event NewEpoch(uint256 indexed epochNo, uint256 tokenMinted, uint256 rewardStaker, uint256 previousEpochFee);
File: contracts/vote-escrow/VoteEscrowCore.sol 67: event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 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); 293: event Supply(uint256 prevSupply, uint256 supply);
File: contracts/vote-escrow/VoteEscrowDelegation.sol 29: event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
require()
/revert()
checks should be refactored to a modifier or functionThe compiler will inline the function, which will avoid JUMP
instructions usually associated with functions
There are 14 instances of this issue:
File: contracts/core/GolomTrader.sol 291: require(msg.sender == o.reservedAddress); 299: require(amount == 1, 'only 1 erc721 at 1 time'); 349: require(status == 3); 350: require(amountRemaining >= amount);
File: contracts/rewards/RewardDistributor.sol 158: require(epochs[index] < epoch); 220: require(address(ve) != address(0), ' VE not added yet');
File: contracts/vote-escrow/VoteEscrowCore.sol 1008: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached'); 874: require(msg.sender == voter); 944: require(_value > 0); // dev: need non-zero value 982: require(_locked.amount > 0, 'No existing lock found'); 983: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw'); 999: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
File: contracts/vote-escrow/VoteEscrowDelegation.sol 211: require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); 186: require(blockNumber < block.number, 'VEDelegation: not yet determined');
#0 - dmvt
2022-11-11T10:38:29Z
L-01 - upgraded N-14, N-17, N-22, N-24 - should be low risk
π Selected for report: JohnSmith
Also found by: 0x1f8b, 0xA5DF, 0xDjango, 0xKitsune, 0xLovesleep, 0xNazgul, 0xSmartContract, 0xmatt, 0xsam, Aymen0909, Bnke0x0, CRYP70, Chandr, Chinmay, CodingNameKiki, Deivitto, Dravee, ElKu, Fitraldys, Funen, GalloDaSballo, Green, IllIllI, JC, Jmaxmanblue, Junnon, Kaiziron, Kenshin, Krow10, Maxime, Migue, MiloTruck, Noah3o6, NoamYakov, Randyyy, RedOneN, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, StyxRave, TomJ, Tomio, _Adam, __141345__, ajtra, ak1, apostle0x01, asutorufos, async, benbaessler, brgltd, c3phas, cRat1st0s, carlitox477, delfin454000, djxploit, durianSausage, ellahi, erictee, fatherOfBlocks, gerdusx, gogo, hyh, jayfromthe13th, jayphbee, joestakey, kaden, kenzo, kyteg, ladboy233, lucacez, m_Rassska, mics, minhquanym, oyc_109, pfapostol, rbserver, reassor, rfa, robee, rokinot, sach1r0, saian, samruna, sashik_eth, simon135, supernova, tofunmi, zuhaibmohd
94.6571 USDC - $94.66
Issue | Instances | |
---|---|---|
[Gβ01] | Use constant checks rather than mapping for supportedInterfaces() | 1 |
[Gβ02] | Using this.<fn>() wastes gas | 4 |
[Gβ03] | Remove or replace unused state variables | 3 |
[Gβ04] | Multiple address /ID mappings can be combined into a single mapping of an address /ID to a struct , where appropriate | 4 |
[Gβ05] | State variables only set in the constructor should be declared immutable | 17 |
[Gβ06] | State variables can be packed into fewer storage slots | 1 |
[Gβ07] | Structs can be packed into fewer storage slots | 1 |
[Gβ08] | Using calldata instead of memory for read-only arguments in external functions saves gas | 5 |
[Gβ09] | Using storage instead of memory for structs/arrays saves gas | 6 |
[Gβ10] | State variables should be cached in stack variables rather than re-reading them from storage | 15 |
[Gβ11] | The result of external function calls should be cached rather than re-calling the function | 3 |
[Gβ12] | internal functions only called once can be inlined to save gas | 7 |
[Gβ13] | Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if -statement | 2 |
[Gβ14] | <array>.length should not be looked up in every loop of a for -loop | 8 |
[Gβ15] | ++i /i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as is the case when used in for - and while -loops | 15 |
[Gβ16] | require() /revert() strings longer than 32 bytes cost extra gas | 9 |
[Gβ17] | keccak256() should only need to be called on a specific string literal once | 2 |
[Gβ18] | Optimize names to save gas | 10 |
[Gβ19] | Using bool s for storage incurs overhead | 5 |
[Gβ20] | Using > 0 costs more gas than != 0 when used on a uint in a require() statement | 2 |
[Gβ21] | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) | 12 |
[Gβ22] | Splitting require() statements that use && saves gas | 4 |
[Gβ23] | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 3 |
[Gβ24] | Using private rather than public for constants, saves gas | 5 |
[Gβ25] | Division by two should use bit shifting | 3 |
[Gβ26] | Stack variable used as a cheaper cache for a state variable is only used once | 1 |
[Gβ27] | require() or revert() statements that check input arguments should be at the top of the function | 3 |
[Gβ28] | Empty blocks should be removed or emit something | 4 |
[Gβ29] | Use custom errors rather than revert() /require() strings to save gas | 44 |
[Gβ30] | Functions guaranteed to revert when called by normal users can be marked payable | 13 |
Total: 212 instances over 30 issues
supportedInterfaces()
In order to check whether the contract supports an interface, a Gcoldsload (2100 gas) must be incurred to look up a value. Rather than using a mapping
a function can be written which just returns true
if any one of those interface IDs is passed in, changing each Gcoldsload to a cheap stack PUSH
There is 1 instance of this issue:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol 58 supportedInterfaces[ERC165_INTERFACE_ID] = true; 59 supportedInterfaces[ERC721_INTERFACE_ID] = true; 60: supportedInterfaces[ERC721_METADATA_INTERFACE_ID] = true;
this.<fn>()
wastes gasCalling an external
function internally, through the use of this
wastes the gas overhead of calling an external function (100 gas). Instead, change the function from external
to public
, and remove the this.
There are 4 instances of this issue:
File: /contracts/vote-escrow/VoteEscrowDelegation.sol 242: this.removeDelegation(_tokenId); 73: require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power'); 171 for (uint256 index = 0; index < delegated.length; index++) { 172 votes = votes + this.balanceOfNFT(delegated[index]); 173: } 189 for (uint256 index = 0; index < delegatednft.length; index++) { 190 votes = votes + this.balanceOfAtNFT(delegatednft[index], blockNumber); 191: }
Saves a storage slot. If the variable is assigned a non-zero value, saves Gsset (20000 gas). If it's assigned a zero value, saves Gsreset (2900 gas). If the variable remains unassigned, there is no gas savings unless the variable is public
, in which case the compiler-generated non-payable getter deployment cost is saved. If the state variable is overriding an interface's public function, mark the variable as constant
or immutable
so that it does not use a storage slot
There are 3 instances of this issue:
File: contracts/core/GolomTrader.sol 72: address public governance;
File: contracts/rewards/RewardDistributor.sol 63: mapping(uint256 => uint256) public rewardLP; // reward minted each epoc for LP 66: mapping(uint256 => uint256) public claimedUpto; // epoch upto which tokenid has claimed
address
/ID mappings can be combined into a single mapping
of an address
/ID to a struct
, where appropriateSaves 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 4 instances of this issue:
File: contracts/core/GolomTrader.sol 42 mapping(address => uint256) public nonces; // all nonces other then this nonce 43: mapping(bytes32 => uint256) public filled;
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 60 mapping(uint256 => uint256) public epochTotalFee; // total fee of epoch 61 mapping(uint256 => uint256) public rewardTrader; // reward minted each epoc for trader 62 mapping(uint256 => uint256) public rewardExchange; // reward minted each epoc for exhange 63 mapping(uint256 => uint256) public rewardLP; // reward minted each epoc for LP 64 mapping(uint256 => uint256) public rewardStaker; // reward minted each epoc for stakers 65 mapping(uint256 => uint256) public epochBeginTime; // what time previous epoch ended 66 mapping(uint256 => uint256) public claimedUpto; // epoch upto which tokenid has claimed 67: mapping(uint256 => mapping(uint256 => uint256)) public claimed; // epoch upto which tokenid has claimed
File: contracts/vote-escrow/VoteEscrowCore.sol 326 mapping(uint256 => address) internal idToOwner; 327 328 /// @dev Mapping from NFT ID to approved address. 329 mapping(uint256 => address) internal idToApprovals; 330 331 /// @dev Mapping from owner address to count of his tokens. 332 mapping(address => uint256) internal ownerToNFTokenCount; 333 334 /// @dev Mapping from owner address to mapping of index to tokenIds 335 mapping(address => mapping(uint256 => uint256)) internal ownerToNFTokenIdList; 336 337 /// @dev Mapping from NFT ID to index of owner 338 mapping(uint256 => uint256) internal tokenToOwnerIndex; 339 340 /// @dev Mapping from owner address to mapping of operator addresses. 341 mapping(address => mapping(address => bool)) internal ownerToOperators; 342 343 /// @dev Mapping of interface id to bool about whether or not it's supported 344: mapping(bytes4 => bool) internal supportedInterfaces;
File: contracts/vote-escrow/VoteEscrowDelegation.sol 44 mapping(uint256 => mapping(uint256 => Checkpoint)) public checkpoints; 45 46 /// @notice The number of checkpoints for each delegated tokenId 47: mapping(uint256 => uint256) public numCheckpoints;
immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32
(3 gas).
There are 17 instances of this issue:
File: contracts/rewards/RewardDistributor.sol /// @audit startTime (constructor) 84: startTime = 1659211200; /// @audit startTime (access) 106: if (block.timestamp > startTime + (epoch) * secsInDay) { /// @audit rewardToken (constructor) 82: rewardToken = ERC20(_token); /// @audit rewardToken (access) 100: if (rewardToken.totalSupply() > 1000000000 * 10**18) { /// @audit rewardToken (access) /// @audit rewardToken (access) 112: uint256 tokenToEmit = (dailyEmission * (rewardToken.totalSupply() - rewardToken.balanceOf(address(ve)))) / /// @audit rewardToken (access) 113: rewardToken.totalSupply(); /// @audit rewardToken (access) /// @audit rewardToken (access) 114: uint256 stakerReward = (tokenToEmit * rewardToken.balanceOf(address(ve))) / rewardToken.totalSupply(); /// @audit rewardToken (access) 122: rewardToken.mint(address(this), tokenToEmit); /// @audit rewardToken (access) 151: rewardToken.transfer(addr, reward); /// @audit rewardToken (access) 165: rewardToken.transfer(addr, reward); /// @audit rewardToken (access) 208: rewardToken.transfer(tokenowner, reward); /// @audit weth (constructor) 80: weth = ERC20(_weth); /// @audit weth (access) 127: weth.deposit{value: address(this).balance}(); /// @audit weth (access) 129: weth.deposit{value: previousEpochFee}(); /// @audit weth (access) 209: weth.transfer(tokenowner, rewardEth);
If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper
There is 1 instance of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit Variable ordering with 20 slots instead of the current 21: /// uint256(32):supply, mapping(32):locked, mapping(32):ownership_change, uint256(32):epoch, mapping(32):point_history, mapping(32):user_point_history, mapping(32):user_point_epoch, mapping(32):slope_changes, mapping(32):attachments, mapping(32):voted, uint256(32):tokenId, mapping(32):idToOwner, mapping(32):idToApprovals, mapping(32):ownerToNFTokenCount, mapping(32):ownerToNFTokenIdList, mapping(32):tokenToOwnerIndex, mapping(32):ownerToOperators, mapping(32):supportedInterfaces, address(20):token, uint8(1):_entered_state, address(20):voter 300: address public token;
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
There is 1 instance of this issue:
File: contracts/core/GolomTrader.sol /// @audit Variable ordering with 15 slots instead of the current 17: /// uint256(32):tokenId, uint256(32):orderType, uint256(32):totalAmt, user-defined(32):exchange, user-defined(32):prePayment, uint256(32):tokenAmt, uint256(32):refererrAmt, bytes32(32):root, uint256(32):nonce, uint256(32):deadline, bytes32(32):r, bytes32(32):s, address(20):collection, bool(1):isERC721, uint8(1):v, address(20):signer, address(20):reservedAddress 47 struct Order { 48 address collection; // NFT contract address 49 uint256 tokenId; // order for which tokenId of the collection 50 address signer; // maker of order address 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 52 uint256 totalAmt; // price value of the trade // total amt maker is willing to give up per unit of amount 53 Payment exchange; // payment agreed by maker of the order to pay on succesful filling of trade this amt is subtracted from totalamt 54 Payment prePayment; // another payment , can be used for royalty, facilating trades 55 bool isERC721; // standard of the collection , if 721 then true , if 1155 then false 56 uint256 tokenAmt; // token amt useful if standard is 1155 if >1 means whole order can be filled tokenAmt times 57 uint256 refererrAmt; // amt to pay to the address that helps in filling your order 58 bytes32 root; // A merkle root derived from each valid tokenId β set to 0 to indicate a collection-level or tokenId-specific order. 59 address reservedAddress; // if not address(0) , only this address can fill the order 60 uint256 nonce; // nonce of order usefull for cancelling in bulk 61 uint256 deadline; // timestamp till order is valid epoch timestamp in secs 62 uint8 v; 63 bytes32 r; 64 bytes32 s; 65: }
calldata
instead of memory
for read-only arguments in external
functions saves gasWhen 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. Note that even if an interface defines a function as having memory
arguments, it's still valid for implementation contracs to use calldata
arguments instead.
If the array is passed to an internal
function which passes the array to another internal function where the array is modified and therefore memory
is used in the external
call, it's still more gass-efficient to use calldata
when the external
function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one
Note that I've also flagged instances where the function is public
but can be marked as external
since it's not called by the contract, and cases where a constructor is involved
There are 5 instances of this issue:
File: contracts/rewards/RewardDistributor.sol /// @audit addr 98: function addFee(address[2] memory addr, uint256 fee) public onlyTrader { /// @audit epochs 141: function traderClaim(address addr, uint256[] memory epochs) public { /// @audit epochs 155: function exchangeClaim(address addr, uint256[] memory epochs) public { /// @audit tokenids /// @audit epochs 172: function multiStakerClaim(uint256[] memory tokenids, uint256[] memory epochs) public {
storage
instead of memory
for structs/arrays saves gasWhen fetching data from a storage location, assigning the data to a memory
variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD
rather than a cheap stack read. Instead of declearing the variable with the memory
keyword, declaring the variable with the storage
keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory
variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory
, or if the array/struct is being read from another memory
array/struct
There are 6 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol 734: Point memory initial_last_point = last_point; 1132: Point memory point_0 = point_history[_epoch]; 1136: Point memory point_1 = point_history[_epoch + 1]; 1194: Point memory last_point = point_history[_epoch]; 1210: Point memory point = point_history[target_epoch]; 1213: Point memory point_next = point_history[target_epoch + 1];
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
There are 15 instances of this issue:
File: contracts/rewards/RewardDistributor.sol /// @audit epoch on line 106 117: uint256 previousEpochFee = epochTotalFee[epoch]; /// @audit epoch on line 118 119: rewardStaker[epoch] = stakerReward; /// @audit epoch on line 119 120: rewardTrader[epoch] = ((tokenToEmit - stakerReward) * 67) / 100; /// @audit epoch on line 120 121: rewardExchange[epoch] = ((tokenToEmit - stakerReward) * 33) / 100; /// @audit epoch on line 121 123: epochBeginTime[epoch] = block.number; /// @audit epoch on line 123 125: if (epoch == 1){ /// @audit epoch on line 125 132: emit NewEpoch(epoch, tokenToEmit, stakerReward, previousEpochFee); /// @audit epoch on line 132 /// @audit epoch on line 134 134: feesTrader[addr[0]][epoch] = feesTrader[addr[0]][epoch] + fee; /// @audit epoch on line 134 /// @audit epoch on line 135 135: feesExchange[addr[1]][epoch] = feesExchange[addr[1]][epoch] + fee; /// @audit epoch on line 135 /// @audit epoch on line 136 136: epochTotalFee[epoch] = epochTotalFee[epoch] + fee; /// @audit epoch on line 224 226: for (uint256 index = 0; index < epoch; index++) {
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit tokenId on line 948 949: uint256 _tokenId = tokenId;
The instances below point to the second+ call of the function within a single function
There are 3 instances of this issue:
File: contracts/rewards/RewardDistributor.sol /// @audit rewardToken.totalSupply() on line 100 112: uint256 tokenToEmit = (dailyEmission * (rewardToken.totalSupply() - rewardToken.balanceOf(address(ve)))) / /// @audit rewardToken.totalSupply() on line 100 113: rewardToken.totalSupply(); /// @audit rewardToken.totalSupply() on line 100 114: uint256 stakerReward = (tokenToEmit * rewardToken.balanceOf(address(ve))) / rewardToken.totalSupply();
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
There are 7 instances of this issue:
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) { 1107: function _balanceOfAtNFT(uint256 _tokenId, uint256 _block) internal view returns (uint256) {
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 {
unchecked {}
for subtractions where the operands cannot underflow because of a previous require()
or if
-statementrequire(a <= b); x = b - a
=> require(a <= b); unchecked { x = b - a }
There are 2 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit if-condition on line 736 /// @audit if-condition on line 736 737: block_slope = (MULTIPLIER * (block.number - last_point.blk)) / (block.timestamp - last_point.ts);
<array>.length
should not be looked up in every loop of a for
-loopThe overheads outlined below are PER LOOP, excluding the first loop
MLOAD
(3 gas)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
There are 8 instances of this issue:
File: contracts/core/GolomTrader.sol 415: for (uint256 i = 0; i < proof.length; i++) {
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++) {
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++) {
++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsThe unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
There are 15 instances of this issue:
File: contracts/core/GolomTrader.sol 415: for (uint256 i = 0; i < proof.length; i++) {
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++) {
File: contracts/vote-escrow/VoteEscrowCore.sol 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) {
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++) {
require()
/revert()
strings longer than 32 bytes cost extra gasEach extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
There are 9 instances of this issue:
File: contracts/governance/GolomToken.sol 24: require(msg.sender == minter, 'GolomToken: only reward distributor can enable');
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');
File: contracts/vote-escrow/VoteEscrowCore.sol 608: revert('ERC721: transfer to non ERC721Receiver implementer'); 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');
File: contracts/vote-escrow/VoteEscrowDelegation.sol 73: require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');
keccak256()
should only need to be called on a specific string literal onceIt should be saved to an immutable variable, and the variable used instead. If the hash is being used as a part of a function selector, the cast to bytes4
should also only be done once
There are 2 instances of this issue:
File: contracts/core/GolomTrader.sol 116: keccak256('payment(uint256 paymentAmt,address paymentAddress)'), 131 keccak256( 132 'order(address collection,uint256 tokenId,address signer,uint256 orderType,uint256 totalAmt,payment exchange,payment prePayment,bool isERC721,uint256 tokenAmt,uint256 refererrAmt,bytes32 root,address reservedAddress,uint256 nonce,uint256 deadline)payment(uint256 paymentAmt,address paymentAddress)' 133: ),
public
/external
function names and public
member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
There are 10 instances of this issue:
File: contracts/core/GolomTrader.sol /// @audit addFee() 36: interface Distributor { /// @audit validateOrder(), fillAsk(), fillBid(), cancelOrder(), incrementNonce(), fillCriteriaBid(), _verifyProof(), setDistributor(), executeSetDistributor() 40: contract GolomTrader is Ownable, ReentrancyGuard {
File: contracts/governance/GolomToken.sol /// @audit mint(), mintAirdrop(), mintGenesisReward(), setMinter(), executeSetMinter() 14: contract GolomToken is ERC20Votes, Ownable {
File: contracts/rewards/RewardDistributor.sol /// @audit mint(), balanceOfNFTAt() 11: interface ERC20 { /// @audit totalSupplyAtT(), balanceOfNFTAt(), totalSupplyAt(), balanceOfAtNFT() 31: interface VE { /// @audit addFee(), traderClaim(), exchangeClaim(), multiStakerClaim(), stakerRewards(), traderRewards(), exchangeRewards(), changeTrader(), executeChangeTrader(), addVoteEscrow(), executeAddVoteEscrow() 43: contract RewardDistributor is Ownable {
File: contracts/vote-escrow/TokenUriHelper.sol /// @audit _tokenURI() 65: library TokenUriHelper {
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit get_last_user_slope(), user_point_history__ts(), locked__end(), isApprovedOrOwner(), setVoter(), voting(), abstain(), attach(), detach(), merge(), block_number(), checkpoint(), deposit_for(), create_lock_for(), create_lock(), increase_amount(), increase_unlock_time(), balanceOfNFT(), balanceOfNFTAt(), balanceOfAtNFT(), totalSupplyAtT(), totalSupplyAt() 275: contract VoteEscrowCore is IERC721, IERC721Metadata {
File: contracts/vote-escrow/VoteEscrowDelegation.sol /// @audit balanceOf(), balanceOfAtNFT() 12: interface IVoteEscrow { /// @audit delegate(), getVotes(), getPriorVotes(), removeDelegation(), changeMinVotingPower() 20: contract VoteEscrow is VoteEscrowCore, Ownable {
bool
s 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:
File: contracts/governance/GolomToken.sol 20: bool public isAirdropMinted; 21: bool public isGenesisRewardMinted;
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;
> 0
costs more gas than != 0
when used on a uint
in a require()
statementThis change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.
There are 2 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol 927: require(_value > 0); // dev: need non-zero value 944: require(_value > 0); // dev: need non-zero value
++i
costs less gas than i++
, especially when it's used in for
-loops (--i
/i--
too)Saves 5 gas per loop
There are 12 instances of this issue:
File: contracts/core/GolomTrader.sol 415: for (uint256 i = 0; i < proof.length; i++) {
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++) {
File: contracts/vote-escrow/TokenUriHelper.sol 138: digits++;
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++) {
require()
statements that use &&
saves gasSee 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 by 3 gas
There are 4 instances of this issue:
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');
File: contracts/vote-escrow/VoteEscrowDelegation.sol 239: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen 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.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8
) as compared to ones involving uint256
, due to the compiler having to clear the higher bits of the memory word before operating on the uint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
There are 3 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit int128 old_dslope 803: old_dslope += u_old.slope; /// @audit int128 old_dslope 805: old_dslope -= u_new.slope; // It was a new deposit, not extension /// @audit int128 new_dslope 812: new_dslope -= u_new.slope; // old slope disappeared at this point
private
rather than public
for constants, saves gasIf needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 5 instances of this issue:
File: contracts/core/GolomTrader.sol 41: bytes32 public immutable EIP712_DOMAIN_TYPEHASH;
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;
<x> / 2
is the same as <x> >> 1
. While the compiler uses the SHR
opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMP
s to and from a compiler utility function that introduces checks which can be avoided by using unchecked {}
around the division by two
There are 3 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol 1049: uint256 _mid = (_min + _max + 1) / 2; 1120: uint256 _mid = (_min + _max + 1) / 2;
File: contracts/vote-escrow/VoteEscrowDelegation.sol 150: uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend
There is 1 instance of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol 1193: uint256 _epoch = epoch;
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
There are 3 instances of this issue:
File: contracts/vote-escrow/VoteEscrowCore.sol /// @audit expensive op on line 894 895: require(_from != _to); /// @audit expensive op on line 925 927: require(_value > 0); // dev: need non-zero value /// @audit expensive op on line 942 944: require(_value > 0); // dev: need non-zero value
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract
and the function signatures be added without any default implementation. If the block is an empty if
-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...}
=> if(!x){if(y){...}else{...}}
). Empty receive()
/fallback() payable
functions that are not used, can be removed to save deployment gas.
There are 4 instances of this issue:
File: contracts/core/GolomTrader.sol 459: fallback() external payable {} 461: receive() external payable {}
File: contracts/rewards/RewardDistributor.sol 313: fallback() external payable {} 315: receive() external payable {}
revert()
/require()
strings to save gasCustom 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 save deployment gas
There are 44 instances of this issue:
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'); 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'); 299: require(amount == 1, 'only 1 erc721 at 1 time'); 359: require(amount == 1, 'only 1 erc721 at 1 time'); 455: require(distributorEnableDate <= block.timestamp, 'not allowed');
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');
File: contracts/rewards/RewardDistributor.sol 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');
File: contracts/vote-escrow/VoteEscrowCore.sol 538: require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached'); 894: require(attachments[_from] == 0 && !voted[_from], 'attached'); 928: require(_locked.amount > 0, 'No existing lock found'); 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'); 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');
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');
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 13 instances of this issue:
File: contracts/core/GolomTrader.sol 444: function setDistributor(address _distributor) external onlyOwner { 454: function executeSetDistributor() external onlyOwner {
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 {
File: contracts/rewards/RewardDistributor.sol 98: function addFee(address[2] memory addr, uint256 fee) public onlyTrader { 285: function changeTrader(address _trader) external onlyOwner { 291: function executeChangeTrader() external onlyOwner { 298: function addVoteEscrow(address _voteEscrow) external onlyOwner { 308: function executeAddVoteEscrow() external onlyOwner {
File: contracts/vote-escrow/VoteEscrowDelegation.sol 260: function changeMinVotingPower(uint256 _newMinVotingPower) external onlyOwner {