started on orderlib
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ cache/
|
|||||||
out/
|
out/
|
||||||
.idea/
|
.idea/
|
||||||
.env
|
.env
|
||||||
|
gen/
|
||||||
|
|||||||
19
bin/build.sh
Executable file
19
bin/build.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# this script requires the jq command $(sudo apt install jq)
|
||||||
|
|
||||||
|
# first-pass build
|
||||||
|
forge build
|
||||||
|
|
||||||
|
# calculate the Vault init code hash using the bytecode generated for Vault
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
VAULT_INIT_CODE_HASH=$(cast keccak $(jq -r .bytecode.object < out/Vault.sol/Vault.json))
|
||||||
|
|
||||||
|
# put the hash value into the VaultAddress.sol source file
|
||||||
|
sed -i "s/bytes32 internal constant VAULT_INIT_CODE_HASH = .*;/bytes32 internal constant VAULT_INIT_CODE_HASH = $VAULT_INIT_CODE_HASH;/" src/VaultAddress.sol
|
||||||
|
|
||||||
|
# generate a javascript file with the constant
|
||||||
|
mkdir gen &> /dev/null
|
||||||
|
echo "export const VAULT_INIT_CODE_HASH='$VAULT_INIT_CODE_HASH';" > gen/vaultHash.js
|
||||||
|
|
||||||
|
# final build after hash values are set
|
||||||
|
forge build
|
||||||
@@ -1,5 +1,40 @@
|
|||||||
# General Design
|
# General Design
|
||||||
|
|
||||||
Creating a separate contract for each user address allows users to deposit coins into their "account" using standard ERC20 sends without extra approvals. Withdrawals require a contract call, but again no approval step. Furthermore, this clarifies the no-custody nature of the setup, since DexOrder never has any claim to ownership of the user's contract. Of course this costs extra gas up front to create the contract for the user, but on L2's it should be minimal. What about ETH? Hmmm... The alternative is to have a single contract which keeps an accounting of everyone's everything. Deposits would require approvals and a contract call. Using separate vaults will be an easier, more secure experience for frequent traders who are more likely to be our users rather than casual, occasional traders.
|
## Vault
|
||||||
|
Creating a separate contract for each user address allows users to deposit coins into their "account" using standard
|
||||||
|
ERC20 sends without extra approvals. Withdrawals require a contract call, but again no approval step. Furthermore,
|
||||||
|
this clarifies the no-custody nature of the setup, since DexOrder never has any claim to ownership of the user's
|
||||||
|
contract. Of course this costs extra gas up front to create the contract for the user, but on L2's it should be
|
||||||
|
minimal. What about ETH? Hmmm... The alternative is to have a single contract which keeps an accounting of everyone's
|
||||||
|
everything. Deposits would require approvals and a contract call. Using separate vaults will be an easier, more secure
|
||||||
|
experience for frequent traders who are more likely to be our users rather than casual, occasional traders.
|
||||||
|
|
||||||
|
## Orders
|
||||||
|
Orders are defined using an in-token, an out-token, a route/pool, and an amount of either input or output currency.
|
||||||
|
Each order may have many tranches, and each tranch may have its own price/time constraints. Each tranche tracks its
|
||||||
|
own filled amount in addition to incrementing the global filled counter. OCO support. Conditional orders (stoploss)
|
||||||
|
are implemented as a separate order which starts with amount 0 but receives any filled amounts from orders that
|
||||||
|
reference it.
|
||||||
|
|
||||||
|
## Reorg Resilience
|
||||||
|
Blockchain reorganizations happen, so we need a system that can revert recent activity, restore to a previous state,
|
||||||
|
and replay a new set of truths. We need to track the current set of open orders with their computable pool
|
||||||
|
dependencies, and we need to ensure that sent/mined transactions are not lost after a reorg but retried or safely
|
||||||
|
discarded.
|
||||||
|
|
||||||
|
We run a single synchronous process which generates a batch of all the transactions and event logs, tagged
|
||||||
|
with the block height, hash, and parent, plus a dexorder global sequence number. All subsequent processing is done
|
||||||
|
within that block context, reading the state from the parent block and writing the state for the new block. State
|
||||||
|
is kept entirely in-memory as a base state from an old block plus diffs organized in a tree of blockchain branches.
|
||||||
|
All getters of a state object return the base value plus any diffs found by walking the current worker context's
|
||||||
|
blockchain path up through its parent block. All setters write update requests to the current block hash, including
|
||||||
|
its global seq. After all layers of jobs have provably completed, the actions are executed in seq order, and thte block
|
||||||
|
may be marked as completed. As blocks age out, orphans/uncles are simply dropped from memory while old main-chain blocks
|
||||||
|
become new root blocks for the in-memory diff tree.
|
||||||
|
|
||||||
|
Active orders with an opportunity to trade are poked whenever they appear on any fork.
|
||||||
|
|
||||||
|
Transactions generated by jobs carry their original block context. If a sent tx has an origin block that gets too old,
|
||||||
|
the tx will be retried if the origin block is main chain but not if the origin block was a reorged fork. Whenever we
|
||||||
|
get a transaction receipt, the receipt is added to the order. As the order ages out, transaction receipts are
|
||||||
|
re-checked for validity
|
||||||
|
|||||||
70
src/OrderLib.sol
Normal file
70
src/OrderLib.sol
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity =0.7.6;
|
||||||
|
|
||||||
|
library OrderLib {
|
||||||
|
|
||||||
|
uint64 internal constant NO_CHAIN = type(uint64).max;
|
||||||
|
uint8 internal constant NUM_OCO_GROUPS = 6;
|
||||||
|
|
||||||
|
enum SwapOrderState {
|
||||||
|
Open, Canceled, Filled, Template
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapOrder {
|
||||||
|
address tokenIn;
|
||||||
|
address tokenOut;
|
||||||
|
uint24 fee;
|
||||||
|
uint256 amount;
|
||||||
|
bool amountIsInput;
|
||||||
|
bool outputDirectlyToOwner;
|
||||||
|
Tranche[] tranches;
|
||||||
|
uint64 chainOrder; // use NO_CHAIN for no chaining. chainOrder index must be < than this order's index for safety (written first) and chainOrder state must be Template
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapOrderStatus {
|
||||||
|
SwapOrderState state;
|
||||||
|
SwapOrder order;
|
||||||
|
uint256 filled;
|
||||||
|
uint256 net; // received after fees, conversions, taxes, etc
|
||||||
|
bool[NUM_OCO_GROUPS] ocoTriggered; // if true then the group has been canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConstraintMode {
|
||||||
|
Limit,
|
||||||
|
Barrier,
|
||||||
|
Trailing,
|
||||||
|
Time
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PriceConstraint {
|
||||||
|
PriceConstraintMode mode;
|
||||||
|
bool isAbove;
|
||||||
|
bool isRatio;
|
||||||
|
uint160 valueSqrtX96;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum TimeMode {
|
||||||
|
Timestamp, // absolute timestamp
|
||||||
|
SinceOrderStart // relative to order creation (useful for chained orders)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Time {
|
||||||
|
TimeMode mode;
|
||||||
|
uint32 time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Time constant DISTANT_PAST = Time(TimeMode.Timestamp, 0);
|
||||||
|
Time constant DISTANT_FUTURE = Time(TimeMode.Timestamp, type(uint32).max);
|
||||||
|
|
||||||
|
uint8 internal constant NO_OCO = 255;
|
||||||
|
|
||||||
|
struct Tranche {
|
||||||
|
uint64 fraction; // 18-decimal fraction of the order amount which is available to this tranche. must be <= 1
|
||||||
|
uint8 ocoGroup; // 0-5 are six valid groups, indexing ocoTriggered. use NO_OCO to disable oco functionality.
|
||||||
|
Time earliest; // earliest block timestamp for execution. use DISTANT_PAST to disable
|
||||||
|
Time latest; // latest block timestamp for execution (inclusive). use DISTANT_FUTURE to disable
|
||||||
|
PriceConstraint[] constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,14 +18,23 @@ contract Vault {
|
|||||||
owner = owner_;
|
owner = owner_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transfer(address payable recipient, uint256 amount) public {
|
event DexorderReceived(address, uint256);
|
||||||
require(msg.sender == owner);
|
|
||||||
recipient.transfer(amount);
|
receive() external payable {
|
||||||
|
emit DexorderReceived(msg.sender, msg.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transfer(uint256 amount) public {
|
function withdraw(uint256 amount) public {
|
||||||
|
_withdrawNative(msg.sender, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdraw(address payable recipient, uint256 amount) public {
|
||||||
|
_withdrawNative(recipient, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _withdrawNative(address payable reipient, uint256 amount) internal {
|
||||||
require(msg.sender == owner);
|
require(msg.sender == owner);
|
||||||
msg.sender.transfer(amount);
|
reipient.transfer(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withdraw(IERC20 token, uint256 amount) public {
|
function withdraw(IERC20 token, uint256 amount) public {
|
||||||
|
|||||||
Reference in New Issue
Block a user