diff --git a/pkg/source/dracula/abi/DraculaFactory.json b/pkg/source/dracula/abi/DraculaFactory.json new file mode 100644 index 000000000..04446cf6b --- /dev/null +++ b/pkg/source/dracula/abi/DraculaFactory.json @@ -0,0 +1,311 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_treasury", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "indexed": false, + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "allPairsLength", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptPauser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPairs", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPairsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "address", + "name": "pair", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPairs", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializable", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isPair", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pairCodeHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "pauser", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingPauser", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_state", + "type": "bool" + } + ], + "name": "setPause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauser", + "type": "address" + } + ], + "name": "setPauser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "setSwapFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/source/dracula/abi/DraculaPair.json b/pkg/source/dracula/abi/DraculaPair.json new file mode 100644 index 000000000..8159da24e --- /dev/null +++ b/pkg/source/dracula/abi/DraculaPair.json @@ -0,0 +1,1241 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "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": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Claim", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Fees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "FeesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + } + ], + "name": "Sync", + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Treasury", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "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": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blockTimestampLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "chainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimFees", + "outputs": [ + { + "internalType": "uint256", + "name": "claimed0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimed1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "claimable0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "claimable1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "name": "current", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentCumulativePrices", + "outputs": [ + { + "internalType": "uint256", + "name": "reserve0Cumulative", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve1Cumulative", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fees", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + } + ], + "name": "getAmountOut", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint112", + "name": "_reserve0", + "type": "uint112" + }, + { + "internalType": "uint112", + "name": "_reserve1", + "type": "uint112" + }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "index0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "index1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastObservation", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve0Cumulative", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve1Cumulative", + "type": "uint256" + } + ], + "internalType": "struct IPair.Observation", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "metadata", + "outputs": [ + { + "internalType": "uint256", + "name": "dec0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dec1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "r0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "r1", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "st", + "type": "bool" + }, + { + "internalType": "address", + "name": "t0", + "type": "address" + }, + { + "internalType": "address", + "name": "t1", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "observationLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve0Cumulative", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserve1Cumulative", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "points", + "type": "uint256" + } + ], + "name": "prices", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "granularity", + "type": "uint256" + } + ], + "name": "quote", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve0CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve1CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "points", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "window", + "type": "uint256" + } + ], + "name": "sample", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "setSwapFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "skim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyIndex0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyIndex1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "swapFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sync", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokens", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/source/dracula/abis.go b/pkg/source/dracula/abis.go new file mode 100644 index 000000000..c69beefea --- /dev/null +++ b/pkg/source/dracula/abis.go @@ -0,0 +1,34 @@ +package dracula + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + pairABI abi.ABI + factoryABI abi.ABI +) + +func init() { + builder := []struct { + ABI *abi.ABI + data []byte + }{ + { + &pairABI, pairABIData, + }, + { + &factoryABI, factoryABIData, + }, + } + + 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/source/dracula/config.go b/pkg/source/dracula/config.go new file mode 100644 index 000000000..8ec2ba436 --- /dev/null +++ b/pkg/source/dracula/config.go @@ -0,0 +1,7 @@ +package dracula + +type Config struct { + DexID string `json:"dexID"` + FactoryAddress string `json:"factoryAddress"` + NewPoolLimit int `json:"newPoolLimit"` +} diff --git a/pkg/source/dracula/constant.go b/pkg/source/dracula/constant.go new file mode 100644 index 000000000..1651ef086 --- /dev/null +++ b/pkg/source/dracula/constant.go @@ -0,0 +1,20 @@ +package dracula + +const ( + DexTypeDracula = "dracula" + defaultTokenWeight = 50 + + poolFactoryMethodAllPairLength = "allPairsLength" + poolFactoryMethodAllPairs = "allPairs" + + poolMethodMetadata = "metadata" + poolMethodGetReserves = "getReserves" + poolMethodSwapFee = "swapFee" + + reserveZero = "0" + bps float64 = 10000 +) + +var ( + DefaultGas = Gas{Swap: 227000} +) diff --git a/pkg/source/dracula/embed.go b/pkg/source/dracula/embed.go new file mode 100644 index 000000000..553b6ea78 --- /dev/null +++ b/pkg/source/dracula/embed.go @@ -0,0 +1,9 @@ +package dracula + +import _ "embed" + +//go:embed abi/DraculaPair.json +var pairABIData []byte + +//go:embed abi/DraculaFactory.json +var factoryABIData []byte diff --git a/pkg/source/dracula/helper.go b/pkg/source/dracula/helper.go new file mode 100644 index 000000000..b8845703a --- /dev/null +++ b/pkg/source/dracula/helper.go @@ -0,0 +1,9 @@ +package dracula + +import "encoding/json" + +func extractStaticExtra(s string) (staticExtra StaticExtra, err error) { + err = json.Unmarshal([]byte(s), &staticExtra) + + return +} diff --git a/pkg/source/dracula/math.go b/pkg/source/dracula/math.go new file mode 100644 index 000000000..c993fdb8b --- /dev/null +++ b/pkg/source/dracula/math.go @@ -0,0 +1,151 @@ +package dracula + +import ( + "math/big" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" +) + +// reference from smart-contract: +// +// function getAmountOut(uint amountIn, address tokenIn) external view returns (uint) +// https://explorer.zksync.io/address/0x68e03D7B8B3F9669750C1282AD6d36988f4FE18e#contract +func getAmountOut( + amountIn *big.Int, + reserveIn *big.Int, + reserveOut *big.Int, + decimalIn *big.Int, + decimalOut *big.Int, + swapFee *big.Int, + stable bool, +) *big.Int { + var amountAfterFee = calAmountAfterFee(amountIn, swapFee) + if amountAfterFee.Cmp(bignumber.ZeroBI) <= 0 { + return bignumber.ZeroBI + } + + return getExactQuote(amountAfterFee, reserveIn, reserveOut, decimalIn, decimalOut, stable) +} + +func getExactQuote( + amountIn *big.Int, + reserveIn *big.Int, + reserveOut *big.Int, + decimalIn *big.Int, + decimalOut *big.Int, + stable bool, +) *big.Int { + amountOut := big.NewInt(0) + + if amountIn.Cmp(bignumber.ZeroBI) <= 0 { + return amountOut + } + + if stable { + xy := _k(reserveIn, reserveOut, decimalIn, decimalOut, stable) + _reserveIn := new(big.Int).Div(new(big.Int).Mul(reserveIn, bignumber.BONE), decimalIn) + _reserveOut := new(big.Int).Div(new(big.Int).Mul(reserveOut, bignumber.BONE), decimalOut) + _amountIn := new(big.Int).Div(new(big.Int).Mul(amountIn, bignumber.BONE), decimalIn) + + y := new(big.Int).Sub(_reserveOut, _get_y(new(big.Int).Add(_amountIn, _reserveIn), xy, _reserveOut)) + + amountOut = new(big.Int).Div(new(big.Int).Mul(y, decimalOut), bignumber.BONE) + } else { + // (x*In)/(y+In) + numerator := new(big.Int).Mul(amountIn, reserveOut) + denominator := new(big.Int).Add(reserveIn, amountIn) + + if denominator.Cmp(bignumber.ZeroBI) > 0 { + amountOut = new(big.Int).Div(numerator, denominator) + } + } + + if !validateAmountOut(amountIn, amountOut, reserveIn, reserveOut, decimalIn, decimalOut, stable) { + return bignumber.ZeroBI + } + + return amountOut +} + +func calAmountAfterFee(amountIn, swapFee *big.Int) *big.Int { + // In - (fee*In)/Bone + return new(big.Int).Sub(amountIn, new(big.Int).Div(new(big.Int).Mul(swapFee, amountIn), bignumber.BONE)) +} + +func _k(x, y, decimals0, decimals1 *big.Int, stable bool) *big.Int { + if stable { + _x := new(big.Int).Div(new(big.Int).Mul(x, bignumber.BONE), decimals0) + _y := new(big.Int).Div(new(big.Int).Mul(y, bignumber.BONE), decimals1) + + _a := new(big.Int).Div(new(big.Int).Mul(_x, _y), bignumber.BONE) + _b := new(big.Int).Div(new(big.Int).Add(new(big.Int).Mul(_x, _x), new(big.Int).Mul(_y, _y)), bignumber.BONE) + + // x3y+y3x >= k + return new(big.Int).Div(new(big.Int).Mul(_a, _b), bignumber.BONE) + } else { + // xy >= k + return new(big.Int).Mul(x, y) + } +} + +func _get_y(x0, xy, y *big.Int) *big.Int { + _y := new(big.Int).Set(y) + + for i := 0; i < 255; i++ { + y_prev := new(big.Int).Set(_y) + + k := _f(x0, _y) + d := _d(x0, _y) + if d.Cmp(bignumber.ZeroBI) <= 0 { + return bignumber.ZeroBI + } + + if k.Cmp(xy) < 0 { + dy := new(big.Int).Div(new(big.Int).Mul(new(big.Int).Sub(xy, k), bignumber.BONE), d) + _y.Add(_y, dy) + } else { + dy := new(big.Int).Div(new(big.Int).Mul(new(big.Int).Sub(k, xy), bignumber.BONE), d) + _y.Sub(_y, dy) + } + + diff := new(big.Int).Sub(_y, y_prev) + if diff.CmpAbs(big.NewInt(1)) <= 0 { + return _y + } + } + + return _y +} + +func _f(x0, y *big.Int) *big.Int { + // y^3*x + y3x := new(big.Int).Mul(new(big.Int).Mul(y, y), new(big.Int).Mul(y, x0)) + // x^3*y + x3y := new(big.Int).Mul(new(big.Int).Mul(x0, x0), new(big.Int).Mul(x0, y)) + + numerator := new(big.Int).Add(y3x, x3y) + denominator := new(big.Int).Mul(new(big.Int).Mul(bignumber.BONE, bignumber.BONE), bignumber.BONE) + + return new(big.Int).Div(numerator, denominator) +} + +func _d(x0, y *big.Int) *big.Int { + // 3*x*y^2 + x^3 + numerator := new(big.Int).Add(new(big.Int).Mul(new(big.Int).Mul(bignumber.Three, x0), new(big.Int).Mul(y, y)), new(big.Int).Mul(new(big.Int).Mul(x0, x0), x0)) + denominator := new(big.Int).Mul(bignumber.BONE, bignumber.BONE) + + return new(big.Int).Div(numerator, denominator) +} + +// The SC required `K` after swap with condition: +// +// require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), 'K'); +// +// validateAmountOut to check if after swap, the condition still valid. +func validateAmountOut(amountIn, amountOut, reserveIn, reserveOut, decimalIn, decimalOut *big.Int, stable bool) bool { + balanceIn := new(big.Int).Add(reserveIn, amountIn) + balanceOut := new(big.Int).Sub(reserveOut, amountOut) + + return _k(balanceIn, balanceOut, decimalIn, decimalOut, stable). + Cmp(_k(reserveIn, reserveOut, decimalIn, decimalOut, stable)) >= 0 +} diff --git a/pkg/source/dracula/math_test.go b/pkg/source/dracula/math_test.go new file mode 100644 index 000000000..86fc117cb --- /dev/null +++ b/pkg/source/dracula/math_test.go @@ -0,0 +1,104 @@ +package dracula + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetAmountOut(t *testing.T) { + testCases := []struct { + Name string + amountIn *big.Int + reserveIn *big.Int + reserveOut *big.Int + decimalIn *big.Int + decimalOut *big.Int + swapFee *big.Int + stable bool + expectedOutput *big.Int + }{ + { + Name: "Test volatile: normal case", + amountIn: setBigValue("1000000"), + reserveIn: setBigValue("734057310691"), + reserveOut: setBigValue("619890288342243016669"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: false, + expectedOutput: setBigValue("844301197094305"), + }, + { + Name: "Test volatile: small input case", + amountIn: setBigValue("1"), + reserveIn: setBigValue("734158559194"), + reserveOut: setBigValue("619810940630240220120"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: false, + expectedOutput: setBigValue("844246699"), + }, + { + Name: "Test volatile: big input case", + amountIn: setBigValue("1000000000000000000000000"), + reserveIn: setBigValue("734158559194"), + reserveOut: setBigValue("619810940630240220120"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: false, + expectedOutput: setBigValue("619810940629785089586"), + }, + { + Name: "Test stable: normal case", + amountIn: setBigValue("1000000"), + reserveIn: setBigValue("1597357611912"), + reserveOut: setBigValue("1069785590448332953758441"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: true, + expectedOutput: setBigValue("984443098823156057"), + }, + { + Name: "Test stable: small input case", + amountIn: setBigValue("1"), + reserveIn: setBigValue("1597357611912"), + reserveOut: setBigValue("1069785590448332953758441"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: true, + expectedOutput: setBigValue("984640112686"), + }, + { + Name: "Test stable: big input case", + amountIn: setBigValue("1000000000000000000000000"), + reserveIn: setBigValue("1597357611912"), + reserveOut: setBigValue("1069785590448332953758441"), + decimalIn: setBigValue("1000000"), + decimalOut: setBigValue("1000000000000000000"), + swapFee: setBigValue("200000000000000"), + stable: true, + expectedOutput: setBigValue("1069785590448332953758440"), + }, + } + + for _, tc := range testCases { + test := tc + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + actualAmountOut := getAmountOut(test.amountIn, test.reserveIn, test.reserveOut, test.decimalIn, test.decimalOut, test.swapFee, test.stable) + + assert.Equal(t, test.expectedOutput, actualAmountOut) + }) + } +} + +func setBigValue(value string) *big.Int { + bigValue, _ := new(big.Int).SetString(value, 10) + return bigValue +} diff --git a/pkg/source/dracula/pool_simulator.go b/pkg/source/dracula/pool_simulator.go new file mode 100644 index 000000000..9daa85c45 --- /dev/null +++ b/pkg/source/dracula/pool_simulator.go @@ -0,0 +1,125 @@ +package dracula + +import ( + "fmt" + "math/big" + "strings" + + "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 PoolSimulator struct { + pool.Pool + Decimals []*big.Int + stable bool + gas Gas +} + +func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { + var swapFeeFl = new(big.Float).Mul(big.NewFloat(entityPool.SwapFee), bignumber.BoneFloat) + var swapFee, _ = swapFeeFl.Int(nil) + + var tokens = make([]string, 2) + tokens[0] = entityPool.Tokens[0].Address + tokens[1] = entityPool.Tokens[1].Address + + var reserves = make([]*big.Int, 2) + reserves[0] = bignumber.NewBig10(entityPool.Reserves[0]) + reserves[1] = bignumber.NewBig10(entityPool.Reserves[1]) + + var decimals = make([]*big.Int, 2) + decimals[0] = bignumber.TenPowInt(entityPool.Tokens[0].Decimals) + decimals[1] = bignumber.TenPowInt(entityPool.Tokens[1].Decimals) + + var info = pool.PoolInfo{ + Address: strings.ToLower(entityPool.Address), + ReserveUsd: entityPool.ReserveUsd, + SwapFee: swapFee, + Exchange: entityPool.Exchange, + Type: entityPool.Type, + Tokens: tokens, + Reserves: reserves, + Checked: false, + } + + staticExtra, err := extractStaticExtra(entityPool.StaticExtra) + if err != nil { + return nil, err + } + + return &PoolSimulator{ + Pool: pool.Pool{Info: info}, + Decimals: decimals, + stable: staticExtra.Stable, + gas: DefaultGas, + }, nil +} + +func (p *PoolSimulator) CalcAmountOut( + tokenAmountIn pool.TokenAmount, + tokenOut string, +) (*pool.CalcAmountOutResult, error) { + var tokenInIndex = p.GetTokenIndex(tokenAmountIn.Token) + var tokenOutIndex = p.GetTokenIndex(tokenOut) + + if tokenInIndex < 0 || tokenOutIndex < 0 { + return &pool.CalcAmountOutResult{}, fmt.Errorf("tokenInIndex %v or tokenOutIndex %v is not correct", tokenInIndex, tokenOutIndex) + } + + amountOut := getAmountOut( + tokenAmountIn.Amount, + p.Info.Reserves[tokenInIndex], + p.Info.Reserves[tokenOutIndex], + p.Decimals[tokenInIndex], + p.Decimals[tokenOutIndex], + p.Info.SwapFee, + p.stable, + ) + + if amountOut.Cmp(bignumber.ZeroBI) <= 0 { + return &pool.CalcAmountOutResult{}, fmt.Errorf("amountOut is %d", amountOut.Int64()) + } + + if amountOut.Cmp(p.Info.Reserves[tokenOutIndex]) > 0 { + return &pool.CalcAmountOutResult{}, fmt.Errorf("amountOut is %d bigger than reserve %d", amountOut.Int64(), p.Info.Reserves[tokenOutIndex]) + } + + tokenAmountOut := &pool.TokenAmount{ + Token: tokenOut, + Amount: amountOut, + } + + fee := &pool.TokenAmount{ + Token: tokenAmountIn.Token, + Amount: nil, + } + + return &pool.CalcAmountOutResult{ + TokenAmountOut: tokenAmountOut, + Fee: fee, + Gas: p.gas.Swap, + }, nil +} + +func (p *PoolSimulator) UpdateBalance(params pool.UpdateBalanceParams) { + input, output := params.TokenAmountIn, params.TokenAmountOut + var inputAmount = calAmountAfterFee(input.Amount, p.Info.SwapFee) + var outputAmount = output.Amount + + for i := range p.Info.Tokens { + if p.Info.Tokens[i] == input.Token { + p.Info.Reserves[i] = new(big.Int).Add(p.Info.Reserves[i], inputAmount) + } + if p.Info.Tokens[i] == output.Token { + p.Info.Reserves[i] = new(big.Int).Sub(p.Info.Reserves[i], outputAmount) + } + } +} + +func (p *PoolSimulator) GetMetaInfo(tokenIn string, tokenOut string) interface{} { + return StaticExtra{ + Stable: p.stable, + } +} diff --git a/pkg/source/dracula/pool_simulator_test.go b/pkg/source/dracula/pool_simulator_test.go new file mode 100644 index 000000000..ce5f949c1 --- /dev/null +++ b/pkg/source/dracula/pool_simulator_test.go @@ -0,0 +1,51 @@ +package dracula + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "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" +) + +func TestCalcAmountOut(t *testing.T) { + //token0: 0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4 (USDC=>A) + //token1: 0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91 (WETH=>B) + //test data from https://explorer.zksync.io/address/0xB51E60f61c48d8329843F86d861AbF50E4DC918d + testcases := []struct { + in string + inAmount string + out string + expectedOutAmount string + }{ + {"A", "100", "B", "58175411896"}, + {"B", "100000000000", "A", "171"}, + } + + p, err := NewPoolSimulator(entity.Pool{ + Exchange: "", + Type: "", + SwapFee: 0.0025, + Reserves: entity.PoolReserves{"20694815319", "12039294111262365027"}, + Tokens: []*entity.PoolToken{{Address: "A", Decimals: 6}, {Address: "B", Decimals: 18}}, + StaticExtra: "{\"stable\": false}", + }) + require.Nil(t, err) + + assert.Equal(t, []string{"A"}, p.CanSwapTo("B")) + assert.Equal(t, []string{"B"}, p.CanSwapTo("A")) + + for idx, tc := range testcases { + t.Run(fmt.Sprintf("test %d", idx), func(t *testing.T) { + amountIn := pool.TokenAmount{Token: tc.in, Amount: bignumber.NewBig10(tc.inAmount)} + out, err := p.CalcAmountOut(amountIn, tc.out) + require.Nil(t, err) + assert.Equal(t, bignumber.NewBig10(tc.expectedOutAmount), out.TokenAmountOut.Amount) + assert.Equal(t, tc.out, out.TokenAmountOut.Token) + }) + } +} diff --git a/pkg/source/dracula/pool_tracker.go b/pkg/source/dracula/pool_tracker.go new file mode 100644 index 000000000..92015e168 --- /dev/null +++ b/pkg/source/dracula/pool_tracker.go @@ -0,0 +1,75 @@ +package dracula + +import ( + "context" + "math/big" + "time" + + "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/logger" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" +) + +type PoolTracker struct { + config *Config + ethrpcClient *ethrpc.Client +} + +func NewPoolTracker( + cfg *Config, + ethrpcClient *ethrpc.Client, +) (*PoolTracker, error) { + return &PoolTracker{ + config: cfg, + ethrpcClient: ethrpcClient, + }, nil +} + +func (d *PoolTracker) GetNewPoolState(ctx context.Context, p entity.Pool) (entity.Pool, error) { + logger.WithFields(logger.Fields{ + "address": p.Address, + }).Infof("[%s] Start getting new state of pool", p.Type) + + var ( + reserve Reserves + poolFee *big.Int + ) + + calls := d.ethrpcClient.NewRequest().SetContext(ctx) + + calls.AddCall(ðrpc.Call{ + ABI: pairABI, + Target: p.Address, + Method: poolMethodGetReserves, + Params: nil, + }, []interface{}{&reserve}) + + calls.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: p.Address, + Method: poolMethodSwapFee, + Params: nil, + }, []interface{}{&poolFee}) + + if _, err := calls.Aggregate(); err != nil { + logger.WithFields(logger.Fields{ + "poolAddress": p.Address, + "error": err, + }).Errorf("failed to aggregate to get pool data") + + return entity.Pool{}, err + } + + swapFee := poolFee.Int64() + + p.Reserves = entity.PoolReserves{reserve.Reserve0.String(), reserve.Reserve1.String()} + p.SwapFee = float64(swapFee) / bps + p.Timestamp = time.Now().Unix() + + logger.WithFields(logger.Fields{ + "address": p.Address, + }).Infof("[%s] Finish getting new state of pool", p.Type) + + return p, nil +} diff --git a/pkg/source/dracula/pools_list_updater.go b/pkg/source/dracula/pools_list_updater.go new file mode 100644 index 000000000..eadc5760f --- /dev/null +++ b/pkg/source/dracula/pools_list_updater.go @@ -0,0 +1,188 @@ +package dracula + +import ( + "context" + "encoding/json" + "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/util" +) + +type PoolListUpdater struct { + config *Config + ethrpcClient *ethrpc.Client +} + +func NewPoolListUpdater( + cfg *Config, + ethrpcClient *ethrpc.Client, +) *PoolListUpdater { + return &PoolListUpdater{ + config: cfg, + ethrpcClient: ethrpcClient, + } +} + +func (d *PoolListUpdater) GetNewPools(ctx context.Context, metadataBytes []byte) ([]entity.Pool, []byte, error) { + var metadata Metadata + if len(metadataBytes) != 0 { + err := json.Unmarshal(metadataBytes, &metadata) + if err != nil { + return nil, metadataBytes, err + } + } + + // Add timestamp to the context so that each run iteration will have something different + ctx = util.NewContextWithTimestamp(ctx) + + var lengthBI *big.Int + if _, err := d.ethrpcClient.NewRequest().AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: poolFactoryMethodAllPairLength, + Params: nil, + }, []interface{}{&lengthBI}).Call(); err != nil { + logger.WithFields(logger.Fields{ + "error": err, + }).Errorf("failed to get number of pools from factory") + + return nil, metadataBytes, err + } + totalNumberOfPools := int(lengthBI.Int64()) + + batchSize := d.config.NewPoolLimit + currentOffset := metadata.Offset + if currentOffset+batchSize > totalNumberOfPools { + batchSize = totalNumberOfPools - currentOffset + if batchSize <= 0 { + return nil, metadataBytes, nil + } + } + + getPoolAddressRequest := d.ethrpcClient.NewRequest() + var poolAddresses = make([]common.Address, batchSize) + for j := 0; j < batchSize; j++ { + getPoolAddressRequest.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: poolFactoryMethodAllPairs, + Params: []interface{}{big.NewInt(int64(currentOffset + j))}, + }, []interface{}{&poolAddresses[j]}) + } + if _, err := getPoolAddressRequest.Aggregate(); err != nil { + logger.WithFields(logger.Fields{ + "error": err, + }).Errorf("failed to get pool address") + + return nil, metadataBytes, err + } + + pools, err := d.processBatch(ctx, poolAddresses) + if err != nil { + logger.WithFields(logger.Fields{ + "error": err, + }).Errorf("failed to process update new pool") + + return nil, metadataBytes, err + } + + if len(pools) > 0 { + logger.WithFields(logger.Fields{ + "dexID": d.config.DexID, + "batchSize": batchSize, + "totalNumberOfUpdatedPools": currentOffset + len(pools), + "totalNumberOfPools": totalNumberOfPools, + }).Infof("scan DraculaFactory") + } + + nextOffset := currentOffset + len(pools) + newMetadataBytes, err := json.Marshal(Metadata{ + Offset: nextOffset, + }) + if err != nil { + return nil, metadataBytes, err + } + + return pools, newMetadataBytes, nil + +} + +func (d *PoolListUpdater) processBatch(ctx context.Context, poolAddresses []common.Address) ([]entity.Pool, error) { + var ( + limit = len(poolAddresses) + poolMetadata = make([]DraculaMetadata, limit) + pools = make([]entity.Pool, 0, limit) + ) + + calls := d.ethrpcClient.NewRequest() + calls.SetContext(ctx) + + for i := 0; i < limit; i++ { + calls.AddCall(ðrpc.Call{ + ABI: pairABI, + Target: poolAddresses[i].Hex(), + Method: poolMethodMetadata, + Params: nil, + }, []interface{}{&poolMetadata[i]}) + } + + if _, err := calls.Aggregate(); err != nil { + logger.WithFields(logger.Fields{ + "error": err, + }).Errorf("failed to aggregate to get tokens from pool") + + return nil, err + } + + for i, pAddr := range poolAddresses { + poolAddress := strings.ToLower(pAddr.Hex()) + token0Address := strings.ToLower(poolMetadata[i].T0.Hex()) + token1Address := strings.ToLower(poolMetadata[i].T1.Hex()) + + var token0 = entity.PoolToken{ + Address: token0Address, + Weight: defaultTokenWeight, + Decimals: uint8(len(poolMetadata[i].Dec0.String()) - 1), + Swappable: true, + } + + var token1 = entity.PoolToken{ + Address: token1Address, + Weight: defaultTokenWeight, + Decimals: uint8(len(poolMetadata[i].Dec1.String()) - 1), + Swappable: true, + } + + staticExtra := StaticExtra{ + Stable: poolMetadata[i].St, + } + staticExtraBytes, err := json.Marshal(staticExtra) + if err != nil { + logger.WithFields(logger.Fields{ + "error": err, + }).Errorf("failed to marshaling the static extra data") + return nil, err + } + + newPool := entity.Pool{ + Address: poolAddress, + Exchange: d.config.DexID, + Type: DexTypeDracula, + Timestamp: time.Now().Unix(), + Reserves: []string{reserveZero, reserveZero}, + Tokens: []*entity.PoolToken{&token0, &token1}, + StaticExtra: string(staticExtraBytes), + } + + pools = append(pools, newPool) + } + + return pools, nil +} diff --git a/pkg/source/dracula/type.go b/pkg/source/dracula/type.go new file mode 100644 index 000000000..f48a608fe --- /dev/null +++ b/pkg/source/dracula/type.go @@ -0,0 +1,35 @@ +package dracula + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type Metadata struct { + Offset int `json:"offset"` +} + +type DraculaMetadata struct { + Dec0 *big.Int + Dec1 *big.Int + R0 *big.Int + R1 *big.Int + St bool + T0 common.Address + T1 common.Address +} + +type StaticExtra struct { + Stable bool `json:"stable"` +} + +type Reserves struct { + Reserve0 *big.Int + Reserve1 *big.Int + BlockTimestampLast *big.Int +} + +type Gas struct { + Swap int64 +}