# Krypto 第二組第三週討論資料 ![](https://i.imgur.com/ckMIvvN.png) # 如何製作交易所 1. 管理資金、訂單 2. 撮合引擎 # 自動做市商 AMM 資金池 代表 uniswap、curve [AMM進化史](https://news.cnyes.com/news/id/4943990) ## 最簡易 上次想最簡易的是交易所儲備很多種幣如usdt ETH,用預言機取得幣價,然後讓用戶直接用這個價格賣給交易所, ```solidity= // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.18 <0.9.0; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../W1/erc20.sol"; contract Pool { uint256 public ethTotal; uint256 public tokenTotal; address public creator; ERC20 public token = ERC20(0xae6B0f75b55fa4c90b2768e3157b7000241A41c5); AggregatorV3Interface internal ethPriceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e); constructor() payable { ethTotal = msg.value; // Store the amount of ETH sent while deploying the contract tokenTotal = 1_000_000 * 10 ** token.decimals(); // Total supply of 1,000,000 tokens with decimals creator = msg.sender; } function getLatestEthPrice() public view returns (uint256) { (, int256 answer,,,) = ethPriceFeed.latestRoundData(); return uint256(answer); } function buyEthWithTokens(uint256 tokensToSpend) public { require(token.balanceOf(msg.sender) >= tokensToSpend, "Not enough tokens to spend"); uint256 ethPrice = getLatestEthPrice(); uint256 ethToBuy = (tokensToSpend * 1e18) / ethPrice; require(address(this).balance >= ethToBuy, "Not enough ETH in the pool"); ethTotal -= ethToBuy; tokenTotal += tokensToSpend; token.transferFrom(msg.sender, creator, tokensToSpend); payable(msg.sender).transfer(ethToBuy); } function sellEthForTokens() public payable { uint256 ethPrice = getLatestEthPrice(); uint256 tokensToReturn = (msg.value * ethPrice) / 1e18; require(token.balanceOf(creator) >= tokensToReturn, "Not enough tokens in the pool"); ethTotal += msg.value; tokenTotal -= tokensToReturn; token.transferFrom(creator, msg.sender, tokensToReturn); } function withdraw() public { require(msg.sender == creator, "Only the contract creator can call this function"); payable(msg.sender).transfer(address(this).balance); } } ``` ## 基礎資金池 有一種恆乘積的方法,xy=k,x,y是兩種幣的數量,k是常數,在一開始創立資金池以及改變資金池深度會改變k=xy,這時x幣的價格是x=k/y。 # 訂單簿 代表 dydx、幣安交易所、中心化交易所 ## 鏈上訂單簿 [Gridex Protocol](https://github.com/GridexProtocol/core) [Gridex Protocol: Integrating First Fully On-chain Order Book For a New Generation of DEX](https://news.taiwannet.com.tw/c8/87529/Gridex-Protocol-Integrating-First-Fully-On-chain-Order-Book-For-a-New-Generation-of-DEX.html) 代表 [D5 excange](https://trade.d5.xyz/) ## sample code 參附件一 ```solidity= struct Order //訂單 address trader; uint256 amount; uint256 price; uint256 timestamp; } ``` MinHeap存賣單 MaxHeap存買單 ![](https://i.imgur.com/kbi47Yq.png) 怎麼掛買單買東西的手續費這麼高???? [orderbook](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W1/orderbook.sol) [erc20](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W1/erc20.sol) [heap](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W1/heap.sol) 收到訂單先看沒不能成交,成交就不用存起來,還沒成交再存起來 ## 鏈下訂單簿 dYdX V4 將採用鏈外訂單簿機制,改為在每個驗證節點的內存中運行 # 產品亮點 ## K線圖 去中化交易所幾乎都沒有提供K線圖,所以我們也可以提供可能小時級別的價格連線。 現成去中心化交易所K線圖小工具 [dexGuru](https://dex.guru/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2-eth/history) [astro](https://app.astrotools.io/) ## 多幣gas fee 有時候會出先一些尷尬的情況,我只有USDT,要去uniswap買ETH,但是要付ETH的手續費,但是我就是沒有ETH才要買ETH,結果你跟我說要ETH當手續費。 如果不小心把所有以太幣去質押,如果要拿回來還需要手續費, 遇到以上情況,可以從中心化交易所、朋友那邊轉一些ETH到錢包,很麻煩。但是如果使用者可以付多種幣當手續費,雖然手續費可能比較多,但是節省麻煩,還是有人願意,不然在去中心化世界一點交易都無法做。 ## 多幣解決方案 我覺得有幾種方案可以解決這個問題,手續費有兩種, 1. 一種是數據上鏈的手續費由L1ETH或是L2 polygon、arbitrum決定 2. 為了維持交易所運轉要收的 ### 方案 1. 其中一個是先由交易所代付手續費,把ETH交給使用者的時候就已經扣掉交易手續費以及上鏈手續費。 2. 讓用戶付多種的幣,然後在合約直接買成ETH付上鏈手續費,交易手續費直接扣在給用戶的錢。 # 附件一 還在測試階段 ```solidity= // SPDX-License-Identifier: MIT //0xd9145CCE52D386f254917e481eB44e9943F39138 pragma solidity ^0.8.19; import "./heap.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract OrderBook_tGD_ETH { // sell tGD get ETH buy tGD pay ETH //enum OrderType { Buy, Sell } MinHeap private buyHeap; MaxHeap private sellHeap; IERC20 tGD = IERC20(0xd9145CCE52D386f254917e481eB44e9943F39138); constructor() { buyHeap = new MinHeap(); sellHeap = new MaxHeap(); } function getSellPrice() public view returns (uint256) { return sellHeap.top().price; } function getBuyPrice() public view returns (uint256) { return buyHeap.top().price; } function buyOrder(uint256 _amount, uint256 price) public payable{ uint256 amount = _amount; require(msg.value >= price); if(buyHeap.top().price >= price) { buyHeap.push(MinHeap.Order(msg.sender, amount, price, block.timestamp)); } if(sellHeap.top().price<=price ) { //match if(sellHeap.top().price == price) { while(sellHeap.top().price == price) { if(sellHeap.top().amount > amount) { address payable receiver = payable(sellHeap.top().trader); receiver.transfer(amount); sellHeap.top().amount -= amount; amount = 0; break; } else { address payable receiver = payable(sellHeap.top().trader); receiver.transfer(sellHeap.top().amount); amount -= sellHeap.top().amount; sellHeap.pop(); } } } tGD.transferFrom(msg.sender, address(this), _amount-amount); if(amount > 0) { buyHeap.push(MinHeap.Order(msg.sender, amount, price, block.timestamp)); } } } function buyOrder(uint256 _amount) public payable { uint256 amount = _amount; require(msg.value >= amount* buyHeap.top().price); while(amount > 0) { if(buyHeap.top().amount > amount) { address payable receiver = payable(sellHeap.top().trader); receiver.transfer(amount); buyHeap.top().amount -= amount; amount = 0; break; } else { address payable receiver = payable(sellHeap.top().trader); receiver.transfer(sellHeap.top().amount); amount -= buyHeap.top().amount; buyHeap.pop(); } } } function sellOrder(uint256 _amount) public { uint256 amount = _amount; require(tGD.balanceOf(msg.sender) >= _amount); while(amount > 0) { if(sellHeap.top().amount > amount) { tGD.transferFrom(msg.sender, buyHeap.top().trader, _amount-amount); sellHeap.top().amount -= amount; amount = 0; break; } else { tGD.transferFrom(msg.sender, buyHeap.top().trader, buyHeap.top().amount); amount -= sellHeap.top().amount; sellHeap.pop(); } } } function sellOrder(uint256 _amount, uint256 price) public { uint256 amount = _amount; require(tGD.balanceOf(msg.sender) >= _amount); if(sellHeap.top().price <= price) { tGD.transferFrom(msg.sender, address(this), _amount); sellHeap.push(MaxHeap.Order(msg.sender, amount, price, block.timestamp)); return; } if(buyHeap.top().price>=price ) { //match if(buyHeap.top().price == price) { while(buyHeap.top().price == price) { if(buyHeap.top().amount > amount) { tGD.transferFrom(msg.sender, buyHeap.top().trader, _amount-amount); buyHeap.top().amount -= amount; amount = 0; break; } else { tGD.transferFrom(msg.sender, buyHeap.top().trader, buyHeap.top().amount); amount -= buyHeap.top().amount; buyHeap.pop(); } } } address payable receiver = payable(msg.sender); receiver.transfer(_amount-amount); if(amount > 0) { sellHeap.push(MaxHeap.Order(msg.sender, amount, price, block.timestamp)); } } } } ``` ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract MinHeap { struct Order { address trader; uint256 amount; uint256 price; uint256 timestamp; } Order[] private heap; constructor() { heap.push(Order(address(0), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)); } function push (Order memory o) public { heap.push(o); uint256 i = heap.length - 1; while (i > 0) { uint256 p = (i - 1) / 2; if (_conpare(i,p)) { _swap(p, i); i = p; } else { break; } } } function pop() public { require(heap.length > 0, "heap is empty"); heap[0] = heap[heap.length - 1]; delete heap[heap.length - 1]; heap.pop(); _heapify(0); } function top() public view returns (Order memory) { require(heap.length > 0, "heap is empty"); return heap[0]; } function _heapify(uint256 _i) private { uint256 l = 2 * _i + 1; uint256 r = 2 * _i + 2; uint256 smallest = _i; if (l < heap.length && _conpare(l ,smallest)) { smallest = l; } if (r < heap.length && _conpare(r ,smallest)) { smallest = r; } if (smallest != _i) { _swap(_i, smallest); _heapify(smallest); } } function _conpare(uint256 i,uint256 j) private view returns (bool) { if(heap[i].price == heap[j].price) { if(heap[i].timestamp == heap[j].timestamp) { if(heap[i].amount == heap[j].amount) { return heap[i].trader < heap[j].trader; } return heap[i].amount < heap[j].amount; } return heap[i].timestamp < heap[j].timestamp; } return heap[i].price < heap[j].price; } function _swap(uint256 a, uint256 b) private { Order memory tmp = heap[a]; heap[a] = heap[b]; heap[b] = tmp; } } contract MaxHeap{ struct Order { address trader; uint256 amount; uint256 price; uint256 timestamp; } Order[] private heap; constructor() { heap.push(Order(address(0), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)); } function push (Order memory o) public { heap.push(o); uint256 i = heap.length - 1; while (i > 0) { uint256 p = (i - 1) / 2; if (_conpare(i,p)) { _swap(p, i); i = p; } else { break; } } } function pop() public { require(heap.length > 0, "heap is empty"); heap[0] = heap[heap.length - 1]; delete heap[heap.length - 1]; heap.pop(); _heapify(0); } function top() public view returns (Order memory) { require(heap.length > 0, "heap is empty"); return heap[0]; } function _heapify(uint256 _i) private { uint256 l = 2 * _i + 1; uint256 r = 2 * _i + 2; uint256 largest = _i; if (l < heap.length && _conpare(l ,largest)) { largest = l; } if (r < heap.length && _conpare(r ,largest)) { largest = r; } if (largest != _i) { _swap(_i, largest); _heapify(largest); } } function _conpare(uint256 i,uint256 j) private view returns (bool) { if(heap[i].price == heap[j].price) { if(heap[i].timestamp == heap[j].timestamp) { if(heap[i].amount == heap[j].amount) { return heap[i].trader < heap[j].trader; } return heap[i].amount < heap[j].amount; } return heap[i].timestamp < heap[j].timestamp; } return heap[i].price > heap[j].price; } function _swap(uint256 a, uint256 b) private { Order memory tmp = heap[a]; heap[a] = heap[b]; heap[b] = tmp; } } ```