Elektro Market Architecture And Settlement Examples

Elektro contract storage

Each ElektroRouter contract represents a market for a specific currency pair. When a new currency pair market is needed - new Elektro contract is deployed. Within each market trades for different contractId’s which use this currency pair can be made. I.e. We can trade CALL and PUT options with different expiration dates using the same Elektro contract. The storage of each Elektro contract has respective addresses of underlying and strike currency which together represent a currency pair used by this market.

contract ElektroStorage {
    // ... omitted
    address public underlyingCurrency;
    address public strikeCurrency;
    // ... omitted
}

Within each Elektro contract client positions are represented by respective mapping. Positions are per contractId per trader. Each position denotes amount of option contracts each trader has in respective contractId (size). The size can be either negative (Sell) or positive (Buy).

contract ElektroStorage {
    // ... omitted
    mapping(uint32 => mapping(address => int64)) public clientPositions;
    // ... omitted
}

FundLock Storage

All data regarding actual funds/tokens of clients goes all the way down to FundLock and is updated in FundLockStorage. All clients' colalteral and premium flows along with token flow from spot trades and other operations is updated in balanceSheetS storage mapping per user per token. This mapping is unified across all markets and trading contracts and outlines amount of funds available to client in every token used by the client for trades.

// client's address => tokenAddress => balanceAmount
mapping (address => mapping(address => uint256)) internal balanceSheetS;

Elektro contract update flow

Orders settlement flow updatePositions function accepts arrays of arguments. Logically these arrays can be split onto 2 virtual data structures: "Fund Movement" and "Position". Those will be represented by array groups passed as arguments.

contract ElektroLedgerUpdate {
  function updatePositions(
    // Position update part of arguments
    address[] memory positionClients,
    uint32[] memory positionContractIds,
    int64[] memory positionSizes,
    // Fund Movement part of arguments
    address[] memory fundMovementClients,
    int64[] memory underlyingAmounts,
    int64[] memory strikeAmounts,
    // backend tracking arg
    uint64 backendId
  ) external;
    // ... omitted
}
  • The purpose of Position part is to record position information to smart contract state.

  • The purpose of FundMovement part is to move funds in the FundLock without recording any information into ElektroStorage state. We represent things like premiums, collaterals, spot trades, margin loan underlying transfers as Fund Movement.

Please also note that arrays of positionClients, positionContractIds, positionSizes should always have the same lengths and this property is enforced by smart contract. Similarly arrays fundMovementClients, underlyingAmounts, strikeAmounts, would also have the same length which is also enforced within smart contract. On the contrary positionClients would not necessarily have the same length as fundMovementClients as they do represent different things. Also, it is possible for Backend to not send one part or the other (e.g. Spot Trading would only have Fund Movements part and arrays for positions will be empty), however smart contract will revert if both positionClients and fundMovementClients arrays are empty signifying an error on the backend or the fact that a TX is being sent with no data in it, for which we would still have to pay gas.

First three arguments represent a Position part with single structure sharing the same array index.

address[] memory positionClients,
uint32[] memory positionContractIds,
int64[] memory positionSizes,

The following three arguments represent Fund Movement part with single structure sharing the same array index.

address[] memory fundMovementClients,
int64[] memory underlyingAmounts,
int64[] memory strikeAmounts,

Lastly the uint64 backendId argument is meant for injecting a backend generated transaction id to be able to immediately assign it to transaction without waiting for txHash to become available. This backendId is always used to emit an appropriate event in ElektroEventEmitter contract with the following signature:

event PositionsUpdated(
    uint64 indexed backendId
);

Naive Matching example data

A simple example of orders and expected input of updatePositions is as follows:

Order #sizePriceSideStrikeOption typeUserMatch pricecontractId

Order 1

20

11

BID

15

CALL

User 1

10

100500

Order 2

20

9

ASK

15

CALL

User 2

10

100500

Movement of funds need to include premium information in our case. With the following representation we have premium subtracted from account of User 1 and added to account of User 2. Importantly we introduce Moves funds in FundLock utility column in the following 2 tables. This column is never passed to smart contract functions as argument and is present rather to denote the fact that such row would either make FundLock contract to move funds or not do anything. In general all non-zero rows would move funds.

In the below example, User 1 pays premium to User 2 for his BID/BUY and User 2 pays collateral to the market for his ASK/SELL.

Please note the sign of the arguments. Here positive sign means client pays and negative means he receives.

Positions row indexpositionClientpositionContractIdpositionSizeMoves funds in Fundlock

0

User 1

100500

20

no

1

User 2

100500

-20

no

Fund movements row indexclientAddressunderlyingAmountstrikeAmountMoves funds in FundLock

0

User 1

0

200

yes

1

User 2

20

-200

yes

Position change example data

Current smart contract state is important for validating incoming position's data. We provide a number of examples which describe situations when Users exit from their respective positions or are swapping long position for short one and vice-versa. After these examples we summarise the rules that apply to validate the input data and explain why those validations are appropriate. All following examples assume a 2 step flow. The second step is provided in each of the respective examples. Setup data for all the cases below is shared and similar to Naive Matching example data. After Naive Matching example data is sent to the contract the state is as follows:

Elektro State

{
  clientPositions: {
    100500: {
      'User 1': 20,
      'User 2': -20,
    }
  },
}

FundLock State

{
  balanceSheetS: {
    'User 1': {
      'underlyingAddress': IB`,
      'strikeAddress': IB` - 200
    },
    'User 2': {
      'underlyingAddress': IB` - 20,
      'strikeAddress': IB` + 200
    }
  }
}


` IB - Initial Balance

Example 1. Short position partial and full exit.

The second step is when User 2 submits an order for long position with same contractId:

Order #sizePriceSideStrikeOption typeUserMatch pricecontractId

Order 1

10

11

BID

15

CALL

User 2

10

100500

Order 2

10

9

ASK

15

CALL

User 3

10

100500

Such submission would result in the following input data to the updatePositions function:

Positions row indexclientAddresspositionContractIdpositionSizeMoves funds in Fundlock

0

User 2

100500

10

no

1

User 3

100500

-10

no

Fund movements row indexfundMovementClientunderlyingAmountstrikeAmountMoves funds in FundLock

0

User 2

-10

100

yes

1

User 3

10

-100

yes

After these changes are applied to state it would look as follows:

Elektro State

{
  clientPositions: {
    100500: {
      'User 1': 20,
      'User 2': -10,
      'User 3': -10
    }
  },
}

FundLock State

{
  balanceSheetS: {
    'User 1': {
      'underlyingAddress': IB`,
      'strikeAddress': IB` - 200
    },
    'User 2': {
      'underlyingAddress': IB` - 20 + 10,
      'strikeAddress': IB` + 200 - 100
    }
    'User 3': {
      'underlyingAddress': IB` - 10,
      'strikeAddress': IB` + 100
    }
  }
}


` IB - Initial Balance

As a result of this execution User 2 would get 10 underlying currency tokens back to his account and would have to pay 100 strike tokens in premium. User 3 would have to, in-turn, deposit 10 underlying currency tokens to back newly formed option.

Full exit example data is almost the same as the one provided in the above example. Distinction is only in the size of the order which would be 20. The input data for positions and the resulting state would be as follows:

Positions row indexpositionClientpositionContractIdpositionSizeMoves funds in Fundlock

0

User 3

100500

-20

no

1

User 2

100500

20

no

{
  clientPositions: {
    100500: {
      'User 1': 20,
      'User 2': 0,
      'User 3': -20
    }
  },
}

Example 2. Long position partial and full exit.

The second step is when User 1 submits an order for short position with same contractId:

Order #sizePriceSideStrikeOption typeUserMatch pricecontractId

Order 1

10

11

BID

15

CALL

User 3

10

100500

Order 2

10

9

ASK

15

CALL

User 1

10

100500

Such submission would result in the following input data to the updatePositions function:

Positions row indexclientAddresscontractIdpositionSizeMoves funds in Fundlock

0

User 3

100500

10

no

1

User 1

100500

-10

no

Fund movements row indexclientAddressunderlyingAmountstrikeAmountMoves funds in FundLock

0

User 3

0

100

yes

1

User 1

0

-100

yes

After these changes are applied to state it would look as follows:

Elektro State

{
  clientPositions: {
    100500: {
      'User 1': 10,
      'User 2': -20,
      'User 3': 10
    }
  },
}

FundLock State

{
  balanceSheetS: {
    'User 1': {
      'underlyingAddress': IB`,
      'strikeAddress': IB` - 200 + 100
    },
    'User 2': {
      'underlyingAddress': IB` - 20,
      'strikeAddress': IB` + 200
    },
    'User 3': {
      'underlyingAddress': IB`,
      'strikeAddress': IB` - 100
    }
  }
}


` IB - Initial Balance

As a result of this execution User 1 would have to post 0 underlying currency tokens as collateral even though he is engaging in a short position. He would also receive 100 underlying tokens in premium. There is no collateral modification in Elektro contract when this trade executes since all necessary collateral is already provided during the first trade.

To summarise the validation which would be applied: When user has positive position in Elektro state and engages in negative position trade as a result the absolute value for his position's state would be decreased both of the collateral values for his position row should be zero. Full exit example data is the almost the same as the one provided in this example. Distinction is only in the size of the order which would be 20. The input data for positions and the resulting state would be as follows:

Positions row indexpositionClientpositionContractIdpositionSizeMoves funds in Fundlock

0

User 3

100500

20

no

1

User 1

100500

-20

no

{
  clientPositions: {
    100500: {
      'User 1': 0,
      'User 2': -20,
      'User 3': 20
    }
  },
}

Example 3. Short position switch to long position.

The second step is when User 2 submits an order for long position with same contractId and the size of this position is larger then the size of the short position currently owned by User 2:

Order #sizePriceSideStrikeOption typeUserMatch pricecontractId

Order 1

30

11

BID

15

CALL

User 2

10

100500

Order 2

30

9

ASK

15

CALL

User 3

10

100500

Such submission would result in the following input data to the updatePositions function:

Positions row indexpositionClientpositionContractIdpositionSizeMoves funds in Fundlock

0

User 2

100500

30

no

1

User 3

100500

-30

no

Fund movements row indexfundMovementClientunderlyingAmountstrikeAmountMoves funds in FundLock

0

User 2

-20

300

yes

1

User 3

30

-300

yes

After these changes are applied to Elektro state it would look as follows:

{
  clientPositions: {
    100500: {
      'User 1': 20,
      'User 2': 10,
      'User 3': -30
    }
  },
}

As a result of this execution User 2 would get 20 underlying currency tokens back to his account and would have to pay 300 strike tokens in premium. User 3 would have to in-turn deposit 30 underlying currency tokens to back newly formed option. User 1 and User 2 are now paired on the long side with User 3.

Example 4. Long position switch to short position.

The second step is when User 1 submits an order for short position with same contractId and the size of this position is larger then the size of the long position currently owned by User 1:

Order #sizePriceSideStrikeOption typeUserMatch pricecontractId

Order 1

30

11

BID

15

CALL

User 3

10

100500

Order 2

30

9

ASK

15

CALL

User 1

10

100500

Such submission would result in the following input data to the updatePositions function:

Positions row indexpositionClientpositionContractIdpositionSizeMoves funds in Fundlock

0

User 3

100500

30

no

1

User 1

100500

-30

no

Fund movements row indexclientAddressunderlyingAmountstrikeAmountMoves funds in FundLock

0

User 3

0

300

yes

1

User 1

10

-300

yes

After these changes are applied to Elektro state it would look as follows:

{
  clientPositions: {
    100500: {
      'User 1': -10,
      'User 2': -20,
      'User 3': 30
    }
  },
}

Last updated