# Security Audit of `VELA` contracts.
## Conclusion
This audit was made by
Auditor: Vladimir Smelov <vladimirfol@gmail.com>.
Date: 2022DEC22
No security issues found.
## Scope
repository: https://github.com/VelaExchange/vela-contracts
initial audit commit: 6034f43cb18c705f2daa27a0008ac375ba124b16
reaudit commit: b3200f08472ab6db8e62aa3d9a766b4dbe7b70cb
## Methodology
1. Blind audit. Understand the structure of the code without reading any docs.
2. Ask questions to developers.
3. Run static analyzers.
4. Find problems with:
- backdoors;
- bugs;
- math;
- potential leaking of funds;
- potential locking of the contract;
- validate arguments and events;
- others.
## Result
#### CRITICAL-1 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L219
wrong argument, it should be _account
##### Status.
FIXED
______
#### CRITICAL-2 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L647
wrong math, some feeReserves are never withdrawn by anyone.
To take the intuitive feeliing why it does happen, let's imagine that feeRewardBasisPoints=100% (stakers receive full reward, and owner receives nothing), and Alice stake and unstake some amount of tokens, then she will obviously receive nothing (see how user.lastFeeReserves is updated) and the owner will also receive nothing, so stakeFee tokens will be just lost on the contract. The same problem is still in place if feeReward<100%.
It makes whole fee sharing system unfair, because user fee reward is really dependent on which moment they claim fee reward. Because the totalVLP part is volatile.
Also let's take a look on a simple python script with simplified Math from the contract:
```python=
# let 1vlp = 1usd
# let _rewardToken = USDT
# let _token = USDT
from collections import Counter
staking_fee = 0.1
reward_fee = 0.7
fee_reserves = 0
total_VLP = 0
user_amount = Counter()
last_fee_reserves = Counter()
received_rewards = Counter()
owner_last_fee_reserves = 0
balances = Counter()
balances['aaa'] = 100
balances['bbb'] = 100
balances['ccc'] = 100
def transferFrom(_from, _to, amount):
balances[_from] -= amount
assert balances[_from] >= 0
balances[_to] += amount
def update_reward(user):
global fee_reserves, total_VLP, owner_last_fee_reserves
if total_VLP == 0: return
r = reward_fee * (fee_reserves-last_fee_reserves[user]) * user_amount[user] / total_VLP
transferFrom('this', user, r)
received_rewards[user] += r
print(f'update_reward {user=} {r=}')
def stake(user, amount):
global fee_reserves, total_VLP, owner_last_fee_reserves
update_reward(user)
afterFeeAmount = amount * (1 - staking_fee)
user_amount[user] += afterFeeAmount
fee_reserves += (amount - afterFeeAmount)
last_fee_reserves[user] = fee_reserves
total_VLP += afterFeeAmount
transferFrom(user, 'this', amount)
print(f'stake({user=}, {amount=}) {afterFeeAmount}')
def unstake(user, amount):
global fee_reserves, total_VLP, owner_last_fee_reserves
update_reward(user)
total_VLP -= amount
afterFeeAmount = amount * (1 - staking_fee)
user_amount[user] -= amount
fee_reserves += (amount - afterFeeAmount)
last_fee_reserves[user] = fee_reserves
transferFrom('this', user, afterFeeAmount)
print(f'unstake({user=}, {amount=}) {afterFeeAmount}')
def owner_withdraw_fees():
global fee_reserves, total_VLP, owner_last_fee_reserves
r = (fee_reserves - owner_last_fee_reserves) * (1 - reward_fee)
owner_last_fee_reserves = fee_reserves
transferFrom('this', 'owner', r)
received_rewards['owner'] += r
print(f'owner_withdraw_fees() - {r=}')
def main0():
global fee_reserves, total_VLP, owner_last_fee_reserves
stake('aaa', 1)
stake('bbb', 1)
stake('ccc', 1)
owner_withdraw_fees()
fee_reserves += 1000000
balances['this'] += 1000000
unstake('aaa', user_amount['aaa'])
unstake('bbb', user_amount['bbb'])
unstake('ccc', user_amount['ccc'])
owner_withdraw_fees()
def main1():
stake('aaa', 100)
stake('bbb', 100)
stake('ccc', 100)
owner_withdraw_fees()
unstake('bbb', user_amount['bbb'])
unstake('ccc', user_amount['ccc'])
unstake('aaa', user_amount['aaa'])
owner_withdraw_fees()
def main2():
stake('aaa', 100)
stake('bbb', 100)
unstake('aaa', 0.000001) # trigger update_reward
stake('ccc', 100)
unstake('aaa', 0.000001) # trigger update_reward
unstake('bbb', user_amount['bbb'])
unstake('aaa', 0.000001) # trigger update_reward
unstake('ccc', user_amount['ccc'])
unstake('aaa', 0.000001) # trigger update_reward
unstake('aaa', user_amount['aaa'])
owner_withdraw_fees()
def main3():
stake('aaa', 100)
unstake('aaa', user_amount['aaa'])
owner_withdraw_fees()
if __name__ == '__main__':
# main1() # fee_reserves=57.0, sum(received_rewards.values())=49.18333333333334, received_rewards[aaa]=26.6
# main2() # fee_reserves=57.0, sum(received_rewards.values())=37.86666680083334, received_rewards[aaa]=15.28
# main3()
main0()
print(f'{balances=}')
print(f'{fee_reserves=}')
print(f'{sum(received_rewards.values())=}')
print(f'{received_rewards=}')
print(f'{user_amount=}')
```
##### Status.
FIXED - feeReserves is not used anymore
but there is some new logic to distribute fees
______
#### CRITICAL-3 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/VaultUtils.sol#L278
Anyone can set this important setting. The access should be restricted.
##### Status.
FIXED - setFundingRateFactor was removed
______
#### CRITICAL-4 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/VaultPriceFeed.sol#L57
you should compare `p>0`, since uint256(-1)=type(uint256).max
proof - https://gist.github.com/vsmelov/13ab37cc7baf2954f37e9c44d7b12f97
##### Status.
FIXED
function latestAnswer returns uint256 instead of int256.
______
#### CRITICAL-5 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/TokenFarm.sol#L438
wrong Math
imagine if we have tierLevels=[1000, 2000, 3000],
tierPercents=[a,b,c]
this function will make such calculations:
IF 1000 <= stakedAmount and stakedAmount < 2000:
tierPercent = b
ELSE IF stakedAmount > 3000:
tierPercent = c
ELSE:
tierPercent = BASIS_POINTS_DIVISOR
so for range [2000, 3000) it will BASIS_POINTS_DIVISOR
it looks incorrect.
Moreover, if tierLevels.len()==1, for loop will never run.
##### ReAudit commentary
let's say
tierLevels = [10, 20]
tierPercent = [x, y]
basicly the calculations should be
if (10 <= amount && amount < 20) {
tierPercent = x
} else if (amount >= 20) {
tierPercent = y
} else {
tierPercent = BASIS_POINTS_DIVISOR
}
but for the latest code
uint256 tierPercent = BASIS_POINTS_DIVISOR;
for (uint256 i = 1; i < tierLevels.length; i++) {
if (tierLevels[i - 1] <= user.amount && user.amount < tierLevels[i]){
tierPercent = tierPercents[i - 1];
}
if (i == tierLevels.length - 1) {
if (user.amount > tierLevels[i])
{
tierPercent = tierPercents[i];
}
}
}
there will be only one iteration in the loop
i = 1
you can unwrap it as
tierPercent = BASIS_POINTS_DIVISOR
if (10 <= amount && amount < 20) {
tierPercent = x
}
if (amount > 20) {
tierPercent = y
}
I see several problems here
1) HIGH what if amount==20? then tierPercent = BASIS_POINTS_DIVISOR, it does not look correct, intervals should border value
2) LOW conditions for i==length-1 are checking twice, first
if (tierLevels[i - 1] <= user.amount && user.amount < tierLevels[i])
and
if (user.amount > tierLevels[i])
3) LOW it makes sense to break iteration after you found a match to not waste the gas and dont go to next iterations
4) LOW you check
if (i == tierLevels.length - 1)
on every iteration
but you can just do it once
5) LOW you read storage value tierLevels.length on the every iteration step
6) LOW safeMath for solc 0.8.X could be disabled for this part of code to save gas
consider refactoring as
uint256 length = tierLevels.length; // vsm: read storage once
uint256 tierPercent = BASIS_POINTS_DIVISOR;
if (length == 0) return tierPercent; // vsm: skip checks for empty intervals
unchecked { // vsm: overflow/underflow for i+1, length-1 isn't possible, so disable default safeMath behaviour of solc 0.8.X
if (user.amount >= tierLevels[length-1]) { // vsm: check the final condition once
tierPercent = tierPercents[length-1]; // vsm: note, I changed > to >=
} else { // vsm: search over intervals
for (uint256 i = 0; i < length-1; ++i) {
if (tierLevels[i] <= user.amount && user.amount < tierLevels[i+1]){
tierPercent = tierPercents[i];
break; // vsm: break on the first successful match
}
}
}
}
##### Status.
FIXED
this looks correct
```
function getTier(
uint256 _pid,
address _account
) external view override returns (uint256) {
UserInfo storage user = userInfo[_pid][_account];
if (tierLevels.length == 0 || user.amount < tierLevels[0]) {
return BASIS_POINTS_DIVISOR;
}
unchecked {
for (uint16 i = 1; i != tierLevels.length; ++i) {
if (user.amount < tierLevels[i]) {
return tierPercents[i - 1];
}
}
return tierPercents[tierLevels.length - 1];
}
}
```
BUT it's better to replace uint16 with uint256 because it's more gas efficient.
______
#### MAJOR-1 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/BetaTrading.sol#L23
There is no restriction to call this function, I can call it 10000 times from different accounts.
Consider creation of whitelist.
##### Status.
FIXED - the contract was removed
______
#### MAJOR-2 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/BetaTrading.sol#L47
it may give not correct accounts, since tokens could be burned or new tokens could be minted. The purpose of this function is unclear.
##### Status.
FIXED - the contract was removed
______
#### MAJOR-3 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L346
how should it work in very begin when the chainlink does not know the price of VLP token?
##### Status.
FIXED with the usage of DEFAULT_VLP_PRICE
______
#### MAJOR-4 (TODO).
At
- https://github.com/VelaExchange/vela-contracts/blob/b3200f08472ab6db8e62aa3d9a766b4dbe7b70cb/contracts/core/Vault.sol#L497
- https://github.com/VelaExchange/vela-contracts/blob/b3200f08472ab6db8e62aa3d9a766b4dbe7b70cb/contracts/core/Vault.sol#L616
what if user transferred our VLP? VLP is transferable token.
if ALICE calls this flow
- stake receiving 100 VLP
- transfer to Bob 100 VLP
- unstake(_vlpAmount=100VLP)
this transaction will fail, neither Bob will not be able to call unstake.
No one will be able to unstake (until Alice will buy back 100VLP)
is it how it is supposed to be?
##### Status.
TODO
##### Client's comment
> I see your point on transferring VLP. However, we want users to be able to stake VLP to earn extra rewards. Any suggestions on how we can have it remain transferrable but prevent this?
##### Re-audit
If VLP is a usual ERC20 token, then anybody can create DEX POOL, e.g. USDT-VLP.
Let's say, we have 1 VLP = 100 USDT DEX POOL
1) Alice calls Vault.stake 1000 USDT for minted 10 VLP
2) Alice calls DEX.swap 10 VLP to 1000 USDT on DEX
3) Alice calls Vault.stake 1000 USDT for minted 10 VLP
4) Alice calls DEX.swap 10 VLP to 1000 USDT on DEX
5) ...
in fact, because of the fees and slippages on every step, the minted amount will be smaller and the price move on the DEX will make the VLP price lower.
Also, in the end, the price on DEX will not match Vault.getVLPPrice() function.
Is it OK?
______
#### MAJOR-5 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L650
totalVLP is changed often the calculations are not fair
##### Status.
FIXED - updateReward function was removed
______
#### MAJOR-6 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L654
why are you so sure that there will be enough _rewardToken? What should happen if the contract has not enough reward token on the balance?
##### Status.
ACKNOWLEDGED
##### Client's comment
> We will ensure enough reward token is there
______
#### MAJOR-7 (TODO).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/VaultPriceFeed.sol#L50
what should it return if chainlink is offline, consider revert always.
##### Status.
TODO
##### Client's comment
> We are not using chainlink, we are using custom price feed
##### Re-audit
even if you use FastPriceFeed contract it's possible that admin who must calls setLatestAnswer will go offline
and you will have very old data in answers and latestAts
consider adding a check of the latest timestamp update to latestAnswer function like
```
function latestAnswer() external view override returns (uint256) {
require(block.timestamp - latestAts[roundId] > 1 minute, "price feed is online");
return answer;
}
```
even in this case price may move drastically in a second, it should be explained in the contract NatSpec what should happen if the token price moves significantly BEFORE priceFeed was updated.
______
#### MAJOR-8 (TODO).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/TokenFarm.sol#L368
what about reflactionary tokens?
##### Status.
TODO
##### Client's comment
> Why not, it is correct
##### Re-audit
This note about the possibility of the usage of deflationary tokens (or tokens with fee on transfer) is still not 100% for me.
For example in `stake` function you call
```
function _transferIn(
address _account,
address _token,
uint256 _amount
) internal {
IERC20(_token).safeTransferFrom(_account, address(this), _amount);
}
```
but it does not mean that address(this) will actually receive _amount of tokens. Because it may have deflationary mechanism under the hood or just have fee (as even in USDT).
The manner how such tokens will be handled should be described in details in contract comments.
##### Client's comment
> deflationary tokens - we are responsible for loading the tokenfarms, so we can avoid those, but is there an easy way to exclude them from ever being loaded?
##### Re-audit
No, in fact you cannot avoid it.
You should be aware that your calculations may break on usage of a deflationary token or even a feeOnTransfer token.
To make calculations more robust you may modify your functions like
```
function _transferIn(
address _account,
address _token,
uint256 _amount
) internal returns(uint256 actualAmount) {
uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(_account, address(this), _amount);
actualAmount = IERC20(_token).balanceOf(address(this)) - balanceBefore;
}
```
or
```
function _transferIn(
address _account,
address _token,
uint256 _amount
) internal {
uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(_account, address(this), _amount);
require(IERC20(_token).balanceOf(address(this)) - balanceBefore == _amount, "failed transfer");
}
```
______
#### MAJOR-9 (FIXED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/ComplexRewardPerSec.sol#L154
it's difficult to calculate preciesly proper eth amount, this call may often fail. consider transferring rest back
##### Status.
FIXED - isNative branch was removed
______
#### MAJOR-10 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/ComplexRewardPerSec.sol#L178
declare emergency state
The contract owner may intentionally or unintentionally break the contract state if call these functions outside of real emergency situation.
##### Status.
ACKNOWLEDGED
##### Client's comment
> it's correct
-------
#### MAJOR-11 (NEW).
At:
- https://github.com/VelaExchange/vela-contracts/blob/b3200f08472ab6db8e62aa3d9a766b4dbe7b70cb/contracts/core/SettingsManager.sol#L248
you have a function to set fundingInterval
```
function setFundingInterval(uint256 _fundingInterval) external onlyGov {
require(
_fundingInterval >= MIN_FUNDING_RATE_INTERVAL,
"fundingInterval should be greater than MIN"
);
require(
_fundingInterval <= MAX_FUNDING_RATE_INTERVAL,
"fundingInterval should be smaller than MAX"
);
fundingInterval = _fundingInterval; //xxx MAJOR should be immutable check updateCumulativeFundingRate
emit SetFundingInterval(fundingInterval);
}
```
the updating of this setting by itself should not affect any user profits.
But let's say, you have fundingInterval = 8 hours
and you call setFundingInterval(4 hours)
On the next call of `updateCumulativeFundingRate`
you will have
```
cumulativeFundingRates[_token][_isLong] += getNextFundingRate(
_token,
_isLong
);
```
and
```
function getNextFundingRate(
address _token,
bool _isLong
) internal view returns (uint256) {
uint256 intervals = (block.timestamp -
lastFundingTimes[_token][_isLong]) / fundingInterval;
if (vault.poolAmounts(_token, _isLong) == 0) {
return 0;
}
return
((
fundingRateFactor[_token][_isLong] > 0
? fundingRateFactor[_token][_isLong]
: DEFAULT_FUNDING_RATE_FACTOR
) *
vault.reservedAmounts(_token, _isLong) *
intervals) / vault.poolAmounts(_token, _isLong);
}
```
so intervals will be twice bigger, (assumpting fundingRateFactor did not change).
and it will lead to twice bigger funding rate.
##### Recommendation
Consider declaring `fundingInterval` as immutable variable, to prevent this miscalculations.
Or consider adding a check for `fundingRateFactor` to be updated according to the `fundingInterval` change.
______
#### WARNING-1 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol
why do we need variable totalVLP
why not use VLP.balanceOf(address(this))
why store the same data in two places?
##### Status.
ACKNOWLEDGED
______
#### WARNING-2 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L54
unclear purpose, prices of locked token may change
##### Status.
ACKNOWLEDGED
##### Client's comment
> Unclear. What you are asking
##### Re-audit
totalUSDC does not say how many value in USD are stored on the Vault balance, because the price of every token in USD is always changing. Be careful about it. The fact that you allow only stable coins as a staked token should be mentioned in the contract comments.
Be aware that sometimes even stablecoins could drastically change their price ( check USTC price plot - https://coinmarketcap.com/currencies/terrausd/ )
if you allow e.g. 3 stablecoins and one of them will fail (as USTC did), it may break your project.
##### Client's comment
> totalUSDC means the rewardable USDC amount + deposited USDC for stake.
so with this way, we can distribute rewards to VLP holders,
It is not for the USD are stored on the Vault balance.
______
#### WARNING-3 (TODO).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/VaultPriceFeed.sol#L60
how will it work with tokens if they have decimals>>30?
will it loose the accuracy?
##### Status.
TODO
______
#### WARNING-4 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/libraries/BoringERC20.sol#L71
this fallback is dangerous, not all erc20 tokens with no public decimals() method has 18 digits in implementation. Consider revert.
##### Status.
function safeDecimals(IBoringERC20 token) internal view returns (uint8) {
(bool success, bytes memory data) = address(token).staticcall(
abi.encodeWithSelector(SIG_DECIMALS)
);
return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; //xx CRITICAL this fallback is dangerous
}
it still looks dangerous for me
Im pretty sure I will be able to find some token which has no implementation of decimals() method, but use some other number rather than 18 under the hood
https://eips.ethereum.org/EIPS/eip-20
there is no exact note that if a token has no "decimals()" method it shoud be considered as having 18 as a default decimals
for example, this contract - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.2.0/contracts/token/ERC20/extensions/ERC20Wrapper.sol
if someone use it as a template to wrap USDC (with decimals=6) and create wUSDC
BoringERC20(wUSDC).safeDecimals() == 18
but in reality it's 6
this is potentially dangerous, dont rely on something if it is not exactly given 🙂
the severety of the issue could be decreased from CRITICAL ot WARNING, because its not used or any financial calculations as far as i can see
but be aware
##### Status.
ACKNOWLEDGED
______
#### WARNING-5 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/staking/ComplexRewardPerSec.sol#L144
restrict the window to be 7 days to match the comment about 1 year at the beginning of the contract.
##### Status.
ACKNOWLEDGED
##### Client's comment
> it's correct
##### Re-audit
In fact, nothing prevents the owner to place other intervals rather than 7 days so taking into account rewardInfoLimit=52 it will be different from 1 year
______
#### WARNING-6 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/oracle/PriceFeed.sol#L44
Why last arguments are always zero? What is their purpose?
##### Client's comment
> We use zeros to match interface.
##### Status.
ACKNOWLEDGED
______
#### WARNING-7 (ACKNOWLEDGED).
At
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L400
stuck user deposits if token was disabled after stake.
Owner may intentionally or unintentionally block users' tokens.
##### Status.
ACKNOWLEDGED
-------
#### WARNING-8 (NEW).
At:
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L92
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L114
you have a code like
```
event NewOrder(
bytes32 key,
address indexed account,
address indexToken,
bool isLong,
uint256 posId,
uint256 positionType,
OrderStatus orderStatus,
uint256[] triggerData
);
```
the key is not indexed, however other events key attribute are indexed.
Consider declaring as an indexed attribute.
-------
#### WARNING-9 (NEW).
At:
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L834
you have a code like
```
if (position.size == 0) {
position.averagePrice = price;
}
if (position.size > 0 && _sizeDelta > 0) {
position.averagePrice = priceManager.getNextAveragePrice(
_indexToken,
position.size,
position.averagePrice,
_isLong,
price,
_sizeDelta
);
}
```
it's not clear, is it ok to keep averagePrice without any change if `position.size > 0` but `_sizeDelta==0`? It seels like a possible case, since `_sizeDelta` is given by a user.
-------
#### WARNING-10 (NEW).
At:
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L242
you have a confusing code
```
if (order.positionType == POSITION_TRAILING_STOP) {
order.status = OrderStatus.FILLED;
order.positionType = POSITION_MARKET;
} else {
order.status = OrderStatus.CANCELED;
}
```
it's not clear why the order.status is set to FILLED however in fact, the order was manually cancelled.
-------
#### WARNING-11 (NEW).
At:
- https://github.com/VelaExchange/vela-contracts/blob/6034f43cb18c705f2daa27a0008ac375ba124b16/contracts/core/Vault.sol#L322
you have a confusing error message
```
require(
(settingsManager.checkDelegation(_account, msg.sender)) &&
_amount > 0,
"Vault: zero amount not allowed for deposit"
);
```
if delegation was not enabled for this msg.sender, he will get confusing error message "Vault: zero amount not allowed for deposit".
Consider splitting this require in 2 with a clear error messages.