GithubHelp home page GithubHelp logo

2021-04-vader-findings's People

Contributors

c4-staff avatar code423n4 avatar joshuashort avatar sockdrawermoney avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

2021-04-vader-findings's Issues

Result of moveTokenToPools not used in swapWithSynthsWithLimit

Handle

gpersoon

Vulnerability details

Impact

The function swapWithSynthsWithLimit of Router.sol calls the function moveTokenToPools.
moveTokenToPools has a safeguard mechanism to check tokens have actually been transferred, and returns the count as function result.
However swapWithSynthsWithLimit doesn't check the results.

This means that a "bad" token could be used that doesn't revert on impossible transfers.
This would not be caught by swapWithSynthsWithLimit, which would execute the rest of its function.

This could be an ingredient of a bigger hack.

Proof of Concept

Router.sol:

function swapWithSynthsWithLimit(uint inputAmount, address inputToken, bool inSynth, address outputToken, bool outSynth, uint slipLimit) public returns (uint outputAmount) {
address _member = msg.sender;
if(!inSynth){
moveTokenToPools(inputToken, inputAmount);
} else {
moveTokenToPools(iPOOLS(POOLS).getSynth(inputToken), inputAmount);
}

function moveTokenToPools(address _token, uint _amount) internal returns(uint safeAmount) {
  ...
    } else {
        uint _startBal = iERC20(_token).balanceOf(POOLS);
        iERC20(_token).transferFrom(msg.sender, POOLS, _amount);
        safeAmount = iERC20(_token).balanceOf(POOLS) - _startBal;
    }
}

Tools Used

Editor

Recommended Mitigation Steps

Use the results of moveTokenToPools or explicitly check that transferFrom went as expected.

Uninformative error message on L82 of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

Use of informative error messages helps troubleshoot exceptional conditions during transaction failures or unexpected behavior. Otherwise, it can be misleading and waste crucial time during exploits or emergency conditions.

For reference, see Note 2 in OpenZeppelin's Audit of Compound Governor Bravo: https://blog.openzeppelin.com/compound-governor-bravo-audit/

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L82

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use a more meaningful error message which specifically describes the conditional failure.

wrong logic check in in _getFee(address, address, uint){}

Handle

JMukesh

Vulnerability details

Impact

user can escape from paying fee if one of the address is excluded

Proof of Concept

https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code

// Calculate Fee amount
function _getFee(address _from, address _to, uint _value) private view returns (uint) {
if (mapAddress_Excluded[_from] || mapAddress_Excluded[_to]) {
return 0; // No fee if excluded
} else {
return (_value / 1000); // Fee amount = 0.1%
}
}

In this function , wrong condition is used in if(){} statement due to which, if one of the address is excluded , it will return 0 fees . Instead of || condition it should be &&

Tools Used

no tool used

Recommended Mitigation Steps

change the condition in if(){} statement

Decimal value of token should be of uint8 type not uint

Pay double fees in addExcluded of Vether.sol

Handle

gpersoon

Vulnerability details

Impact

In the function addExcluded of Vether.sol (https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code)
a call is made to _transfer to pay a fee of 128 Vether, however to do that _transfer you have to pay an additional fee of 0.128 Vether.
Right after this call mapAddress_Excluded is set, which prevents paying fees.
It seems logical to just pay fees once.

Proof of Concept

function addExcluded(address excluded) external {    
    if(!mapAddress_Excluded[excluded]){
        _transfer(msg.sender, address(this), mapEra_Emission[1]/16);                    // Pay fee of 128 Vether
        mapAddress_Excluded[excluded] = true;                                           // Add desired address
        excludedArray.push(excluded); excludedCount +=1;                                // Record details
        totalFees += mapEra_Emission[1]/16;                                             // Record fees
    }              
}

Tools Used

Editor

Recommended Mitigation Steps

Check if the conclusions are sound.
Then do the mapAddress_Excluded[excluded] = true; ...
before the _transfer

events can be emitted even after failed transaction

Handle

JMukesh

Vulnerability details

Impact

when anyone try to remove liquidity or wanted swap , their transaction may get failed. Even though transaction got failed , event will be emitted which can be problematic if we are keeping track record offchain

Proof of Concept

In Pools.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L92

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L101

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L163

in _removeLiquidity(){} function, swap(){} function, burnSynth(){} function, event is emitted before transferOut(){} function get completed , since transferOut(){} function does not check return value from transfer

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L211

transaction may get failed even though event is emitted

Tools Used

No tool used

Recommended Mitigation Steps

check return value from transfer function in order to know whether transaction got successfully executed or not

Transfer fee avoidance

Handle

toastedsteaksandwich

Vulnerability details

Description

The Vether4.addExcluded() function on mainnet (0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279) allows a user to exclude an address from transfer fees for a cost of 128 VETH. By exploiting the conditions in which fees are taken, it is possible to set up a contract for a once-off cost in which all users can use to avoid transfer fees.

Impact

All transfer fees can be avoided by routing transfers through an excluded contract. An estimated $140k of transfer fees was accumulated at the time of writing. These fees can be avoided in future, causing an indirect loss of funds for the contract.

Proof of Concept

I've listed the a test case and the transferForwarder contract source in the following gist: https://gist.github.com/toastedsteaksandwich/2057bfeca5f0340838970c7ee9c9d7ab

Tools Used

Hardhat with mainnet forking pinned to block 12227519

Recommended Mitigation Steps

The _transfer() function should be updated to only exclude transfer fees if the sender has been excluded, not both the sender and the recipient. This would prevent any user from being able to set up a central transfer forwarder as demonstrated. Moreover, the Transfer(_from, address(this), _fee); event should only be emitted if the sender has been excluded from transfer fees.

Named return variable in harvest() and other functions of Vault.sol and contracts

Handle

0xRajeev

Vulnerability details

Impact

There is an inconsistent use of implicit named return variables across the entire codebase which makes readability and maintainability hard.

Reference Note 6 from OpenZeppelin’s audit of Holdefi: https://blog.openzeppelin.com/holdefi-audit/#notes

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L101

Tools Used

Manual Analysis

Recommended Mitigation Steps

Consider removing all named return variables, explicitly declaring them as local variables in the body of the function, and adding the necessary explicit return statements where appropriate. This will favor both explicitness and readability of the codebase.

Uninformative error message in grant() function of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

Use of informative error messages helps troubleshoot exceptional conditions during transaction failures or unexpected behavior. Otherwise, it can be misleading and waste crucial time during exploits or emergency conditions.

For reference, see Note 2 in OpenZeppelin's Audit of Compound Governor Bravo: https://blog.openzeppelin.com/compound-governor-bravo-audit/

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L69

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use a more meaningful error message which specifically describes the conditional failure.

Circumvent several checks

Handle

gpersoon

Vulnerability details

Impact

You can take a random token contract "R", give the pools contract a few of the "R" tokens.
Then call addLiquidity ==> this will set _isAsset(R) == true
Then call replacePool ==> this will set _isCurated == true
When you then call getProtection ==> if(iROUTER(ROUTER).isCurated(token)) will be true.

This could be a part of a contract hack

Proof of Concept

Pools.sol
function addLiquidity(address base, address token, address member) external returns(uint liquidityUnits) {
require(token != USDV && token != VADER); // Prohibited
...
if(base == VADER){
..
} else if (base == USDV) {
if(!isAsset(token)){
_isAsset[token] = true; //=> set _isAsset with a small amount for addLiquidity
}
....
}

Router.sol
function replacePool(address oldToken, address newToken) external {
require(iPOOLS(POOLS).isAsset(newToken)); // ==> this check can be passed now
if(iPOOLS(POOLS).getBaseAmount(newToken) > iPOOLS(POOLS).getBaseAmount(oldToken)){ // Must be deeper
_isCurated[oldToken] = false;
_isCurated[newToken] = true; // ==> set _isCurated to true
....
}
}

utils.sol
function getProtection(address member, address token, uint basisPoints, uint timeForFullProtection) public view returns(uint protection) {
uint _coverage = getCoverage(member, token);
if(iROUTER(ROUTER).isCurated(token)){ // ==> this check can be passed now
...
}
return calcPart(basisPoints, protection);
}

Tools Used

Editor

Recommended Mitigation Steps

Add additional checks to addLiquidity (and perhaps the other functions)

Some unused code

Handle

gpersoon

Vulnerability details

Impact

There is some unused / redundant code present.

Router.sol defines repayDelay but it is never used
Vault.sol initializes POOLS twice, with the same value.

Proof of Concept

Router.sol: uint public repayDelay = 3600;

Vault.sol:
function init(address _vader, address _usdv, address _router, ...
..
POOLS = _pool;
..
POOLS = _pool;

Tools Used

Editor

Recommended Mitigation Steps

Remove redundant code

ERC20 race condition for allowances

Handle

toastedsteaksandwich

Vulnerability details

Impact

Due to the implementation of the approve() function in Vader.sol, Vether.sol and mainnet Vether4 at 0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279, it's possible for a user to over spend their allowance in certain situations.

Proof of Concept

The steps to the attack are as follows:

  1. Alice approves an allowance of 5000 VETH to Bob.
  2. Alice attempts to lower the allowance to 2500 VETH.
  3. Bob notices the transaction in the mempool and front-runs it by using up the full allowance with a transferFrom call.
  4. Alice's lowered allowance is confirmed and Bob now has an allowance of 2500 VETH, which can be spent further for a total of 7500 VETH.

Overall, Bob was supposed to be approved for at most 5000 VETH but got 7500 VETH. The POC code can be found here: https://gist.github.com/toastedsteaksandwich/db32472ae5c19c2eb188f07abddd02fa

Note that in the POC, Bob receives 7492.5 VETH instead of 7500 VETH due to transfer fees.

Tools Used

Hardhat with mainnet forks, pinned to block 12227519.

Recommended Mitigation Steps

Instead of having a direct setter for allowances, decreaseAllowance and increaseAllowance functions should be exposed which decreases and increases allowances for a recipient respectively. In this way, if the decreaseAllowance call is front-run, the call should revert as there is insufficient allowance to be decreased. This leaves Bob with at most 5000 VETH, the original allowance.

sortArray optimizable

Handle

gpersoon

Vulnerability details

Impact

The function sortArray is only called by getAnchorPrice() in Router.sol
Then it is only used to get the second element of the sorted array.
With this knowledge it is possible to write a function that is more efficient.
Sort functions are relative expensive functions.

Proof of Concept

Router.sol:
function getAnchorPrice() public view returns (uint anchorPrice) {
....
uint[] memory _sortedAnchorFeed = iUTILS(UTILS()).sortArray(arrayPrices);
anchorPrice = _sortedAnchorFeed[2];

Tools Used

Recommended Mitigation Steps

Make a partial sort function that only returns the required value

Init function can be called by everyone

Handle

gpersoon

Vulnerability details

Impact

Most of the solidity contracts have an init function that everyone can call.
This could lead to a race condition when the contract is deployed. At that moment a hacker could call the init function and make the deployed contracts useless. Then it would have to be redeployed, costing a lot of gas.

Proof of Concept

DAO.sol: function init(address _vader, address _usdv, address _vault) public {
Factory.sol: function init(address _pool) public {
Pools.sol: function init(address _vader, address _usdv, address _router, address _factory) public {
Router.sol: function init(address _vader, address _usdv, address _pool) public {
USDV.sol: function init(address _vader, address _vault, address _router) external {
Utils.sol: function init(address _vader, address _usdv, address _router, address _pools, address _factory) public {
Vader.sol: function init(address _vether, address _USDV, address _utils) external {
Vault.sol: function init(address _vader, address _usdv, address _router, address _factory, address _pool) public {

Tools Used

Editor

Recommended Mitigation Steps

Add a check to the init function, for example that only the deployer can call the function.

State variable that can be declared as constant to save gas

Handle

JMukesh

Vulnerability details

These are the state variable which can be declared as constant to save gas

  1. In Vether.sol

    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vether.sol#L13

uint public override decimals = 18;

  1. In Util.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Utils.sol#L16

uint private one = 10**18;
uint private _10k = 10000;
uint private _year = 31536000;
  1. In token1.sol and token2.sol

    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Token2.sol#L13

uint public override decimals = 18;

  1. In Synth.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Synth.sol#L16

    uint public override decimals = 18;

  2. In Router.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Router.sol#L17

    uint one = 10**18;

User can escape from paying fees

Handle

JMukesh

Vulnerability details

Impact

its impact will be high since, collecting fee from user is one of the source of revenue. If user are able to bypass the fees collected by protocol then it will decrease the revenue

Proof of Concept

In Vether.sol
https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vether.sol#L93

function addExcluded(address excluded) public {
mapAddress_Excluded[excluded] = true;
}

this function can be called by anyone , due to which anyone can their map their own address. Since this address will be excluded, because of that _getFee(address, address, uint){} will return 0 fees which leads to bypass of transaction fees when it call transfer(address, uint){} function.

Tools Used

No tool used

Recommended Mitigation Steps

Add access control modifier

Missing event for critical init() function in Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

The init() function initialises critical protocol parameters for this contract but is missing an event emission for off-chain monitoring tools to monitor this on-chain change.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L45-L57

Tools Used

Manual Analysis

Recommended Mitigation Steps

Add an init event and emit that at the end of init() function.

Lack of zero address validation in init() function

Handle

JMukesh

Vulnerability details

Impact

The parameter that are used in init() function to initialize the state variable,these state variable are used in other function to perform operation. since it lacks zero address validation, it will be problematic if there is error in these state variable. some of the function will loss their functionality which can cause the redeployment of contract

Proof of Concept

  1. Vault.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vault.sol#L45

  2. Vader.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vader.sol#L74

  3. Utils.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Utils.sol#L30

  4. Router.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Router.sol#L77

  5. Pools.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L43

  1. Factory.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Factory.sol#L27

  1. Dao.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/DAO.sol#L46

Tools Used

slither

Recommended Mitigation Steps

add require condition which check zero address validation

function isMember(address){} will always return false value, irrespective of what address is

Handle

JMukesh

Vulnerability details

Impact

isMember(){} will always return false value

Proof of Concept

In Pools.sol
https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L215

function isMember(address member) public view returns(bool) {
return _isMember[member];
}

it will always return false because _isMember never got initialized

Tools Used

slither

Recommended Mitigation Steps

remove the function if it serve no purpose

Functions with implicit return values

Handle

gpersoon

Vulnerability details

Impact

Several functions have implicit return values. Although in that case 0 or false is returned, this might not be obvious to the casual reader.

Proof of Concept

USDV.sol:
function isMature() public view returns(bool isMatured){
if(lastBlock[tx.origin] + blockDelay <= block.number){ // Stops an EOA doing a flash attack in same block
return true;
}
}

Note there are several other functions where this happens.

Tools Used

Editor

Recommended Mitigation Steps

Use explicit return values or add a comment than an implicit value is used.

totalFees of Vether.sol can be calculated

Handle

gpersoon

Vulnerability details

Impact

The value of totalFees of Vether.sol (https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code) is
increased at the same same as _balances[address(this)] is increased
This means it can also be calculated, by retrieving _balances[address(this)]

This save a bit of gas and also makes the code easier to read.

Proof of Concept

Vether.sol:
function addExcluded(address excluded) external {
if(!mapAddress_Excluded[excluded]){
_transfer(msg.sender, address(this), mapEra_Emission[1]/16); // Pay fee of 128 Vether
...
totalFees += mapEra_Emission[1]/16; // Record fees
}
}

function _transfer(address _from, address _to, uint _value) private {
...
_balances[address(this)] += _fee; // Add fee to self
totalFees += _fee; // Track fees collected
....
}

Tools Used

Editor

Recommended Mitigation Steps

Double check if the ideas above are sound
Remove the lines with totalFees
Make a function to return _balances[address(this)]

Public function that could be declared external

Handle

JMukesh

Vulnerability details

Impact

public functions that are never called by the contract should be declared external to save gas.

Proof of Concept

  1. In Vault.sol -- > init() and grant()

    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vault.sol#L45

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vault.sol#L68

  1. Vader.sol -- > burn()

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vader.sol#L146

  1. Utils.sol -- > init(), getProtection()

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Utils.sol#L30

  1. Router.sol -- >

init(address,address,address)
getVADERAmount(uint256)
getUSDVAmount(uint256)
borrow(uint256,address,address)
repay(uint256,address,address)
checkLiquidate()
getSystemCollateral(address,address)
getSystemDebt(address,address)
getSystemInterestPaid()

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Router.sol#L77

  1. Pools.sol

init(address,address,address,address)
isMember(address)
isSynth(address)

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol

  1. Dao.sol

init(address,address,address)
newGrantProposal(address,uint256)
newAddressProposal(address,string)
voteProposal(uint256)
cancelProposal(uint256,uint256)
finaliseProposal(uint256)

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/DAO.sol#L46

Tools Used

slither

Recommended Mitigation Steps

use external instead of public visibility to save gas

Misleading comment for deposit() function of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

Use of accurate comments helps read, audit and maintain code. Otherwise, it can be misleading and miss the flagging of or cause the introduction of vulnerabilities.

Misleading comment that below functions allow USDV and Synths but the code only allows Synths.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L76-L77

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use accurate and descriptive comments (even NatSpec) correctly describing the function behavior, parameters and return values.

deploySynth of VADER and USDV is possible

Handle

gpersoon

Vulnerability details

Impact

The function deploySynth of Pools.sol has the following require:
require(token != VADER || token != USDV);
This require never stops execution because the following statement is always true:
(token != VADER || token != USDV)

  • suppose token==VADER => (VADER != VADER || VADER != USDV) ==> false || true ==> true
  • suppose token==USDV => (USDV != VADER || USDV != USDV) ==> true || false ==> true
    So this means a synth token for both VADER and USDV can be create which is probably unwanted

Proof of Concept

Pools.sol:
function deploySynth(address token) external {
require(token != VADER || token != USDV);
iFACTORY(FACTORY).deploySynth(token);
}

Tools Used

Editor

Recommended Mitigation Steps

The statement should be:
require(token != VADER && token != USDV);

Gas savings by avoiding re-initialization of POOLS variable in init() function of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

POOLS address is initialized on L48 but re-initialized on L53. This unnecessary storage write will cost 200 gas because it is overwritten with the same value as earlier (see EIP-1087).

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L48

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L53

Tools Used

Manual Analysis

Recommended Mitigation Steps

Remove re-initialization on L53.

Result of ERC20 transfer not checked

Handle

gpersoon

Vulnerability details

Impact

The function transferOut of Pools.sol contains a iERC20(_token).transfer where the result of the function isn't checked.
This could result in transfers that don't succeed are undetected.

Proof of Concept

Pools.sol:
function transferOut(address _token, uint _amount, address _recipient) internal {
if(_token == VADER){
pooledVADER = pooledVADER - _amount; // Accounting
} else if(_token == USDV) {
pooledUSDV = pooledUSDV - _amount; // Accounting
}
if(_recipient != address(this)){
iERC20(_token).transfer(_recipient, _amount);
}
}

Tools Used

Editor

Recommended Mitigation Steps

Add a require statement to check the result:
require(...transfer(...) )

totalBurnt might be wrong

Handle

gpersoon

Vulnerability details

Impact

Vether.sol (https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code)
is the 4th contract version.
It takes the totalBurnt value of the 2nd version of the contract an continues on that.
It seem more logical to use the totalBurnt value of the 3rd version of the contract an continues on that.
This way the value of totalBurnt is probably not the real value.

Proof of Concept

vether.sol:
contract Vether4 is ERC20 {
constructor() public {
...
vether2 = 0x01217729940055011F17BeFE6270e6E59B7d0337; // Second Vether
vether3 = 0x75572098dc462F976127f59F8c97dFa291f81d8b;
...
totalBurnt = VETH(vether2).totalBurnt(); totalFees = 0;

Tools Used

Editor

Recommended Mitigation Steps

Check if indeed vether3 should be used and update the code to use vether3

Vault functions can be called before initialization in init() of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

The public/external functions can be called by anyone even before the contract is initialized. This can lead to state corruption and incorrect accounting, which may require redeployment of contract.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L45-L57

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L77

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L81

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L101

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L123

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L152

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use a factory pattern that will deploy and initialize atomically to prevent front-running of the initialization, or
Given that contracts are not using delegatecall proxy pattern, it is not required to use a separate init() function to initialize parameters when the same can be done in a constructor. If the reason for doing so is to get the deployment addresses of the various contracts which may not all be available at the same time then consider rearchitecting to create a “globals” contract which can hold all the globally required addresses of various contracts. (see Maple protocol’s MapleGlobals.sol for example) or
Prevent external/public functions from being called until after initialization is done by checking initialization state tracked by the inited variable.

Vader.redeemToMember() vulnerable to front running

Handle

toastedsteaksandwich

Vulnerability details

Details

The USDV balance of the Vader contract is vulnerable to theft through the Vader.redeemToMember() function. A particular case is through USDV redemption front-running. Users can redeem USDV for Vader through the USDV.redeemForMember() function or the Vader.redeemToMember() function. In the case of Vader.redeemToMember(), a user would need to send their USDV to the contract before redemption. However, as this process does not happen in a single call, the victim's call is vulnerable to front running and could have their redeemed USDV stolen by an attacker.

Impact

User's redeem USDV could be stolen by an attacker front running their Vader.redeemToMember() call.

Proof of Concept

The steps are as follows:

  1. User sends USDV to Vader contract to be redeemed
  2. User calls Vader.redeemToMember()
  3. The Vader.redeemToMember() call is detected by an attacker, who front-runs the call by calling Vader.redeemToMember() specifying their own address as the member parameter.
  4. The full USDV balance of the Vader contract is redeemed and sent to the attacker.

Note that while this particular case is front running a redemption call, any USDV balance could be stolen in this manner. Please find the POC showing the above steps here: https://gist.github.com/toastedsteaksandwich/39bfed78b21d7e6c02fe13ea5b2023c3

Tools Used

Hardhat on a local hardhat network

Recommended Mitigation Steps

The Vader.redeemToMember() function should be restricted so that only the USDV contract can call it. Moreover, the amount parameter from USDV.redeem() or USDV.redeemForMember() should also be passed to Vader.redeemToMember() to avoid the need to sweep the entire USDV balance. In this way, the member's redemption happens in a single tx, and would only be allocated as much Vader as redeemed in USDV.

Wrong logic check in _getFee(){}

Handle

JMukesh

Vulnerability details

Impact

user can bypass the fees if any one of the address is exluded

Proof of Concept

In Vether.sol
https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vether.sol#L86

function _getFee(address _from, address _to, uint _value) private view returns (uint) {
    if (mapAddress_Excluded[_from] || mapAddress_Excluded[_to]) {
       return 0;                                                                        // No fee if excluded
    } else {
        return (_value / 1000);                                                         // Fee amount = 0.1%
    }
}

In this function, in if(){} statement { || condition is used}
if any of the address that is _from and _to
is excluded then user can bypass the fees. Instead of { || condition, it should be && }

Tools Used

No tool used

Recommended Mitigation Steps

use && condition in if(){}

Missing DAO functionality to call setParams() function in Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

setParams() is authorized to be called only from the DAO (per modifier) but DAO contract has no corresponding functionality to call setParams() function. As a result, erasToEarn, minimumDepositTime and minGrantTime are stuck with initialized values and cannot be changed.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L61-L65

Tools Used

Manual Analysis

Recommended Mitigation Steps

Add functionality to DAO to be able to call setParams() of Vault.sol.

Testing form configuration

Handle

adamavenir

Vulnerability details

Impact

Provide a detailed description of the impact this bug/vulnerability has on the overall system under test.

Proof of Concept

Provide screenshots, logs, or any other relevant proof that illustrates the concept of the bug/vulnerability you have identified.

Tools Used

Describe the tools used throughout your testing and analysis process.

Recommended Mitigation Steps

Describe the recommended steps that a project should use to mitigate the bugs or vulnerabilities you have identified.

Lack of input validation in init() function of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

init() function initialises various critical parameters of the contract including the addresses of different contracts. However, there is no zero-address validation of the address parameters. Using a zero address by mistake will require redeployment because initialization can be done only once due to the inited check.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L45-L57

Tools Used

Manual Analysis

Recommended Mitigation Steps

Add zero-address validation for all the address parameters of init() function.

Anyone can propose new grants and new address proposal in Dao.sol

Handle

JMukesh

Vulnerability details

Impact

Anyone can propose new grants , due to lack of absence of access control in newGrantProposal() and lack of Minimum threshold to propose.
similarly anyone can propose new address proposal due to lack of absence of access control in newAddressProposal() , since this address can be used to move rewardAddress and utilities , anyone can take advantage of this exploit for their own benefit.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/DAO.sol#L57

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/DAO.sol#L69

Tools Used

No tool use

Recommended Mitigation Steps

add access control modifier in both function

Lack of zero address validation in init() function

Handle

JMukesh

Vulnerability details

Impact

The parameter that are used in init() function to initialize the state variable,these state variable are used in other function to perform operation. since it lacks zero address validation, it will be problematic if there is error in these state variable. some of the function will loss their functionality which can cause the redeployment of contract

Proof of Concept

  1. Vault.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vault.sol#L45

  2. Vader.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Vader.sol#L74

  3. Utils.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Utils.sol#L30

  4. Router.sol
    https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Router.sol#L77

  5. Pools.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L43

  1. Factory.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Factory.sol#L27

  1. Dao.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/DAO.sol#L46

Tools Used

slither

Recommended Mitigation Steps

add require condition which check zero address validation

Zero amount of synth token can be minted

Handle

JMukesh

Vulnerability details

Impact

Zero amount of synth can be minted due to lack of input validation of parameter. it will impact the user due which they will end paying gas fees for nothing

Proof of Concept

In Factory.sol
https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Factory.sol#L43

function mintSynth(address synth, address member, uint amount) external onlyPOOLS returns(bool) {
Synth(synth).mint(member, amount);
return true;
}

In this function we have no validation of uint amount , whether it is greater than 0 or not, then this amount is passed in

Synth(synth).mint(member, amount).

In Synth.sol

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Synth.sol#L86

function mint(address account, uint amount) external virtual onlyFACTORY {
require(account != address(0), "recipient");
totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}

It also lack input validation of uint amount.

conclusion : due to lack of input validation in both function ,
zero amount of synth tokens can be minted

Tools Used

No tool used

Recommended Mitigation Steps

add a condition to check the uint values

isMember(address member) function never been called in another function to check wether given address is member or not

Handle

JMukesh

Vulnerability details

Impact

isMember(address member) function never been called in another function to check wether given address is member or not. Due to uninitialized of _isMember state variable , it will always return false which make this function useless.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/main/vader-protocol/contracts/Pools.sol#L215

Tools Used

no tool used

Recommended Mitigation Steps

this function should be removed if it serve no purpose

Gas savings by converting storage variables to immutable by moving initialization from init() function to constructor in Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

From Solidity’s documentation (https://docs.soliditylang.org/en/v0.8.4/contracts.html#constant-and-immutable-state-variables), “State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time. The compiler does not reserve a storage slot for these variables, and every occurrence is replaced by the respective value. Compared to regular state variables, the gas costs of constant and immutable variables are much lower.”

The address variables VADER, USDV, ROUTER, POOLS and FACTORY can be made immutable if they are initialized at construction time within a constructor. This will avoid the use of five storage slots and lead to gas savings.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L19-L23

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L48-L52

Tools Used

Manual Analysis

Recommended Mitigation Steps

Move initialization from init() to a constructor and make address variables VADER, USDV, ROUTER, POOLS and FACTORY immutable.

use of constants and immutable

Handle

gpersoon

Vulnerability details

Impact

Several variables are initialized with a constant value are never changed.
Several other variables (especially address of contracts) are initialized a value are never changed.
These could very well be chanced to constant or immutable. This reduced the risk on accidental changes of the values and also saves a bit of gas.

Proof of Concept

Router.sol
function init(address _vader, address _usdv, address _pool) public {
require(inited == false, "inited");
inited = true;
VADER = _vader;
USDV = _usdv;
POOLS = _pool;

vader.sol
constructor() {
name = 'VADER PROTOCOL TOKEN';
symbol = 'VADER';
decimals = 18;
_1m = 10**6 * 10 ** decimals; //1m
baseline = _1m;
totalSupply = 0;
maxSupply = 2 * _1m;
currentEra = 1;
secondsPerEra = 1; //86400;
nextEraTime = block.timestamp + secondsPerEra;
emissionCurve = 900;
DAO = msg.sender;
burnAddress = 0x0111011001100001011011000111010101100101;
}

Tools Used

Editor

Recommended Mitigation Steps

Change variables to constant or immutable where possible.

Initialization can be front-run in Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

Given the public access, this is susceptible to front-running by an attacker who can initialize this with arbitrary assets before the deployer. Reinitialization will require contract redeployment because initialization can be done only once.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L45-L57

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use a factory pattern that will deploy and initialize atomically to prevent front-running of the initialization, or
Ensure the deployment scripts are robust in case of a front-running attack or
Given that contracts are not using delegatecall proxy pattern, it is not required to use a separate init() function to initialize parameters when the same can be done in a constructor. If the reason for doing so is to get the deployment addresses of the various contracts which may not all be available at the same time then consider rearchitecting to create a “globals” contract which can hold all the globally required addresses of various contracts. (see Maple protocol’s MapleGlobals.sol for example)

Reference: See finding #12 from Trail of Bits audit of Hermez Network: https://github.com/trailofbits/publications/blob/master/reviews/hermez.pdf

excludedCount of Vether.sol can be calculated

Handle

gpersoon

Vulnerability details

Impact

In Vether.sol (https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code)
The variable excludedCount is increased every time an element is pushed on the array excludedArray
This value can also be calculated by taking the length of excludedArray
This saves a bit of gas and also makes the code easier to read

Proof of Concept

Vether.sol
constructor() public {
...
excludedArray.push(address(this)); excludedCount = 1;
...
excludedArray.push(burnAddress); excludedCount +=1;

function addExcluded(address excluded) external {    
...
        excludedArray.push(excluded); excludedCount +=1;                                // Record details

Tools Used

Editor

Recommended Mitigation Steps

Create a function that returns excludedArray.length
Remove the code for excludedCount

Different pragma solidity

Handle

gpersoon

Vulnerability details

Impact

Vault.sol has a different pragma statement than the rest, it contains an additional "^".

For the record the Vether.sol contract (as deployed here https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code),
has a different solidity version.

It's cleaner to use the same versions.

Proof of Concept

DAO.sol:pragma solidity 0.8.3;
Factory.sol:pragma solidity 0.8.3;
Pools.sol:pragma solidity 0.8.3;
Router.sol:pragma solidity 0.8.3;
Synth.sol:pragma solidity 0.8.3;
USDV.sol:pragma solidity 0.8.3;
Utils.sol:pragma solidity 0.8.3;
Vader.sol:pragma solidity 0.8.3;
Vault.sol:pragma solidity ^0.8.3;
Vether.sol:pragma solidity 0.6.4;

Tools Used

Editor

Recommended Mitigation Steps

Use the same solidity versions

I really love The Tokens its hard to find a vulnerability

Handle

individual

Vulnerability details

Impact

Detailed description of the impact of this finding.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

Tools Used

Recommended Mitigation Steps

Gas savings by replacing public visibility with external for init() function of Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

init() function is never called from within the contract and so does not require public visibility. As described in https://mudit.blog/solidity-gas-optimization-tips/: “For all the public functions, the input parameters are copied to memory automatically, and it costs gas. If your function is only called externally, then you should explicitly mark it as external. External function’s parameters are not copied into memory but are read from calldata directly. This small optimization in your solidity code can save you a lot of gas when the function input parameters are huge.”

Given the five address parameters of init() function, this will save a reasonable amount of gas.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L45

Tools Used

Manual Analysis

Recommended Mitigation Steps

Change visibility of init() to external

Gas savings by replacing modifier with internal function in Vault.sol

Handle

0xRajeev

Vulnerability details

Impact

onlyDAO() modifier is applied on two functions in this contract. Modifier code is inlined for each of those functions. Instead, change modifier to an internal function which is called in the beginning of all those functions. This will prevent the duplication of the modifier code at two locations to reduce contract size and save gas.

Proof of Concept

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L37-L41

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L61

https://github.com/code-423n4/2021-04-vader/blob/3041f20c920821b89d01f652867d5207d18c8703/vader-protocol/contracts/Vault.sol#L68

Tools Used

Manual Analysis

Recommended Mitigation Steps

Replace onlyDAO() modifier with an internal function and call this function at the beginning of functions currently using this modifier.

Optimization possible at _transfer

Handle

gpersoon

Vulnerability details

Impact

The function _transfer of Vether.sol contains are relative complicated statement to determine if an emit should be done for the _fee.
This could be simplified, which saves a bit of gas and is also easier to read

Proof of Concept

function _transfer(address _from, address _to, uint _value) private {
    require(_balances[_from] >= _value, 'Must not send more than balance');
    require(_balances[_to] + _value >= _balances[_to], 'Balance overflow');
    _balances[_from] =_balances[_from].sub(_value);
    uint _fee = _getFee(_from, _to, _value);                                            // Get fee amount
    _balances[_to] += (_value.sub(_fee));                                               // Add to receiver
    _balances[address(this)] += _fee;                                                   // Add fee to self
    totalFees += _fee;                                                                  // Track fees collected
    emit Transfer(_from, _to, (_value.sub(_fee)));                                      // Transfer event
    if (!mapAddress_Excluded[_from] && !mapAddress_Excluded[_to]) {
        emit Transfer(_from, address(this), _fee);                                      // Fee Transfer event
    }
}

Tools Used

Editor

Recommended Mitigation Steps

if (!mapAddress_Excluded[_from] && !mapAddress_Excluded[_to]) {
can be replaced with:
if (_fee > 0) {

Unchecked return value from low-level call

Handle

JMukesh

Vulnerability details

Impact

In this case , if return value is not checked and transaction get failed, wrong value will get updated because of that. Due to update of wrong info , it will be difficult to track how much actual ether has been burned

Proof of Concept

https://etherscan.io/address/0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279#code

receive() external payable {
burnAddress.call.value(msg.value)(""); // Burn ether
_recordBurn(msg.sender, msg.sender, currentEra, currentDay, msg.value); // Record Burn
}

// Burn ether for nominated member
function burnEtherForMember(address member) external payable {
    burnAddress.call.value(msg.value)("");                                              // Burn ether
    _recordBurn(msg.sender, member, currentEra, currentDay, msg.value);                 // Record Burn
}

In these two function call() method is used to burn ether. since it does not check return value from call , transaction may get failed and burn will be recorded even after failed transaction

Tools Used

slither

Recommended Mitigation Steps

check return value from low-level calls

All ok

Handle

Alonso

Vulnerability details

Impact

Provide a detailed description of the impact this bug/vulnerability has on the overall system under test.

Proof of Concept

Provide screenshots, logs, or any other relevant proof that illustrates the concept of the bug/vulnerability you have identified.

Tools Used

Describe the tools used throughout your testing and analysis process.

Recommended Mitigation Steps

Describe the recommended steps that a project should use to mitigate the bugs or vulnerabilities you have identified.

Not always reason at require

Handle

gpersoon

Vulnerability details

Impact

Several requires don't have a reason string (error message) to explain why it has failed. This makes identifying the problems more difficult.

Proof of Concept

DAO.sol: require(inited == false);
Factory.sol: require(inited == false);
Pools.sol: require(inited == false);
Pools.sol: require(token != USDV && token != VADER); // Prohibited
Pools.sol: require(base == USDV || base == VADER);
Pools.sol: require(token != VADER || token != USDV);
Router.sol: require(iUTILS(UTILS()).calcSwapSlip(inputAmount, iPOOLS(POOLS).getTokenAmount(inputToken)) <= slipLimit);
Router.sol: require(iUTILS(UTILS()).calcSwapSlip(inputAmount, iPOOLS(POOLS).getBaseAmount(outputToken)) <= slipLimit);
Router.sol: require(iUTILS(UTILS()).calcSwapSlip(inputAmount, iPOOLS(POOLS).getTokenAmount(inputToken)) <= slipLimit);
Router.sol: require(iUTILS(UTILS()).calcSwapSlip(inputAmount, iPOOLS(POOLS).getBaseAmount(outputToken)) <= slipLimit);
Router.sol: require(iPOOLS(POOLS).isAsset(token) || iPOOLS(POOLS).isAnchor(token));
Router.sol: require(iPOOLS(POOLS).isAsset(newToken));
Router.sol: require(arrayAnchors.length < anchorLimit); // Limit
Router.sol: require(iPOOLS(POOLS).isAnchor(token)); // Must be anchor
Router.sol: require(iERC20(_token).transferTo(address(this), _amount));
Router.sol: require(iERC20(_token).transferFrom(msg.sender, address(this), _amount));
Router.sol: require(iERC20(_token).transfer(_member, _amount));
USDV.sol: require(inited == false);
USDV.sol: require(iERC20(token).transferTo(address(this), amount));
USDV.sol: require(iERC20(token).transferFrom(msg.sender, address(this), amount));
Vader.sol: require(inited == false);
Vader.sol: require(iERC20(VETHER).transferFrom(msg.sender, burnAddress, amount));
Vault.sol: require(inited == false);
Vault.sol: require(iERC20(synth).transferTo(address(this), amount));
Vault.sol: require(iERC20(synth).transferFrom(msg.sender, address(this), amount));
Vault.sol: require(iERC20(synth).transfer(member, amount));
Vether.sol: require(msg.sender == deployer);
Vether.sol: function purgeDeployer() public{require(msg.sender == deployer);deployer = address(0);}

Tools Used

grep require *.sol | grep -v """

Recommended Mitigation Steps

Add reasons (error messages) to the requires

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.