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: 3/179
Findings: 15
Award: $7,861.33
🌟 Selected for report: 5
🚀 Solo Findings: 1
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0x52, 0xA5DF, 0xsanson, CRYP70, GimelSec, Krow10, TrungOre, auditor0517, hansfriese, hyh, panprog, rajatbeladiya, rbserver, teddav
93.2805 USDC - $93.28
All ETH and GOLOM that accumulate in this contract for stakers would be irretrievable
ve = VE(pendingVoteEscrow);
L300 sets ve to the wrong variable. It sets VE = pendingVotingEscrow which is never set elsewhere and is address(0). This means that the else statement is never triggered and pendingVoteEscrow is always address(0), making it impossible to ever set VE to anything other than address(0).
Change L300 to:
ve = VE(_voteEscrow);
#0 - okkothejawa
2022-08-04T12:35:59Z
Duplicate of #611
🌟 Selected for report: GimelSec
Also found by: 0x52, 0xA5DF, 0xSky, 0xsanson, Bahurum, CertoraInc, GalloDaSballo, JohnSmith, Lambda, MEP, Twpony, arcoun, berndartmueller, cryptphi, hansfriese, kenzo, kyteg, panprog, rajatbeladiya, scaraven, simon135, zzzitron
26.7695 USDC - $26.77
Delegation is completely broken
uint256 nCheckpoints = numCheckpoints[toTokenId]; if (nCheckpoints > 0) { Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1]; checkpoint.delegatedTokenIds.push(tokenId); _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds); } else { uint256[] memory array = new uint256[](1); array[0] = tokenId; _writeCheckpoint(toTokenId, nCheckpoints, array); }
In the above code, for the first checkpoint (numCheckpoints[toTokenId] = 0) L85 will be executed passing in nCheckpoints = 0 to _writeCheckpoint.
Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
This causes _writeCheckpoint to underflow at L101 (shown above) causing a revert. Since no token can ever be checkpoint for the first time, the delegation system is completely broken
Create a nested if statement and move L101 up inside as shown below:
function _writeCheckpoint( uint256 toTokenId, uint256 nCheckpoints, uint256[] memory _delegatedTokenIds ) internal { require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more'); if (nCheckpoints > 0){ Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1]; if (oldCheckpoint.fromBlock == block.number) { oldCheckpoint.delegatedTokenIds = _delegatedTokenIds; } } else { checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds); numCheckpoints[toTokenId] = nCheckpoints + 1; } }
#0 - KenzoAgada
2022-08-02T09:18:46Z
Duplicate of #630
93.2805 USDC - $93.28
Sales of amount > 1 give too little ETH and will always revert if more than 200 are sold at one time
uint256 protocolfee = ((o.totalAmt * 50) / 10000) * amount; if (o.refererrAmt > 0 && referrer != address(0)) { payEther(o.refererrAmt * amount, referrer); payEther( (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) * amount - p.paymentAmt, msg.sender ); } else { payEther( (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - p.paymentAmt, msg.sender ); }
protocolFee already accounts for the amount being sold when calculated in L381. When calling payEther in L390 and L397, protocolfee is multiplied by amount a second time. This results in msg.sender not receiving the proper amount of ETH and loss of user funds. If amount >= 200 then protocolfee >= o.totalAmt which will cause an underflow error. This affects fillBid and fillCriteriaBid because both call _settleBalances for ETH transfers.
Move protocolfee out of the parentheses as shown below:
if (o.refererrAmt > 0 && referrer != address(0)) { payEther(o.refererrAmt * amount, referrer); payEther( (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) * amount - protocolfee - p.paymentAmt , msg.sender ); } else { payEther( (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - protocolfee - p.paymentAmt, msg.sender ); }
#0 - KenzoAgada
2022-08-02T06:31:35Z
Duplicate of #240
26.7695 USDC - $26.77
Submitting as high risk because even though there is no direct loss of funds, the large amount of votes could be used to hijack governance which could lead to loss of funds.
Unfair number of votes
delegate has no check that tokenID has not already been delegated. This allows a user to multiply their votes by delegating repeatedly. In L80 tokenID gets pushed onto the end of delegatedTokenID each time delegate is called. When getVotes is called, tokenID's votes will get counted each time it is in the array, inflating the voting power associated with it.
I recommend adding the following code to the start of delegate:
currentDelegate = delegates[tokenId] if (currentDelegate != address(0)) { require(currentDelegate != toTokenId) removeDelegation(tokenId) }
#0 - KenzoAgada
2022-08-02T12:02:09Z
Duplicate of #169
Protocol fees will no longer be distributed after GOLOM inflation ends
if (rewardToken.totalSupply() > 1000000000 * 10**18) { // if supply is greater then a billion dont mint anything, dont add trades return; }
addFee contains the lines above that short circuit the function when there are not more GOLOM rewards to mint. This works to prevent additional GOLOM rewards from being minted and distributed but it also stops tracking the amount of ETH fees and epoch. This means that after GOLOM rewards run out, distribution of WETH rewards will also stop because they are no longer properly tracked
Move all epoch and protocol fee logic to before the short circuit so that it still functions after GOLOM inflation ends
#0 - KenzoAgada
2022-08-03T12:18:24Z
Duplicate of #320 Actually might be a better main
93.2805 USDC - $93.28
Submitting as high risk because even though there is no direct loss of funds, the misrepresentation of votes could be detrimental for governance which by extent could lead to loss of funds.
Delegation isn't actually removed in a majority of cases
The function always removes tokenID from it's own checkpoint.delegatedTokenIds. This would only effectively remove delegation from the token if it was delegated to itself. Instead it should remove tokenId from whatever token it is currently delegated to.
Rewrite removeDelegation as follows, removing the delegation from where it is currently delegated to:
function removeDelegation(uint256 tokenId) external { require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); currentDelegate = delegates[tokenId] if (currentDelegate != address(0)) { uint256 nCheckpoints = numCheckpoints[currentDelegate]; Checkpoint storage checkpoint = checkpoints[currentDelegate][nCheckpoints - 1]; removeElement(checkpoint.delegatedTokenIds, tokenId); _writeCheckpoint(currentDelegate, nCheckpoints, checkpoint.delegatedTokenIds); } }
#0 - KenzoAgada
2022-08-02T08:25:24Z
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
The use of the deprecated transfer() function for an address payable will cause transactions to fail in a large number of scenarios - see https://github.com/code-423n4/2022-05-bunker-findings/issues/116
Use call() instead of transfer()
#0 - KenzoAgada
2022-08-03T14:16:48Z
Duplicate of #343
🌟 Selected for report: TomJ
Also found by: 0x4non, 0x52, 0xDjango, 0xNazgul, 0xf15ers, 0xsanson, 8olidity, Bnke0x0, CertoraInc, Ch_301, Chom, Dravee, GalloDaSballo, GimelSec, JC, Jujic, Kenshin, Kumpa, Lambda, M0ndoHEHE, PaludoX0, RedOneN, Ruhum, Sm4rty, Treasure-Seeker, TrungOre, Twpony, Waze, _Adam, __141345__, apostle0x01, arcoun, benbaessler, bin2chen, brgltd, cccz, cloudjunky, cryptonue, djxploit, ellahi, erictee, hansfriese, i0001, minhquanym, oyc_109, peritoflores, rbserver, reassor, rokinot, rotcivegaf, saian, shenwilly, sseefried
0.1513 USDC - $0.15
Judge has assessed an item in Issue #312 as Medium risk. The relevant finding follows:
[L-02] Use safeTransferFrom instead of transferFrom for ERC721 transfers Description Always best practice to use safeTransferFrom instead of transferFrom to protect users from input mistakes. Avoids a large number of other potential problems and should be the default anytime tokens are transferred to an arbitrary address.
Lines of Code https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L9 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L236 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L301 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L361
#0 - dmvt
2022-10-21T13:19:40Z
Duplicate of #342
1272.1849 USDC - $1,272.18
Order unable to be cancelled by cancelOrder
filled[hashStruct] = o.tokenAmt + 1;
cancelOrder will overflow in the line shown above if o.tokenAmt is type(uint256).max causing the transaction to always revert for that order.
I don't see any reason why 1 should be added to o.tokenAmt, change to:
filled[hashStruct] = o.tokenAmt;
🌟 Selected for report: 0x52
2827.0776 USDC - $2,827.08
Potential downtime in GolomTrader
GolomToken.sol doesn't have a function to mint the treasury tokens as specified in the docs (https://docs.golom.io/tokenomics-and-airdrop). In order for these tokens to be minted, the minter would have to be changed via setMinter() and executeSetMinter() to a contract that can mint the treasury tokens. Because of the 24 hour timelock, this would lead to downtime for GolomTrader.sol if trading has already begun. This is because GolomTrader.sol calls RewardDistributor.sol#addFees each time there is a filled order. When the epoch changes, RewardDistributor.sol will try to call the mint function in GolomToken.sol. Because of the timelock, there will be at least a 24 hours period where RewardDistributor.sol is not the minter and doesn't have the permission to mint. This means that during that period all trades will revert.
Add a function to GolomToken.sol to mint the treasury tokens similar to the mintAirdrop() and mintGenesisReward() functions.
#0 - 0xsaruman
2022-09-04T09:46:35Z
🌟 Selected for report: 0x52
Also found by: IllIllI, berndartmueller, kenzo, rotcivegaf
370.9691 USDC - $370.97
Burn NFTs remained delegated causing bloat and wasting gas
VoteEscrowDelegation.sol doesn't change the withdraw or _burn functions inherited from VoteEscrowCore.sol. These functions are ignorant of the delegation system and don't properly remove the delegation when burning an NFT. The votes for the burned NFT will be removed but the reference will still be stored in the delegation list where it was last delegated. This creates a few issues. 1) It adds bloat to both getVotes and getPriorVotes because it adds a useless element that must be looped through. 2) The max number of users that can delegate to another NFT is 500 and the burned NFT takes up one of those spots reducing the number of real users that can delegate. 3) Adds gas cost when calling removeDelegation which adds gas cost to _transferFrom because removeElement has to cycle through a larger number of elements.
Override _burn in VoteEscrowDelegation and add this.removeDelegation(_tokenId), similar to how it was done in _transferFrom
#0 - zeroexdead
2022-09-03T19:12:42Z
#1 - dmvt
2022-10-14T15:48:50Z
I agree with the wardens and sponsor who rate this as medium. It does negatively impact the functioning of the protocol, but none of the reporting wardens have shown how it can be used as a direct attack vector IMO.
1272.1849 USDC - $1,272.18
Rewards owed burned NFT are permanently locked
function _burn(uint256 _tokenId) internal { require(_isApprovedOrOwner(msg.sender, _tokenId), 'caller is not owner nor approved'); address owner = ownerOf(_tokenId); // Clear approval approve(address(0), _tokenId); // Remove token _removeTokenFrom(msg.sender, _tokenId); emit Transfer(owner, address(0), _tokenId); } function _removeTokenFrom(address _from, uint256 _tokenId) internal { // Throws if `_from` is not the current owner assert(idToOwner[_tokenId] == _from); // Change the owner idToOwner[_tokenId] = address(0); // Update owner token index tracking _removeTokenFromOwnerList(_from, _tokenId); // Change count tracking ownerToNFTokenCount[_from] -= 1; }
After an NFT is burned, owner of token is set to address(0).
rewardToken.transfer(tokenowner, reward);
This causes issues in multiStakerClaim L208. GOLOM uses OZ's implementation of ERC20 which doesn't allow tokens to be sent to address(0). Because the "owner" of the burned NFT is address(0) multiStakerClaim will always revert when called for a burned NFT trapping rewards in contract forever.
Implement a clawback clause inside the multiStakerClaim function. If the token is burned (i.e. owned by address(0)) the rewards should be transferred to different address. These rewards could be claimed to the treasury or burned, etc.
if (tokenowner == address(0){ rewardToken.transfer(treasury, reward); weth.transfer(treasury, rewardEth); }
#0 - 0xsaruman
2022-08-29T16:23:10Z
i think its QA because burning NFT means the owner doesnt want anything to do with rewards or the NFT anymore
Loss of delegation to specific NFTs
require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
In L99 delegation is blocked if the NFT being delegated to currently has 500 delegatees. This limitation prevents OOG errors that could originate from removeElement if the list of delegatees grows too long. By implementing a cap it allows a malicious user to DOS legitimate users by creating 500 NFTs and delegating to another NFT to keep it from being able to be delegated to by legitimate users.
This can be mitigated by implementing OZ's enumerableSet library - https://docs.openzeppelin.com/contracts/3.x/api/utils. Gas cost for removal of an element is fixed for any length list allowing the removal of the 500 delegatee cap, removing this DOS vector. I strongly recommend implementing this even if only to save the gas costs of users who wish to delegate to NFTs with a high number of delegatees.
#0 - zeroexdead
2022-08-29T02:54:08Z
Duplicate of #289
#1 - dmvt
2022-10-14T16:11:41Z
Duplicate of #707
1272.1849 USDC - $1,272.18
Submitting as high risk because it breaks a fundamental operation (transferring) for a large number of tokens
NFTs that don't have a checkpoint can't be transferred
uint256 nCheckpoints = numCheckpoints[tokenId]; Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1];
L242 of transferFrom() calls removeDelegation() with the tokenId of the token being transferred. For tokens that don't have any checkpoints, L212 will return 0. This was cause an underflow error and revert in L213.
Make removeDelegation simply return if nCheckpoints = 0
#0 - KenzoAgada
2022-08-02T09:12:28Z
Dunno if high risk, but warden correctly identified the issue (that some others didn't) that the underflow in removeDelegation
will prevent tokens from being transferred.
#1 - dmvt
2022-10-12T14:18:51Z
Marking this as a medium risk because it only temporarily breaks functionality. The workaround would be to delegate the token and then transfer it, making the impact aggravating but ultimately minimal.
🌟 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
167.5763 USDC - $167.58
Both 1e18 vs 1018 are equivalent and both work fine but 1018 is more typical. Regardless of which you want to use, pick one and stick to it.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L44 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/governance/GolomToken.sol#L52 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L48 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L100
Pick either 1e18 vs 10**18 and use it consistently across all contracts
(o.totalAmt * 50) / 10000 is calculated repeatedly in fillAsk. Having the same repeated calculation can lead to errors if changed later and some instances are missed. Better to do it once and reuse the results.
(o.totalAmt * 50) / 10000 should just be calculated once, stored and reused like it is in _settleBalance
In fillCriteriaBid and fillBid o.collection is first stored as nftcontract then the transfer is called on nftcontract. In fillAsk it is just called directly. Both methods are valid but they should match in format for consistency and readability.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L234-L239 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L298-L305 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L298-L305
Change fillAsk to use the same format as fillCriteriaBid and fillBid
No reason to declare variables then never use them.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L63 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L66
Remove variables
stakerRewards returns an array of 0's and 1's but multiStakerClaim requires an array with the numbers of the epochs that need to be claimed. This means that the output of stakersRewards must be interpreted to use with multiStakerClaim
stakerRewards should be changed to return the numbers of the epochs that need to be claimed. Remove L227 and add the following after L228
unclaimedepochs.push(index);
traderRewards and exchangeRewards don't return the epochs that need to be claimed, they only return the amount that can be claim which has limited usage. traderClaim and exchangeClaim both require an array of epochs to claim. It would be useful if traderRewards and exchangeRewards were changed to return the epoch numbers that need to be claimed.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L254-L265 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L269-L280
Example of how to change exchangeRewards:
function exchangeRewards(address addr) public view returns ( uint256, uint256[] ){ uint256 reward = 0; uint256[] memory unclaimedepochs = new uint256[] for (uint256 index = 0; index < epoch; index++) { uint256 epochReward = (rewardExchange[index] * feesExchange[addr][index]) / epochTotalFee[index]; if (epochReward > 0) { unclaimedepochs.push(index) reward = reward + epochReward } } return (reward, unclaimedepochs); }
GolomToken.sol implements ERC20Votes but never uses any of the features associated with it. This adds needless complexity and increases gas costs to deploy and use the tokens due to the checkpoint system implemented in ERC20Votes
N/A
Remove ERC20Votes. Alternatively inherit ERC20Permit if the use of permits is desired or simply inherit ERC20
Always best practice to use safeTransferFrom instead of transferFrom to protect users from input mistakes. Avoids a large number of other potential problems and should be the default anytime tokens are transferred to an arbitrary address.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L9 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L236 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L301 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L361
Change transferFrom to safeTransferFrom
fillCriteriaBid doesn't check for p.paymentAmt like fillBid does. Not having it doesn't present any vulnerabilities but it helps to revert an invalid transaction earlier and save user gas.
Change L342 to match L285-287
Interface is called ERC20 but it's not actually ERC20 because ERC20 doesn't implement a withdraw function. This is only true of WETH. To avoid any confusion, the interface should be called WETH not ERC20
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L26 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/rewards/RewardDistributor.sol#L11
Change interface name from ERC20 to WETH
The startTime in the constructor has already passed and will cause a large amount of minting as soon the contract is deployed and users begin trading. This is becuase epochs are calculated as a fixed value from startTime in L106. This will cause the epoch to rapidly snap forward one epoch each trade until it catches up to realtime. This will cause many days of emissions in the first day that it's launched.
Update time before launching contract
delegate and removeDelegation require that only the owner can call the function but every other function in VoteEscrowDelegation and VoteEscrowCore uses both owner and approved
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L72 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L211
Change to:
require(_isApprovedOrOwner(msg.sender, _tokenId));
Assert consumes all of user's remaining gas. There isn't any reason for this and require should be used instead.
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L493 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L506 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L519 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L666 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L679 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L861 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L977 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L981 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L991 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L1007 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L1023 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L1110 https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowCore.sol#L1206
Replace assert with require
Contracts should be deployed with the same compiler version and flags that they have been designed and tested. Different compiler versions/flags may introduce bugs that affect the contract negatively. Locking the pragma helps avoid this.
Match the rest of the code base and lock to 0.8.11:
pragma solidity 0.8.11;