From ea7b4c962bafb465c12d22864c853197b889402e Mon Sep 17 00:00:00 2001 From: SunSpirit <48086732+sunspirit99@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:37:13 +0700 Subject: [PATCH] feat : Implement Uniswap-v1 (#552) --- pkg/liquidity-source/uniswap-v1/abis.go | 42 + .../uniswap-v1/abis/ERC20.json | 222 ++++ .../uniswap-v1/abis/multicall.json | 21 + .../uniswap-v1/abis/uniswap_exchange.json | 1005 +++++++++++++++++ .../uniswap-v1/abis/uniswap_factory.json | 137 +++ pkg/liquidity-source/uniswap-v1/config.go | 10 + pkg/liquidity-source/uniswap-v1/constant.go | 31 + pkg/liquidity-source/uniswap-v1/embed.go | 15 + .../uniswap-v1/pool_list_updater.go | 249 ++++ .../uniswap-v1/pool_simulator.go | 206 ++++ .../uniswap-v1/pool_simulator_test.go | 80 ++ .../uniswap-v1/pool_tracker.go | 116 ++ pkg/liquidity-source/uniswap-v1/types.go | 23 + pkg/msgpack/register_pool_types.go | 2 + pkg/pooltypes/pooltypes.go | 3 + pkg/valueobject/exchange.go | 2 + 16 files changed, 2164 insertions(+) create mode 100644 pkg/liquidity-source/uniswap-v1/abis.go create mode 100644 pkg/liquidity-source/uniswap-v1/abis/ERC20.json create mode 100644 pkg/liquidity-source/uniswap-v1/abis/multicall.json create mode 100644 pkg/liquidity-source/uniswap-v1/abis/uniswap_exchange.json create mode 100644 pkg/liquidity-source/uniswap-v1/abis/uniswap_factory.json create mode 100644 pkg/liquidity-source/uniswap-v1/config.go create mode 100644 pkg/liquidity-source/uniswap-v1/constant.go create mode 100644 pkg/liquidity-source/uniswap-v1/embed.go create mode 100644 pkg/liquidity-source/uniswap-v1/pool_list_updater.go create mode 100644 pkg/liquidity-source/uniswap-v1/pool_simulator.go create mode 100644 pkg/liquidity-source/uniswap-v1/pool_simulator_test.go create mode 100644 pkg/liquidity-source/uniswap-v1/pool_tracker.go create mode 100644 pkg/liquidity-source/uniswap-v1/types.go diff --git a/pkg/liquidity-source/uniswap-v1/abis.go b/pkg/liquidity-source/uniswap-v1/abis.go new file mode 100644 index 000000000..f17659835 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/abis.go @@ -0,0 +1,42 @@ +package uniswapv1 + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + erc20ABI abi.ABI + uniswapExchangeABI abi.ABI + uniswapFactoryABI abi.ABI + multicallABI abi.ABI +) + +func init() { + builder := []struct { + ABI *abi.ABI + data []byte + }{ + { + &erc20ABI, erc20ABIJson, + }, + { + &uniswapExchangeABI, exchangeABIJson, + }, + { + &uniswapFactoryABI, factoryABIJson, + }, + { + &multicallABI, multicallABIJson, + }, + } + + for _, b := range builder { + var err error + *b.ABI, err = abi.JSON(bytes.NewReader(b.data)) + if err != nil { + panic(err) + } + } +} diff --git a/pkg/liquidity-source/uniswap-v1/abis/ERC20.json b/pkg/liquidity-source/uniswap-v1/abis/ERC20.json new file mode 100644 index 000000000..3b0ab2f1a --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/abis/ERC20.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/uniswap-v1/abis/multicall.json b/pkg/liquidity-source/uniswap-v1/abis/multicall.json new file mode 100644 index 000000000..c6d0eebac --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/abis/multicall.json @@ -0,0 +1,21 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/uniswap-v1/abis/uniswap_exchange.json b/pkg/liquidity-source/uniswap-v1/abis/uniswap_exchange.json new file mode 100644 index 000000000..bc528355a --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/abis/uniswap_exchange.json @@ -0,0 +1,1005 @@ +[ + { + "name": "TokenPurchase", + "inputs": [ + { + "type": "address", + "name": "buyer", + "indexed": true + }, + { + "type": "uint256", + "name": "eth_sold", + "indexed": true + }, + { + "type": "uint256", + "name": "tokens_bought", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "EthPurchase", + "inputs": [ + { + "type": "address", + "name": "buyer", + "indexed": true + }, + { + "type": "uint256", + "name": "tokens_sold", + "indexed": true + }, + { + "type": "uint256", + "name": "eth_bought", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "AddLiquidity", + "inputs": [ + { + "type": "address", + "name": "provider", + "indexed": true + }, + { + "type": "uint256", + "name": "eth_amount", + "indexed": true + }, + { + "type": "uint256", + "name": "token_amount", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RemoveLiquidity", + "inputs": [ + { + "type": "address", + "name": "provider", + "indexed": true + }, + { + "type": "uint256", + "name": "eth_amount", + "indexed": true + }, + { + "type": "uint256", + "name": "token_amount", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "Transfer", + "inputs": [ + { + "type": "address", + "name": "_from", + "indexed": true + }, + { + "type": "address", + "name": "_to", + "indexed": true + }, + { + "type": "uint256", + "name": "_value", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "Approval", + "inputs": [ + { + "type": "address", + "name": "_owner", + "indexed": true + }, + { + "type": "address", + "name": "_spender", + "indexed": true + }, + { + "type": "uint256", + "name": "_value", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "setup", + "outputs": [], + "inputs": [ + { + "type": "address", + "name": "token_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 175875 + }, + { + "name": "addLiquidity", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "min_liquidity" + }, + { + "type": "uint256", + "name": "max_tokens" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": true, + "type": "function", + "gas": 82616 + }, + { + "name": "removeLiquidity", + "outputs": [ + { + "type": "uint256", + "name": "out" + }, + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "amount" + }, + { + "type": "uint256", + "name": "min_eth" + }, + { + "type": "uint256", + "name": "min_tokens" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 116814 + }, + { + "name": "__default__", + "outputs": [], + "inputs": [], + "constant": false, + "payable": true, + "type": "function" + }, + { + "name": "ethToTokenSwapInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "min_tokens" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": true, + "type": "function", + "gas": 12757 + }, + { + "name": "ethToTokenTransferInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "min_tokens" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + } + ], + "constant": false, + "payable": true, + "type": "function", + "gas": 12965 + }, + { + "name": "ethToTokenSwapOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": true, + "type": "function", + "gas": 50463 + }, + { + "name": "ethToTokenTransferOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + } + ], + "constant": false, + "payable": true, + "type": "function", + "gas": 50671 + }, + { + "name": "tokenToEthSwapInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_eth" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 47503 + }, + { + "name": "tokenToEthTransferInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_eth" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 47712 + }, + { + "name": "tokenToEthSwapOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "eth_bought" + }, + { + "type": "uint256", + "name": "max_tokens" + }, + { + "type": "uint256", + "name": "deadline" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 50175 + }, + { + "name": "tokenToEthTransferOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "eth_bought" + }, + { + "type": "uint256", + "name": "max_tokens" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 50384 + }, + { + "name": "tokenToTokenSwapInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_tokens_bought" + }, + { + "type": "uint256", + "name": "min_eth_bought" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "token_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 51007 + }, + { + "name": "tokenToTokenTransferInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_tokens_bought" + }, + { + "type": "uint256", + "name": "min_eth_bought" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "address", + "name": "token_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 51098 + }, + { + "name": "tokenToTokenSwapOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "max_tokens_sold" + }, + { + "type": "uint256", + "name": "max_eth_sold" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "token_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 54928 + }, + { + "name": "tokenToTokenTransferOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "max_tokens_sold" + }, + { + "type": "uint256", + "name": "max_eth_sold" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "address", + "name": "token_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 55019 + }, + { + "name": "tokenToExchangeSwapInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_tokens_bought" + }, + { + "type": "uint256", + "name": "min_eth_bought" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "exchange_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 49342 + }, + { + "name": "tokenToExchangeTransferInput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + }, + { + "type": "uint256", + "name": "min_tokens_bought" + }, + { + "type": "uint256", + "name": "min_eth_bought" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "address", + "name": "exchange_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 49532 + }, + { + "name": "tokenToExchangeSwapOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "max_tokens_sold" + }, + { + "type": "uint256", + "name": "max_eth_sold" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "exchange_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 53233 + }, + { + "name": "tokenToExchangeTransferOutput", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + }, + { + "type": "uint256", + "name": "max_tokens_sold" + }, + { + "type": "uint256", + "name": "max_eth_sold" + }, + { + "type": "uint256", + "name": "deadline" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "address", + "name": "exchange_addr" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 53423 + }, + { + "name": "getEthToTokenInputPrice", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "eth_sold" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 5542 + }, + { + "name": "getEthToTokenOutputPrice", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_bought" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 6872 + }, + { + "name": "getTokenToEthInputPrice", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "tokens_sold" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 5637 + }, + { + "name": "getTokenToEthOutputPrice", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "eth_bought" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 6897 + }, + { + "name": "tokenAddress", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1413 + }, + { + "name": "factoryAddress", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1443 + }, + { + "name": "balanceOf", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "_owner" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 1645 + }, + { + "name": "transfer", + "outputs": [ + { + "type": "bool", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "_to" + }, + { + "type": "uint256", + "name": "_value" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 75034 + }, + { + "name": "transferFrom", + "outputs": [ + { + "type": "bool", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "_from" + }, + { + "type": "address", + "name": "_to" + }, + { + "type": "uint256", + "name": "_value" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 110907 + }, + { + "name": "approve", + "outputs": [ + { + "type": "bool", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "_spender" + }, + { + "type": "uint256", + "name": "_value" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 38769 + }, + { + "name": "allowance", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "_owner" + }, + { + "type": "address", + "name": "_spender" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 1925 + }, + { + "name": "name", + "outputs": [ + { + "type": "bytes32", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1623 + }, + { + "name": "symbol", + "outputs": [ + { + "type": "bytes32", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1653 + }, + { + "name": "decimals", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1683 + }, + { + "name": "totalSupply", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1713 + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/uniswap-v1/abis/uniswap_factory.json b/pkg/liquidity-source/uniswap-v1/abis/uniswap_factory.json new file mode 100644 index 000000000..08db6225d --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/abis/uniswap_factory.json @@ -0,0 +1,137 @@ +[ + { + "name": "NewExchange", + "inputs": [ + { + "type": "address", + "name": "token", + "indexed": true + }, + { + "type": "address", + "name": "exchange", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "initializeFactory", + "outputs": [], + "inputs": [ + { + "type": "address", + "name": "template" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 35725 + }, + { + "name": "createExchange", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "token" + } + ], + "constant": false, + "payable": false, + "type": "function", + "gas": 187911 + }, + { + "name": "getExchange", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "token" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 715 + }, + { + "name": "getToken", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [ + { + "type": "address", + "name": "exchange" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 745 + }, + { + "name": "getTokenWithId", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [ + { + "type": "uint256", + "name": "token_id" + } + ], + "constant": true, + "payable": false, + "type": "function", + "gas": 736 + }, + { + "name": "exchangeTemplate", + "outputs": [ + { + "type": "address", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 633 + }, + { + "name": "tokenCount", + "outputs": [ + { + "type": "uint256", + "name": "out" + } + ], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 663 + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/uniswap-v1/config.go b/pkg/liquidity-source/uniswap-v1/config.go new file mode 100644 index 000000000..fda25fdcc --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/config.go @@ -0,0 +1,10 @@ +package uniswapv1 + +import "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" + +type Config struct { + ChainID valueobject.ChainID `json:"chainID"` + MulticallContractAddress string `json:"multicallContractAddress"` + FactoryAddress string `json:"factoryAddress"` + NewPoolLimit int `json:"newPoolLimit"` +} diff --git a/pkg/liquidity-source/uniswap-v1/constant.go b/pkg/liquidity-source/uniswap-v1/constant.go new file mode 100644 index 000000000..f783c7bb4 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/constant.go @@ -0,0 +1,31 @@ +package uniswapv1 + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +const ( + DexType = "uniswap-v1" + + DefaultSwapFee float64 = 0.003 +) + +var ( + defaultGas = Gas{Swap: 165000} + + ZERO_ADDRESS = common.Address{} + + U997 = uint256.NewInt(997) + U1000 = uint256.NewInt(1000) +) + +const ( + multicallGetEthBalanceMethod = "getEthBalance" + + erc20BalanceOfMethod = "balanceOf" + + factoryTokenCountMethod = "tokenCount" + factoryGetTokenWithIDMethod = "getTokenWithId" + factoryGetExchangeMethod = "getExchange" +) diff --git a/pkg/liquidity-source/uniswap-v1/embed.go b/pkg/liquidity-source/uniswap-v1/embed.go new file mode 100644 index 000000000..1f6dc24b8 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/embed.go @@ -0,0 +1,15 @@ +package uniswapv1 + +import _ "embed" + +//go:embed abis/uniswap_exchange.json +var exchangeABIJson []byte + +//go:embed abis/uniswap_factory.json +var factoryABIJson []byte + +//go:embed abis/multicall.json +var multicallABIJson []byte + +//go:embed abis/ERC20.json +var erc20ABIJson []byte diff --git a/pkg/liquidity-source/uniswap-v1/pool_list_updater.go b/pkg/liquidity-source/uniswap-v1/pool_list_updater.go new file mode 100644 index 000000000..f38a359dc --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/pool_list_updater.go @@ -0,0 +1,249 @@ +package uniswapv1 + +import ( + "context" + "math/big" + "strings" + "time" + + "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/logger" + "github.com/ethereum/go-ethereum/common" + "github.com/goccy/go-json" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" +) + +type ( + PoolsListUpdater struct { + config *Config + ethrpcClient *ethrpc.Client + } + + PoolsListUpdaterMetadata struct { + Offset int `json:"offset"` + } + + ExchangeInfo struct { + ExchangeAddress common.Address + TokenAddress common.Address + } +) + +func NewPoolsListUpdater( + cfg *Config, + ethrpcClient *ethrpc.Client, +) *PoolsListUpdater { + return &PoolsListUpdater{ + config: cfg, + ethrpcClient: ethrpcClient, + } +} + +func (u *PoolsListUpdater) GetNewPools(ctx context.Context, metadataBytes []byte) ([]entity.Pool, []byte, error) { + var startTime = time.Now() + + logger.WithFields(logger.Fields{"dex_id": DexType}).Info("Started getting new pools") + + ctx = util.NewContextWithTimestamp(ctx) + + totalExchanges, err := u.getTotalExchanges(ctx) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": DexType}). + Error("getTotalExchanges failed") + + return nil, metadataBytes, err + } + + offset, err := u.getOffset(metadataBytes) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": DexType, "err": err}). + Warn("getOffset failed") + } + + batchSize := getBatchSize(totalExchanges, u.config.NewPoolLimit, offset) + + exchanges, err := u.listExchanges(ctx, offset, batchSize) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": DexType, "err": err}). + Error("listExchangeAddresses failed") + + return nil, metadataBytes, err + } + + pools, err := u.initPools(exchanges) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": DexType, "err": err}). + Error("initPools failed") + + return nil, metadataBytes, err + } + + newMetadataBytes, err := u.newMetadata(offset + batchSize) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": DexType, "err": err}). + Error("newMetadata failed") + + return nil, metadataBytes, err + } + + logger. + WithFields( + logger.Fields{ + "dex_id": DexType, + "pools_len": len(pools), + "offset": offset, + "duration_ms": time.Since(startTime).Milliseconds(), + }, + ). + Info("Finished getting new pools") + + return pools, newMetadataBytes, nil +} + +func (u *PoolsListUpdater) getTotalExchanges(ctx context.Context) (int, error) { + var totalExchanges *big.Int + + req := u.ethrpcClient.NewRequest().SetContext(ctx) + + req.AddCall(ðrpc.Call{ + ABI: uniswapFactoryABI, + Target: u.config.FactoryAddress, + Method: factoryTokenCountMethod, + Params: nil, + }, []interface{}{&totalExchanges}) + + if _, err := req.Call(); err != nil { + return 0, err + } + + return int(totalExchanges.Int64()), nil +} + +func (u *PoolsListUpdater) getOffset(metadataBytes []byte) (int, error) { + if len(metadataBytes) == 0 { + return 0, nil + } + + var metadata PoolsListUpdaterMetadata + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + return 0, err + } + + return metadata.Offset, nil +} + +func (u *PoolsListUpdater) listExchanges(ctx context.Context, offset int, batchSize int) ([]ExchangeInfo, error) { + listTokenResult := make([]common.Address, batchSize) + + getTokensRequest := u.ethrpcClient.NewRequest().SetContext(ctx) + + for i := 0; i < batchSize; i++ { + index := big.NewInt(int64(offset + i)) + + getTokensRequest.AddCall(ðrpc.Call{ + ABI: uniswapFactoryABI, + Target: u.config.FactoryAddress, + Method: factoryGetTokenWithIDMethod, + Params: []interface{}{index}, + }, []interface{}{&listTokenResult[i]}) + } + + _, err := getTokensRequest.TryAggregate() + if err != nil { + return nil, err + } + + getExchangesRequest := u.ethrpcClient.NewRequest().SetContext(ctx) + + listExchangeResult := make([]common.Address, len(listTokenResult)) + for i, tokenAddress := range listTokenResult { + getExchangesRequest.AddCall(ðrpc.Call{ + ABI: uniswapFactoryABI, + Target: u.config.FactoryAddress, + Method: factoryGetExchangeMethod, + Params: []interface{}{tokenAddress}, + }, []interface{}{&listExchangeResult[i]}) + } + + resp, err := getExchangesRequest.TryAggregate() + if err != nil { + return nil, err + } + + var exchanges = make([]ExchangeInfo, 0, len(listExchangeResult)) + for i, isSuccess := range resp.Result { + if !isSuccess || listExchangeResult[i] == ZERO_ADDRESS { + continue + } + + exchanges = append(exchanges, ExchangeInfo{ + ExchangeAddress: listExchangeResult[i], + TokenAddress: listTokenResult[i], + }) + } + + return exchanges, nil +} + +func (u *PoolsListUpdater) initPools(exchanges []ExchangeInfo) ([]entity.Pool, error) { + pools := make([]entity.Pool, 0, len(exchanges)) + + for _, exchange := range exchanges { + token0 := &entity.PoolToken{ + Address: strings.ToLower(valueobject.WETHByChainID[u.config.ChainID]), + Swappable: true, + } + + token1 := &entity.PoolToken{ + Address: strings.ToLower(exchange.TokenAddress.Hex()), + Swappable: true, + } + + var newPool = entity.Pool{ + Address: strings.ToLower(exchange.ExchangeAddress.Hex()), + Exchange: string(valueobject.ExchangeUniSwapV1), + Type: DexType, + Timestamp: time.Now().Unix(), + Reserves: []string{"0", "0"}, + Tokens: []*entity.PoolToken{token0, token1}, + SwapFee: DefaultSwapFee, + } + + pools = append(pools, newPool) + } + + return pools, nil +} + +func (u *PoolsListUpdater) newMetadata(newOffset int) ([]byte, error) { + metadata := PoolsListUpdaterMetadata{ + Offset: newOffset, + } + + metadataBytes, err := json.Marshal(metadata) + if err != nil { + return nil, err + } + + return metadataBytes, nil +} + +func getBatchSize(length int, limit int, offset int) int { + if offset == length { + return 0 + } + + if offset+limit >= length { + return length - offset + } + + return limit +} diff --git a/pkg/liquidity-source/uniswap-v1/pool_simulator.go b/pkg/liquidity-source/uniswap-v1/pool_simulator.go new file mode 100644 index 000000000..869c468a1 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/pool_simulator.go @@ -0,0 +1,206 @@ +package uniswapv1 + +import ( + "errors" + "math/big" + + "github.com/KyberNetwork/blockchain-toolkit/integer" + "github.com/KyberNetwork/blockchain-toolkit/number" + "github.com/holiman/uint256" + "github.com/samber/lo" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + poolpkg "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + utils "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" +) + +var ( + ErrInvalidToken = errors.New("invalid token") + ErrInvalidReserve = errors.New("invalid reserve") + ErrInvalidAmountIn = errors.New("invalid amount in") + ErrInsufficientInputAmount = errors.New("INSUFFICIENT_INPUT_AMOUNT") + ErrInvalidAmountOut = errors.New("invalid amount out") + ErrInsufficientOutputAmount = errors.New("INSUFFICIENT_OUTPUT_AMOUNT") + ErrInsufficientLiquidity = errors.New("INSUFFICIENT_LIQUIDITY") +) + +type ( + PoolSimulator struct { + poolpkg.Pool + gas Gas + } + + Gas struct { + Swap int64 + } +) + +func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { + return &PoolSimulator{ + Pool: poolpkg.Pool{Info: poolpkg.PoolInfo{ + Address: entityPool.Address, + ReserveUsd: entityPool.ReserveUsd, + Exchange: entityPool.Exchange, + Type: entityPool.Type, + Tokens: lo.Map(entityPool.Tokens, func(item *entity.PoolToken, index int) string { return item.Address }), + Reserves: lo.Map(entityPool.Reserves, func(item string, index int) *big.Int { return utils.NewBig(item) }), + BlockNumber: entityPool.BlockNumber, + }}, + gas: defaultGas, + }, nil +} + +func (s *PoolSimulator) CalcAmountOut(param poolpkg.CalcAmountOutParams) (*poolpkg.CalcAmountOutResult, error) { + var ( + tokenAmountIn = param.TokenAmountIn + tokenOut = param.TokenOut + ) + + indexIn, indexOut := s.GetTokenIndex(tokenAmountIn.Token), s.GetTokenIndex(tokenOut) + if indexIn < 0 || indexOut < 0 { + return nil, ErrInvalidToken + } + + amountIn, overflow := uint256.FromBig(tokenAmountIn.Amount) + if overflow { + return nil, ErrInvalidAmountIn + } + + if amountIn.Cmp(number.Zero) <= 0 { + return nil, ErrInsufficientInputAmount + } + + reserveIn, overflow := uint256.FromBig(s.Pool.Info.Reserves[indexIn]) + if overflow { + return nil, ErrInvalidReserve + } + + reserveOut, overflow := uint256.FromBig(s.Pool.Info.Reserves[indexOut]) + if overflow { + return nil, ErrInvalidReserve + } + + amountOut, err := s.getInputPrice(amountIn, reserveIn, reserveOut) + if err != nil { + return nil, err + } + + if amountOut.LtUint64(1) { + return nil, ErrInsufficientOutputAmount + } + + return &poolpkg.CalcAmountOutResult{ + TokenAmountOut: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexOut], Amount: amountOut.ToBig()}, + Fee: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexIn], Amount: integer.Zero()}, + Gas: s.gas.Swap, + }, nil +} + +func (s *PoolSimulator) CalcAmountIn(param poolpkg.CalcAmountInParams) (*poolpkg.CalcAmountInResult, error) { + var ( + tokenAmountOut = param.TokenAmountOut + tokenIn = param.TokenIn + ) + indexIn, indexOut := s.GetTokenIndex(tokenIn), s.GetTokenIndex(tokenAmountOut.Token) + if indexIn < 0 || indexOut < 0 { + return nil, ErrInvalidToken + } + + amountOut, overflow := uint256.FromBig(tokenAmountOut.Amount) + if overflow { + return nil, ErrInvalidAmountOut + } + + if amountOut.Cmp(number.Zero) <= 0 { + return nil, ErrInsufficientOutputAmount + } + + reserveIn, overflow := uint256.FromBig(s.Pool.Info.Reserves[indexIn]) + if overflow { + return nil, ErrInvalidReserve + } + + reserveOut, overflow := uint256.FromBig(s.Pool.Info.Reserves[indexOut]) + if overflow { + return nil, ErrInvalidReserve + } + + amountIn, err := s.getOutputPrice(amountOut, reserveIn, reserveOut) + if err != nil { + return nil, err + } + + if amountIn.Cmp(reserveIn) > 0 { + return nil, ErrInsufficientLiquidity + } + + return &poolpkg.CalcAmountInResult{ + TokenAmountIn: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexOut], Amount: amountIn.ToBig()}, + Fee: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexIn], Amount: integer.Zero()}, + Gas: s.gas.Swap, + }, nil +} + +func (s *PoolSimulator) UpdateBalance(params poolpkg.UpdateBalanceParams) { + indexIn, indexOut := s.GetTokenIndex(params.TokenAmountIn.Token), s.GetTokenIndex(params.TokenAmountOut.Token) + if indexIn < 0 || indexOut < 0 { + return + } + + s.Pool.Info.Reserves[indexIn] = new(big.Int).Add(s.Pool.Info.Reserves[indexIn], params.TokenAmountIn.Amount) + s.Pool.Info.Reserves[indexOut] = new(big.Int).Sub(s.Pool.Info.Reserves[indexOut], params.TokenAmountOut.Amount) +} + +func (s *PoolSimulator) GetMetaInfo(_ string, _ string) interface{} { + return PoolMeta{ + BlockNumber: s.Pool.Info.BlockNumber, + } +} + +// def getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256: +// +// assert input_reserve > 0 and output_reserve > 0 +// input_amount_with_fee: uint256 = input_amount * 997 +// numerator: uint256 = input_amount_with_fee * output_reserve +// denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee +// return numerator / denominator +func (s *PoolSimulator) getInputPrice(inputAmount, inputReserve, outputReserve *uint256.Int) (*uint256.Int, error) { + if inputReserve.CmpUint64(0) <= 0 || outputReserve.CmpUint64(0) <= 0 { + return nil, ErrInsufficientLiquidity + } + + var inputAmountWithFee, numerator, denominator uint256.Int + + inputAmountWithFee.Mul(inputAmount, U997) + + numerator.Mul(&inputAmountWithFee, outputReserve) + + denominator.Mul(inputReserve, U1000) + denominator.Add(&denominator, &inputAmountWithFee) + + return numerator.Div(&numerator, &denominator), nil +} + +// def getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256: +// +// assert input_reserve > 0 and output_reserve > 0 +// numerator: uint256 = input_reserve * output_amount * 1000 +// denominator: uint256 = (output_reserve - output_amount) * 997 +// return numerator / denominator + 1 +func (s *PoolSimulator) getOutputPrice(outputAmount, inputReserve, outputReserve *uint256.Int) (*uint256.Int, error) { + if inputReserve.CmpUint64(0) <= 0 || outputReserve.CmpUint64(0) <= 0 { + return nil, ErrInsufficientLiquidity + } + + var numerator, denominator uint256.Int + + numerator.Mul(inputReserve, outputAmount) + numerator.Mul(&numerator, U1000) + + denominator.Sub(outputReserve, outputAmount) + denominator.Mul(&denominator, U997) + + result := new(uint256.Int).Div(&numerator, &denominator) + + return result.AddUint64(result, 1), nil +} diff --git a/pkg/liquidity-source/uniswap-v1/pool_simulator_test.go b/pkg/liquidity-source/uniswap-v1/pool_simulator_test.go new file mode 100644 index 000000000..b4cd6c7ce --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/pool_simulator_test.go @@ -0,0 +1,80 @@ +package uniswapv1 + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + + poolpkg "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + utils "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/testutil" +) + +func TestPoolSimulator_CalcAmountOut(t *testing.T) { + testCases := []struct { + name string + poolSimulator PoolSimulator + tokenAmountIn poolpkg.TokenAmount + tokenOut string + expectedAmountOut *big.Int + expectedError error + }{ + { + name: "[swap0to1] it should return correct amountOut", + poolSimulator: PoolSimulator{ + Pool: poolpkg.Pool{ + Info: poolpkg.PoolInfo{ + Address: "0x3041cbd36888becc7bbcbc0045e3b1f144466f5f", + Tokens: []string{"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7"}, + Reserves: []*big.Int{utils.NewBig("10089138480746"), utils.NewBig("10066716097576")}, + }, + }, + }, + tokenAmountIn: poolpkg.TokenAmount{ + Amount: utils.NewBig("125224746"), + Token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + }, + tokenOut: "0xdac17f958d2ee523a2206206994597c13d831ec7", + expectedAmountOut: utils.NewBig("124570062"), + expectedError: nil, + }, + { + name: "[swap1to0] it should return correct amountOut", + poolSimulator: PoolSimulator{ + Pool: poolpkg.Pool{ + Info: poolpkg.PoolInfo{ + Address: "0x576cea6d4461fcb3a9d43e922c9b54c0f791599a", + Tokens: []string{"0x32a7c02e79c4ea1008dd6564b35f131428673c41", "0xdac17f958d2ee523a2206206994597c13d831ec7"}, + Reserves: []*big.Int{utils.NewBig("70361282326226590645832"), utils.NewBig("54150601005")}, + }, + }, + }, + tokenAmountIn: poolpkg.TokenAmount{ + Amount: utils.NewBig("124570062"), + Token: "0xdac17f958d2ee523a2206206994597c13d831ec7", + }, + tokenOut: "0x32a7c02e79c4ea1008dd6564b35f131428673c41", + expectedAmountOut: utils.NewBig("161006857684289764421"), + expectedError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := testutil.MustConcurrentSafe[*poolpkg.CalcAmountOutResult](t, func() (any, error) { + return tc.poolSimulator.CalcAmountOut(poolpkg.CalcAmountOutParams{ + TokenAmountIn: tc.tokenAmountIn, + TokenOut: tc.tokenOut, + Limit: nil, + }) + }) + + if tc.expectedError != nil { + assert.ErrorIs(t, tc.expectedError, err) + } else { + assert.Equal(t, tc.expectedAmountOut, result.TokenAmountOut.Amount) + } + }) + } +} diff --git a/pkg/liquidity-source/uniswap-v1/pool_tracker.go b/pkg/liquidity-source/uniswap-v1/pool_tracker.go new file mode 100644 index 000000000..7c08ae7d9 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/pool_tracker.go @@ -0,0 +1,116 @@ +package uniswapv1 + +import ( + "context" + "math/big" + "strings" + "time" + + "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/logger" + "github.com/ethereum/go-ethereum/common" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" +) + +type PoolTracker struct { + config *Config + ethrpcClient *ethrpc.Client +} + +func NewPoolTracker( + config *Config, + ethrpcClient *ethrpc.Client, +) (*PoolTracker, error) { + return &PoolTracker{ + config: config, + ethrpcClient: ethrpcClient, + }, nil +} + +func (d *PoolTracker) GetNewPoolState( + ctx context.Context, + p entity.Pool, + params pool.GetNewPoolStateParams, +) (entity.Pool, error) { + startTime := time.Now() + + logger.WithFields(logger.Fields{"pool_id": p.Address}).Info("Started getting new pool state") + + reserves, blockNumber, err := d.getReserves(ctx, p.Address, p.Tokens) + if err != nil { + return p, err + } + + if p.BlockNumber > blockNumber.Uint64() { + logger. + WithFields( + logger.Fields{ + "pool_id": p.Address, + "pool_block_number": p.BlockNumber, + "data_block_number": blockNumber.Uint64(), + }, + ). + Info("skip update: data block number is less than current pool block number") + return p, nil + } + + oldReserves := p.Reserves + + newReserves := make(entity.PoolReserves, 0, len(reserves)) + for _, reserve := range reserves { + newReserves = append(newReserves, reserve.String()) + } + + p.Reserves = newReserves + p.BlockNumber = blockNumber.Uint64() + p.Timestamp = time.Now().Unix() + + logger. + WithFields( + logger.Fields{ + "pool_id": p.Address, + "old_reserve": oldReserves, + "new_reserve": p.Reserves, + "old_block_number": p.BlockNumber, + "new_block_number": blockNumber, + "duration_ms": time.Since(startTime).Milliseconds(), + }, + ). + Info("Finished getting new pool state") + + return p, nil +} + +func (d *PoolTracker) getReserves(ctx context.Context, poolAddress string, tokens []*entity.PoolToken) ([]*big.Int, *big.Int, error) { + var reserves = make([]*big.Int, len(tokens)) + + req := d.ethrpcClient.NewRequest().SetContext(ctx) + + for i, token := range tokens { + if strings.EqualFold(token.Address, valueobject.WETHByChainID[d.config.ChainID]) { + req.AddCall(ðrpc.Call{ + ABI: multicallABI, + Target: d.config.MulticallContractAddress, + Method: multicallGetEthBalanceMethod, + Params: []interface{}{common.HexToAddress(poolAddress)}, + }, []interface{}{&reserves[i]}) + } else { + req.AddCall(ðrpc.Call{ + ABI: erc20ABI, + Target: token.Address, + Method: erc20BalanceOfMethod, + Params: []interface{}{common.HexToAddress(poolAddress)}, + }, []interface{}{&reserves[i]}) + } + } + + resp, err := req.TryBlockAndAggregate() + if err != nil { + return nil, nil, err + } + + return reserves, resp.BlockNumber, nil +} diff --git a/pkg/liquidity-source/uniswap-v1/types.go b/pkg/liquidity-source/uniswap-v1/types.go new file mode 100644 index 000000000..6d79d3976 --- /dev/null +++ b/pkg/liquidity-source/uniswap-v1/types.go @@ -0,0 +1,23 @@ +package uniswapv1 + +import "math/big" + +type ReserveData struct { + Reserve0 *big.Int + Reserve1 *big.Int +} + +func (d ReserveData) IsZero() bool { + return d.Reserve0 == nil && d.Reserve1 == nil +} + +type Extra struct { + Fee uint64 `json:"fee"` + FeePrecision uint64 `json:"feePrecision"` +} + +type PoolMeta struct { + Fee uint64 `json:"fee"` + FeePrecision uint64 `json:"feePrecision"` + BlockNumber uint64 `json:"blockNumber"` +} diff --git a/pkg/msgpack/register_pool_types.go b/pkg/msgpack/register_pool_types.go index 6d4733f5d..f9af2d5d9 100644 --- a/pkg/msgpack/register_pool_types.go +++ b/pkg/msgpack/register_pool_types.go @@ -49,6 +49,7 @@ import ( pkg_liquiditysource_swaapv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swaap-v2" pkg_liquiditysource_swell_rsweth "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swell/rsweth" pkg_liquiditysource_swell_sweth "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swell/sweth" + pkg_liquiditysource_uniswapv1 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/uniswap-v1" pkg_liquiditysource_uniswapv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/uniswap-v2" pkg_liquiditysource_usd0pp "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/usd0pp" pkg_liquiditysource_velocorev2_cpmm "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/cpmm" @@ -162,6 +163,7 @@ func init() { msgpack.RegisterConcreteType(&pkg_liquiditysource_swaapv2.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_swell_rsweth.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_swell_sweth.PoolSimulator{}) + msgpack.RegisterConcreteType(&pkg_liquiditysource_uniswapv1.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_uniswapv2.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_usd0pp.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_velocorev2_cpmm.PoolSimulator{}) diff --git a/pkg/pooltypes/pooltypes.go b/pkg/pooltypes/pooltypes.go index 8a3555cb8..7ede09a02 100644 --- a/pkg/pooltypes/pooltypes.go +++ b/pkg/pooltypes/pooltypes.go @@ -46,6 +46,7 @@ import ( swaapv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swaap-v2" "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swell/rsweth" "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/swell/sweth" + uniswapv1 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/uniswap-v1" uniswapv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/uniswap-v2" "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/usd0pp" velocorev2cpmm "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/cpmm" @@ -183,6 +184,7 @@ type Types struct { Smardex string Integral string Fxdx string + UniswapV1 string UniswapV2 string QuickPerps string BalancerV1 string @@ -309,6 +311,7 @@ var ( Smardex: smardex.DexTypeSmardex, Integral: integral.DexTypeIntegral, Fxdx: fxdx.DexTypeFxdx, + UniswapV1: uniswapv1.DexType, UniswapV2: uniswapv2.DexType, QuickPerps: quickperps.DexTypeQuickperps, BalancerV1: balancerv1.DexType, diff --git a/pkg/valueobject/exchange.go b/pkg/valueobject/exchange.go index 2f2aba284..3571e7fcd 100644 --- a/pkg/valueobject/exchange.go +++ b/pkg/valueobject/exchange.go @@ -26,6 +26,7 @@ var ( ExchangeEmpireDex Exchange = "empiredex" ExchangePhotonSwap Exchange = "photonswap" ExchangeUniSwap Exchange = "uniswap" + ExchangeUniSwapV1 Exchange = "uniswap-v1" ExchangeUniSwapV2 Exchange = "uniswap-v2" ExchangeShibaSwap Exchange = "shibaswap" ExchangeDefiSwap Exchange = "defiswap" @@ -383,6 +384,7 @@ var AMMSourceSet = map[Exchange]struct{}{ ExchangeEmpireDex: {}, ExchangePhotonSwap: {}, ExchangeUniSwap: {}, + ExchangeUniSwapV1: {}, ExchangeUniSwapV2: {}, ExchangeShibaSwap: {}, ExchangeDefiSwap: {},