From 207b483cf6f2175e7e3f8e5aa25bf47799917615 Mon Sep 17 00:00:00 2001 From: SunSpirit <48086732+sunspirit99@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:57:50 +0700 Subject: [PATCH] feat : Integrate Virtual Fun (#648) --- pkg/liquidity-source/virtual-fun/abis.go | 42 + .../virtual-fun/abis/FERC20.json | 394 +++++++++ .../virtual-fun/abis/FFactory.json | 508 +++++++++++ .../virtual-fun/abis/FPair.json | 361 ++++++++ .../virtual-fun/abis/bonding.json | 833 ++++++++++++++++++ pkg/liquidity-source/virtual-fun/config.go | 12 + pkg/liquidity-source/virtual-fun/constant.go | 35 + pkg/liquidity-source/virtual-fun/embed.go | 15 + .../virtual-fun/pool_list_updater.go | 299 +++++++ .../virtual-fun/pool_simulator.go | 357 ++++++++ .../virtual-fun/pool_simulator_test.go | 235 +++++ .../virtual-fun/pool_tracker.go | 208 +++++ pkg/liquidity-source/virtual-fun/types.go | 33 + pkg/msgpack/register_pool_types.go | 2 + pkg/pooltypes/pooltypes.go | 3 + pkg/valueobject/exchange.go | 2 + 16 files changed, 3339 insertions(+) create mode 100644 pkg/liquidity-source/virtual-fun/abis.go create mode 100644 pkg/liquidity-source/virtual-fun/abis/FERC20.json create mode 100644 pkg/liquidity-source/virtual-fun/abis/FFactory.json create mode 100644 pkg/liquidity-source/virtual-fun/abis/FPair.json create mode 100644 pkg/liquidity-source/virtual-fun/abis/bonding.json create mode 100644 pkg/liquidity-source/virtual-fun/config.go create mode 100644 pkg/liquidity-source/virtual-fun/constant.go create mode 100644 pkg/liquidity-source/virtual-fun/embed.go create mode 100644 pkg/liquidity-source/virtual-fun/pool_list_updater.go create mode 100644 pkg/liquidity-source/virtual-fun/pool_simulator.go create mode 100644 pkg/liquidity-source/virtual-fun/pool_simulator_test.go create mode 100644 pkg/liquidity-source/virtual-fun/pool_tracker.go create mode 100644 pkg/liquidity-source/virtual-fun/types.go diff --git a/pkg/liquidity-source/virtual-fun/abis.go b/pkg/liquidity-source/virtual-fun/abis.go new file mode 100644 index 000000000..19ff24ec5 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/abis.go @@ -0,0 +1,42 @@ +package virtualfun + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + erc20ABI abi.ABI + pairABI abi.ABI + factoryABI abi.ABI + bondingABI abi.ABI +) + +func init() { + builder := []struct { + ABI *abi.ABI + data []byte + }{ + { + &erc20ABI, erc20ABIJson, + }, + { + &pairABI, pairABIJson, + }, + { + &factoryABI, factoryABIJson, + }, + { + &bondingABI, bodingABIJson, + }, + } + + 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/virtual-fun/abis/FERC20.json b/pkg/liquidity-source/virtual-fun/abis/FERC20.json new file mode 100644 index 000000000..14c812ffe --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/abis/FERC20.json @@ -0,0 +1,394 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint256", + "name": "supply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxTx", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_maxTx", + "type": "uint256" + } + ], + "name": "MaxTxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "excludeFromMaxTx", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "maxTx", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxTx", + "type": "uint256" + } + ], + "name": "updateMaxTx", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/virtual-fun/abis/FFactory.json b/pkg/liquidity-source/virtual-fun/abis/FFactory.json new file mode 100644 index 000000000..8037c4389 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/abis/FFactory.json @@ -0,0 +1,508 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CREATOR_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPairsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "buyTax", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "taxVault_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "buyTax_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sellTax_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "pairs", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sellTax", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router_", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newVault_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "buyTax_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sellTax_", + "type": "uint256" + } + ], + "name": "setTaxParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "taxVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/virtual-fun/abis/FPair.json b/pkg/liquidity-source/virtual-fun/abis/FPair.json new file mode 100644 index 000000000..bb0340c29 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/abis/FPair.json @@ -0,0 +1,361 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "router_", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approval", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "assetBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "kLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "priceALast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceBLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "tokenA", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/virtual-fun/abis/bonding.json b/pkg/liquidity-source/virtual-fun/abis/bonding.json new file mode 100644 index 000000000..1f7aa1344 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/abis/bonding.json @@ -0,0 +1,833 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "agentToken", + "type": "address" + } + ], + "name": "Graduated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "Launched", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "K", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "agentFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "assetRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "buy", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "contract FFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getUserTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gradThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory_", + "type": "address" + }, + { + "internalType": "address", + "name": "router_", + "type": "address" + }, + { + "internalType": "address", + "name": "feeTo_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialSupply_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetRate_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxTx_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "agentFactory_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "gradThreshold_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_ticker", + "type": "string" + }, + { + "internalType": "uint8[]", + "name": "cores", + "type": "uint8[]" + }, + { + "internalType": "string", + "name": "desc", + "type": "string" + }, + { + "internalType": "string", + "name": "img", + "type": "string" + }, + { + "internalType": "string[4]", + "name": "urls", + "type": "string[4]" + }, + { + "internalType": "uint256", + "name": "purchaseAmount", + "type": "uint256" + } + ], + "name": "launch", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "maxTx", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "profile", + "outputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "profiles", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "contract FRouter", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "setAssetRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "tbaSalt", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tbaImplementation", + "type": "address" + }, + { + "internalType": "uint32", + "name": "daoVotingPeriod", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "daoThreshold", + "type": "uint256" + } + ], + "internalType": "struct Bonding.DeployParams", + "name": "params", + "type": "tuple" + } + ], + "name": "setDeployParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newFeeTo", + "type": "address" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "setGradThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newSupply", + "type": "uint256" + } + ], + "name": "setInitialSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxTx_", + "type": "uint256" + } + ], + "name": "setMaxTx", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenInfo", + "outputs": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "internalType": "address", + "name": "agentToken", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "internalType": "uint256", + "name": "supply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "marketCap", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume24H", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "prevPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastUpdated", + "type": "uint256" + } + ], + "internalType": "struct Bonding.Data", + "name": "data", + "type": "tuple" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "string", + "name": "image", + "type": "string" + }, + { + "internalType": "string", + "name": "twitter", + "type": "string" + }, + { + "internalType": "string", + "name": "telegram", + "type": "string" + }, + { + "internalType": "string", + "name": "youtube", + "type": "string" + }, + { + "internalType": "string", + "name": "website", + "type": "string" + }, + { + "internalType": "bool", + "name": "trading", + "type": "bool" + }, + { + "internalType": "bool", + "name": "tradingOnUniswap", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenInfos", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "srcTokenAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + } + ], + "name": "unwrapToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/virtual-fun/config.go b/pkg/liquidity-source/virtual-fun/config.go new file mode 100644 index 000000000..6af7f6173 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/config.go @@ -0,0 +1,12 @@ +package virtualfun + +import "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" + +type Config struct { + ChainID valueobject.ChainID `json:"chainID"` + BondingAddress string `json:"bondingAddress"` + FactoryAddress string `json:"factoryAddress"` + AssetToken string `json:"assetToken"` + IgnoreUntradablePools bool `json:"ignoreUntradablePools"` + NewPoolLimit int `json:"newPoolLimit"` +} diff --git a/pkg/liquidity-source/virtual-fun/constant.go b/pkg/liquidity-source/virtual-fun/constant.go new file mode 100644 index 000000000..32c923817 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/constant.go @@ -0,0 +1,35 @@ +package virtualfun + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +var ( + defaultGas = Gas{Swap: 165000} + + ZERO_ADDRESS = common.Address{} + + U100 = uint256.NewInt(100) + ZERO = uint256.NewInt(0) +) + +const ( + DexType = "virtual-fun" + + erc20BalanceOfMethod = "balanceOf" + + pairTokenAMethod = "tokenA" + pairTokenBMethod = "tokenB" + pairGetReservesMethod = "getReserves" + pairKLastMethod = "kLast" + + factoryAllPairsLengthMethod = "allPairsLength" + factoryGetPairMethod = "pairs" + + factorySellTaxMethod = "sellTax" + factoryBuyTaxMethod = "buyTax" + + bondingTokenInfoMethod = "tokenInfo" + bondingUnwrapTokenMethod = "unwrapToken" +) diff --git a/pkg/liquidity-source/virtual-fun/embed.go b/pkg/liquidity-source/virtual-fun/embed.go new file mode 100644 index 000000000..d30875665 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/embed.go @@ -0,0 +1,15 @@ +package virtualfun + +import _ "embed" + +//go:embed abis/bonding.json +var bodingABIJson []byte + +//go:embed abis/FFactory.json +var factoryABIJson []byte + +//go:embed abis/FPair.json +var pairABIJson []byte + +//go:embed abis/FERC20.json +var erc20ABIJson []byte diff --git a/pkg/liquidity-source/virtual-fun/pool_list_updater.go b/pkg/liquidity-source/virtual-fun/pool_list_updater.go new file mode 100644 index 000000000..327d05ec9 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/pool_list_updater.go @@ -0,0 +1,299 @@ +package virtualfun + +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"` + } +) + +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 ( + dexID = DexType + startTime = time.Now() + ) + + logger.WithFields(logger.Fields{"exchange": dexID}).Info("Started getting new pools") + + ctx = util.NewContextWithTimestamp(ctx) + + allPairsLength, err := u.getAllPairsLength(ctx) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": dexID}). + Error("getAllPairsLength failed") + + return nil, metadataBytes, err + } + + offset, err := u.getOffset(metadataBytes) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": dexID, "err": err}). + Warn("getOffset failed") + } + + batchSize := getBatchSize(allPairsLength, u.config.NewPoolLimit, offset) + + pairAddresses, err := u.listPairAddresses(ctx, offset, batchSize) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": dexID, "err": err}). + Error("listPairAddresses failed") + + return nil, metadataBytes, err + } + + pools, err := u.initPools(ctx, pairAddresses) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": dexID, "err": err}). + Error("initPools failed") + + return nil, metadataBytes, err + } + + newMetadataBytes, err := u.newMetadata(offset + batchSize) + if err != nil { + logger. + WithFields(logger.Fields{"dex_id": dexID, "err": err}). + Error("newMetadata failed") + + return nil, metadataBytes, err + } + + logger. + WithFields( + logger.Fields{ + "dex_id": dexID, + "pools_len": len(pools), + "offset": offset, + "duration_ms": time.Since(startTime).Milliseconds(), + }, + ). + Info("Finished getting new pools") + + return pools, newMetadataBytes, nil +} + +// getAllPairsLength gets number of pairs from the factory contracts +func (u *PoolsListUpdater) getAllPairsLength(ctx context.Context) (int, error) { + var allPairsLength *big.Int + + getAllPairsLengthRequest := u.ethrpcClient.NewRequest().SetContext(ctx) + + getAllPairsLengthRequest.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: u.config.FactoryAddress, + Method: factoryAllPairsLengthMethod, + Params: nil, + }, []interface{}{&allPairsLength}) + + if _, err := getAllPairsLengthRequest.Call(); err != nil { + return 0, err + } + + return int(allPairsLength.Int64()), nil +} + +// getOffset gets index of the last pair that is fetched +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 +} + +// listPairAddresses lists address of pairs from offset +func (u *PoolsListUpdater) listPairAddresses(ctx context.Context, offset int, batchSize int) ([]common.Address, error) { + listPairAddressesResult := make([]common.Address, batchSize) + + listPairAddressesRequest := u.ethrpcClient.NewRequest().SetContext(ctx) + + for i := 0; i < batchSize; i++ { + index := big.NewInt(int64(offset + i)) + + listPairAddressesRequest.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: u.config.FactoryAddress, + Method: factoryGetPairMethod, + Params: []interface{}{index}, + }, []interface{}{&listPairAddressesResult[i]}) + } + + resp, err := listPairAddressesRequest.TryAggregate() + if err != nil { + return nil, err + } + + var pairAddresses []common.Address + for i, isSuccess := range resp.Result { + if !isSuccess { + continue + } + + pairAddresses = append(pairAddresses, listPairAddressesResult[i]) + } + + return pairAddresses, nil +} + +// initPools fetches token data and initializes pools +func (u *PoolsListUpdater) initPools(ctx context.Context, pairAddresses []common.Address) ([]entity.Pool, error) { + token0List, err := u.getListToken0(ctx, pairAddresses) + if err != nil { + return nil, err + } + + pools := make([]entity.Pool, 0, len(pairAddresses)) + + for i, pairAddress := range pairAddresses { + if token0List[i].Cmp(ZERO_ADDRESS) == 0 { + continue + } + + token0 := &entity.PoolToken{ + Address: strings.ToLower(token0List[i].Hex()), + Swappable: true, + } + + token1 := &entity.PoolToken{ + Address: strings.ToLower(u.config.AssetToken), + Swappable: true, + } + + staticExtra, err := json.Marshal(&StaticExtra{ + BondingAddress: u.config.BondingAddress, + }) + if err != nil { + return nil, err + } + + var newPool = entity.Pool{ + Address: strings.ToLower(pairAddress.Hex()), + Exchange: string(valueobject.ExchangeVirtualFun), + Type: DexType, + Timestamp: time.Now().Unix(), + Reserves: []string{"0", "0"}, + Tokens: []*entity.PoolToken{token0, token1}, + StaticExtra: string(staticExtra), + } + + pools = append(pools, newPool) + } + + return pools, nil +} + +func (u *PoolsListUpdater) getListToken0(ctx context.Context, pairAddresses []common.Address) ([]common.Address, error) { + listToken0Result := make([]common.Address, len(pairAddresses)) + + listTokensRequest := u.ethrpcClient.NewRequest().SetContext(ctx) + + for i, pairAddress := range pairAddresses { + listTokensRequest.AddCall(ðrpc.Call{ + ABI: pairABI, + Target: pairAddress.Hex(), + Method: pairTokenAMethod, + Params: nil, + }, []interface{}{&listToken0Result[i]}) + } + + res, err := listTokensRequest.Aggregate() + if err != nil { + return nil, err + } + + if u.config.IgnoreUntradablePools { + req := u.ethrpcClient.NewRequest().SetContext(ctx).SetBlockNumber(res.BlockNumber) + + var temp *struct{} + for _, token := range listToken0Result { + req.AddCall(ðrpc.Call{ + ABI: bondingABI, + Target: u.config.BondingAddress, + Method: bondingUnwrapTokenMethod, + Params: []interface{}{token, []common.Address{}}, + }, []interface{}{temp}) + } + + result, err := req.TryAggregate() + if err != nil { + return nil, err + } + + for i := range result.Result { + if result.Result[i] { + listToken0Result[i] = ZERO_ADDRESS // ignore the pool has this token + } + } + } + + return listToken0Result, 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 +} + +// getBatchSize +// @params length number of pairs (factory tracked) +// @params limit number of pairs to be fetched in one run +// @params offset index of the last pair has been fetched +// @returns batchSize +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/virtual-fun/pool_simulator.go b/pkg/liquidity-source/virtual-fun/pool_simulator.go new file mode 100644 index 000000000..7d8b06ea6 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/pool_simulator.go @@ -0,0 +1,357 @@ +package virtualfun + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/holiman/uint256" + "github.com/samber/lo" + + "github.com/KyberNetwork/blockchain-toolkit/number" + "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 + + buyTax *uint256.Int + sellTax *uint256.Int + + reserveA *uint256.Int + reserveB *uint256.Int + + kLast *uint256.Int + bondingAddress string + } + + Gas struct { + Swap int64 + } +) + +func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { + var extra Extra + err := json.Unmarshal([]byte(entityPool.Extra), &extra) + if err != nil { + return nil, err + } + + var staticExtra StaticExtra + err = json.Unmarshal([]byte(entityPool.StaticExtra), &staticExtra) + if err != nil { + return nil, err + } + + p := &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, + }}, + + buyTax: uint256.MustFromBig(extra.BuyTax), + sellTax: uint256.MustFromBig(extra.SellTax), + kLast: uint256.MustFromBig(extra.KLast), + reserveA: uint256.MustFromBig(extra.ReserveA), + reserveB: uint256.MustFromBig(extra.ReserveB), + bondingAddress: staticExtra.BondingAddress, + + gas: defaultGas, + } + + return p, 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 + } + + balanceA, overflow := uint256.FromBig(s.Pool.Info.Reserves[0]) + if overflow { + return nil, ErrInvalidReserve + } + + balanceB, overflow := uint256.FromBig(s.Pool.Info.Reserves[1]) + if overflow { + return nil, ErrInvalidReserve + } + + var ( + isBuy bool + amountOut *uint256.Int + newBalanceA, newBalanceB *uint256.Int + newReserveA, newReserveB *uint256.Int + ) + + // Swap from token X to VIRTUAL + if indexIn == 0 { + var amountOutBeforeFee *uint256.Int + + amountOutBeforeFee, amountOut = s.sell(amountIn) + if amountOut.Cmp(balanceB) >= 0 { + return nil, ErrInsufficientOutputAmount + } + + newBalanceA = new(uint256.Int).Add(balanceA, amountIn) + newBalanceB = new(uint256.Int).Sub(balanceA, amountOut) + + newReserveA = new(uint256.Int).Add(s.reserveA, amountIn) + newReserveB = new(uint256.Int).Sub(s.reserveB, amountOutBeforeFee) + + } else { + var amountInAfterFee *uint256.Int + + amountInAfterFee, amountOut = s.buy(amountIn) + if amountOut.Cmp(balanceA) >= 0 { + return nil, ErrInsufficientOutputAmount + } + + newBalanceA = new(uint256.Int).Sub(balanceA, amountOut) + newBalanceB = new(uint256.Int).Add(balanceB, amountInAfterFee) + + newReserveA = new(uint256.Int).Sub(s.reserveA, amountOut) + newReserveB = new(uint256.Int).Add(s.reserveB, amountInAfterFee) + + isBuy = true + } + + 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: ZERO.ToBig()}, + Gas: s.gas.Swap, + SwapInfo: SwapInfo{ + IsBuy: isBuy, + BondingAddress: s.bondingAddress, + TokenAddress: s.Pool.Info.Tokens[0], + NewReserveA: newReserveA, + NewReserveB: newReserveB, + NewBalanceA: newBalanceA, + NewBalanceB: newBalanceB, + }, + }, 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, ErrInsufficientInputAmount + } + + balanceA, overflow := uint256.FromBig(s.Pool.Info.Reserves[0]) + if overflow { + return nil, ErrInvalidReserve + } + + balanceB, overflow := uint256.FromBig(s.Pool.Info.Reserves[1]) + if overflow { + return nil, ErrInvalidReserve + } + + var ( + isBuy bool + amountInNeeded *uint256.Int + newBalanceA, newBalanceB *uint256.Int + newReserveA, newReserveB *uint256.Int + err error + ) + + // Swap from token X to VIRTUAL + if indexOut == 1 { + if amountOut.Cmp(balanceB) >= 0 { + return nil, ErrInsufficientOutputAmount + } + + var amountOutBeforeFee *uint256.Int + + amountInNeeded, amountOutBeforeFee, err = s.sellExactOut(amountOut) + if err != nil { + return nil, err + } + + newBalanceA = new(uint256.Int).Add(balanceA, amountInNeeded) + newBalanceB = new(uint256.Int).Sub(balanceA, amountOut) + + newReserveA = new(uint256.Int).Add(s.reserveA, amountInNeeded) + newReserveB = new(uint256.Int).Sub(s.reserveB, amountOutBeforeFee) + } else { + if amountOut.Cmp(balanceA) >= 0 { + return nil, ErrInsufficientOutputAmount + } + + amountInNeeded, err = s.buyExactOut(amountOut) + if err != nil { + return nil, err + } + + newBalanceA = new(uint256.Int).Sub(balanceA, amountOut) + newBalanceB = new(uint256.Int).Add(balanceB, amountInNeeded) + + newReserveA = new(uint256.Int).Sub(s.reserveA, amountOut) + newReserveB = new(uint256.Int).Add(s.reserveB, amountInNeeded) + + isBuy = true + } + + return &poolpkg.CalcAmountInResult{ + TokenAmountIn: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexIn], Amount: amountInNeeded.ToBig()}, + Fee: &poolpkg.TokenAmount{Token: s.Pool.Info.Tokens[indexOut], Amount: ZERO.ToBig()}, + Gas: s.gas.Swap, + SwapInfo: SwapInfo{ + IsBuy: isBuy, + BondingAddress: s.bondingAddress, + TokenAddress: s.Pool.Info.Tokens[0], + NewReserveA: newReserveA, + NewReserveB: newReserveB, + NewBalanceA: newBalanceA, + NewBalanceB: newBalanceB, + }, + }, nil +} + +func (s *PoolSimulator) UpdateBalance(params poolpkg.UpdateBalanceParams) { + if swapInfo, ok := params.SwapInfo.(SwapInfo); ok && len(s.Pool.GetReserves()) == 2 { + s.reserveA = swapInfo.NewReserveA + s.reserveB = swapInfo.NewReserveB + s.Pool.Info.Reserves[0] = swapInfo.NewBalanceA.ToBig() + s.Pool.Info.Reserves[1] = swapInfo.NewBalanceB.ToBig() + } +} + +func (s *PoolSimulator) GetMetaInfo(_ string, _ string) interface{} { + return PoolMeta{ + BlockNumber: s.Pool.Info.BlockNumber, + } +} + +func (s *PoolSimulator) sell(amountIn *uint256.Int) (*uint256.Int, *uint256.Int) { + amountOut := s.getAmountsOut(amountIn, false) + + fee := s.sellTax + + txFee := new(uint256.Int).Div( + new(uint256.Int).Mul(fee, amountOut), + U100, + ) + + amountOutAfterFee := new(uint256.Int).Sub(amountOut, txFee) + + return amountOut, amountOutAfterFee +} + +func (s *PoolSimulator) buy(amountIn *uint256.Int) (*uint256.Int, *uint256.Int) { + fee := s.buyTax + + txFee := new(uint256.Int).Div( + new(uint256.Int).Mul(fee, amountIn), + U100, + ) + + amountInAfterFee := new(uint256.Int).Sub(amountIn, txFee) + + amountOut := s.getAmountsOut(amountInAfterFee, true) + + return amountInAfterFee, amountOut +} + +func (s *PoolSimulator) getAmountsOut(amountIn *uint256.Int, isBuy bool) *uint256.Int { + var amountOut = new(uint256.Int) + + if isBuy { + newReserveB := new(uint256.Int).Add(s.reserveB, amountIn) + newReserveA := new(uint256.Int).Div(s.kLast, newReserveB) + amountOut = amountOut.Sub(s.reserveA, newReserveA) + } else { + newReserveA := new(uint256.Int).Add(s.reserveA, amountIn) + newReserveB := new(uint256.Int).Div(s.kLast, newReserveA) + amountOut = amountOut.Sub(s.reserveB, newReserveB) + } + + return amountOut +} + +func (s *PoolSimulator) sellExactOut(amountOut *uint256.Int) (amountIn, amountOutBeforeFee *uint256.Int, err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() + + amountOutBeforeFee = new(uint256.Int).Div( + new(uint256.Int).Mul(amountOut, U100), + new(uint256.Int).Sub(U100, s.sellTax), + ) + + newReserveB := new(uint256.Int).Sub(s.reserveB, amountOutBeforeFee) + newReserveA := new(uint256.Int).Div(s.kLast, newReserveB) + amountIn = new(uint256.Int).Sub(newReserveA, s.reserveA) + + return +} + +func (s *PoolSimulator) buyExactOut(amountOut *uint256.Int) (amountInBeforeFee *uint256.Int, err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() + + newReserveA := new(uint256.Int).Sub(s.reserveA, amountOut) + newReserveB := new(uint256.Int).Div(s.kLast, newReserveA) + amountIn := new(uint256.Int).Sub(newReserveB, s.reserveB) + + amountInBeforeFee = new(uint256.Int).Div( + new(uint256.Int).Mul(amountIn, U100), + new(uint256.Int).Sub(U100, s.buyTax), + ) + + return +} diff --git a/pkg/liquidity-source/virtual-fun/pool_simulator_test.go b/pkg/liquidity-source/virtual-fun/pool_simulator_test.go new file mode 100644 index 000000000..0da6da502 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/pool_simulator_test.go @@ -0,0 +1,235 @@ +package virtualfun + +import ( + "math/big" + "testing" + + "github.com/goccy/go-json" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + poolpkg "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" +) + +func createTestPoolSimulator() *PoolSimulator { + extra := Extra{ + BuyTax: big.NewInt(5), // 5% buy tax + SellTax: big.NewInt(10), // 10% sell tax + KLast: big.NewInt(1500000), + ReserveA: big.NewInt(1500), + ReserveB: big.NewInt(1000), + } + extraBytes, _ := json.Marshal(extra) + + staticExtra := StaticExtra{ + BondingAddress: "0xF66DeA7b3e897cD44A5a231c61B6B4423d613259", + } + staticExtraBytes, _ := json.Marshal(staticExtra) + + entityPool := entity.Pool{ + Address: "0xc321c3a7f730608b51e4747b72aeb18e0a3d32c4", + Exchange: string(valueobject.ExchangeVirtualFun), + Type: DexType, + Tokens: []*entity.PoolToken{{Address: "TokenA"}, {Address: "TokenB"}}, + Reserves: []string{"1000", "1000"}, + Extra: string(extraBytes), + StaticExtra: string(staticExtraBytes), + } + + poolSimulator, err := NewPoolSimulator(entityPool) + if err != nil { + panic(err) + } + + return poolSimulator +} + +func TestNewPoolSimulator(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + assert.NotNil(t, poolSimulator) + assert.Equal(t, "0xc321c3a7f730608b51e4747b72aeb18e0a3d32c4", poolSimulator.Pool.Info.Address) + assert.Equal(t, uint256.NewInt(5), poolSimulator.buyTax) + assert.Equal(t, uint256.NewInt(10), poolSimulator.sellTax) + assert.Equal(t, uint256.NewInt(1500000), poolSimulator.kLast) + assert.Equal(t, uint256.NewInt(1500), poolSimulator.reserveA) + assert.Equal(t, uint256.NewInt(1000), poolSimulator.reserveB) + assert.Equal(t, "0xF66DeA7b3e897cD44A5a231c61B6B4423d613259", poolSimulator.bondingAddress) + +} + +func TestCalcAmountOut_SellToken(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + amountIn, _ := uint256.FromBig(big.NewInt(187)) + params := poolpkg.CalcAmountOutParams{ + TokenAmountIn: poolpkg.TokenAmount{ + Token: "TokenA", + Amount: amountIn.ToBig(), + }, + TokenOut: "TokenB", + } + + result, err := poolSimulator.CalcAmountOut(params) + require.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "TokenB", result.TokenAmountOut.Token) + assert.Equal(t, big.NewInt(100), result.TokenAmountOut.Amount) +} + +func TestCalcAmountOut_BuyToken(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + amountIn, _ := uint256.FromBig(big.NewInt(100)) + params := poolpkg.CalcAmountOutParams{ + TokenAmountIn: poolpkg.TokenAmount{ + Token: "TokenB", + Amount: amountIn.ToBig(), + }, + TokenOut: "TokenA", + } + + result, err := poolSimulator.CalcAmountOut(params) + require.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "TokenA", result.TokenAmountOut.Token) + assert.Equal(t, big.NewInt(131), result.TokenAmountOut.Amount) +} + +func TestCalcAmountIn_SellExactOut(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + amountOut, _ := uint256.FromBig(big.NewInt(100)) + params := poolpkg.CalcAmountInParams{ + TokenAmountOut: poolpkg.TokenAmount{ + Token: "TokenB", + Amount: amountOut.ToBig(), + }, + TokenIn: "TokenA", + } + + result, err := poolSimulator.CalcAmountIn(params) + require.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "TokenA", result.TokenAmountIn.Token) + assert.Equal(t, big.NewInt(187), result.TokenAmountIn.Amount) + + // Swap back the calculated input amount and verify the output + _params := poolpkg.CalcAmountOutParams{ + TokenAmountIn: poolpkg.TokenAmount{ + Token: "TokenA", + Amount: result.TokenAmountIn.Amount, + }, + TokenOut: "TokenB", + } + + _result, err := poolSimulator.CalcAmountOut(_params) + require.NoError(t, err) + assert.NotNil(t, _result) + assert.Equal(t, "TokenB", _result.TokenAmountOut.Token) + assert.Equal(t, amountOut.ToBig(), _result.TokenAmountOut.Amount) +} + +func TestCalcAmountIn_BuyExactOut(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + amountOut, _ := uint256.FromBig(big.NewInt(100)) + params := poolpkg.CalcAmountInParams{ + TokenAmountOut: poolpkg.TokenAmount{ + Token: "TokenA", + Amount: amountOut.ToBig(), + }, + TokenIn: "TokenB", + } + + result, err := poolSimulator.CalcAmountIn(params) + require.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "TokenB", result.TokenAmountIn.Token) + assert.Equal(t, big.NewInt(74), result.TokenAmountIn.Amount) + + // Swap back the calculated input amount and verify the output + _params := poolpkg.CalcAmountOutParams{ + TokenAmountIn: poolpkg.TokenAmount{ + Token: "TokenB", + Amount: result.TokenAmountIn.Amount, + }, + TokenOut: "TokenA", + } + + _result, err := poolSimulator.CalcAmountOut(_params) + require.NoError(t, err) + assert.NotNil(t, _result) + assert.Equal(t, "TokenA", _result.TokenAmountOut.Token) + assert.Equal(t, amountOut.ToBig(), _result.TokenAmountOut.Amount) +} + +func TestUpdateBalance(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + newReserveA := uint256.NewInt(1200) + newReserveB := uint256.NewInt(800) + swapInfo := SwapInfo{ + IsBuy: true, + NewReserveA: newReserveA, + NewReserveB: newReserveB, + NewBalanceA: newReserveA, + NewBalanceB: newReserveB, + } + + params := poolpkg.UpdateBalanceParams{ + SwapInfo: swapInfo, + } + + poolSimulator.UpdateBalance(params) + + assert.Equal(t, newReserveA.ToBig(), poolSimulator.Pool.Info.Reserves[0]) + assert.Equal(t, newReserveB.ToBig(), poolSimulator.Pool.Info.Reserves[1]) + assert.Equal(t, newReserveA, poolSimulator.reserveA) + assert.Equal(t, newReserveB, poolSimulator.reserveB) +} + +func TestErrorCases(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + t.Run("Insufficient Input Amount", func(t *testing.T) { + params := poolpkg.CalcAmountOutParams{ + TokenAmountIn: poolpkg.TokenAmount{ + Token: "TokenA", + Amount: big.NewInt(0), + }, + TokenOut: "TokenB", + } + + _, err := poolSimulator.CalcAmountOut(params) + assert.Error(t, err) + assert.Equal(t, ErrInsufficientInputAmount, err) + }) + + t.Run("Insufficient Output Amount", func(t *testing.T) { + params := poolpkg.CalcAmountInParams{ + TokenAmountOut: poolpkg.TokenAmount{ + Token: "TokenA", + Amount: big.NewInt(10000), // exceeds reserveOut + }, + TokenIn: "TokenB", + } + + _, err := poolSimulator.CalcAmountIn(params) + assert.Error(t, err) + assert.Equal(t, ErrInsufficientOutputAmount, err) + }) +} + +func TestGetMetaInfo(t *testing.T) { + poolSimulator := createTestPoolSimulator() + + metaInfo := poolSimulator.GetMetaInfo("", "") + poolMeta, ok := metaInfo.(PoolMeta) + + assert.True(t, ok) + assert.Equal(t, uint64(0), poolMeta.BlockNumber) +} diff --git a/pkg/liquidity-source/virtual-fun/pool_tracker.go b/pkg/liquidity-source/virtual-fun/pool_tracker.go new file mode 100644 index 000000000..49ece46e4 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/pool_tracker.go @@ -0,0 +1,208 @@ +package virtualfun + +import ( + "context" + "encoding/json" + "math/big" + "time" + + "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/logger" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient/gethclient" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" +) + +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) { + return d.getNewPoolState(ctx, p, params, nil) +} + +func (d *PoolTracker) GetNewPoolStateWithOverrides( + ctx context.Context, + p entity.Pool, + params pool.GetNewPoolStateWithOverridesParams, +) (entity.Pool, error) { + return d.getNewPoolState(ctx, p, pool.GetNewPoolStateParams{Logs: params.Logs}, params.Overrides) +} + +func (d *PoolTracker) getNewPoolState( + ctx context.Context, + p entity.Pool, + _ pool.GetNewPoolStateParams, + overrides map[common.Address]gethclient.OverrideAccount, +) (entity.Pool, error) { + if !p.Tokens[0].Swappable && !p.Tokens[1].Swappable { + return p, nil + } + + logger.WithFields(logger.Fields{"pool_id": p.Address}).Info("Started getting new pool state") + + tokenReserves, pairReserves, canPoolTradable, blockNumber, err := d.getReserves(ctx, p.Address, p.Tokens, overrides) + if err != nil { + return p, err + } + + if p.BlockNumber > blockNumber.Uint64() { + return p, nil + } + + // Disable pool : need a solution to clear these pools + if !canPoolTradable { + p.Tokens[0].Swappable = false + p.Tokens[1].Swappable = false + p.Reserves[0] = "0" + p.Reserves[1] = "0" + + return p, nil + } + + newReserves := make(entity.PoolReserves, 0, len(tokenReserves)) + for _, reserve := range tokenReserves { + if reserve == nil { + newReserves = append(newReserves, "0") + } else { + newReserves = append(newReserves, reserve.String()) + } + } + + buyTax, sellTax, kLast, err := d.getTax(ctx, p.Address, blockNumber) + if err != nil { + return p, err + } + + var extra = Extra{ + SellTax: sellTax, + BuyTax: buyTax, + ReserveA: pairReserves[0], + ReserveB: pairReserves[1], + KLast: kLast, + } + + newExtra, err := json.Marshal(&extra) + if err != nil { + return p, err + } + + p.Reserves = newReserves + p.BlockNumber = blockNumber.Uint64() + p.Timestamp = time.Now().Unix() + p.Extra = string(newExtra) + + return p, nil +} + +func (d *PoolTracker) getReserves( + ctx context.Context, + poolAddress string, + tokens []*entity.PoolToken, + overrides map[common.Address]gethclient.OverrideAccount, +) ([]*big.Int, [2]*big.Int, bool, *big.Int, error) { + var ( + tokenReserves = make([]*big.Int, len(tokens)) + pairReserves [2]*big.Int + tradable = true + ) + + req := d.ethrpcClient.NewRequest().SetContext(ctx) + if overrides != nil { + req.SetOverrides(overrides) + } + + // Fetch individual token balances for the pool + for i, token := range tokens { + req.AddCall(ðrpc.Call{ + ABI: erc20ABI, + Target: token.Address, + Method: erc20BalanceOfMethod, + Params: []interface{}{common.HexToAddress(poolAddress)}, + }, []interface{}{&tokenReserves[i]}) + } + + // Fetch pair reserves used for AMM calculations + req.AddCall(ðrpc.Call{ + ABI: pairABI, + Target: poolAddress, + Method: pairGetReservesMethod, + Params: nil, + }, []interface{}{&pairReserves}) + + // Call to detect if pool can tradable ? Tradable if there is an error + req.AddCall(ðrpc.Call{ + ABI: bondingABI, + Target: d.config.BondingAddress, + Method: bondingUnwrapTokenMethod, + Params: []interface{}{common.HexToAddress(tokens[0].Address), []common.Address{}}, + }, []interface{}{&struct{}{}}) + + resp, err := req.TryBlockAndAggregate() + if err != nil { + return nil, [2]*big.Int{}, tradable, nil, err + } + + // Check the last call result + if resp.Result[len(resp.Result)-1] { + tradable = false + } + + return tokenReserves, pairReserves, tradable, resp.BlockNumber, nil +} + +func (d *PoolTracker) getTax(ctx context.Context, poolAddress string, blocknumber *big.Int) (*big.Int, *big.Int, *big.Int, error) { + var ( + buyTax, sellTax = bignumber.ZeroBI, bignumber.ZeroBI + kLast = bignumber.ZeroBI + ) + + req := d.ethrpcClient.NewRequest().SetContext(ctx) + + if blocknumber != nil { + req.SetBlockNumber(blocknumber) + } + + req.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: factoryBuyTaxMethod, + Params: nil, + }, []interface{}{&buyTax}) + req.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: factorySellTaxMethod, + Params: nil, + }, []interface{}{&sellTax}) + req.AddCall(ðrpc.Call{ + ABI: pairABI, + Target: poolAddress, + Method: pairKLastMethod, + Params: nil, + }, []interface{}{&kLast}) + + if _, err := req.Aggregate(); err != nil { + return nil, nil, nil, err + } + + return buyTax, sellTax, kLast, nil +} diff --git a/pkg/liquidity-source/virtual-fun/types.go b/pkg/liquidity-source/virtual-fun/types.go new file mode 100644 index 000000000..736cf6e60 --- /dev/null +++ b/pkg/liquidity-source/virtual-fun/types.go @@ -0,0 +1,33 @@ +package virtualfun + +import ( + "math/big" + + "github.com/holiman/uint256" +) + +type StaticExtra struct { + BondingAddress string `json:"bondingAddress"` +} + +type Extra struct { + KLast *big.Int `json:"kLast"` + BuyTax *big.Int `json:"buyTax"` + SellTax *big.Int `json:"sellTax"` + ReserveA *big.Int `json:"reserveA"` + ReserveB *big.Int `json:"reserveB"` +} + +type SwapInfo struct { + IsBuy bool `json:"isBuy"` + BondingAddress string `json:"bondingAddress"` + TokenAddress string `json:"tokenAddress"` + NewReserveA *uint256.Int `json:"-"` + NewReserveB *uint256.Int `json:"-"` + NewBalanceA *uint256.Int `json:"-"` + NewBalanceB *uint256.Int `json:"-"` +} + +type PoolMeta struct { + BlockNumber uint64 `json:"blockNumber"` +} diff --git a/pkg/msgpack/register_pool_types.go b/pkg/msgpack/register_pool_types.go index 444b8a223..12258dcff 100644 --- a/pkg/msgpack/register_pool_types.go +++ b/pkg/msgpack/register_pool_types.go @@ -72,6 +72,7 @@ import ( pkg_liquiditysource_velocorev2_wombatstable "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/wombat-stable" pkg_liquiditysource_velodromev1 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velodrome-v1" pkg_liquiditysource_velodromev2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velodrome-v2" + pkg_liquiditysource_virtualfun "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/virtual-fun" pkg_liquiditysource_woofiv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/woofi-v2" pkg_liquiditysource_woofiv21 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/woofi-v21" pkg_source_algebrav1 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/algebrav1" @@ -202,6 +203,7 @@ func init() { msgpack.RegisterConcreteType(&pkg_liquiditysource_velocorev2_wombatstable.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_velodromev1.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_velodromev2.PoolSimulator{}) + msgpack.RegisterConcreteType(&pkg_liquiditysource_virtualfun.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_woofiv2.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_liquiditysource_woofiv21.PoolSimulator{}) msgpack.RegisterConcreteType(&pkg_source_algebrav1.PoolSimulator{}) diff --git a/pkg/pooltypes/pooltypes.go b/pkg/pooltypes/pooltypes.go index cd1e20b78..c48fd6c10 100644 --- a/pkg/pooltypes/pooltypes.go +++ b/pkg/pooltypes/pooltypes.go @@ -68,6 +68,7 @@ import ( velocorev2wombatstable "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/wombat-stable" velodrome "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velodrome-v1" velodromev2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velodrome-v2" + virtualfun "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/virtual-fun" woofiv2 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/woofi-v2" woofiv21 "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/woofi-v21" "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/algebrav1" @@ -269,6 +270,7 @@ type Types struct { AlgebraIntegral string MxTrading string LO1inch string + VirtualFun string } var ( @@ -411,5 +413,6 @@ var ( AlgebraIntegral: algebraintegral.DexType, MxTrading: mxtrading.DexType, LO1inch: lo1inch.DexType, + VirtualFun: virtualfun.DexType, } ) diff --git a/pkg/valueobject/exchange.go b/pkg/valueobject/exchange.go index 3fe1afad3..b129b3c10 100644 --- a/pkg/valueobject/exchange.go +++ b/pkg/valueobject/exchange.go @@ -382,6 +382,7 @@ var ( ExchangeHorizonIntegral Exchange = "horizon-integral" ExchangeMxTrading Exchange = "mx-trading" ExchangeLO1inch Exchange = "lo1inch" + ExchangeVirtualFun Exchange = "virtual-fun" ) var AMMSourceSet = map[Exchange]struct{}{ @@ -696,6 +697,7 @@ var AMMSourceSet = map[Exchange]struct{}{ ExchangeSilverSwap: {}, ExchangeScribe: {}, ExchangeHorizonIntegral: {}, + ExchangeVirtualFun: {}, } func IsAMMSource(exchange Exchange) bool {