Bluefin Pro is an optimistic exchange that is the users trades are executed off-chain and they are returned their updated position state instantly without needing to wait for the on-chain finality. To ensure that the on-chain trade execution does not revert and yields the same result as off-chain execution we need to:
The first point is not part of the audit scope so lets assume that both on/off-chain codes are the same. Think of them like 2 same automatons (pieces of code) just written for 2 different languages.
Assuming that both automatons are same, we should receive the same output as long as both are receiving the same input in the same order. To ensure that both off and on-chain systems are processing trades and other requests in the same order and they are both consuming the same data, we have introduced the concept of Sequence Hash
The IDS upon creation starts with sequence hash 0x00000000000000000000000000000000 and the off-chain sequencer service has the same sequence hash on genesis. When a new deposit, trade or any perpetual update etc.. is processed off-chain and then on-chain the next sequence hash is computed based on the data being consumed or the input of the contract call and stored on the IDS. There is no sequence hash on EDS and the direct contract calls to EDS can be made by any one with any input in what ever order they may like, it doesn’t impact the IDS or its sequence hash. We are concerned with only ensuring that the state of the off-chain margining engine and the on-chain IDS are the same.
To understand how the next sequence is computed lets look at what happens when a deposit is made by an account into the External Asset Bank / EDS. When a deposit is made to EDS the following event is emitted:
struct MarginBankDeposit has copy, drop {
id: ID,
asset:String,
sender: address,
account: address,
amount: u128,
nonce: u128
}
This event upon consumption off-chain the next sequence hash is computed as follows:
The next sequence hash calculated off-chain is still of 32 bytes and is now moved to 0xA from its initial state 0x0 - Once off-chain has processed this deposit event and updated user’s state with the new bank balance ( off-chain does all the on-chain validations before updating user state like ensuring that the nonce of the deposit event exists in EDS to prevent Tx replay etc.. but this is again out of scope of audits)
Once the off-chain state is updated, the ME requests sequencer to perform deposit_to_internal_bank() so that on-chain IDS can come to know about the new bank balance of the account. The method takes the nonce as input and using that nonce IDS pulls up the deposit entry from EDS records.
The same 5 pieces of data 1) bcs id 2) asset 3) account 4) amount and 5) nonce are BCS encoded appended to existing 0x0 hash and hashed using blake2b to compute the next sequence hash. Each contract call that mutates the state of IDS requires the off-chain sequencer to pass in expected sequence hash as input params sequence_hash: vector<u8> - This is off-chain’s way of letting the on-chain margining engine know that you should get this sequence hash after processing the request. If the sequence hash computed on-chain is different the one computed off-chain and expected from the contract call, the contract call will revert with error code EInvalidSequenceHash
All requests that are signed off-chain like: