From 1399278f74531ea29f1716ab41ff8c288c6889ee Mon Sep 17 00:00:00 2001 From: josojo Date: Fri, 19 Jul 2019 10:16:11 +0200 Subject: [PATCH 01/28] new specificition draft, as discussed with Martin --- dFusion/balance-state-hash.png | Bin 0 -> 55857 bytes dFusion/dFusion.rst | 436 +++++++++++++--------------- dFusion/intermediate-state-hash.png | Bin 0 -> 55507 bytes 3 files changed, 202 insertions(+), 234 deletions(-) create mode 100644 dFusion/balance-state-hash.png create mode 100644 dFusion/intermediate-state-hash.png diff --git a/dFusion/balance-state-hash.png b/dFusion/balance-state-hash.png new file mode 100644 index 0000000000000000000000000000000000000000..4f54dba8e2e393ca981599b0d724862a774f9873 GIT binary patch literal 55857 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

Ry~0Ft@4}lk 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. +The following specification describes a scalable fully decentralized exchange with decentralized order matching. +Scalability is achieved by storing only hashed information onchain and allowing any bonded party to propose state-transitions. +Predefined on-chain verification smart contracts allow any client to generate fraud proofs and thereby revert invalid state transitions. + Orders are matched in a batch auction with an arbitrage-free price clearing technique developed by Gnosis: `Uniform Clearing Prices `_. Summary ======= -The envisioned exchange will enable **K** accounts to trade via limit orders between **T** predefined ERC20 tokens. -Limit orders are collected and matched in batches, with each batch containing up to **M** orders. +The envisioned exchange will enable **K** (in the magnitue of 2**24) accounts to trade via limit orders between **T** (in the magnitude of 2**10) predefined ERC20 tokens. +Limit orders are collected onchain in an orderstream and subsets of these orders are matched in batches, with each batch containing up to **M** (in the magnitude of 2**10) orders. Orders within a batch can be matched directly (e.g. an order trading token A for B against another order trading token B for A) or in arbitrarily long "ringtrades" (e.g. an order trading token A for B, with another one trading token B for C, with a third one trading token C for A). The matching process is decentralized. -After orders have been collected over a certain amount of time, a batch is frozen and any sufficiently bonded participant can suggest a matching of orders in the current batch. +After orders have been collected over at least a certain amount of time, a batch is frozen and any sufficiently bonded participant can suggest a matching of orders in the current batch. -If more than one solution is submitted within a certain amount of time after the batch closes, the one that generates the largest "trader surplus" (detailed explanation below, for now think "trading volume") is selected and executed. +If more than one solution is submitted within a certain amount of time after the batch closes, the one that generates the largest "trader utility" (detailed explanation below, for now think "trading volume") is selected and executed. For this, the party whose solution proposal was selected must post sufficient information on-chain, so that other participants can quickly validate. -Anyone can challenge a solution on-chain (while providing a bond and an alternative solution) within a certain time-period after posting. -Such disputes are resolved by verifying a zkSnark proof on-chain that checks if the provided solution fulfills a number of constraints (specified in detail below). -If the verification succeeds, the challenger loses their bond and the state is "finalized". -If the verification fails the original proposer loses their bond and the provided alternative state is assumed correct unless also challenged within a certain time. -In case no-one submits a proof, the verification automatically fails after a certain amount of time. - -The reason for having such a bonded challenge-response interaction is that generating zkSnark proofs is very time consuming (much longer than the turnaround time we envision for each batch). -We therefore "optimistically" accept solutions and set a crypto-economic incentive to only post valid solutions. +Anyone can point out incorrect solutions on-chain within the so called _finalization_ period . +Within this finalization period, callouts are facilitated via so called fraud proofs. +For any non-satisfied constraints of a solution (specified in detail below), a specific fraud proof can be generated and the fraud can be validated onchain. +Due to the nature of fraud proofs, the system sets a crypto-economic incentive to only post valid solutions. State stored in the smart contract ================================== -For each account, ERC20 token balance are chained together and stored as a pedersen hash (not merkleized) in the anchor smart contract. +For each account, ERC20 token balance are merklized in a Merkle tree with the sha-hash operation in the anchor smart contract. This "compressed" representation of all account balances is collision resistant and can thus be used to uniquely commit to the complete "uncompressed" state containing all balances explicitly. The "uncompressed" state will be stored off-chain and all state transitions will be announced via smart contract events. Thus, the full state will be fully reproducible for any participant by replaying all blocks since the creation of the smart contract. The following diagram shows how the "compressed" state hash is constructed: -.. image:: rootHash.png +.. image:: balance-state-hash.png + To allow **K** to be small, a bi-map of an accounts public key (on-chain address) to its **accountId** will be stored in the anchor contract as well. -Accounts will pay "rent" to occupy an active account. The account index can be used to locate a users token balances in the state. +The account index can be used to locate a users token balances in the state. Furthermore, we store a bi-map of token address to its index **0 <= t <= T**, for each token that can be traded on the exchange. When specifying tokens inside orders, deposits and withdrawel requests, we use the token's index **0 <= t <= T** instead of the full address. As limit orders and deposits and withdrawal requests are collected they are not directly stored in the smart contract. Doing so would require a **SSTORE** EVM instruction for each item. -This would be too gas-expensive: -Assuming an order can be encoded in 256 bits, storing a batch of 10.000 on chain would cost ~50M gas (5.000 gas for SSTORE * 10.000 orders). -Instead the smart contract emits a smart contract event containing the relevant order information (account, from_token, to_token, limit) and stores a rolling SHA hash. +This would be too gas-expensive. Instead the smart contract emits a smart contract event containing the relevant order information (account_id, from_token, to_token, buy_Amount, sell_Amount) and stores a rolling SHA hash. For a new order, the rolling hash is computed by hashing the previous rolling hash with the current order. +Only after **O** orders are hashed via this rolling hash, the smart contract saves an ordercheckpoint hash, which is the current rolling hash. +Storing ordercheckpoints allows to quickly lookup orders for later verification processes. Any participants can apply pending deposit and withdrawal requests to the current account balance state. To do so, they provide a new state commitment that represents all account balances after the application of pending requests. -Moreover, as the new state is stored on the smart contract, pending requests are reset. +Moreover, as the new state is stored on the smart contract, pending requests are not reseted directly, only after a longer finalization period. When a party applies withdrawal requests to the account balance state, they also provide the list of valid withdraws (in form of their Merkle root) which we store in the smart contract inside a mapping (**transitionId** -> **valid withdraw Merkle root**). Participants can later claim their withdraw by providing a merkle inclusion proof of their withdraw in any of the "valid withdraw merkle-roots". @@ -74,10 +73,10 @@ As soon as the matching of a closed batch is applied, pending withdrawls and dep *// TODO state for snark challenge/response, e.g. hashBatchInfo* To summarize, here is a list of state that is stored inside the smart contract: -- Hash of all token balances for each account chained together (Pedersen) +- Merkle-root-state-hash of all token balances - Bi-Map of accounts public keys (ethereum addresses) to dƒusion accountId - Bi-Map of ERC20 token addresses to internal dƒusion tokenId that the exchange supports -- Rolling hash of pending orders, withdrawls and deposit requests (SHA) +- Several rolling hash of pending orders, withdrawls and deposit requests (SHA) - Map of stateTransitionId to pair of "valid withdrawel requests merkle-root" (SHA) and bitmap of already claimed withdraws - Current state of the batch auction (e.g. *price-finding* vs. *order-collection*) @@ -89,8 +88,8 @@ The trading workflow consists of the following sequential processes: 0. Account opening, deposits & withdrawals 1. On-Chain order collection -2. Finding the batch price: optimization of batch trading surplus (off-chain) -3. Verifying batch price and trade execution (zkSnark) +2. Finding the batch price: optimization of batch trading utility (off-chain) +3. Verifying batch price and trade execution 4. Restart with step 1 @@ -110,7 +109,6 @@ On the level of contract storage, these contraints imply a bijective mapping {1, Note that: Registering accounts by specified index (rather than incrementing) enables the possiblity for accounts to be closed and account slots to be made available. -TODO - holding an account will likely incur some kind of fee. Registering tokens ~~~~~~~~~~~~~~~~~~ @@ -142,7 +140,8 @@ Upon successful transfer, the deposit is included in the appropriate depositRequ - Amount Transferred, - Deposit Slot -Where, deposit slot is deterministically governed the EVM's current block number as the integer division of block number by 20. This allows for asynchronicity so that one knows (after a certain block) that the deposit hash is no longer **active** (i.e. will not change). This is required for the asynchronous handling of in-flight transactions. +Where, deposit slot is deterministically governed the EVM's current block number as the integer division of block number by 20. This allows for asynchronicity so that one knows (after a certain block) that the deposit hash is no longer **active** (i.e. will not change). +This is required for the asynchronous handling of in-flight transactions. Processing Deposits ~~~~~~~~~~~~~~~~~~~ @@ -150,7 +149,7 @@ Processing Deposits Deposits may be applied by specifying deposit slot and updated **stateRoot**. This new state root is computed by - gathering all the deposit events for that slot, - computing the updated balances for all cooresponding deposit transactions and -- computing the pedersen hash of all account balances +- computing the new Merkle-root-hash of all account balances For security reasons, the **applyDeposits** function must be called with the following parameters - slot, @@ -184,25 +183,35 @@ TODO On-Chain order collection ------------------------- -All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, limitPrice, amount, batchId, signature)**. -The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the *limitPrice* and the *amount* specified. +All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. +The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the ratio *buyAmount* / *sellAmount*. +Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise he would like to sell at most *sellAmount* tokens. +Any placed order is placed into an order stream, a queue data type. +Any order in the orderstream is valid, until the auction with the id validUntilAuctionId is reached or the order is popped out of the queue data, due to a new insertation. + + +The order stream is made up by last 2**24 placed orders or order cancelations. +The order stream is finate, as any solutions needs to index orders with a certain amount of bits (24). +Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches The *batchId* and *signature* allow a third party to submit an order on behalf of others (saving gas when batching multiple orders together). -The user only has to specify which batch their order is valid for and sign all the information with their private key. The anchor smart contract on ethereum will offer the following function: .. code:: js - function appendOrders(bytes32 [] orders) { + function appendOrders(bytes [] orders) { // some preliminary checks limiting the number of orders.. // update of orderHashSha for(i=0; i`_. -Calculating the uniform clearing prices is an np-hard optimization problem and most likely the global optimum will not be found in the pre-defined short time frame: **SolvingTime** - estimated between 3-10 minutes. +Calculating the uniform clearing prices is an np-hard optimization problem and most likely the global optimum will not be found in the pre-defined short time frame of 3-10 minutes. While we are unlikely to find a global optimum, the procedure is still fair, as everyone can submit their best solution. -Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders surplus' they claim there solution is able to achieve. -The anchor contract will store all submissions and will select the solution with the maximal 'traders surplus' as the final solution. +However, many heuristic approaches might exist to find reasonable solutions in a short timeframe. + +Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders utility' they claim there solution is able to achieve and a bond. +The anchor contract will collect the best submissions for **C** minutes and will select the solution with the maximal 'traders utility' as the proposed solution. +This proposed solution will become a - for the present being - a valid solution, if the solution submitter will load all details of his solution onchain within another **C/2** minutes. +If he does not do it, he will slashed and in the next **C/2** minutes anyone can submit another full solution and the best fully submitted solution will be accepted by the anchor contract. This means the uniform clearing price of the auction is calculated in a permission-less decentralized way. Each time a solution is submitted to the anchor contract, the submitter also needs to bond themselves so that they can be penalized if their solutions later turns out incorrect. The participant providing the winning solution will later also have to provide the updated account balances that result from applying their order matching. In return for their efforts, solution providers will be rewarded with a fraction of transaction fees that are collected for each order. -zkSnarks -======== -Verifying batch price and trade execution ------------------------------------------ -After the solution submission period, the best solution with the highest trading surplus will be chosen by the anchor contract. -The submitter of this solution then needs to post the full solution into the ethereum chain as calldata payload. + +Providing data for fraud proofs of solutions +-------------------------------------------------------------------------- + +The submitter of a solution needs to post the full solution into the ethereum chain as calldata payload. +The payload will contain: (new balance state, prices, touched orders, trading volume per order, intermediate state hashes). The solution is a new stateHash with the updated account balances, a price vector **P**: -===== ================= ===== ================= - P Token_1:Token_1 ... Token_T:Token_1 -===== ================= ===== ================= -price p_1 ... p_T -===== ================= ===== ================= +================= ================= ===== ================= + Index_0 Index_1 ... Index_S +================= ================= ===== ================= +sizeVector(=S) P_1, token_index ... P_S, token_index +================= ================= ===== ================= + +of all prices relative to a reference token **Token_1** with token_index = 0. +The maximal size of the array is the total amount of tokens registed in the anchor contract. +Though it is expected that only a small fraction of tokens is actually touched and has a positive trading volume. +All tokens with a positive trading volume are required to be listed in the vector and have a well defined price. + +Since prices are arbitrage-free, we can calculate the **price Token_i: Token_k** = **(Token_i:Token_1):(Token_1:Token_k)**. +Each price is a 32-bit number and each token_index is a 10-bit number. + +The touched orders is a vector of the following format: -of all prices relative to a reference token **Token_1**. Since prices are arbitrage-free, we can calculate the **price Token_i: Token_k** = **(Token_i:Token_1):(Token_1:Token_k)**. +================= ================= ===== ================= + Index_0 Index_1 ... Index_K +================= ================= ===== ================= +sizeVector(=K) orderIndex_1 ... orderIndex_K +================= ================= ===== ================= -Along with the prices, the solution submitter also has to post a vector **V** of **buyVolumes** for each order: +The orderIndex is always referring to the orderIndex in the orderstream. +Since during the batch closing a snap-shot of the latest order batch is taken, we can refer to any order in the stream relative to that snap-shot. +The orderIndex 2**24 will reference to the last order in the batch. +And the orderIndex 0 will refere to the order submitted 2**24 order before the last one. +Each orderIndex is a 24 bit number. +The size of orders in the batch is bound by the amount of orders that we can verify on-chain within one fraud-proof. +Currently, the limit should be 1024 orders. +Solutons with any K< 2**10 can be valid solutions and maximizing the traders utility. + +Along with the orders, the solution submitter also has to post a vector **V** of **buyVolumes** and **sellVolumes** for each order: ========= ======= === ======= V order_1 ... order_K ========= ======= === ======= buyVolume o_1 ... o_K ========= ======= === ======= +sellVolume s_1 ... s_K +========= ======= === ======= + +Each Volume is a 32 bit float number. -Anyone can caluclate the **sellVolume** from the price of the token pair and the buyVolume. +Theoretically, it would be sufficient to provide only the **sellVolumes** and then caculate the **buyVolumes** from the prices. +However, then rounding errors could happen, which will also effect the constraints of the optimization problem in unforseen ways and finding a solution generally becomes harder. +Hence, we provide both volumes. -The solution submitter also submits a pedersen hash of all orders that were inside the applied batch. -This pedersen hash is assumed to be equivalent to the sha hash of all orders that is already stored in the smart contract (the equivalence can be challenged). -The reason we prefer having the hash as a pedersen hash is that it can be calculated much more efficiently inside a snark. -The size of our batch is bound by the amount of orders that the **applyAuction** snark (see below) can compute. -By spending less computation on hashing we can fit process larger batches inside **applyAuction**. +Furthermore, for the feasibility of the fraud proofs, intermediate state-hashes need to be provided. +Intermediate state-hashes are a mixture of temporary variables for the order processing ( such as current trader utility and total buyVolume - sellVolume per token) and the intemediate state root hashes. -*//TODO what if the participant that claimed the surplus never submits? Are we sequentially degrading to second best, third best solution, or at that point allowing any solution?* +.. image:: intermediate-state-hash.png -The new state is optimistically assumed correct and the pedersen hash equivalent of orderHash is stored alongside as trasition metadata. -V and P are provided as data payload to the anchor contract which will hash them together into **hashBatchInfo** (which is also stored as transition metadata). +The number of intermediate hashes depends on the amount of orders in a solution. +The exact number will be optimized at a later state, but roughly the number will be around _amount_of_orders_ / 5 . + +The new state is optimistically assumed correct and only fraud proofs can invalidate them. +All data is provided as data payload to the anchor contract which will hash them together into **hashBatchInfo** (which is also stored as transition metadata). With this hash the solution is unambiguously "committed" on-chain with a minimum amount of gas. If someone challenges the solution later, the smart contract can verify that a proof is for this particular solution by requiring that the private inputs to the proof hash to the values stored metadata. The full uncompressed solution is also emitted as a smart contract event so that everyone can check whether the provided solution is actually a valid one. -If it is not valid, then anyone can challenge the solution submitter (again providing a bond and an alternative solution). - -*// TODO: I could "win" the price-finding by committing to an absurdly large surplus, then submit a wrong solution, challenge myself with a correct solution that is much worse than the second surplus best.* - -There are two types of challenges: -1.) Challenging that the pedersen hash of all orders doesn't match the sha hash already stored in the smart contract -2.) Challenging that the matching logic is incorrect (e.g. not arbitrage free, not respecting limit prices of an orders, or adjusting balance incorrectly) - -To resolve a challenge of type 1), the solution submitter needs to prove that his solution is correct by providing proof for the following zkSnark: - -.. code:: python - - zkSnark - TransitionHashes&Validation ( - public input: orderHashSha, - public input: orderHashPedersen, - Private input: [orders]) - - -It will do the following checks: - -- **orders** hashes to **input.orderHashSha** -- **orders** hashes to **input.orderHashPedersen** - -To resolve a challenge of type 2), the solution submitter needs to prove that his solution is correct by providing proof for the following zkSnark: - -.. code:: python - - zkSnark - applyAuction( - Public: state, - Public: tradingWelfare, - Public: hashBatchInfo, - Public: orderHashPedersen, - Private: priceMatrix PxP, - Private: volumeVector - Private: balances - Private: orders, - Output: newstate - ) - -The snark verifies the following: - -- **priceVector** and **buyVolumes** hashes to **input.hashBatchInfo** (with sha) -- **balances** hashes to **input.state** (with pedersen) -- **orders** hashes to **input.orderHash** (with pedersen) -- for each **order** in **orders** - - **order.buyVolume** and **order.sellVolume** have same ratio as **order.buyToken** and **order.sellToken** - - Verify tnhe order only has non-zero volume if the limit price is below the market price - - Verify the order has not more volume than specified in **order.amount** - - Calculate trader surplus for this order - - Increment total surplus according to surplus of order - - Increment **totalSellVolume[order.sellToken]** by **order.sellAmount** - - Increment **totalBuyVolume[order.BuyToken]** by **order.buyAmount** - - Update the balance of the order author by subtracting **order.sellVolume** from **balance[order.sellToken]** - - Update the balance of the order author by addint **order.buyVolume** from **balance[order.buyToken]** - -- For all tokens **t**, check that **totalSellVolume[t] == totalBuyVolume[t]** (solution doesn't mint or burn tokens) -- Check that **tradingSurplus == input.tradingSurplus** -- For all balances, check that **balance > 0** -- return **newstate** by hashing all balances together (with pedersen) - -Processing of pending exits and deposits ----------------------------------------- - -Deposits and withdraws need to be processed and incorporated into the 'stateHash' as well. For this, we make again use of snarks and specific challenging periods. +Fraud-Proofs for auction settlements +==================================== + +There are a variety of fraud-proofs: + +State Transition & Utility & Conservation of value updates +--------------------------------------------------------- +- Reprovide the solution as payload +- For each order processed, verify the order from orderstream by reconstructing the stored order rolling hash of the referenced batch +- Verify hashed volume calldata matches committed volume hash of solution +- For each volume: +- Find the account + buy and sell token balance of the order belonging to this volume +- verify merkle path of buy token balance to current state root +- add buy volume to buy token balance +- recompute intermediate-state-root with updated buy token leaf +- update the trader's utility and token volumes according to the trade +- recompute intermediate-state-root with updated token volume leaf and trader's utility +- do the same for sell token balance (but subtract sell volume) +- Check that resulting hash is equal to newAccountRootHash of the solution. + +State Transition +---------------- +- verify via a merkle proof that the last intermediate-state-root does not hold the the claimed new-balance-state-root + +Utility +------- +- verify via a merkle proof that hte last intermediate-state-root does not hold the claimed utility + +Conservation of value +--------------------- +- verify via a merkle proof of the last intermediate-state-hash has a non-valid conservation of value +(meaning that the | buyVolumes - Sellvolumes | > /eplsion ) + +Negative Account Balance +------------------------ +- Provide merkle proof to account balance including it's value as calldata +- verify merkle proof results in newAccountRootHash of the solution +- check value < 0 + +Prices Coherence +---------------- +- Provide volumes, prices and orders as well as index of order that is bad as calldata +- Verify order/volume/price calldata hash matches solution's order/volume/price hash +- Check at index if buyVolume/sellVolume ≠ price(buyToken)/price(sellToken) + +Limit price & amount compliance +------------------------------- +- Provide volumes, prices and orders as well as index of order that is bad as calldata +- Verify order/volume/price hash matches committed order/volume/price hash +- Check at index if buyVolume/sellVolume >= order.buyAmount/order.sellAmount +- Check at index if buyVolume >= order.buyAmount && sellVolume <= order.sellAmount + +Order validity +-------------- +- provide unique reference to invalid order in solution +- reconstruct the order by on-chain reconstructing its rolling order hash +- show that order validity is no longer valid in current batch + +Order cancelation +----------------- +- provide unique reference to canceled order in solution +- provide unique reference to cancelation: provide index and stored rolling order hash for cancelation +- verify that cancelation happend after order placement +- verify that cancelation is valid by reconstructing rolling order hash + + +Fraud-Proofs for deposits and withdrawals +========================================= + +Deposits and withdraws need to be processed and incorporated into the 'balance-state-hash' as well. In order to deposit funds into the exchange, one would send funds into the following function of the anchor contract: @@ -376,35 +425,25 @@ The deposits can be incorporated by any significantly bonded party by calling th This function would update the **state** by incorporating the deposits received from **blockNr** to **blockNr+19**. -Everyone can check whether the **stateRH** has been updated correctly. If it has not been updated correctly, then the person submitting this solution can be challenged by providing a bond. - -To resolve the challenge one must provide the following snark: - -.. code:: python +Everyone can check whether the **stateRH** has been updated correctly. If it has not been updated correctly, then they can revert the deposits by providing a fraud proof. - snark-deposits( - Public: oldState - Public: depositHash - Private: [deposit informations] - Private: [old balances] - Output: newState - ) +State Transition for Deposits +-------------------------------------------- +- Reprovide data of deposits as payload and verify that it hashes to deposithash +- For each deposit: +- Provide current balance leave value and verify it by a merkle proof to the current balance-state-hash +- add deposit to balance +- recompute current balance-state-hash with updated leaf +- Check that resulting hash is equal to newAccountRootHash of the proposed deposit state transtion. -This snark would check that: - -- By SHA256 hashing the **[deposit information]**, we are getting the **depositHash** -- Calculate the stateHash based on current balances and make sure it matches input -- for( deposits in **[deposit information]**) - - Update the leaf with the current balance, -- Recalculate the stateHash based on updated balances Something quite similar will be done with exit requests. If a user wants to exit, they first need to do an exit request by calling the following function in the anchor contract: .. code:: js - Function exitRequest (address token, uint amount){ + Function requestWithdrawal (address token, uint amount){ // verify that not too much exists request have already been done, uint accountId = ... //lookup accountId from msg.sender @@ -414,104 +453,33 @@ Something quite similar will be done with exit requests. If a user wants to exit } -Then any significantly bonded party can incorporate these bundled exit requests into the current stateRH by calling the following function: +Then any significantly bonded party can incorporate these bundled exit requests into the current balance-state-hash by calling the following function: .. code:: js - Function incorporateWithdrawals(uint blockNr, bytes32 newState, bytes32 withdrawalRH) + Function applyWithdraws(uint blockNr, bytes32 newState, bytes32 withdrawalRH) Here, all withdrawal requests are processed, which were registered between the blocks blockNr and blockNr+19. **withdrawalRH** is the merkle root of all valid finalized withdrawals for the given block period. Again, if the incorporatedWithdrawals results were incorrectly provided, this can be challenged. In case it is challenged, the solution submitter needs to provide the snark proof: -.. code:: python - - snark-withdrawals( - Public oldState - Public: newState - Public: exitRequestHash - Private: [exitRequest informaiton] - Private: [current balances] - Output: withdrawalRH - ) - - -This snark would check that: +The fraud proof is similar to the deposit fraud proof. -- By hashing the **[exitRequest informaiton]**, we are getting the **exitRequestHash** -- Calculate the stateHash based on current balances and make sure it matches input -- for( withdrawal in **[exitRequest information]**) - - if **withdrawal.amount <= stateRHToken.amount** - - Update the leaf with the current balance - - incorporate the **withdrawal.amount** into **withdrawalRH** -- Recalculate the stateHash based on updated balances +Fee model +========= -After the challenge period has passed, any user can trigger their withdrawal by providing Merkle proof of the balance stored in **withdrawalAmounts[blockNr]**. +TBD -.. code:: python - Function processWithdrawal(uint blockNrOfReg, uint amount, address token, bytes MerkleProof){ - // Ensure sufficient time has passed - require(blockNrOfReg + TimeDelta < now) - - // Verify that withdrawal is legit - require(withdrawalAmounts[blockNrOfReg].CheckInclusionProof(amount, MerkleProof)) - - // Update withdrawalAmounts[blockNrOfReg] - - // Transfer tokens - require(Token(token).transfer(..)) - } - -Feasibility-study +Feasibility Study ================= -There are two main limiting factors for the scalability of this system. The costs associated with sending information to ethereum as payload and the number of constraints from the snarks. - -Order costs as payload ----------------------- - -An order is constructed in the following manner: **(accountLeafIndex, fromTokenIndex, toTokenIndex, limitPrice, amount, signature)**. If impose the following constraints: -- There are at most 2^6 different tokens in our exchange -- There are at most 2^16 different leafIndices -- Price is encoded with an accuracy of 64 bits using floating points (61 bits are exponent, last 3 are mantissa) -- Amounts are encoded with an accuracy of 64 bits using floating points (61 bits are exponent, last 3 are mantissa) - -Then we can store any order in 2 bytes32 and the total gas costs to k orders would be: - -.. code:: python - - transaction initiation costs + k* order as payload costs + k* signature verification cost + k* hashing costs + updating the orderHashSha - = 21000+k*(6+16+16+64+64)*68/8+k*3000+k*60+5000 - - -This means that up to 1000 orders can be stored within a single ethereum block. - -Constraints from snarks ------------------------ - -The DIZK paper showed that it is possible to calculate snarks for up to several billion constraints. However, the parallelization described in this methods only works if the prime-1 of the underlying elliptic curve is sufficiently often divisible by 2. The prime-1 of the alt-bn128 curve from ethereum is divisible by 2^28 and hence, we can compute snarks for the constraints system with up to 2^28 ~ 268M constraints. - -Certainly, our biggest constraint system comes with the snark checking the actual trade and updating all balances. In the following, we estimate the number of circuits by estimating how often we have to hash something. Such and estimation should suffice, as the total number of constraints is heavily dominated by the circuits of the hash function. - -In the snark-applyAuction the snark circuits are dominated by the following operations: - -- Check price matrix, trading welfare volume matches SHA256 - - #sha_constraints * ((bits_per_volume * orders) + (bits_per_float * tokens)) -- Calculate sateHash (both old/new) - - #pedersen_constraints * #accounts * #tokens * bits_per_float * 2 -- Order hash validation - - #pedersen_constraints * #order * #bits_per_order - -We think that we can solve this problem e.g. for 100 tokens, 1k accounts and 10k orders per batch. - -Price manipulation ------------------- - -One concern is that the limited space of orders is filled up by an attacker, after a profitiable market order (an order with a low limit sell price) was submitted. This way, the attacker could prevent fair price finding, as others wouldn't be able to submit their legitimate orders. Consequently, the attacker could profit from the off-price by buying the market order cheaply. +Theoretic gas evaluation: +https://docs.google.com/spreadsheets/d/1Abpo2IN0MRbonihmZskuqIbML9rLgvgE1v5rbNdiiFg/edit?usp=sharing -This can be prevent by two methods: +Coded fraud proof validation: +https://github.com/gnosis/dex-contracts/tree/on_chain_verifier -- **Order encryption:** Order can be encrypted using a distributed key generation sheme and only be decrypted after the order finalization is finished. Then the attacker would not be aware of the good price of an "market order". -- **Futures on order-participation:** A significant proportion (say 98%) of the order space would be distributed using the usual fee model while the rest (say 2%) could be reserved for people, who used their GNO/OWl or some other token. This way it would be much harder for an attacker to fill the order space. +Some gas estimates: +https://github.com/gnosis/dex-contracts/issues/87 \ No newline at end of file diff --git a/dFusion/intermediate-state-hash.png b/dFusion/intermediate-state-hash.png new file mode 100644 index 0000000000000000000000000000000000000000..b94a0f99fd8df3dce1daa957a1344297ff189a6f GIT binary patch literal 55507 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%0DFfCAk`f3bNS@`eOFp5;nR&uT7zrYNGO$y1b1H% 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 z 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 literal 0 HcmV?d00001 From 1b6603e706acb257609ced38e387d94f0f85d55a Mon Sep 17 00:00:00 2001 From: josojo Date: Fri, 19 Jul 2019 10:49:19 +0200 Subject: [PATCH 02/28] updates on order validity --- dFusion/dFusion.rst | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 96480a1..2d4ec42 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -185,14 +185,18 @@ On-Chain order collection All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the ratio *buyAmount* / *sellAmount*. -Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise he would like to sell at most *sellAmount* tokens. +Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise, he would like to sell at most *sellAmount* tokens. Any placed order is placed into an order stream, a queue data type. -Any order in the orderstream is valid, until the auction with the id validUntilAuctionId is reached or the order is popped out of the queue data, due to a new insertation. +Any order in the orderstream is valid until the auction with the id *validUntilAuctionId* is reached or the order is popped out of the queue data, due to a new insert. +Additionally, an order can be resubmitted with the positive *flagIsCancelation* and then the order is also considered to be canceled. +It does not matter, whether the cancelation order is before or after the actual order, in any case, the order is canceled. +If we would not have this logic, then anyone could just replay canceled orders. +The order stream is made up of last 2**24 placed orders or order cancelations. +The order stream is finite, as any solutions need to index orders with a certain amount of bits (24). +Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. +Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. -The order stream is made up by last 2**24 placed orders or order cancelations. -The order stream is finate, as any solutions needs to index orders with a certain amount of bits (24). -Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches The *batchId* and *signature* allow a third party to submit an order on behalf of others (saving gas when batching multiple orders together). The anchor smart contract on ethereum will offer the following function: @@ -229,6 +233,8 @@ Also notice, the system allows orders, which might not be covered by any balance These orders will be sorted out later in the settlement of an auction. During the auction settlement, only a subset of the orderstream is actually considered and can be settled. +Any order once touched in a solution will also never be considered as a valid order, as we can not differeniate between intentially replayed orders and maliciously replayed ordres. + Finding the batch price: optimization of batch trading utility (off-chain) -------------------------------------------------------------------------- @@ -392,6 +398,12 @@ Order cancelation - verify that cancelation happend after order placement - verify that cancelation is valid by reconstructing rolling order hash +Order already settled in previous auction +----------------------------------------- +- provide unique reference to solution with settled order and its order index +- resubmit as payload the whole referenced solution and validate the order was touched with positive trading volume +- validate the order was part of current solution +Note: this means that a settled order can never be used in the system again with the exact order data. If we want to allow it, we should use nonces. Fraud-Proofs for deposits and withdrawals ========================================= From 218deca845d1bc7c478971cf2fa0fa6ce03d22a3 Mon Sep 17 00:00:00 2001 From: josojo Date: Fri, 19 Jul 2019 11:37:04 +0200 Subject: [PATCH 03/28] spelling --- dFusion/dFusion.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 2d4ec42..1b589dc 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -25,7 +25,7 @@ After orders have been collected over at least a certain amount of time, a batch If more than one solution is submitted within a certain amount of time after the batch closes, the one that generates the largest "trader utility" (detailed explanation below, for now think "trading volume") is selected and executed. For this, the party whose solution proposal was selected must post sufficient information on-chain, so that other participants can quickly validate. -Anyone can point out incorrect solutions on-chain within the so called _finalization_ period . +Anyone can point out incorrect solutions on-chain within the so called *finalization* period . Within this finalization period, callouts are facilitated via so called fraud proofs. For any non-satisfied constraints of a solution (specified in detail below), a specific fraud proof can be generated and the fraud can be validated onchain. @@ -76,7 +76,7 @@ To summarize, here is a list of state that is stored inside the smart contract: - Merkle-root-state-hash of all token balances - Bi-Map of accounts public keys (ethereum addresses) to dƒusion accountId - Bi-Map of ERC20 token addresses to internal dƒusion tokenId that the exchange supports -- Several rolling hash of pending orders, withdrawls and deposit requests (SHA) +- Several rolling hashes of pending orders, withdrawls and deposit requests (SHA) - Map of stateTransitionId to pair of "valid withdrawel requests merkle-root" (SHA) and bitmap of already claimed withdraws - Current state of the batch auction (e.g. *price-finding* vs. *order-collection*) From 878054f3f19de7b0abf0d336b6c988e30e4341ba Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 06:37:52 +0200 Subject: [PATCH 04/28] minor reformulations --- dFusion/dFusion.rst | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 1b589dc..d1ff8f7 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -34,24 +34,25 @@ Due to the nature of fraud proofs, the system sets a crypto-economic incentive t State stored in the smart contract ================================== -For each account, ERC20 token balance are merklized in a Merkle tree with the sha-hash operation in the anchor smart contract. -This "compressed" representation of all account balances is collision resistant and can thus be used to uniquely commit to the complete "uncompressed" state containing all balances explicitly. +For each account, ERC20 token balances are represted in a Merkle tree constructed by the sha-hash operation. +This "compressed" representation of all account balances by the Merkle root - called the balance-state-hash - is collision resistant and can thus be used to uniquely commit to the complete "uncompressed" state containing all balances explicitly. The "uncompressed" state will be stored off-chain and all state transitions will be announced via smart contract events. Thus, the full state will be fully reproducible for any participant by replaying all blocks since the creation of the smart contract. -The following diagram shows how the "compressed" state hash is constructed: +The following diagram shows how the "compressed" balance-state-hash is constructed: .. image:: balance-state-hash.png To allow **K** to be small, a bi-map of an accounts public key (on-chain address) to its **accountId** will be stored in the anchor contract as well. -The account index can be used to locate a users token balances in the state. +The account index can be used to locate a users token balances in the Merkle-tree representing the state. Furthermore, we store a bi-map of token address to its index **0 <= t <= T**, for each token that can be traded on the exchange. When specifying tokens inside orders, deposits and withdrawel requests, we use the token's index **0 <= t <= T** instead of the full address. As limit orders and deposits and withdrawal requests are collected they are not directly stored in the smart contract. Doing so would require a **SSTORE** EVM instruction for each item. -This would be too gas-expensive. Instead the smart contract emits a smart contract event containing the relevant order information (account_id, from_token, to_token, buy_Amount, sell_Amount) and stores a rolling SHA hash. +This would be too gas-expensive. +Instead the smart contract emits a smart contract event containing the relevant order information (account_id, from_token, to_token, buy_Amount, sell_Amount) and stores a rolling SHA hash. For a new order, the rolling hash is computed by hashing the previous rolling hash with the current order. Only after **O** orders are hashed via this rolling hash, the smart contract saves an ordercheckpoint hash, which is the current rolling hash. Storing ordercheckpoints allows to quickly lookup orders for later verification processes. @@ -70,8 +71,6 @@ Participants can provide state transitions that apply pending deposits and withd Since price finding and order matching is a computationally expensive task, the account state must not change while the optimization problem is ongoing, as this could potentially invalidate correct solutions (e.g. a withdraw could lead to insufficient balance for a matched trade). As soon as the matching of a closed batch is applied, pending withdrawls and deposits can again be applied to the state. -*// TODO state for snark challenge/response, e.g. hashBatchInfo* - To summarize, here is a list of state that is stored inside the smart contract: - Merkle-root-state-hash of all token balances - Bi-Map of accounts public keys (ethereum addresses) to dƒusion accountId @@ -192,12 +191,12 @@ Additionally, an order can be resubmitted with the positive *flagIsCancelation* It does not matter, whether the cancelation order is before or after the actual order, in any case, the order is canceled. If we would not have this logic, then anyone could just replay canceled orders. -The order stream is made up of last 2**24 placed orders or order cancelations. +The order stream is queue able to hold 2**24 placed orders or order cancelations. The order stream is finite, as any solutions need to index orders with a certain amount of bits (24). Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. -The *batchId* and *signature* allow a third party to submit an order on behalf of others (saving gas when batching multiple orders together). +The *signature* must sign the order with the private key of the *accountID*. The anchor smart contract on ethereum will offer the following function: @@ -223,7 +222,8 @@ The anchor smart contract on ethereum will offer the following function: This function will update the rolling hash of pending orders, chaining all orders with a valid signature. This function is callable by any party. -However, it is possible that “decentralized operators” accept orders from users, bundle them and then submit them all together in one function call. +It is possible that “decentralized operators” accept orders from users, bundle them and then submit them all together in one function call. +This allows for big gas savings, when batching multiple orders together. Notice, that the orders are only sent over as transaction payload, but will not be “stored” in the EVM (to save gas). All relevant information is emitted as events. @@ -248,19 +248,16 @@ Calculating the uniform clearing prices is an np-hard optimization problem and m While we are unlikely to find a global optimum, the procedure is still fair, as everyone can submit their best solution. However, many heuristic approaches might exist to find reasonable solutions in a short timeframe. -Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders utility' they claim there solution is able to achieve and a bond. +Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders utility' they claim there solution is able to achieve, the new balance-state-hash after the auction settlement and a bond. The anchor contract will collect the best submissions for **C** minutes and will select the solution with the maximal 'traders utility' as the proposed solution. This proposed solution will become a - for the present being - a valid solution, if the solution submitter will load all details of his solution onchain within another **C/2** minutes. If he does not do it, he will slashed and in the next **C/2** minutes anyone can submit another full solution and the best fully submitted solution will be accepted by the anchor contract. This means the uniform clearing price of the auction is calculated in a permission-less decentralized way. Each time a solution is submitted to the anchor contract, the submitter also needs to bond themselves so that they can be penalized if their solutions later turns out incorrect. -The participant providing the winning solution will later also have to provide the updated account balances that result from applying their order matching. In return for their efforts, solution providers will be rewarded with a fraction of transaction fees that are collected for each order. - - Providing data for fraud proofs of solutions -------------------------------------------------------------------------- From efabea2d2ef5db560b21fbebf2f1dad441c175c6 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 06:47:24 +0200 Subject: [PATCH 05/28] a little bit of reformatting --- dFusion/dFusion.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index d1ff8f7..ba94ed6 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -262,14 +262,14 @@ Providing data for fraud proofs of solutions -------------------------------------------------------------------------- The submitter of a solution needs to post the full solution into the ethereum chain as calldata payload. -The payload will contain: (new balance state, prices, touched orders, trading volume per order, intermediate state hashes). -The solution is a new stateHash with the updated account balances, a price vector **P**: +The payload will contain: (new balance-state-hash, prices, touched orders, trading volume per order, intermediate state hashes). +The solution is a new balance-state-hash with the updated account balances, a price vector **P**: ================= ================= ===== ================= - Index_0 Index_1 ... Index_S + Index_1 ... Index_S ================= ================= ===== ================= -sizeVector(=S) P_1, token_index ... P_S, token_index +(price, index) P_1, token_index ... P_S, token_index ================= ================= ===== ================= of all prices relative to a reference token **Token_1** with token_index = 0. @@ -283,9 +283,9 @@ Each price is a 32-bit number and each token_index is a 10-bit number. The touched orders is a vector of the following format: ================= ================= ===== ================= - Index_0 Index_1 ... Index_K + Index_1 ... Index_K ================= ================= ===== ================= -sizeVector(=K) orderIndex_1 ... orderIndex_K +orders orderIndex_1 ... orderIndex_K ================= ================= ===== ================= The orderIndex is always referring to the orderIndex in the orderstream. @@ -356,7 +356,7 @@ State Transition Utility ------- -- verify via a merkle proof that hte last intermediate-state-root does not hold the claimed utility +- verify via a merkle proof that the last intermediate-state-root does not hold the claimed utility Conservation of value --------------------- @@ -478,7 +478,7 @@ The fraud proof is similar to the deposit fraud proof. Fee model ========= -TBD +TBD; https://docs.google.com/document/d/1-AFNjDT9wbg3yT5eV8HRkTi9aMBSfn0ZtLlRl6jwtsM/edit Feasibility Study From 5ce703f2c22955e13dd9149022763c6e91f2ef36 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 22 Jul 2019 11:58:20 +0200 Subject: [PATCH 06/28] Apply suggestions from code review --- dFusion/dFusion.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index ba94ed6..86dc068 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -220,8 +220,7 @@ The anchor smart contract on ethereum will offer the following function: } -This function will update the rolling hash of pending orders, chaining all orders with a valid signature. -This function is callable by any party. +This function will update the rolling hash of pending orders, chaining all orders with a valid signature and is callable by any party. It is possible that “decentralized operators” accept orders from users, bundle them and then submit them all together in one function call. This allows for big gas savings, when batching multiple orders together. @@ -491,4 +490,4 @@ Coded fraud proof validation: https://github.com/gnosis/dex-contracts/tree/on_chain_verifier Some gas estimates: -https://github.com/gnosis/dex-contracts/issues/87 \ No newline at end of file +https://github.com/gnosis/dex-contracts/issues/87 From 95632125e1843bbb27696913d88ba74aeffbe4a0 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:14:43 +0200 Subject: [PATCH 07/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 86dc068..f6cfe85 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -191,7 +191,7 @@ Additionally, an order can be resubmitted with the positive *flagIsCancelation* It does not matter, whether the cancelation order is before or after the actual order, in any case, the order is canceled. If we would not have this logic, then anyone could just replay canceled orders. -The order stream is queue able to hold 2**24 placed orders or order cancelations. +The order stream is a queue able to hold 2^24 placed orders or order cancelations. The order stream is finite, as any solutions need to index orders with a certain amount of bits (24). Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. From 31511bf89a857e0a3778fef7c79c993dfd298cf7 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:14:59 +0200 Subject: [PATCH 08/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index f6cfe85..cf12d91 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -182,7 +182,7 @@ TODO On-Chain order collection ------------------------- -All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. +All orders are encoded as limit sell orders: **(accountId, buyTokenId, sellTokenId, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the ratio *buyAmount* / *sellAmount*. Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise, he would like to sell at most *sellAmount* tokens. Any placed order is placed into an order stream, a queue data type. From eea3da528a00f89dc202fb86f77607550ec77bc0 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:15:19 +0200 Subject: [PATCH 09/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index cf12d91..ff447b0 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -183,7 +183,7 @@ On-Chain order collection ------------------------- All orders are encoded as limit sell orders: **(accountId, buyTokenId, sellTokenId, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. -The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the ratio *buyAmount* / *sellAmount*. +The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *buyTokenId* for *sellTokenId* for at most the ratio *buyAmount* / *sellAmount*. Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise, he would like to sell at most *sellAmount* tokens. Any placed order is placed into an order stream, a queue data type. Any order in the orderstream is valid until the auction with the id *validUntilAuctionId* is reached or the order is popped out of the queue data, due to a new insert. From 0283bec662e651145f0b446ef5578368320f64ad Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:15:32 +0200 Subject: [PATCH 10/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index ff447b0..31166ec 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -186,7 +186,7 @@ All orders are encoded as limit sell orders: **(accountId, buyTokenId, sellToken The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *buyTokenId* for *sellTokenId* for at most the ratio *buyAmount* / *sellAmount*. Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise, he would like to sell at most *sellAmount* tokens. Any placed order is placed into an order stream, a queue data type. -Any order in the orderstream is valid until the auction with the id *validUntilAuctionId* is reached or the order is popped out of the queue data, due to a new insert. +Any order in the order stream is valid until the auction with the id *validUntilAuctionId* is reached or the order is popped out of the queue data, due to a new insert. Additionally, an order can be resubmitted with the positive *flagIsCancelation* and then the order is also considered to be canceled. It does not matter, whether the cancelation order is before or after the actual order, in any case, the order is canceled. If we would not have this logic, then anyone could just replay canceled orders. From ac9eb1fb90d9e13476e3178a8c36e83c57e9b55a Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:15:47 +0200 Subject: [PATCH 11/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 31166ec..6e1f79a 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -192,7 +192,7 @@ It does not matter, whether the cancelation order is before or after the actual If we would not have this logic, then anyone could just replay canceled orders. The order stream is a queue able to hold 2^24 placed orders or order cancelations. -The order stream is finite, as any solutions need to index orders with a certain amount of bits (24). +The order stream is finite, as any solutions need to index orders with a fixed number of bits (24). Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. From b6c8880b752268ebe3ff88d8dd1fabb05a79bd52 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:18:02 +0200 Subject: [PATCH 12/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 6e1f79a..0d38f1b 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -193,7 +193,7 @@ If we would not have this logic, then anyone could just replay canceled orders. The order stream is a queue able to hold 2^24 placed orders or order cancelations. The order stream is finite, as any solutions need to index orders with a fixed number of bits (24). -Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. +Orders in the order stream are batched into smaller batches of size 2^7, and for each of these batches, the rolling order hash is stored on-chain. Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. The *signature* must sign the order with the private key of the *accountID*. From c4f15a3128e449c6eb26d17e8b41c7ef1d7aa930 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:19:57 +0200 Subject: [PATCH 13/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 0d38f1b..5c26474 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -194,7 +194,7 @@ If we would not have this logic, then anyone could just replay canceled orders. The order stream is a queue able to hold 2^24 placed orders or order cancelations. The order stream is finite, as any solutions need to index orders with a fixed number of bits (24). Orders in the order stream are batched into smaller batches of size 2^7, and for each of these batches, the rolling order hash is stored on-chain. -Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. +Each solution will just be able to consider the orders from the order stream stored in the last 2^(24-7) rolling order batches. The *signature* must sign the order with the private key of the *accountID*. From 573b36174408bb911cfeeb6a0b259c3057bf01a6 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 12:21:25 +0200 Subject: [PATCH 14/28] many small corrections --- dFusion/dFusion.rst | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index ba94ed6..94df473 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -6,17 +6,17 @@ dFusion - Decentralized Scalable Onchain Exchange A specification developed by Gnosis. -The following specification describes a scalable fully decentralized exchange with decentralized order matching. -Scalability is achieved by storing only hashed information onchain and allowing any bonded party to propose state-transitions. -Predefined on-chain verification smart contracts allow any client to generate fraud proofs and thereby revert invalid state transitions. +The following specification describes a scalable, fully decentralized exchange with decentralized order matching. +Scalability is achieved by storing only hashed information on-chain and allowing any bonded party to propose state-transitions. +Pre-deployed on-chain verification smart contracts allow any client to generate fraud proofs and thereby revert invalid state transitions. Orders are matched in a batch auction with an arbitrage-free price clearing technique developed by Gnosis: `Uniform Clearing Prices `_. Summary ======= -The envisioned exchange will enable **K** (in the magnitue of 2**24) accounts to trade via limit orders between **T** (in the magnitude of 2**10) predefined ERC20 tokens. -Limit orders are collected onchain in an orderstream and subsets of these orders are matched in batches, with each batch containing up to **M** (in the magnitude of 2**10) orders. +The envisioned exchange will enable **K** (in the magnitude of 2^24) accounts to trade via limit orders between **T** (in the magnitude of 2^10) predefined ERC20 tokens. +Limit orders are collected on-chain in an orderstream and subsets of these orders are matched in batches, with each batch containing up to **M** (in the magnitude of 2^10) orders. Orders within a batch can be matched directly (e.g. an order trading token A for B against another order trading token B for A) or in arbitrarily long "ringtrades" (e.g. an order trading token A for B, with another one trading token B for C, with a third one trading token C for A). The matching process is decentralized. @@ -25,26 +25,26 @@ After orders have been collected over at least a certain amount of time, a batch If more than one solution is submitted within a certain amount of time after the batch closes, the one that generates the largest "trader utility" (detailed explanation below, for now think "trading volume") is selected and executed. For this, the party whose solution proposal was selected must post sufficient information on-chain, so that other participants can quickly validate. -Anyone can point out incorrect solutions on-chain within the so called *finalization* period . -Within this finalization period, callouts are facilitated via so called fraud proofs. -For any non-satisfied constraints of a solution (specified in detail below), a specific fraud proof can be generated and the fraud can be validated onchain. +Anyone can point out incorrect solutions on-chain within the so called *finalization* period. +Within this finalization period, callouts are facilitated via _fraud proofs_. +For any non-satisfied constraints of a solution (specified in detail below), a specific fraud proof can be generated and the fraud can be validated on-chain. Due to the nature of fraud proofs, the system sets a crypto-economic incentive to only post valid solutions. State stored in the smart contract ================================== -For each account, ERC20 token balances are represted in a Merkle tree constructed by the sha-hash operation. -This "compressed" representation of all account balances by the Merkle root - called the balance-state-hash - is collision resistant and can thus be used to uniquely commit to the complete "uncompressed" state containing all balances explicitly. -The "uncompressed" state will be stored off-chain and all state transitions will be announced via smart contract events. +For each account, ERC20 token balances are represented in a Merkle tree constructed by the sha-hash operation. +This succinct representation of all account balances by the Merkle root, called the balance-state-hash, is collision resistant and can thus be used to uniquely commit to the complete "uncompressed" state containing all balances explicitly. +The actual state will be stored off-chain and all state transitions will be announced via smart contract events. Thus, the full state will be fully reproducible for any participant by replaying all blocks since the creation of the smart contract. -The following diagram shows how the "compressed" balance-state-hash is constructed: +The following diagram shows how the succinct balance-state-hash is constructed: .. image:: balance-state-hash.png To allow **K** to be small, a bi-map of an accounts public key (on-chain address) to its **accountId** will be stored in the anchor contract as well. -The account index can be used to locate a users token balances in the Merkle-tree representing the state. +The account index can be used to locate a users token balances in the Merkle tree representing the state. Furthermore, we store a bi-map of token address to its index **0 <= t <= T**, for each token that can be traded on the exchange. When specifying tokens inside orders, deposits and withdrawel requests, we use the token's index **0 <= t <= T** instead of the full address. @@ -52,14 +52,14 @@ When specifying tokens inside orders, deposits and withdrawel requests, we use t As limit orders and deposits and withdrawal requests are collected they are not directly stored in the smart contract. Doing so would require a **SSTORE** EVM instruction for each item. This would be too gas-expensive. -Instead the smart contract emits a smart contract event containing the relevant order information (account_id, from_token, to_token, buy_Amount, sell_Amount) and stores a rolling SHA hash. +Instead the smart contract emits an event containing the relevant order information (accountId, sellToken, buyToken, buyAmount, sellAmount) and stores a rolling SHA hash. For a new order, the rolling hash is computed by hashing the previous rolling hash with the current order. -Only after **O** orders are hashed via this rolling hash, the smart contract saves an ordercheckpoint hash, which is the current rolling hash. -Storing ordercheckpoints allows to quickly lookup orders for later verification processes. +Only after **O** orders are hashed via this rolling hash does the smart contract save an order-checkpoint hash, which is the current rolling hash. +Storing order-checkpoints allows to quickly lookup orders for later verification processes. Any participants can apply pending deposit and withdrawal requests to the current account balance state. To do so, they provide a new state commitment that represents all account balances after the application of pending requests. -Moreover, as the new state is stored on the smart contract, pending requests are not reseted directly, only after a longer finalization period. +Moreover, as the new state is stored on the smart contract, pending requests are not reset directly, only after a longer finalization period. When a party applies withdrawal requests to the account balance state, they also provide the list of valid withdraws (in form of their Merkle root) which we store in the smart contract inside a mapping (**transitionId** -> **valid withdraw Merkle root**). Participants can later claim their withdraw by providing a merkle inclusion proof of their withdraw in any of the "valid withdraw merkle-roots". @@ -71,12 +71,12 @@ Participants can provide state transitions that apply pending deposits and withd Since price finding and order matching is a computationally expensive task, the account state must not change while the optimization problem is ongoing, as this could potentially invalidate correct solutions (e.g. a withdraw could lead to insufficient balance for a matched trade). As soon as the matching of a closed batch is applied, pending withdrawls and deposits can again be applied to the state. -To summarize, here is a list of state that is stored inside the smart contract: -- Merkle-root-state-hash of all token balances +To summarize, here is a list of state that is storeorder-checkpointd inside the smart contract: +- blanace-state-hash of all token balances - Bi-Map of accounts public keys (ethereum addresses) to dƒusion accountId - Bi-Map of ERC20 token addresses to internal dƒusion tokenId that the exchange supports - Several rolling hashes of pending orders, withdrawls and deposit requests (SHA) -- Map of stateTransitionId to pair of "valid withdrawel requests merkle-root" (SHA) and bitmap of already claimed withdraws +- Map of stateTransitionId to pair of "valid withdrawal requests merkle-root" (SHA) and bitmap of already claimed withdraws - Current state of the batch auction (e.g. *price-finding* vs. *order-collection*) @@ -85,14 +85,14 @@ Batch Auction Workflow The trading workflow consists of the following sequential processes: -0. Account opening, deposits & withdrawals +0. Account registration, deposits & withdrawals 1. On-Chain order collection 2. Finding the batch price: optimization of batch trading utility (off-chain) 3. Verifying batch price and trade execution 4. Restart with step 1 -Account opening & token registration +Account & token registration ------------------------------------ Registering accounts @@ -182,21 +182,21 @@ TODO On-Chain order collection ------------------------- -All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, buyAmount, sellAmount, validUntilAuctionId, flagIsBuy, flagIsCancelation, signature)**. +All orders are encoded as limit sell orders: **(accountId, fromTokenIndex, toTokenIndex, buyAmount, sellAmount, validUntilAuctionId, IsBuyFlag, cancelationFlag, signature)**. The order should be read in the following way: the user occupying the specified *accountId* would like to sell the token *fromTokenIndex* for *toTokenIndex* for at most the ratio *buyAmount* / *sellAmount*. -Additionally, the user would like to buy at most *buyAmount* tokens if the *flag_isBuy* is true, otherwise, he would like to sell at most *sellAmount* tokens. +Additionally, the user would like to buy at most *buyAmount* tokens if the *IsBuyFlag* is true, otherwise, he would like to sell at most *sellAmount* tokens. Any placed order is placed into an order stream, a queue data type. Any order in the orderstream is valid until the auction with the id *validUntilAuctionId* is reached or the order is popped out of the queue data, due to a new insert. -Additionally, an order can be resubmitted with the positive *flagIsCancelation* and then the order is also considered to be canceled. +Additionally, an order can be resubmitted with the positive *cancelationFlag* and then the order is also considered to be canceled. It does not matter, whether the cancelation order is before or after the actual order, in any case, the order is canceled. If we would not have this logic, then anyone could just replay canceled orders. -The order stream is queue able to hold 2**24 placed orders or order cancelations. +The order stream is a queue able to hold 2**24 placed orders or order cancelations. The order stream is finite, as any solutions need to index orders with a certain amount of bits (24). Orders in the order stream are batched into smaller batches of size 2**7, and for each of these batches, the rolling order hash is stored on-chain. Each solution will just be able to consider the orders from the order stream stored in the last 2**(24-7) rolling order batches. -The *signature* must sign the order with the private key of the *accountID*. +The *signature* must be provided by owner and is signed by its private key of the *accountID*. The anchor smart contract on ethereum will offer the following function: @@ -250,7 +250,7 @@ However, many heuristic approaches might exist to find reasonable solutions in a Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders utility' they claim there solution is able to achieve, the new balance-state-hash after the auction settlement and a bond. The anchor contract will collect the best submissions for **C** minutes and will select the solution with the maximal 'traders utility' as the proposed solution. -This proposed solution will become a - for the present being - a valid solution, if the solution submitter will load all details of his solution onchain within another **C/2** minutes. +This proposed solution will become a - for the present being - a valid solution, if the solution submitter will load all details of his solution on-chain within another **C/2** minutes. If he does not do it, he will slashed and in the next **C/2** minutes anyone can submit another full solution and the best fully submitted solution will be accepted by the anchor contract. This means the uniform clearing price of the auction is calculated in a permission-less decentralized way. @@ -392,15 +392,15 @@ Order cancelation ----------------- - provide unique reference to canceled order in solution - provide unique reference to cancelation: provide index and stored rolling order hash for cancelation -- verify that cancelation happend after order placement -- verify that cancelation is valid by reconstructing rolling order hash +- verify that cancelation is valid by reconstructing rolling order hash Order already settled in previous auction ----------------------------------------- - provide unique reference to solution with settled order and its order index - resubmit as payload the whole referenced solution and validate the order was touched with positive trading volume - validate the order was part of current solution -Note: this means that a settled order can never be used in the system again with the exact order data. If we want to allow it, we should use nonces. +Note: this means that a settled order can never be used in the system again with the exact order data (Even, when it is only touched by an eplsion). +If we want to allow it, we should use nonces. Fraud-Proofs for deposits and withdrawals ========================================= From 62783feabb8e293529d5b440df688776db3307c3 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 13:28:27 +0200 Subject: [PATCH 15/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index e7f09fa..afd68f2 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -239,7 +239,7 @@ Any order once touched in a solution will also never be considered as a valid or Finding the batch price: optimization of batch trading utility (off-chain) -------------------------------------------------------------------------- -After a certain time-frame, anyone can trigger a "batch-freeze" and the a snap-shot of the latest orderstream is made. +After a certain time-frame, anyone can trigger a "batch-freeze" and a snap-shot of the latest order-stream is made. A new batch could immediately start collecting new orders while the previous one is being processed. To process a batch, participants compute the uniform clearing price maximizing the trading utility between all trading pairs. The traders utility of an order is defined as the difference between the uniform clearning price and the limit price, multipied by the volume of the order with respect to some reference token. From f1d87ba76daaf392338a7563f97972a9764f1699 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 13:28:43 +0200 Subject: [PATCH 16/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index afd68f2..1413820 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -244,7 +244,7 @@ A new batch could immediately start collecting new orders while the previous one To process a batch, participants compute the uniform clearing price maximizing the trading utility between all trading pairs. The traders utility of an order is defined as the difference between the uniform clearning price and the limit price, multipied by the volume of the order with respect to some reference token. The exact procedure is described `here `_. -Calculating the uniform clearing prices is an np-hard optimization problem and most likely the global optimum will not be found in the pre-defined short time frame of 3-10 minutes. +Calculating the uniform clearing prices is an np-hard optimization problem and most likely the global optimum will not be found in the pre-defined time frame of 3-10 minutes. While we are unlikely to find a global optimum, the procedure is still fair, as everyone can submit their best solution. However, many heuristic approaches might exist to find reasonable solutions in a short timeframe. From 96134cfdf3637539c40dc05a1270d5a01b0954bb Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 13:29:18 +0200 Subject: [PATCH 17/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 1413820..6c922d8 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -242,7 +242,7 @@ Finding the batch price: optimization of batch trading utility (off-chain) After a certain time-frame, anyone can trigger a "batch-freeze" and a snap-shot of the latest order-stream is made. A new batch could immediately start collecting new orders while the previous one is being processed. To process a batch, participants compute the uniform clearing price maximizing the trading utility between all trading pairs. -The traders utility of an order is defined as the difference between the uniform clearning price and the limit price, multipied by the volume of the order with respect to some reference token. +The trader's utility of an order is defined as the difference between the uniform clearing price and the limit price, multiplied by the volume of the order with respect to some reference token. The exact procedure is described `here `_. Calculating the uniform clearing prices is an np-hard optimization problem and most likely the global optimum will not be found in the pre-defined time frame of 3-10 minutes. While we are unlikely to find a global optimum, the procedure is still fair, as everyone can submit their best solution. From 4104500212b19509b3ddbfc97fd376b1746c2b86 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 13:57:07 +0200 Subject: [PATCH 18/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 6c922d8..74b4517 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -316,7 +316,7 @@ However, then rounding errors could happen, which will also effect the constrain Hence, we provide both volumes. Furthermore, for the feasibility of the fraud proofs, intermediate state-hashes need to be provided. -Intermediate state-hashes are a mixture of temporary variables for the order processing ( such as current trader utility and total buyVolume - sellVolume per token) and the intemediate state root hashes. +Intermediate state-hashes are a mixture of temporary variables for the order processing (such as current trader utility and total buyVolume - sellVolume per token) and the intermediate state root hashes. .. image:: intermediate-state-hash.png From 94d7eb0a373dbf4ab0ab0bf4978475ff19696f61 Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 22 Jul 2019 13:58:19 +0200 Subject: [PATCH 19/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 74b4517..fde8da2 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -290,7 +290,7 @@ orders orderIndex_1 ... orderIndex_K The orderIndex is always referring to the orderIndex in the orderstream. Since during the batch closing a snap-shot of the latest order batch is taken, we can refer to any order in the stream relative to that snap-shot. -The orderIndex 2**24 will reference to the last order in the batch. +The orderIndex 2^24 will reference to the last order in the batch. And the orderIndex 0 will refere to the order submitted 2**24 order before the last one. Each orderIndex is a 24 bit number. From 1c98063debb64cf6cab354ba40571ec323d4d3e1 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 22 Jul 2019 14:13:19 +0200 Subject: [PATCH 20/28] Update dFusion/dFusion.rst --- dFusion/dFusion.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index fde8da2..bba0446 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -247,7 +247,6 @@ The exact procedure is described `here Date: Sat, 27 Jul 2019 16:15:34 +0200 Subject: [PATCH 21/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index bba0446..b43ff57 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -337,7 +337,7 @@ There are a variety of fraud-proofs: State Transition & Utility & Conservation of value updates --------------------------------------------------------- - Reprovide the solution as payload -- For each order processed, verify the order from orderstream by reconstructing the stored order rolling hash of the referenced batch +- For each order processed, verify the order from order-stream by reconstructing the stored order rolling hash of the referenced batch - Verify hashed volume calldata matches committed volume hash of solution - For each volume: - Find the account + buy and sell token balance of the order belonging to this volume From 438c55c9ab1c5cb25ab25645c3697e6f19e6a320 Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:15:45 +0200 Subject: [PATCH 22/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index b43ff57..c0bf8ad 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -334,7 +334,7 @@ Fraud-Proofs for auction settlements There are a variety of fraud-proofs: -State Transition & Utility & Conservation of value updates +State Transition, Utility & Conservation of Value Updates --------------------------------------------------------- - Reprovide the solution as payload - For each order processed, verify the order from order-stream by reconstructing the stored order rolling hash of the referenced batch From bad4b8fcc6eb0eb11ebcb16add97fc1b15f00826 Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:16:06 +0200 Subject: [PATCH 23/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index c0bf8ad..e3d6a6a 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -247,7 +247,7 @@ The exact procedure is described `here Date: Sat, 27 Jul 2019 16:16:54 +0200 Subject: [PATCH 24/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index e3d6a6a..6ec816b 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -249,7 +249,7 @@ While we are unlikely to find a global optimum, the procedure is still fair, as However, many heuristic approaches might exist to find reasonable solutions in a short timeframe. Since posting the complete solution (all prices and traded volumes) would be too gas expensive to put on-chain for each candidate solution, participants only submit the 'traders utility' they claim there solution is able to achieve, the new balance-state-hash after the auction settlement along with a bond. The anchor contract will collect the best submissions for **C** minutes and will select the solution with the maximal 'traders utility' as the proposed solution. -This proposed solution will become a - for the present being - a valid solution, if the solution submitter will load all details of his solution on-chain within another **C/2** minutes. +This proposed solution will be (for the time being) a valid solution, if the solution submitter will load all details of his solution on-chain within another **C/2** minutes. If he does not do it, he will slashed and in the next **C/2** minutes anyone can submit another full solution and the best fully submitted solution will be accepted by the anchor contract. This means the uniform clearing price of the auction is calculated in a permission-less decentralized way. From c403b4002e212456b6b1d52cfa7c694591feb255 Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:17:13 +0200 Subject: [PATCH 25/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 6ec816b..0b66202 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -260,7 +260,7 @@ In return for their efforts, solution providers will be rewarded with a fraction Providing data for fraud proofs of solutions -------------------------------------------------------------------------- -The submitter of a solution needs to post the full solution into the ethereum chain as calldata payload. +The submitter of a solution must post the full solution onto the ethereum chain as calldata/payload. The payload will contain: (new balance-state-hash, prices, touched orders, trading volume per order, intermediate state hashes). The solution is a new balance-state-hash with the updated account balances, a price vector **P**: From eb630538134ecdecc664ba81f7e5dd25fd9dee94 Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:18:05 +0200 Subject: [PATCH 26/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 0b66202..ab495a7 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -287,7 +287,7 @@ The touched orders is a vector of the following format: orders orderIndex_1 ... orderIndex_K ================= ================= ===== ================= -The orderIndex is always referring to the orderIndex in the orderstream. +The orderIndex is always referring to the order's index in the order-stream. Since during the batch closing a snap-shot of the latest order batch is taken, we can refer to any order in the stream relative to that snap-shot. The orderIndex 2^24 will reference to the last order in the batch. And the orderIndex 0 will refere to the order submitted 2**24 order before the last one. From fb5a5bddacadf3f8c52c7319a24aaa0da947b28d Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:18:18 +0200 Subject: [PATCH 27/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index ab495a7..1fdf1db 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -295,7 +295,7 @@ Each orderIndex is a 24 bit number. The size of orders in the batch is bound by the amount of orders that we can verify on-chain within one fraud-proof. Currently, the limit should be 1024 orders. -Solutons with any K< 2**10 can be valid solutions and maximizing the traders utility. +Solutons with any K < 2^10 can be valid solutions and maximizing the traders utility. Along with the orders, the solution submitter also has to post a vector **V** of **buyVolumes** and **sellVolumes** for each order: From e9deb1f4f7b1eca54b6ca13e6aa7a69be9079e36 Mon Sep 17 00:00:00 2001 From: josojo Date: Sat, 27 Jul 2019 16:18:51 +0200 Subject: [PATCH 28/28] Update dFusion/dFusion.rst Co-Authored-By: Benjamin Smith --- dFusion/dFusion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dFusion/dFusion.rst b/dFusion/dFusion.rst index 1fdf1db..c4d81d3 100644 --- a/dFusion/dFusion.rst +++ b/dFusion/dFusion.rst @@ -290,7 +290,7 @@ orders orderIndex_1 ... orderIndex_K The orderIndex is always referring to the order's index in the order-stream. Since during the batch closing a snap-shot of the latest order batch is taken, we can refer to any order in the stream relative to that snap-shot. The orderIndex 2^24 will reference to the last order in the batch. -And the orderIndex 0 will refere to the order submitted 2**24 order before the last one. +And the orderIndex 0 will refer to the order submitted 2^24 order before the last one. Each orderIndex is a 24 bit number. The size of orders in the batch is bound by the amount of orders that we can verify on-chain within one fraud-proof.