Platform: Code4rena
Start Date: 21/12/2021
Pot Size: $30,000 USDC
Total HM: 20
Participants: 20
Period: 5 days
Judge: Jack the Pug
Total Solo HM: 13
Id: 70
League: ETH
Rank: 1/20
Findings: 16
Award: $10,626.07
🌟 Selected for report: 20
🚀 Solo Findings: 8
141.5133 USDC - $141.51
TomFrenchBlockchain
Total loss of user-held USDV which has been approved on VaderPoolV2
VaderPoolV2
allows minting of sythentic tokens ("synths") using USDV with the mintSynth
function
Crucially this function allows a user supplied value for from
which specifies where the USDV should be pulled from. An attacker can then provide any address which has a token approval onto VaderPoolV2
for USDV and mint themselves a supported synth - stealing the underlying USDV.
The attacker than then simply burn the synth and sell the USDV to get a safe asset.
Remove from
argument and use msg.sender instead or a more sophisticated access control method.
#0 - jack-the-pug
2022-03-12T04:15:43Z
Dup of #147
🌟 Selected for report: TomFrenchBlockchain
1437.9245 USDC - $1,437.92
TomFrenchBlockchain
Possible theft of all user assets with an ERC20 approval on VaderPoolV2
The owner of VaderPoolV2
can call the setTokenSupport
function which allows the caller to supply any address from which to take the assets to provide the initial liquidity, the owner can also specify who shall receive the resulting LP NFT and so can take ownership over these assets. This call will succeed for any address which has an ERC20 approval on VaderPoolV2
for USDV and foreignAsset
.
This in effect gives custody over all assets in user wallets which are approved on VaderPoolV2
to Vader Protocol governance. This is especially problematic in the case of Vader Protocol as there's a single entity (i.e. the Council) which can force through a proposal to steal these assets for themselves with only the timelock giving protection to users, for this reason I give this high severity.
Enforce that the initial liquidity is provided by the VaderPoolV2 owner.
🌟 Selected for report: TomFrenchBlockchain
388.2396 USDC - $388.24
TomFrenchBlockchain
Invalid values returned from oracle for USDV and VADER prices in situations where the oracle uses more than one foreign asset.
The USDV price is calculated as so (for simplicity we'll consider a two pairs):
totalUSD = (PriceForeign0InUSD * liquidityWeights[0] + PriceForeign1InUSD * liquidityWeights[1]) / totalUSDVLiquidityWeight;
totalUSD
is then the average price of the foreign assets paired against USDV in terms of USD, weighted by the TVL of the relevant liquidity pool
totalUSDV = (pairData0 .nativeTokenPriceAverage .mul(pairData0.foreignUnit) .decode144() * liquidityWeights[0] + pairData1 .nativeTokenPriceAverage .mul(pairData1.foreignUnit) .decode144() * liquidityWeights[1]) / totalUSDVLiquidityWeight; // in pseudocode for readability totalUSDV = (USDVPriceInForeign0 * liquidityWeights[0] + USDVPriceInForeign1 * liquidityWeights[1]) / totalUSDVLiquidityWeight
totalUSDV
is then the average price of USDV in terms of each of the foreign assets, weighted by the TVL of the relevant liquidity pool.
It should be fairly clear that this is the incorrect calculation as all the terms in totalUSDV
are in different units - you can't average the price of USDV in ETH with the price of USDV in BTC and get a meaningful result.
It appears that the VADER team intended to calculate the price of USDV in terms of USD through a number of different paired assets and then average them at the end based on the liquidity in each pair but have started averaging too early.
High severity issue as the oracle is crucial for determining the exchange rate between VADER and USDV to be used for IL protection and minting/burning of USDV - an incorrect value will result in the protocol losing significant funds.
Review the algorithm used for calculating the prices of assets and ensure that it's calculating what you expect.
🌟 Selected for report: TomFrenchBlockchain
1437.9245 USDC - $1,437.92
TomFrenchBlockchain
Invalid values returned from oracle in vast majority of situations
The LBT oracle does not properly scale values when calculating prices for VADER or USDV. To show this we consider the simplest case where we expect USDV to return a value of $1 and show that the oracle does not return this value.
Consider the case of the LBT oracle tracking a single USDV-DAI pair where USDV trades 1:1 for DAI and Chainlink reports that DAI is exactly $1. We then work through the lines linked below:
For L397 we get a value of 1e8 as Chainlink reports the price of DAI with 8 decimals of accuracy.
foreignPrice = getChainlinkPrice(address(foreignAsset)); foreignPrice = 1e8
We can set liquidityWeights[i]
and totalUSDVLiquidityWeight
both to 1 as we only consider a single pair so L399-401 becomes
totalUSD = foreignPrice; totalUSD = 1e8;
L403-408 is slightly more complex but from looking at the links below we can calculate totalUSDV
as shown
https://github.com/code-423n4/2021-12-vader/blob/00ed84015d4116da2f9db0c68db6742c89e73f65/contracts/dex-v2/pool/VaderPoolV2.sol#L81-L90
https://github.com/code-423n4/2021-12-vader/blob/00ed84015d4116da2f9db0c68db6742c89e73f65/contracts/external/libraries/FixedPoint.sol#L137-L160
totalUSDV = pairData .nativeTokenPriceAverage .mul(pairData.foreignUnit) .decode144() // pairData.nativeTokenPriceAverage == 2**112 // pairData.foreignUnit = 10**18 // decode144(x) = x >> 112 totalUSDV = (2**112).mul(10**18).decode144() totalUSDV = 10**18
Using totalUSD
and totalUSDV
we can then calculate the return value of _calculateUSDVPrice
returnValue = (totalUSD * 1 ether) / totalUSDV; returnValue = 1e8 * 1e18 / 1e18 returnValue = 1e8
For the oracle implementation to be correct we then expect that the Vader protocol to treat values of 1e8 from the oracle to mean USDV is worth $1. However from the lines of code linked below we can safely assume that it is intended to be that values of 1e18 represent $1 rather than 1e8.
https://github.com/code-423n4/2021-12-vader/blob/00ed84015d4116da2f9db0c68db6742c89e73f65/contracts/tokens/USDV.sol#L76 https://github.com/code-423n4/2021-12-vader/blob/00ed84015d4116da2f9db0c68db6742c89e73f65/contracts/tokens/USDV.sol#L109
High severity issue as the oracle is crucial for determining the exchange rate between VADER and USDV to be used for IL protection and minting/burning of USDV - an incorrect value will result in the protocol losing significant funds.
Go over oracle calculation again to ensure that various scale factors are properly accounted for. Some handling of the difference in the number of decimals between the chainlink oracle and the foreign asset should be added.
Build a test suite to ensure that the oracle returns the expected values for simple situations.
🌟 Selected for report: TomFrenchBlockchain
Also found by: hyh
647.066 USDC - $647.07
TomFrenchBlockchain
(Resubmission as the form crashed apologies if this is a duplicate)
Impermanent loss protection can be exploited to drain the reserve.
In VaderPoolV2.burn
we calculate the current losses that the LP has made to impermanent loss.
These losses are then refunded to the LP in VADER tokens from the reserve.
This loss is calculated by the current reserves of the pool so if an LP can manipulate the pool's reserves they can artificially engineer a huge amount of IL in order to qualify for a payout up to the size of their LP position.
The attack is then as follows.
The attacker now holds the majority of their flashloaned funds (minus slippage/swap fees) along with a large fraction of the value of their LP position in VADER paid out from the reserve. The value of their LP position is unchanged. Given a large enough LP position, the IL protection funds extracted from the reserve will exceed the funds lost to swap fees and the attacker will be able to repay their flashloan with a profit.
This is a high risk issue as after a year any large LP is incentivised and able to perform this attack and drain reserve funds.
Use a manipulation resistant oracle for the relative prices of the pool's assets (TWAP, etc.)
🌟 Selected for report: TomFrenchBlockchain
Also found by: certora
647.066 USDC - $647.07
TomFrenchBlockchain
Draining of funds from VaderPoolV2
See the VaderPool.mintSynth
function:
https://github.com/code-423n4/2021-12-vader/blob/fd2787013608438beae361ce1bb6d9ffba466c45/contracts/dex-v2/pool/VaderPoolV2.sol#L153-L194
As the pool's reserves can be manipulated through flashloans similar to on UniswapV2 (the slip mechanism can be mitigated by splitting the manipulation over a number of trades), an attacker may set the exchange rate between nativeAsset
and synths (calculated from the reserves). An attacker can exploit this to drain funds from the pool.
foreignAsset
to the pool. The pool now thinks nativeAsset
is extremely valuable.nativeAsset
to mint synths using VaderPool.mintSynth
. As the pool thinks nativeAsset
is very valuable the attacker will receive a huge amount of synths.foreignAsset
they sold to the pool. nativeAsset
is now back at its normal price, or perhaps artificially low if the attacker wishes.nativeAsset
is considered much less valuable than at the point the synths were minted it takes a lot more of nativeAsset
in order to pay out for the burned synths.For the price of a flashloan and some swap fees, the attacker has now managed to extract a large amount of nativeAsset
from the pool. This process can be repeated as long as it is profitable.
Tie the exchange rate use for minting/burning synths to a manipulation resistant oracle.
🌟 Selected for report: TomFrenchBlockchain
1437.9245 USDC - $1,437.92
TomFrenchBlockchain
Reserve pays out vastly higher (or lower) IL protection than it should
Consider the lines 98 and 102 as shown on the link below:
Here we multiply the IL experienced by the LP by a price for USDV or VADER as returned by the LBT. However the price from the oracle is a fixed point number (scaled up by 1e8 or 1e18 depending on the resolution of finding "Oracle returns an improperly scaled USDV/VADER price") and so a fixed scaling factor should be applied to convert back from a fixed point number to a standard integer.
As it stands depending on the branch which is executed, the amount to be reimbursed will be 1e18 times too large or too small. Should the "else" branch be executed the reserve will pay out much in terms of IL protection resulting in severe loss of funds. High severity.
Apply similar logic to as displayed here:
🌟 Selected for report: leastwood
Also found by: TomFrenchBlockchain
TomFrenchBlockchain
A money pump exists whenever VADER is worth more than $1.
When minting USDV, the amount minted is uAmount = (vPrice * vAmount) / 1e18
where vPrice
is the price of VADER in terms of USD.
When burning USDV, the amount of VADER released is vAmount = (uPrice * uAmount) / 1e18
where uPrice
is the price of USDV in terms of USD.
We're therefore allowing people to claim VADER at an exchange rate of 1 VADER = 1 USD.
Now consider we do a mint and then burn the entire amount of USDV we receive, combining the two expressions above:
vAmountOut = (uPrice * (vPrice * vAmountIn) / 1e18 ) / 1e18 // Assume that uPrice ~= 1e18, i.e. USDV is at peg vAmountOut = (vPrice * vAmountIn) / 1e18
It's then plain to see that if the price of VADER exceeds $1 at any point we can extract value from the system by minting and then burning USDV.
Use the USDV:VADER exchange rate rather than USDV:USD when burning USDV for VADER.
Pay attention to the units implied by different values to ensure you're using them correctly.
#0 - 0xstormtrooper
2021-12-27T08:34:24Z
Actually there is a bug with the pricing when burn. Both for mint and burn, the intention is use evaluate VADER price in USD. So the suggestion will not be applied
See here for our intention https://github.com/code-423n4/2021-12-vader-findings/issues/164
#1 - jack-the-pug
2022-03-13T15:18:28Z
Dup #164
🌟 Selected for report: TomFrenchBlockchain
1437.9245 USDC - $1,437.92
TomFrenchBlockchain
Council can veto proposals to remove them to remain in power.
The Vader governance contract has the concept of a "council" which can unilaterally accept or reject a proposal. To prevent a malicious council preventing itself from being replaced by the token holders, the veto function checks the calldata for any proposal action directed at GovernorAlpha
to see if it matches the changeCouncil
function selector.
Note this is done by reading from the proposal.calldatas
array.
If we look at the structure of a proposal however we can see that the function selector is held (in the form of the signature) in the signatures
array rather than being included in the calldata. The calldata
array then holds just the function arguments for the call rather than specifying which function to call.
Indeed if we look at the TimeLock
contract we see that the signature is hashed to calculate the function selector and is prepended onto the calldata.
Looking at the function signature of the changeCouncil
we can see that the value that the veto
function will check against this.changeCouncil.signature
will be the first 4 bytes of an abi encoded address and so will always be zero no matter what function is being called.
High risk as this issue gives the council absolute control over the DAO such that they cannot be removed.
Hash the function signatures to calculate function selectors and then check those rather than the calldata.
This is something that should be picked up by a test suite however, I'd recommend writing tests to ensure that protections you add to the code have any affect and more broadly check that the code behaves as expected.
🌟 Selected for report: TomFrenchBlockchain
116.4719 USDC - $116.47
TomFrenchBlockchain
Frontrunners can extract up to 100% of the value provided by LPs to VaderPoolV2 as fungible liquidity.
Users can provide liquidity to VaderPoolV2
through the mintFungible
function.
This allows users to provide tokens in any ratio and the pool will calculate what fraction of the value in the pool this makes up and mint the corresponding amount of liquidity units as an ERC20.
However there's no way for users to specify the minimum number of liquidity units they will accept. As the number of liquidity units minted is calculated from the current reserves, this allows frontrunners to manipulate the pool's reserves in such a way that the LP receives fewer liquidity units than they should. e.g. LP provides a lot of nativeAsset
but very little foreignAsset
, the frontrunner can then sell a lot of nativeAsset
to the pool to devalue it.
Once this is done the attacker returns the pool's reserves back to normal and pockets a fraction of the value which the LP meant to provide as liquidity.
Add a user-specified minimum amount of LP tokens to mint.
#0 - jack-the-pug
2022-03-13T06:29:06Z
I'm downgrading this to med
and merging all the issues related to slippage control into this one.
🌟 Selected for report: TomFrenchBlockchain
TomFrenchBlockchain
Potential DOS on swaps on VaderPool
BasePool makes use of a validateGas
modifier on swaps which checks that the user's gas price is below the value returned by _FAST_GAS_ORACLE
.
Should _FAST_GAS_ORACLE
be compromised to always return zero then all swaps will fail. There is no way to recover from this scenario.
Either remove GasThrottle.sol entirely or allow governance to turn it off as is done in VaderPoolV2.sol
🌟 Selected for report: TomFrenchBlockchain
431.3773 USDC - $431.38
TomFrenchBlockchain
IL isn't properly converted from being in terms of USDV to VADER, resulting in reserve paying out incorrect amount.
VaderReserve.reimburseImpermanentLoss
receives an amount
in terms of USDV and converts this to an amount of VADER to send to recipient
.
However as shown in the link if there is a previous price stored for USDV, the amount of VADER tokens to be sent to the recipient
is amount / usdvPrice
.
usdvPrice
is the total USD value of foreign assets divided by the total amount of USDV in a number of pairs. It's then some measure of the inverse of the price of USDV in USD, nothing to do with converting into VADER.
The reserve will then improperly calculate the amount of VADER to pay out once there is a single reading of the USDV price.
It looks like both branches of this if statement are supposed to be run, i.e. convert from USDV to USD and then to VADER but I can't be sure. Care should be taken so that the calculation being performed is the expected one.
🌟 Selected for report: TomFrenchBlockchain
431.3773 USDC - $431.38
TomFrenchBlockchain
I've put this as a medium issue as we're leaking value as users are stuck with assets which are likely to be worth much less as they are deprecated. It could also be low as it's not exploitable by outside parties and the loss isn't taken by the protocol but the user.
Potential for users to lose the right to convert VETH to VADER, being stuck with a deprecated token.
Should a user have a zero allowance of VETH on the converter, no VETH will be taken and no VADER will be paid out as L147 will set the amount to zero.
There is a minVader
check on L153 to enforce a minimum output of VADER but should this be improperly set the transaction would succeed with the user receiving much less VADER than they expect.
Crucially, the merkle proof that was used in this transaction will be marked as invalid so the user will not be able to try again once they have set the proper allowance. Someone can then lose the opportunity to convert their VETH and are left with a worthless deprecated token if they are inattentive.
It seems like this is trying to handle the case where a user doesn't have the full amount of VETH as they are entitled to convert (by setting their allowance equal to their balance?). This is a pretty suboptimal way to go about this as it's extremely implicit so users are liable to make mistakes.
I'd recommend decoupling the merkle proof from conversion of VETH to VADER:
amountEligibleToConvert
value in storage for each user (which would be initially set to amount
).amountEligibleToConvert
value, subtracting the amount converted from this each time.For gas efficiency we can use a sentinel value here to mark a user which has claimed their full quota already distinctly from someone who hasn't provided a merkle proof yet (to avoid having to track this separately in another storage slot)
These two steps could be chained in a single transaction to give a similar UX as currently but would also allow users to recover in the case of partial conversions.
As above. I'd caution against just stating "The frontend will handle this correctly so this isn't an issue", there will be users who interact with the contract manually so it's important to make the interface safe where possible.
🌟 Selected for report: TomFrenchBlockchain
431.3773 USDC - $431.38
TomFrenchBlockchain
Loss of resilience of oracle to a faulty pricing for a single pair.
In the oracle we calculate the TVL of each pool by pulling the reserves and multiplying both assets by the result of a supposedly manipulation resistant oracle (the oracle queries its previous value for USDV and pulls the foreign asset from chainlink).
This value can be manipulated by skewing the reserves of the underlying pair with a flashloan attack. An attacker can then make a pool appear with an arbitrarily large currentLiquidityEvaluation
which will result in all other pairs contributing negligibly to the final result of the oracle.
This doesn't result in loss of funds by itself afaict but should there be an issue for the chainlink price feed for the asset in any pool then an attacker can force the oracle to only use that pool for pricing USDV/VADER
Medium risk as "Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements." External requirements being a malfunctioning or deprecated chainlink pricefeed for any used asset.
Calculating TVL of the pool is equivalent to value of all LP tokens so for more information see this post: https://blog.alphafinance.io/fair-lp-token-pricing/
Calculate fair reserves using the pool invariant and the fair prices of the two assets.
The above link contains a mitigates for Uniswap, a similar calculation would have to be performed which is specific for the Vader invariant.
#0 - SamSteinGG
2021-12-27T10:55:25Z
The evaluation of liquidity for a particular pair is performed based on the reserves of the previous block rendering a flash loan attack impossible. Can the warden clarify how he is expecting this to be exploited?
🌟 Selected for report: TomFrenchBlockchain
143.7924 USDC - $143.79
TomFrenchBlockchain
Confusing argument names for VaderMath library functions resulting in errors in expectations around the value of the units be passed/returned.
The linked functions below have arguments such as vaderDeposited
or releasedVader
which implies that the values being passed to them are in units of VADER tokens. However these functions are used to calculate values in terms of USDV.
This has resulted in situations as described in
"VaderPoolV2 incorrectly calculates the amount of IL protection to send to LPs" finding from the 2021-11-vader
contest where confusion about the values returned from these functions results in improperly reimbursing IL.
Low severity as it's equivalent to misleading documentation.
Rename these arguments to be clear that they are in terms of USDV.
🌟 Selected for report: TomFrenchBlockchain
TomFrenchBlockchain
Minting protections can be bypassed in some circumstances.
See link:
Should we go down the first branch of if statement then we never reach the require statement.
Apply require statement to both branches.
#0 - 0xstormtrooper
2021-12-23T01:11:45Z
Suggested code change
if (cycleTimestamp <= block.timestamp) { cycleTimestamp = block.timestamp + 24 hours; cycleMints = uAmount; require(uAmount <= dailyLimit); } else { cycleMints += uAmount; require( cycleMints <= dailyLimit, "USDV::mint: 24 Hour Limit Reached" ); }
🌟 Selected for report: defsec
Also found by: Jujic, TomFrenchBlockchain, danb, hyh
18.8684 USDC - $18.87
TomFrenchBlockchain
Inability to call _updateUSDVPrice
or _updateVaderPrice
on the LiquidityBasedTWAP and so calculate up to date prices, resulting in minting/burning USDV halting and reserve paying out incorrect values of VADER.
Should any of the Chainlink aggregators used by the LiquidityBasedTWAP becomes stuck in such a state that the checks on L88-93 of LiquidityBasedTWAP.sol
consistently fails (through a botched upgrade, etc.) then the _updateUSDVPrice
and _updateVaderPrice
functions will always revert, preventing the oracle from updating.
There is no method to remove/update a pair used by the oracle so recovery would consist of a redeploy of the contract, repopulating it with pairs and migrating USDV and the reserve to use this new one. This is a complex process during which all minting and burning of USDV would be halted and reimbursements of IL would be incorrect, resulting in leaking value.
#0 - jack-the-pug
2022-03-12T05:05:57Z
Dup of #111
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Gas costs
We hold the previous price and total liquidity weight of USDV and VADER in two fixed size arrays
This creates some strange syntax for reading these values as we store an enum of the indices of VADER and USDV in order to read the correct element.
It would be simpler to just replace L31-L32 with
uint256 public vaderTotalLiquidityWeight; uint256 public vaderPreviousPrices; uint256 public usdvTotalLiquidityWeight; uint256 public usdvPreviousPrices;
to avoid the need for this extra computation. We could even go so far as to pack these variables to avoid an SSTORE/SLOAD, further reducing gas costs.
uint128 public vaderTotalLiquidityWeight; uint128 public vaderPreviousPrice; uint128 public usdvTotalLiquidityWeight; uint128 public usdvPreviousPrice;
uint128
gives enough space for vaderPreviousPrice
and vaderTotalLiquidityWeight
to be a little over 3.6 million times world GDP before they overflow (assuming 18 digit fixed point values) which seems safe.
As above.
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Gas costs
These functions are only necessary for creating the synth's name + symbol and are therefore only necessary on deployment.
If these functions lived in the factory, it could calculate the proper name and symbol for the synth and then pass it in on deployment. Deploying a synth would be cheaper as it doesn't need to store the code for these functions in its bytecode.
Move these functions to factory and pass in results at synth deployment time
🌟 Selected for report: robee
Also found by: TomFrenchBlockchain, defsec
15.5017 USDC - $15.50
TomFrenchBlockchain
Gas costs on deploying LPTokens
The address of VaderPoolV2 is stored implicitly as the owner of the LPWrapper contract.
As we are using the Ownable
contract which allows transfering ownership this involves an SLOAD to get VaderPoolV2
's address. As we never are going to transfer ownership of this contract we could store it in an immutable variable which will save gas costs.
Remove Ownable
dependency and store address in immutable variable.
#0 - jack-the-pug
2022-03-13T11:35:17Z
Dup #19
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Gas consumption
Each claim performs a SSTORE to a fresh slot in order to mark a leaf as claimed. This results in each user paying 20k gas in order to invalidate a leaf whereas if many users shared the same slot they would pay 5k gas instead.
See Uniswap's implementation for inspiration:
https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol
Use bitmap for leaf invalidation
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Extra gas costs from hashing more data
The inclusion of a salt and the current chainId in the merkle leaves doesn't grant any extra security benefits.
Users must then hash extra data for no benefit and pay gas costs to do so.
Remove the salt + chainId components of the merkle leaves.
#0 - 0xstormtrooper
2021-12-23T02:20:46Z
This is protection against replay attack - make merkle leaves unique to contract and chain
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Detailed description of the impact of this finding.
On all swaps VaderPoolV2
checks if the foreign assets used are supported, performing either 1 or 2 SLOADs.
However the pool only allows liquidity to be added to a pair if it is supported
Once a token has liquidity, then it can't be unsupported so checking that a token has liquidity is a sufficient check for it being supported.
If the pool has no liquidity then the trade will fail so these checks are unnecessary
Remove supportedToken
checks from swap functions.
🌟 Selected for report: TomFrenchBlockchain
57.4139 USDC - $57.41
TomFrenchBlockchain
Increased deployment costs of oracle and costs of updating VADER price.
We currently store the address of the VADER token on the LBT:
This is only used for two reasons:
As can be seen here, VADER is always set to be token0
so sorting the tokens in the oracle is unnecessary, as is checking that VADER is the native asset.
We can then safely remove these checks from the oracle along with the vader
immutable variable to reduce deployment and runtime costs.
Remove vader
variable from oracle along with unnecessary checks.