Platform: Code4rena
Start Date: 22/04/2021
Pot Size: $120,000 USDC
Total HM: 41
Participants: 10
Period: 7 days
Judge: LSDan
Total Solo HM: 28
Id: 5
League: ETH
Rank: 4/10
Findings: 10
Award: $6,202.78
🌟 Selected for report: 7
🚀 Solo Findings: 0
14.6655 VETH - $762.61
0.352 ETH - $879.93
jvaqa
transferTo() When Used By A Smart Contract Wallet Tranfers The Relayer's Funds Rather Than The Wallet's Funds.
The transferTo pattern used in Vader.sol, USDV.sol, Synth.sol, Token1.sol, and Token2.sol utilizes tx.origin to determine the sender of tokens. However, smart contract wallets often have a completely unnrelated "relayer" as the actual initiator of the transaction. As written, transferTo would steal the relayer's funds, rather than the smart wallet's funds.
Any time a smart contract relayer owns any Vader, USDV, VaderSynths, or other Vader tokens, Alice can call the following line of code from her smart contract wallet that uses that relayer:
transferTo(aliceEOAAddress, balanceOf(relayer));
And Alice will receive the tokens owned by the relayer at her EOA address.
Remove transferTo() from Vader.sol, USDV.sol, Synth.sol, Token1.sol, and Token2.sol.
#0 - strictly-scarce
2021-05-01T10:32:13Z
The protocol is not designed to be used by relayers.
#1 - dmvt
2021-05-26T18:40:44Z
duplicate of #217
14.6655 VETH - $762.61
0.352 ETH - $879.93
jvaqa
Anyone Can Avoid All Vether Transfer Fees By Adding Their Address to the Vether ExcludedAddresses List.
Vether.sol implements a fee on every token transfer, unless either the sender or the recipient exists on a list of excluded addresses (mapAddress_Excluded). However, the addExcluded() function in Vether.sol has no restrictions on who can call it. So any user can call addExcluded with their own address as the argument, and bypass all transfer fees.
Alice calls:
(1) Vether.addExcluded(aliceAddress), which adds Alice's address to mapAddress_Excluded. (2) Alice can now freely transfer Vether with no fees.
Add restrictions to who can call addExcluded, perhaps by restricting it to a caller set by DAO.sol
#0 - strictly-scarce
2021-05-01T10:31:12Z
Vether contract is outside of contest
#1 - dmvt
2021-05-26T22:27:05Z
https://github.com/code-423n4/2021-04-vader-findings/issues/3#issuecomment-849043144
The warden should be paid out on this issue, in my opinion, because the code was included in the repo to be reviewed. The work to review the contract was done despite the fact that the team has addressed the issue and has already deployed vether.sol. I do not think that any issues related to Vether.sol should be included in the final report generated by @code423n4.
It was unclear to me (and obviously most of the wardens) that Vether.sol was considered out of scope.
14.6655 VETH - $762.61
0.352 ETH - $879.93
jvaqa
Vader.sol secondsPerEra Is Currently Initialized To One Second Instead Of One Day
Vader comments and documentation describe an "era" as taking one calendar day, which is 86400 seconds. However, in Vader.sol, secondsPerEra is currently initialized to one second. This will cause the first call of every single block to Vader._transfer() to make the gas-intensive subcall to _checkEmission(). This will also increment the currentEra counter incredibly quickly.
Vader.sol's constructor includes this line of code: secondsPerEra = 1; //86400;
Change this: secondsPerEra = 1; //86400;
To this: secondsPerEra = 86400;
#0 - dmvt
2021-05-25T12:57:34Z
duplicate of #155
1.7819 VETH - $92.66
0.0428 ETH - $106.91
jvaqa
Anyone Can Call Init() and Lock It Forever For Attack.sol, DAO.sol, Factory.sol, Pools.sol, Router.sol, Vault.sol, and Vader.sol
When trying to deploy vader contracts, an attacker could call init() on each deployed contract and lock it, wasting the deployer's gas and time. If the attacker calls init() on a contract that has already been referenced by a previous contract, the attacker causes that contract to be rendered useless as well. Since init() can only be called once, and then is locked forever, a successful attack leaves the entire contract useless, and wastes Vader's funds.
Alice sees that the Vader team is in the process of deploying their contracts. Alice calls init() on each contract as it is deployed, supplying junk data to the arguments. This wastes Vader's time and money.
Either move init() functionality to the constructor of each contract, or add a restriction to who can call init().
#0 - dmvt
2021-05-26T22:50:41Z
duplicate of #18
0 VETH - $0.00
0.0116 ETH - $29.09
jvaqa
Gas Optimization: in Vader.sol, Turn Variables _1m, maxSupply, and decimals, into Constants/Immutable variables
Currently, in Vader.sol, the variables "_1m", "maxSupply", and "decimals", are kept mutable, incurring an SLOAD call on every invocation. However, the variable "_1m" is only ever used to refer to the constant value of 1 million * weiDecimals, the variable maxSupply is only ever used to refer to 2 million * weiDecimals, and decimals is only ever used to refer to 10 ** 18. If you make these variables constant, the compiler will inline those values into every call to _1m, maxSupply, and decimals, avoiding all SLOAD's in any calls that use them.
In Vader.sol's constructor, the following lines are used: decimals = 18; _1m = 10**6 * 10 ** decimals; //1m maxSupply = 2 * _1m;
If you would like to set the value in the constructor rather than simply hardcoding it, then use the keyword "immutable" to set it once and never use an SLOAD again. Or, you can simply hardcode the value in the variable's declaration, and then use the "constant" keyword.
#0 - 0xBrian
2021-05-11T05:21:34Z
#1 - dmvt
2021-05-26T21:10:24Z
duplicate of #286
🌟 Selected for report: jvaqa
0 VETH - $0.00
0.0638 ETH - $159.60
jvaqa
Avoid Unnecessary Expensive SSTORE Calls In Vether.sol By Checking If _fee Is Non-Zero
SSTORE calls (writes to storage) are very expensive, especially for cold-storage slots (those that have not yet been accessed this transaction). We know that the SSTORE call to totalFees will be a cold storage call, since this is the only place in the whole contract that totalFees is used. Vether.sol makes two SSTORE calls in _transfer that are unnecessary when _fee is zero. It will be common for _fee to be zero, since Vether.sol implements an "excluded addresses" list (mapAddress_Excluded), where _fee is zero when either the sender or the recipient is on the excludedAddresses list. Currently, anyone can add themselves to the excludedAddresses list, but that is probably a mistake. Nevertheless, since it will probably at least include Uniswap, we should add a check for whether _fee is zero.
When _fee is zero, Vether._transfer() nevertheless makes these two unnecessary SSTORE calls:
_balances[address(this)] += _fee; totalFees += _fee;
Change this:
_balances[address(this)] += _fee; totalFees += _fee;
To this:
if(_fee > 0){ _balances[address(this)] += _fee; totalFees += _fee; }
🌟 Selected for report: jvaqa
0 VETH - $0.00
0.0638 ETH - $159.60
jvaqa
This check is unnecessary since Solidity 0.8.x prevents overflows: require(_balances[_to] + _value >= _balances[_to], 'Balance overflow');
Remove the aforementioned line of code, since Vether.sol uses Solidity 0.8.x, so it will revert on an overflow anyways.
0 VETH - $0.00
0.0172 ETH - $43.09
jvaqa
Gas Optimization: Vader.sol Unnecessary Conditional
You can remove this conditional entirely.
In Vader.sol, change this:
if(emitting){ emitting = false; } else { emitting = true; }
To this:
emitting = !emitting;
🌟 Selected for report: jvaqa
0 VETH - $0.00
0.0638 ETH - $159.60
jvaqa
Gas Optimization: Utils.sol Make An Unnecessary Multiplication And Division By An Identical Value
The value "(T1 * B1) / T1" is identical to the value "B1", so you can simplify the expression "B1 + (T1 * B1) / T1" to "B1 + B1".
In Utils.sol, replace this:
uint _redemptionValue = B1 + (T1 * B1) / T1;
With this:
uint _redemptionValue = B1 + B1;
#0 - 0xBrian
2021-05-11T06:56:09Z
🌟 Selected for report: jvaqa
0 VETH - $0.00
0.0638 ETH - $159.60
jvaqa
Gas Optimization: DAO.sol Unnecessary Multiple Return Statements
In DAO.sol, replace this:
if(votes > consensus){ return true; } else { return false; }
With this:
return (votes > consensus)
#0 - 0xBrian
2021-05-11T06:57:06Z
🌟 Selected for report: jvaqa
3.259 VETH - $169.47
0.0782 ETH - $195.54
jvaqa
The Calculation For nextEraTime Drifts, Causing Eras To Occur Further And Further Into The Future.
In Vader.sol, eras are intended to occur every 24 hours. This means that a correct implementation would add 24 hours to the end-time of the previous era to find the end-time of the next era. However, the current method for calculating the next era's end-time uses block.timestamp, rather than the previous era's end-time.
This line of code will cause a perpetual drift of era times, causing each era to actually be 24 hours plus the time between when the last era ended and when Vader._transfer() is next called.
In Vader.sol, change this:
nextEraTime = block.timestamp + secondsPerEra;
to this:
nextEraTime = nextEraTime + secondsPerEra;