# Krypto 第二組第三週討論資料

# 如何製作交易所
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存買單

怎麼掛買單買東西的手續費這麼高????
[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;
}
}
```