搭建去中心化交易所——分享一个简单的DEX项目代码及文档
分享一个简单的DEX项目代码及文档
Dex.top项目源码及文档分享
// DEx.top - Instant Trading on Chain
//
// Author: DEx.top Teampragma solidity 0.4.21;
pragma experimental "v0.5.0";interface Token {function transfer(address to, uint256 value) external returns (bool success);function transferFrom(address from, address to, uint256 value) external returns (bool success);
}contract Dex2 {//------------------------------ Struct Definitions: ---------------------------------------------struct TokenInfo {string symbol; // e.g., "ETH", "ADX"address tokenAddr; // ERC20 token addressuint64 scaleFactor; // <original token amount> = <scaleFactor> x <DEx amountE8> / 1e8uint minDeposit; // mininum deposit (original token amount) allowed for this token}struct TraderInfo {address withdrawAddr;uint8 feeRebatePercent; // range: [0, 100]}struct TokenAccount {uint64 balanceE8; // available amount for tradinguint64 pendingWithdrawE8; // the amount to be transferred out from this contract to the trader}struct Order {uint32 pairId; // <cashId>(16) <stockId>(16)uint8 action; // 0 means BUY; 1 means SELLuint8 ioc; // 0 means a regular order; 1 means an immediate-or-cancel (IOC) orderuint64 priceE8;uint64 amountE8;uint64 expireTimeSec;}struct Deposit {address traderAddr;uint16 tokenCode;uint64 pendingAmountE8; // amount to be confirmed for trading purpose}struct DealInfo {uint16 stockCode; // stock token codeuint16 cashCode; // cash token codeuint64 stockDealAmountE8;uint64 cashDealAmountE8;}struct ExeStatus {uint64 logicTimeSec; // logic timestamp for checking order expirationuint64 lastOperationIndex; // index of the last executed operation}//----------------- Constants: -------------------------------------------------------------------uint constant MAX_UINT256 = 2**256 - 1;uint16 constant MAX_FEE_RATE_E4 = 60; // upper limit of fee rate is 0.6% (60 / 1e4)// <original ETH amount in Wei> = <DEx amountE8> * <ETH_SCALE_FACTOR> / 1e8uint64 constant ETH_SCALE_FACTOR = 10**18;uint8 constant ACTIVE = 0;uint8 constant CLOSED = 2;bytes32 constant HASHTYPES =keccak256('string title', 'address market_address', 'uint64 nonce', 'uint64 expire_time_sec','uint64 amount_e8', 'uint64 price_e8', 'uint8 immediate_or_cancel', 'uint8 action','uint16 cash_token_code', 'uint16 stock_token_code');//----------------- States that cannot be changed once set: --------------------------------------address public admin; // admin address, and it cannot be changedmapping (uint16 => TokenInfo) public tokens; // mapping of token code to token information//----------------- Other states: ----------------------------------------------------------------uint8 public marketStatus; // market status: 0 - Active; 1 - Suspended; 2 - Closeduint16 public makerFeeRateE4; // maker fee rate (* 10**4)uint16 public takerFeeRateE4; // taker fee rate (* 10**4)uint16 public withdrawFeeRateE4; // withdraw fee rate (* 10**4)uint64 public lastDepositIndex; // index of the last deposit operationExeStatus public exeStatus; // status of operation executionmapping (address => TraderInfo) public traders; // mapping of trade address to trader informationmapping (uint176 => TokenAccount) public accounts; // mapping of trader token key to its account informationmapping (uint224 => Order) public orders; // mapping of order key to order informationmapping (uint64 => Deposit) public deposits; // mapping of deposit index to deposit information//------------------------------ Dex2 Events: ----------------------------------------------------event DeployMarketEvent();event ChangeMarketStatusEvent(uint8 status);event SetTokenInfoEvent(uint16 tokenCode, string symbol, address tokenAddr, uint64 scaleFactor, uint minDeposit);event SetWithdrawAddrEvent(address trader, address withdrawAddr);event DepositEvent(address trader, uint16 tokenCode, string symbol, uint64 amountE8, uint64 depositIndex);event WithdrawEvent(address trader, uint16 tokenCode, string symbol, uint64 amountE8, uint64 lastOpIndex);event TransferFeeEvent(uint16 tokenCode, uint64 amountE8, address toAddr);// `balanceE8` is the total balance after this deposit confirmationevent ConfirmDepositEvent(address trader, uint16 tokenCode, uint64 balanceE8);// `amountE8` is the post-fee initiated withdraw amount// `pendingWithdrawE8` is the total pending withdraw amount after this withdraw initiationevent InitiateWithdrawEvent(address trader, uint16 tokenCode, uint64 amountE8, uint64 pendingWithdrawE8);event MatchOrdersEvent(address trader1, uint64 nonce1, address trader2, uint64 nonce2);event HardCancelOrderEvent(address trader, uint64 nonce);event SetFeeRatesEvent(uint16 makerFeeRateE4, uint16 takerFeeRateE4, uint16 withdrawFeeRateE4);event SetFeeRebatePercentEvent(address trader, uint8 feeRebatePercent);//------------------------------ Contract Initialization: ----------------------------------------function Dex2(address admin_) public {admin = admin_;setTokenInfo(0 /*tokenCode*/, "ETH", 0 /*tokenAddr*/, ETH_SCALE_FACTOR, 0 /*minDeposit*/);emit DeployMarketEvent();}//------------------------------ External Functions: ---------------------------------------------function() external {revert();}// Change the market status of DEX.function changeMarketStatus(uint8 status_) external {if (msg.sender != admin) revert();if (marketStatus == CLOSED) revert(); // closed is forevermarketStatus = status_;emit ChangeMarketStatusEvent(status_);}// Each trader can specify a withdraw address (but cannot change it later). Once a trader's// withdraw address is set, following withdrawals of this trader will go to the withdraw address// instead of the trader's address.function setWithdrawAddr(address withdrawAddr) external {if (withdrawAddr == 0) revert();if (traders[msg.sender].withdrawAddr != 0) revert(); // cannot change withdrawAddr once settraders[msg.sender].withdrawAddr = withdrawAddr;emit SetWithdrawAddrEvent(msg.sender, withdrawAddr);}// Deposit ETH from msg.sender for the given trader.function depositEth(address traderAddr) external payable {if (marketStatus != ACTIVE) revert();if (traderAddr == 0) revert();if (msg.value < tokens[0].minDeposit) revert();if (msg.data.length != 4 + 32) revert(); // length condition of param countuint64 pendingAmountE8 = uint64(msg.value / (ETH_SCALE_FACTOR / 10**8)); // msg.value is in Weiif (pendingAmountE8 == 0) revert();uint64 depositIndex = ++lastDepositIndex;setDeposits(depositIndex, traderAddr, 0, pendingAmountE8);emit DepositEvent(traderAddr, 0, "ETH", pendingAmountE8, depositIndex);}// Deposit token (other than ETH) from msg.sender for a specified trader.//// After the deposit has been confirmed enough times on the blockchain, it will be added to the// trader's token account for trading.function depositToken(address traderAddr, uint16 tokenCode, uint originalAmount) external {if (marketStatus != ACTIVE) revert();if (traderAddr == 0) revert();if (tokenCode == 0) revert(); // this function does not handle ETHif (msg.data.length != 4 + 32 + 32 + 32) revert(); // length condition of param countTokenInfo memory tokenInfo = tokens[tokenCode];if (originalAmount < tokenInfo.minDeposit) revert();if (tokenInfo.scaleFactor == 0) revert(); // unsupported token// Need to make approval by calling Token(address).approve() in advance for ERC-20 Tokens.if (!Token(tokenInfo.tokenAddr).transferFrom(msg.sender, this, originalAmount)) revert();if (originalAmount > MAX_UINT256 / 10**8) revert(); // avoid overflowuint amountE8 = originalAmount * 10**8 / uint(tokenInfo.scaleFactor);if (amountE8 >= 2**64 || amountE8 == 0) revert();uint64 depositIndex = ++lastDepositIndex;setDeposits(depositIndex, traderAddr, tokenCode, uint64(amountE8));emit DepositEvent(traderAddr, tokenCode, tokens[tokenCode].symbol, uint64(amountE8), depositIndex);}// Withdraw ETH from the contract.function withdrawEth(address traderAddr) external {if (traderAddr == 0) revert();if (msg.data.length != 4 + 32) revert(); // length condition of param countuint176 accountKey = uint176(traderAddr);uint amountE8 = accounts[accountKey].pendingWithdrawE8;if (amountE8 == 0) return;// Write back to storage before making the transfer.accounts[accountKey].pendingWithdrawE8 = 0;uint truncatedWei = amountE8 * (ETH_SCALE_FACTOR / 10**8);address withdrawAddr = traders[traderAddr].withdrawAddr;if (withdrawAddr == 0) withdrawAddr = traderAddr;withdrawAddr.transfer(truncatedWei);emit WithdrawEvent(traderAddr, 0, "ETH", uint64(amountE8), exeStatus.lastOperationIndex);}// Withdraw token (other than ETH) from the contract.function withdrawToken(address traderAddr, uint16 tokenCode) external {if (traderAddr == 0) revert();if (tokenCode == 0) revert(); // this function does not handle ETHif (msg.data.length != 4 + 32 + 32) revert(); // length condition of param countTokenInfo memory tokenInfo = tokens[tokenCode];if (tokenInfo.scaleFactor == 0) revert(); // unsupported tokenuint176 accountKey = uint176(tokenCode) << 160 | uint176(traderAddr);uint amountE8 = accounts[accountKey].pendingWithdrawE8;if (amountE8 == 0) return;// Write back to storage before making the transfer.accounts[accountKey].pendingWithdrawE8 = 0;uint truncatedAmount = amountE8 * uint(tokenInfo.scaleFactor) / 10**8;address withdrawAddr = traders[traderAddr].withdrawAddr;if (withdrawAddr == 0) withdrawAddr = traderAddr;if (!Token(tokenInfo.tokenAddr).transfer(withdrawAddr, truncatedAmount)) revert();emit WithdrawEvent(traderAddr, tokenCode, tokens[tokenCode].symbol, uint64(amountE8),exeStatus.lastOperationIndex);}// Transfer the collected fee out of the contract.function transferFee(uint16 tokenCode, uint64 amountE8, address toAddr) external {if (msg.sender != admin) revert();if (toAddr == 0) revert();if (msg.data.length != 4 + 32 + 32 + 32) revert();TokenAccount memory feeAccount = accounts[uint176(tokenCode) << 160];uint64 withdrawE8 = feeAccount.pendingWithdrawE8;if (amountE8 < withdrawE8) {withdrawE8 = amountE8;}feeAccount.pendingWithdrawE8 -= withdrawE8;accounts[uint176(tokenCode) << 160] = feeAccount;TokenInfo memory tokenInfo = tokens[tokenCode];uint originalAmount = uint(withdrawE8) * uint(tokenInfo.scaleFactor) / 10**8;if (tokenCode == 0) { // ETHtoAddr.transfer(originalAmount);} else {if (!Token(tokenInfo.tokenAddr).transfer(toAddr, originalAmount)) revert();}emit TransferFeeEvent(tokenCode, withdrawE8, toAddr);}// Replay the trading sequence from the off-chain ledger exactly onto the on-chain ledger.function exeSequence(uint header, uint[] body) external {if (msg.sender != admin) revert();uint64 nextOperationIndex = uint64(header);if (nextOperationIndex != exeStatus.lastOperationIndex + 1) revert(); // check sequence indexuint64 newLogicTimeSec = uint64(header >> 64);if (newLogicTimeSec < exeStatus.logicTimeSec) revert();for (uint i = 0; i < body.length; nextOperationIndex++) {uint bits = body[i];uint opcode = bits & 0xFFFF;bits >>= 16;if ((opcode >> 8) != 0xDE) revert(); // check the magic number// ConfirmDeposit: <depositIndex>(64)if (opcode == 0xDE01) {confirmDeposit(uint64(bits));i += 1;continue;}// InitiateWithdraw: <amountE8>(64) <tokenCode>(16) <traderAddr>(160)if (opcode == 0xDE02) {initiateWithdraw(uint176(bits), uint64(bits >> 176));i += 1;continue;}//-------- The rest operation types are allowed only when the market is active ---------if (marketStatus != ACTIVE) revert();// MatchOrdersif (opcode == 0xDE03) {uint8 v1 = uint8(bits);bits >>= 8; // bits is now the key of the maker orderOrder memory makerOrder;if (v1 == 0) { // order already in storageif (i + 1 >= body.length) revert(); // at least 1 body element leftmakerOrder = orders[uint224(bits)];i += 1;} else {if (orders[uint224(bits)].pairId != 0) revert(); // order must not be already in storageif (i + 4 >= body.length) revert(); // at least 4 body elements leftmakerOrder = parseNewOrder(uint224(bits) /*makerOrderKey*/, v1, body, i);i += 4;}uint8 v2 = uint8(body[i]);uint224 takerOrderKey = uint224(body[i] >> 8);Order memory takerOrder;if (v2 == 0) { // order already in storagetakerOrder = orders[takerOrderKey];i += 1;} else {if (orders[takerOrderKey].pairId != 0) revert(); // order must not be already in storageif (i + 3 >= body.length) revert(); // at least 3 body elements lefttakerOrder = parseNewOrder(takerOrderKey, v2, body, i);i += 4;}matchOrder(uint224(bits) /*makerOrderKey*/, makerOrder, takerOrderKey, takerOrder);continue;}// HardCancelOrder: <nonce>(64) <traderAddr>(160)if (opcode == 0xDE04) {hardCancelOrder(uint224(bits) /*orderKey*/);i += 1;continue;}// SetFeeRates: <withdrawFeeRateE4>(16) <takerFeeRateE4>(16) <makerFeeRateE4>(16)if (opcode == 0xDE05) {setFeeRates(uint16(bits), uint16(bits >> 16), uint16(bits >> 32));i += 1;continue;}// SetFeeRebatePercent: <rebatePercent>(8) <traderAddr>(160)if (opcode == 0xDE06) {setFeeRebatePercent(address(bits) /*traderAddr*/, uint8(bits >> 160) /*rebatePercent*/);i += 1;continue;}} // for loopsetExeStatus(newLogicTimeSec, nextOperationIndex - 1);} // function exeSequence//------------------------------ Public Functions: -----------------------------------------------// Set information of a token.function setTokenInfo(uint16 tokenCode, string symbol, address tokenAddr, uint64 scaleFactor,uint minDeposit) public {if (msg.sender != admin) revert();if (marketStatus != ACTIVE) revert();if (scaleFactor == 0) revert();TokenInfo memory info = tokens[tokenCode];if (info.scaleFactor != 0) { // this token already exists// For an existing token only the minDeposit field can be updated.tokens[tokenCode].minDeposit = minDeposit;emit SetTokenInfoEvent(tokenCode, info.symbol, info.tokenAddr, info.scaleFactor, minDeposit);return;}tokens[tokenCode].symbol = symbol;tokens[tokenCode].tokenAddr = tokenAddr;tokens[tokenCode].scaleFactor = scaleFactor;tokens[tokenCode].minDeposit = minDeposit;emit SetTokenInfoEvent(tokenCode, symbol, tokenAddr, scaleFactor, minDeposit);}//------------------------------ Private Functions: ----------------------------------------------function setDeposits(uint64 depositIndex, address traderAddr, uint16 tokenCode, uint64 amountE8) private {deposits[depositIndex].traderAddr = traderAddr;deposits[depositIndex].tokenCode = tokenCode;deposits[depositIndex].pendingAmountE8 = amountE8;}function setExeStatus(uint64 logicTimeSec, uint64 lastOperationIndex) private {exeStatus.logicTimeSec = logicTimeSec;exeStatus.lastOperationIndex = lastOperationIndex;}function confirmDeposit(uint64 depositIndex) private {Deposit memory deposit = deposits[depositIndex];uint176 accountKey = (uint176(deposit.tokenCode) << 160) | uint176(deposit.traderAddr);TokenAccount memory account = accounts[accountKey];// Check that pending amount is non-zero and no overflow would happen.if (account.balanceE8 + deposit.pendingAmountE8 <= account.balanceE8) revert();account.balanceE8 += deposit.pendingAmountE8;deposits[depositIndex].pendingAmountE8 = 0;accounts[accountKey].balanceE8 += deposit.pendingAmountE8;emit ConfirmDepositEvent(deposit.traderAddr, deposit.tokenCode, account.balanceE8);}function initiateWithdraw(uint176 tokenAccountKey, uint64 amountE8) private {uint64 balanceE8 = accounts[tokenAccountKey].balanceE8;uint64 pendingWithdrawE8 = accounts[tokenAccountKey].pendingWithdrawE8;if (balanceE8 < amountE8 || amountE8 == 0) revert();balanceE8 -= amountE8;uint64 feeE8 = calcFeeE8(amountE8, withdrawFeeRateE4, address(tokenAccountKey));amountE8 -= feeE8;if (pendingWithdrawE8 + amountE8 < amountE8) revert(); // check overflowpendingWithdrawE8 += amountE8;accounts[tokenAccountKey].balanceE8 = balanceE8;accounts[tokenAccountKey].pendingWithdrawE8 = pendingWithdrawE8;// Note that the fee account has a dummy trader address of 0.if (accounts[tokenAccountKey & (0xffff << 160)].pendingWithdrawE8 + feeE8 >= feeE8) { // no overflowaccounts[tokenAccountKey & (0xffff << 160)].pendingWithdrawE8 += feeE8;}emit InitiateWithdrawEvent(address(tokenAccountKey), uint16(tokenAccountKey >> 160) /*tokenCode*/,amountE8, pendingWithdrawE8);}function getDealInfo(uint32 pairId, uint64 priceE8, uint64 amount1E8, uint64 amount2E8)private pure returns (DealInfo deal) {deal.stockCode = uint16(pairId);deal.cashCode = uint16(pairId >> 16);if (deal.stockCode == deal.cashCode) revert(); // we disallow homogeneous tradingdeal.stockDealAmountE8 = amount1E8 < amount2E8 ? amount1E8 : amount2E8;uint cashDealAmountE8 = uint(priceE8) * uint(deal.stockDealAmountE8) / 10**8;if (cashDealAmountE8 >= 2**64) revert();deal.cashDealAmountE8 = uint64(cashDealAmountE8);}function calcFeeE8(uint64 amountE8, uint feeRateE4, address traderAddr)private view returns (uint64) {uint feeE8 = uint(amountE8) * feeRateE4 / 10000;feeE8 -= feeE8 * uint(traders[traderAddr].feeRebatePercent) / 100;return uint64(feeE8);}function settleAccounts(DealInfo deal, address traderAddr, uint feeRateE4, bool isBuyer) private {uint16 giveTokenCode = isBuyer ? deal.cashCode : deal.stockCode;uint16 getTokenCode = isBuyer ? deal.stockCode : deal.cashCode;uint64 giveAmountE8 = isBuyer ? deal.cashDealAmountE8 : deal.stockDealAmountE8;uint64 getAmountE8 = isBuyer ? deal.stockDealAmountE8 : deal.cashDealAmountE8;uint176 giveAccountKey = uint176(giveTokenCode) << 160 | uint176(traderAddr);uint176 getAccountKey = uint176(getTokenCode) << 160 | uint176(traderAddr);uint64 feeE8 = calcFeeE8(getAmountE8, feeRateE4, traderAddr);getAmountE8 -= feeE8;// Check overflow.if (accounts[giveAccountKey].balanceE8 < giveAmountE8) revert();if (accounts[getAccountKey].balanceE8 + getAmountE8 < getAmountE8) revert();// Write storage.accounts[giveAccountKey].balanceE8 -= giveAmountE8;accounts[getAccountKey].balanceE8 += getAmountE8;if (accounts[uint176(getTokenCode) << 160].pendingWithdrawE8 + feeE8 >= feeE8) { // no overflowaccounts[uint176(getTokenCode) << 160].pendingWithdrawE8 += feeE8;}}function setOrders(uint224 orderKey, uint32 pairId, uint8 action, uint8 ioc,uint64 priceE8, uint64 amountE8, uint64 expireTimeSec) private {orders[orderKey].pairId = pairId;orders[orderKey].action = action;orders[orderKey].ioc = ioc;orders[orderKey].priceE8 = priceE8;orders[orderKey].amountE8 = amountE8;orders[orderKey].expireTimeSec = expireTimeSec;}function matchOrder(uint224 makerOrderKey, Order makerOrder,uint224 takerOrderKey, Order takerOrder) private {// Check trading conditions.if (marketStatus != ACTIVE) revert();if (makerOrderKey == takerOrderKey) revert(); // the two orders must not have the same keyif (makerOrder.pairId != takerOrder.pairId) revert();if (makerOrder.action == takerOrder.action) revert();if (makerOrder.priceE8 == 0 || takerOrder.priceE8 == 0) revert();if (makerOrder.action == 0 && makerOrder.priceE8 < takerOrder.priceE8) revert();if (takerOrder.action == 0 && takerOrder.priceE8 < makerOrder.priceE8) revert();if (makerOrder.amountE8 == 0 || takerOrder.amountE8 == 0) revert();if (makerOrder.expireTimeSec <= exeStatus.logicTimeSec) revert();if (takerOrder.expireTimeSec <= exeStatus.logicTimeSec) revert();DealInfo memory deal = getDealInfo(makerOrder.pairId, makerOrder.priceE8, makerOrder.amountE8, takerOrder.amountE8);// Update accounts.settleAccounts(deal, address(makerOrderKey), makerFeeRateE4, (makerOrder.action == 0));settleAccounts(deal, address(takerOrderKey), takerFeeRateE4, (takerOrder.action == 0));// Update orders.if (makerOrder.ioc == 1) { // IOC ordermakerOrder.amountE8 = 0;} else {makerOrder.amountE8 -= deal.stockDealAmountE8;}if (takerOrder.ioc == 1) { // IOC ordertakerOrder.amountE8 = 0;} else {takerOrder.amountE8 -= deal.stockDealAmountE8;}// Write orders back to storage.setOrders(makerOrderKey, makerOrder.pairId, makerOrder.action, makerOrder.ioc,makerOrder.priceE8, makerOrder.amountE8, makerOrder.expireTimeSec);setOrders(takerOrderKey, takerOrder.pairId, takerOrder.action, takerOrder.ioc,takerOrder.priceE8, takerOrder.amountE8, takerOrder.expireTimeSec);emit MatchOrdersEvent(address(makerOrderKey), uint64(makerOrderKey >> 160) /*nonce*/,address(takerOrderKey), uint64(takerOrderKey >> 160) /*nonce*/);}function hardCancelOrder(uint224 orderKey) private {orders[orderKey].pairId = 0xFFFFFFFF;orders[orderKey].amountE8 = 0;emit HardCancelOrderEvent(address(orderKey) /*traderAddr*/, uint64(orderKey >> 160) /*nonce*/);}function setFeeRates(uint16 makerE4, uint16 takerE4, uint16 withdrawE4) private {if (makerE4 > MAX_FEE_RATE_E4) revert();if (takerE4 > MAX_FEE_RATE_E4) revert();if (withdrawE4 > MAX_FEE_RATE_E4) revert();makerFeeRateE4 = makerE4;takerFeeRateE4 = takerE4;withdrawFeeRateE4 = withdrawE4;emit SetFeeRatesEvent(makerE4, takerE4, withdrawE4);}function setFeeRebatePercent(address traderAddr, uint8 feeRebatePercent) private {if (feeRebatePercent > 100) revert();traders[traderAddr].feeRebatePercent = feeRebatePercent;emit SetFeeRebatePercentEvent(traderAddr, feeRebatePercent);}function parseNewOrder(uint224 orderKey, uint8 v, uint[] body, uint i) private view returns (Order) {// bits: <expireTimeSec>(64) <amountE8>(64) <priceE8>(64) <ioc>(8) <action>(8) <pairId>(32)uint240 bits = uint240(body[i + 1]);uint64 nonce = uint64(orderKey >> 160);address traderAddr = address(orderKey);if (traderAddr == 0) revert(); // check zero addr early since `ecrecover` returns 0 on error// verify the signature of the traderbytes32 hash1 = keccak256("\x19Ethereum Signed Message:\n70DEx2 Order: ", address(this), nonce, bits);if (traderAddr != ecrecover(hash1, v, bytes32(body[i + 2]), bytes32(body[i + 3]))) {bytes32 hashValues = keccak256("DEx2 Order", address(this), nonce, bits);bytes32 hash2 = keccak256(HASHTYPES, hashValues);if (traderAddr != ecrecover(hash2, v, bytes32(body[i + 2]), bytes32(body[i + 3]))) revert();}Order memory order;order.pairId = uint32(bits); bits >>= 32;order.action = uint8(bits); bits >>= 8;order.ioc = uint8(bits); bits >>= 8;order.priceE8 = uint64(bits); bits >>= 64;order.amountE8 = uint64(bits); bits >>= 64;order.expireTimeSec = uint64(bits);return order;}} // contract
DEx Smart Contract Spec
Bits format in this doc: The rightmost bit is the LSB, the leftmost bit is the MSB. For example, a byte <1001>(4) <0011>(4) has value 147.
Market States
State that can not be changed:
- admin: address
States that can be changed directly by an external function:
marketStatus: uint8
- 0: Active
- 1: Suspended
- A suspended market temporarily stops accepting deposits and executing operations. Withdrawing is Ok.
- 2: Closed
- A closed status cannot be changed even by the admin. Only withdrawing, setWithdrawAddr, transferFee and operation ConfirmDeposit and InitiateWithdraw are allowed in a closed market.
tokens:
map[tokenCode(16)] -> TokenInfo
- tokenCode is a uint16
- TokenInfo is a struct
struct TokenInfo {bytes[4] symbol // e.g. "ETH ", "ADX " (there is a trailing whitespace 0x20).Address tokenAddr // the address of the ERC20 token contract.uint64 scaleFactor // <original amount> = <Dex amountE8> x scaleFactor / 1e8uint minDeposit // minimum deposit (original token amount) allowed per token }
- Note
- Once a token added, its info cannot be changed.
2. The token code of ETH is 0 and the scale factor is 1e18.
3. Token code [0, 99] are reserved for cash tokens. [100, 999] are reserved for tokens on Ethereum block chain.
4. For an ERC20 token, the scaleFactor of the token must be set as
scaleFactor = 10 ** decimal. For example, demical=3 => scaleFactor=1000, decimal=0 => scaleFactor=1.
States that can only be changed via executing the operation sequence:
makerFeeRateE4: uint16
takerFeeRateE4: uint16
lastDepositIndex: uint64
- Initial value is 0 (i.e. the first deposit has index 1)
exeStatus: struct ExeStatus
struct ExeStatus {uint64 logicTimeSecuint64 lastOperationIndex }
- logicTimeSec: uint64
- This is the time (seconds since the epoch) for checking order expiration.
- Expires can directly check against this value for validation purpose.
- lastOperationIndex: uint64
- Updated upon operation sequence completion.
- Initial value is 0 (i.e. the first operation has index 1)
- logicTimeSec: uint64
Account States (Trader Related States)
traders:
map[traderAddr] -> TraderInfo(256)
- traderAddr is an address
- TraderInfo is a struct
struct TraderInfo {address withdrawAddruint8 feeRebatePercent // between 0~100 }
accounts:
map[tokenAccountKey(176)] -> TokenAccount(256)
- tokenAccountKey is a uint176
<tokenCode>(16) <traderAddr>(160)
- TokenAccount is a struct
struct TokenAccount {uint64 balanceE8 // available amount for tradinguint64 pendingWithdrawE8 }
Note
- The total fee collected in a token is kept as the pendingWithdrawE8 of that token of traderAddr 0, i.e.
accounts[tokenCode << 160].pendingWithdrawE8
- We use address 0 but not a real address to avoid handling the case where the fee address is one of the trader address. Besides, we do not have to set the fee address in advance.
- The total fee collected in a token is kept as the pendingWithdrawE8 of that token of traderAddr 0, i.e.
- tokenAccountKey is a uint176
orders:
map[orderKey(224)] -> Order
- orderKey is a uint224
<orderNonce>(64) <traderAddr>(160)
- Order is a struct
struct Order {uint32 pairId // <cashId>(16) <stockId>(16)uint8 actionuint8 iocuint64 priceE8uint64 amountE8uint64 expireTimeSec }
Note
- action: 0 means BUY, 1 means SELL.
- ioc: 0 means not IOC (Immediate-Or-Cancel), 1 means IOC.
If an order has been hard cancelled, update its amountE8 to 0.
- orderKey is a uint224
deposits:
map[depositIndex(64)] -> Deposit
- depositIndex is a uint64
- Deposit is a struct
struct Deposit {address traderAddruint16 tokenCodeuint64 pendingAmountE8 // the amount to be confirmed for trading }
Operation Sequence
An operation sequence is represented as an array of uint256 with the following format:
<OpSeq>: <header>(256) {[operation],[operation],...}
Format of header:
<header>: <newLogicTimeSec>(64) <beginIndex>(64)
External function:
function exeSequence(uint header, uint[] body)
Operations
Each operation can be one of the following. As a convention, the lowest 16 bits of the first uint256 of an operation is always the opcode.
ConfirmDeposit
- move a pending deposit to balance
- opcode: 0xDE 0x01
- body length (in uint256): 1
<depositIndex>(64) <opcode>(16)
InitiateWithdraw
move fund from balance to pending withdraw
opcode: 0xDE 0x02
length (in uint256): 1
<amountE8>(64) <tokenCode>(16) <traderAddr>(160) <opcode>(16)
Note
- No signature required. The admin keeps the right to clear a trader account by sending the funds of the trader back to the trader’s account.
MatchOrders
execute a pair of matching orders.
opcode: 0xDE 0x03
body length (in uint256): 2 ~ 8
<nonce1>(64) <traderAddr1>(160) <v1>(8) <opcode>(16)
- The following 3 uint256s exists if and only if v1 is NOT 0. (v1 = 0 means that this order is already in the storage.)
<expireTimeSec1>(64) <amount1E8>(64) <price1E8>(64) <ioc1>(8) <action1>(8) <pairId1>(32) <r1>(256) <s1>(256)
<nonce2>(64) <traderAddr2>(160) <v2>(8)
- The following 3 uint256s exists if and only if v2 is NOT 0. (v2 = 0 means that this order is already in the storage.)
<expireTimeSec2>(64) <amount2E8>(64) <price2E8>(64) <ioc2>(8) <action2>(8) <pairId2>(32) <r2>(256) <s2>(256)
Note
- The first order is the maker, the second order is the taker.
- When v1/v2 is not zero, it is either 27 or 28.
- Format of
<pairId>: <cashId>(16) <stockId>(16)
- stockId is the lower u16, cashId is the higher u16
- AmountE8 is referring to stock amount
Signing Scheme 1 (Friendly to API usage)
- The bytes to be hashed (using keccak256) for signing are the concatenation of the following (uints are in big-endian order):
- Prefix “\x19Ethereum Signed Message:\n70”.
- String "DEx2 Order: " (Note the trailing whitespace)
- The market address.
- This is for replay attack protection when we deploy a new market.
<nonce>(64)
<expireTimeSec>(64) <amountE8>(64) <priceE8>(64) <ioc>(8) <action>(8) <pairId>(32)
Signing Scheme 2 (Friendly to UI supporting eth_signTypedData)
The fields to be sent to eth_signTypedData:
string title = "DEx2 Order" address market_address = <market address> uint64 nonce uint64 expire_time_sec uint64 amount_e8 uint64 price_e8 uint8 immediate_or_cancel uint8 action uint16 cash_token_symbol = `<high 16 bits of pairId>` uint16 stock_token_symbol = `<low 16 bits of pairId>`
The bytes to be hashed (using keccak256) for signing are the concatenation of the following:
keccak256(<TYPES_NAMES_HASH>, <DATA_HASH>)
<TYPES_NAMES_HASH> is
u256 0x8382001f45a579e41d75c5535cff758120963de17b4af5e92d5191b9a5f69f1f
- It is the result of:
keccak256( "string title", "address market_address", "uint64 nonce", "uint64 expire_time_sec", "uint64 amount_e8", "uint64 price_e8", "uint8 immediate_or_cancel", "uint8 action", "uint16 cash_token_symbol", "uint16 stock_token_symbol")
- It is the result of:
<DATA_HASH> is the keccak256 result of the following:
"DEx2 Order" <market address>(160) <nonce>(64) <expireTimeSec>(64) <amountE8>(64) <priceE8>(64) <ioc>(8) <action>(8) <pairId>(32)
HardCancelOrder
mark an order as cancelled in the storage, no matter whether it is currently in the storage or not.
opcode: 0xDE 0x04
body length (in uint256): 1
<nonce>(64) <traderAddr>(160) <opcode>(16)
Note
- No signature required. The admin keeps the right to cancel any order.
SetFeeRates
- set the fee rates.
- opcode: 0xDE 0x05
- body length (in uint256): 1
<withdrawFeeRateE4>(16) <takerFeeRateE4>(16) <makerFeeRateE4>(16) <opcode>(16)
SetFeeRebatePercent
- set the rebate fee percentage.
- opcode: 0xDE 0x06
- body length (in uint256): 1
<feeRebatePercent>(8) <traderAddr>(160) <opcode>(16)
Free External Methods
- Free external methods can be called by either the admin or the traders, at anytime, in any order.
Deposit
depositEth(address traderAddr) external payable
depositToken(address traderAddr, uint16 tokenCode, uint originalAmount) external
Note
- confirmDeposit (called by exeSequence) comes after depositEth/depositToken
Withdraw
withdrawEth(address traderAddr) external
withdrawToken(address traderAddr, uint16 tokenCode) external
Note
- initiateWithdraw (called by exeSequence) comes before withdrawEth/withdrawToken
搭建去中心化交易所——分享一个简单的DEX项目代码及文档相关推荐
- 快速搭建去中心化视频分享平台peertube
## 简介 peertube 是一款开源的去中心化视频分享平台,你可以用peertube创建属于自己的实例,相较于传统的视频平台,peertube最大的优点就是采用webtorrent技术,网站管理员 ...
- 大话 Cosmos:除了去中心化交易所,扩容与复杂数据的跨链调用也是 Cosmos 的应用场景...
从区块链技术发展至今,2019 年也许是人们最关心跨链技术的一年. 新的一年,公链从大举扩张进入缓冲期,可扩展性成为公链更深切的主题,在跨链技术出现之前,区块链中各公链是一座座孤岛,无法相互沟通,不仅 ...
- 烤星 DeFi 课堂 | 去中心化交易所适合小白用吗?
王也 打开 DeFi 世界之门,从小白到 DeFi 大神的进阶必修课. 由 Conflux × Odaily星球日报联合出品,星球日报资深记者@王也担任主笔. 烤星 · DeFi 课堂 专注 DeFi ...
- 继FCoin后的下一轮浪潮 —— 去中心化交易所的逆风翻盘
本文由微信公众号DappVision原创首发,转载请联系授权 互联网时代的腥风血雨是在烧钱补贴中结束的,从2011年的千团大战,到17年的ofo与摩拜之争,基本都是这个套路. 而在区块链的时代,FCo ...
- 交易所要变天?去中心化交易所崛起,蚕食用户,抢夺流量
文 | 棘轮 林格 2020年,在DeFi浪潮的推动下,去中心化交易所大红大紫. 从4月开始,它的市场份额飞速增长,并不断蚕食中心化交易所的市场.比如去中心化交易所中的佼佼者Uniswap,就在交易量 ...
- 谈谈我对去中心化交易所鲸交所的感受
在2018年3月1日,币乎上线的第一天,赵公子斗志昂扬的在币乎写下来自己的第一次真情告白:和咕噜共同见证.然而一直到2019年4月1日,我才明白,那次告白是和咕噜见证的不只是币乎,还有他的鲸交所. 当 ...
- Bancor 2.0:ET去中心化交易所的极致体验
伴随着数字资产的浪潮兴起,Token迎来了"百家争鸣"的时代,群雄并起之际,数字资产交易所更是如火如荼,然而随着区块链生态的繁荣数字资产对交易所的要求更高,传统的交易所无法满足币民 ...
- 去中心化交易所研究报告
本文分析了数字货币交易所行业的痛点.现状,去中心化交易所的发展模式.投资逻辑和优缺点,最后对去中心化交易所的未来进行了展望. 鲸准 2018/04/13 09:56字体:宋 中心化交易所因为其易用性. ...
- Cybex在日本:去中心化交易所5大核心功能解决安全问题
点击上方 "蓝色字" 可关注我们! 记者:铅笔盒 True Global Ventures主办的Disruption in Financial Services Event于201 ...
最新文章
- Maven 开 发 规 范
- string 基本用法
- 如何查看linux版本
- C++中标准模板库std::pair的实现
- Luogu P1967 NOIP2013 货车运输
- poj1015 Jury Compromise
- [云炬创业基础笔记] 第四章测试9
- PHP封装对象名字的思路
- Xilinx发布实时视频编码服务器
- 八大排序算法的Python实现
- Replace Error Code with Exception(以异常取代错误码)
- java构造方法赋值内存图_java 面向对象(九):类的结构:构造器(一)简介;属性赋值顺序;JavaBean的概念...
- jquery 停止事件冒泡方法
- 织梦php gbk转换utf8,dedecms 5.1 utf-8版本英文怎么修改
- java 8中排序_如何在JAVA 8中一起使用分组和排序
- bzoj2525 1426
- 关于色域与BT.2020相关学习心得笔记
- 为什么是“深度”学习而不是宽度?
- 如何使用 Zend Expressive 建立 NASA 图片库?
- 重磅!python获取同步输出的桌面网易云音乐歌词(内存偏移获取)