---
# System prepended metadata

title: Security Audit of `VELA` contracts.

---

# 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.