diff --git a/pkg/liquidity-source/velocore-v2/cpmm/abis.go b/pkg/liquidity-source/velocore-v2/cpmm/abis.go new file mode 100644 index 000000000..ee45322b6 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/abis.go @@ -0,0 +1,30 @@ +package cpmm + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + factoryABI abi.ABI + poolABI abi.ABI +) + +func init() { + builder := []struct { + ABI *abi.ABI + data []byte + }{ + {&factoryABI, factoryABIJson}, + {&poolABI, poolABIJson}, + } + + 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/velocore-v2/cpmm/abis/ConstantProductPool.json b/pkg/liquidity-source/velocore-v2/cpmm/abis/ConstantProductPool.json new file mode 100644 index 000000000..34decf59c --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/abis/ConstantProductPool.json @@ -0,0 +1,951 @@ +[ + { + "inputs": [ + { + "internalType": "contract ConstantProductLibrary", + "name": "lib_", + "type": "address" + }, + { + "internalType": "contract IVault", + "name": "vault_", + "type": "address" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "Token[]", + "name": "tokens", + "type": "bytes32[]" + }, + { + "internalType": "uint256[]", + "name": "weights", + "type": "uint256[]" + }, + { + "internalType": "uint32", + "name": "fee1e9_", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "decay", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "x", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "y", + "type": "uint256" + } + ], + "name": "PRBMath_MulDiv18_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "x", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "y", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "PRBMath_MulDiv_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Convert_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Convert_Underflow", + "type": "error" + }, + { + "inputs": [], + "name": "PRBMath_SD59x18_Div_InputTooSmall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "SD59x18", + "name": "x", + "type": "int256" + }, + { + "internalType": "SD59x18", + "name": "y", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Div_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "SD59x18", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Exp2_InputTooBig", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "SD59x18", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Log_InputTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "PRBMath_SD59x18_Mul_InputTooSmall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "SD59x18", + "name": "x", + "type": "int256" + }, + { + "internalType": "SD59x18", + "name": "y", + "type": "int256" + } + ], + "name": "PRBMath_SD59x18_Mul_Overflow", + "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": "decay", + "type": "uint256" + } + ], + "name": "DecayChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fee1e18", + "type": "uint256" + } + ], + "name": "FeeChanged", + "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": "from", + "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": "addr", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IGauge", + "name": "gauge", + "type": "address" + } + ], + "name": "bribeRates", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IGauge", + "name": "gauge", + "type": "address" + } + ], + "name": "bribeTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decayRate", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "emissionShare", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee1e9", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeMultiplier", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastWithdrawTimestamp", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "listedTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lpTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "ret", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "naturalBribes", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "m", + "type": "uint128" + } + ], + "name": "notifyBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "m", + "type": "uint128" + } + ], + "name": "notifyMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "m", + "type": "uint128" + } + ], + "name": "notifyWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "poolBalances", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolParams", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "relevantTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "setFeeToZero", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fee1e9_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "decayRate_", + "type": "uint256" + } + ], + "name": "setParam", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stake", + "outputs": [ + { + "internalType": "Token", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakeableTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "stakedTokens", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakedTokens", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "swapType", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenWeights", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Token", + "name": "tok", + "type": "bytes32" + } + ], + "name": "underlyingTokens", + "outputs": [ + { + "internalType": "Token[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IGauge", + "name": "gauge", + "type": "address" + }, + { + "internalType": "uint256", + "name": "elapsed", + "type": "uint256" + } + ], + "name": "velocore__bribe", + "outputs": [ + { + "internalType": "Token[]", + "name": "bribeTokens", + "type": "bytes32[]" + }, + { + "internalType": "int128[]", + "name": "deltaGauge", + "type": "int128[]" + }, + { + "internalType": "int128[]", + "name": "deltaPool", + "type": "int128[]" + }, + { + "internalType": "int128[]", + "name": "deltaExternal", + "type": "int128[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newEmissions", + "type": "uint256" + } + ], + "name": "velocore__emission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "Token[]", + "name": "tokens", + "type": "bytes32[]" + }, + { + "internalType": "int128[]", + "name": "r", + "type": "int128[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "velocore__execute", + "outputs": [ + { + "internalType": "int128[]", + "name": "", + "type": "int128[]" + }, + { + "internalType": "int128[]", + "name": "", + "type": "int128[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "Token[]", + "name": "tokens", + "type": "bytes32[]" + }, + { + "internalType": "int128[]", + "name": "amounts", + "type": "int128[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "velocore__gauge", + "outputs": [ + { + "internalType": "int128[]", + "name": "deltaGauge", + "type": "int128[]" + }, + { + "internalType": "int128[]", + "name": "deltaPool", + "type": "int128[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/velocore-v2/cpmm/abis/ConstantProductPoolFactory.json b/pkg/liquidity-source/velocore-v2/cpmm/abis/ConstantProductPoolFactory.json new file mode 100644 index 000000000..af593e1b9 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/abis/ConstantProductPoolFactory.json @@ -0,0 +1,192 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "vault_", + "type": "address" + }, + { + "internalType": "contract ConstantProductLibrary", + "name": "lib_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract ConstantProductPool", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "Token", + "name": "t1", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "Token", + "name": "t2", + "type": "bytes32" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "Token", + "name": "quoteToken", + "type": "bytes32" + }, + { + "internalType": "Token", + "name": "baseToken", + "type": "bytes32" + } + ], + "name": "deploy", + "outputs": [ + { + "internalType": "contract ConstantProductPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "begin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLength", + "type": "uint256" + } + ], + "name": "getPools", + "outputs": [ + { + "internalType": "contract ConstantProductPool[]", + "name": "pools", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ConstantProductPool", + "name": "", + "type": "address" + } + ], + "name": "isPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "poolList", + "outputs": [ + { + "internalType": "contract ConstantProductPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Token", + "name": "", + "type": "bytes32" + }, + { + "internalType": "Token", + "name": "", + "type": "bytes32" + } + ], + "name": "pools", + "outputs": [ + { + "internalType": "contract ConstantProductPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "decay_", + "type": "uint32" + } + ], + "name": "setDecay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "fee1e9_", + "type": "uint32" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pkg/liquidity-source/velocore-v2/cpmm/config.go b/pkg/liquidity-source/velocore-v2/cpmm/config.go new file mode 100644 index 000000000..03e84278c --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/config.go @@ -0,0 +1,11 @@ +package cpmm + +import vo "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" + +type Config struct { + DexID string `json:"dexID"` + ChainID vo.ChainID `json:"chainID"` + VaultAddress string `json:"vaultAddress"` + FactoryAddress string `json:"factoryAddress"` + NewPoolLimit int `json:"newPoolLimit"` +} diff --git a/pkg/liquidity-source/velocore-v2/cpmm/constant.go b/pkg/liquidity-source/velocore-v2/cpmm/constant.go new file mode 100644 index 000000000..8d41b805b --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/constant.go @@ -0,0 +1,39 @@ +package cpmm + +import "math/big" + +const ( + DexType = "velocore-v2-cpmm" + + factoryMethodPoolsLength = "poolsLength" + factoryMethodPoolList = "poolList" + + poolMethodPoolBalances = "poolBalances" + poolMethodRelevantTokens = "relevantTokens" + poolMethodTokenWeights = "tokenWeights" + poolMethodFee1e9 = "fee1e9" + poolMethodFeeMultiplier = "feeMultiplier" + + reserveZero = "0" +) + +const ( + // `maxPoolTokenNumber` is equal to `_MAX_TOKENS` (fixed in smart contract, which is 4) add 1 lp token. + // https://github.com/velocore/velocore-contracts/blob/master/src/pools/constant-product/ConstantProductPool.sol#L47 + maxPoolTokenNumber = 5 + + lpTokenNumber = 1 + + unknownInt = -1 +) + +var ( + // (1 << 127) - 1 + unknownBI = new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(127), nil), big.NewInt(1)) + + bigint1e9 = big.NewInt(1e9) +) + +var ( + defaultGas = Gas{Swap: 125000} +) diff --git a/pkg/liquidity-source/velocore-v2/cpmm/embed.go b/pkg/liquidity-source/velocore-v2/cpmm/embed.go new file mode 100644 index 000000000..38827c6d3 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/embed.go @@ -0,0 +1,9 @@ +package cpmm + +import _ "embed" + +//go:embed abis/ConstantProductPoolFactory.json +var factoryABIJson []byte + +//go:embed abis/ConstantProductPool.json +var poolABIJson []byte diff --git a/pkg/liquidity-source/velocore-v2/cpmm/pool_simulartor_test.go b/pkg/liquidity-source/velocore-v2/cpmm/pool_simulartor_test.go new file mode 100644 index 000000000..1466a5f85 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/pool_simulartor_test.go @@ -0,0 +1,266 @@ +package cpmm + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + + "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) { + t.Run("1. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, lp not involved" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocorev2-cpmm","type":"velocorev2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokenAmountIn = pool.TokenAmount{ + Token: "0xa219439258ca9da29e9cc4ce5596924745e12b93", + Amount: new(big.Int).Mul(bignumber.BONE, big.NewInt(23)), + } + tokenOut = "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1" + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.CalcAmountOut(pool.CalcAmountOutParams{ + TokenAmountIn: tokenAmountIn, + TokenOut: tokenOut, + }) + assert.Nil(t, err) + + assert.Equal(t, "1310912514297532736758", result.TokenAmountOut.Amount.String()) + + // simulator: -1310912514297532736758 + // contract: -1310912514297532736758 + }) + + t.Run("2. should return error", func(t *testing.T) { + desc := "pool 2 tokens, lp involved and known r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokenAmountIn = pool.TokenAmount{ + Token: "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca", + Amount: new(big.Int).Mul(big.NewInt(1e4), big.NewInt(4)), + } + tokenOut = "0xa219439258ca9da29e9cc4ce5596924745e12b93" + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + _, err = simulator.CalcAmountOut(pool.CalcAmountOutParams{ + TokenAmountIn: tokenAmountIn, + TokenOut: tokenOut, + }) + assert.ErrorIs(t, err, ErrNonPositiveAmountOut) + + // contract: 0 + }) + + t.Run("3. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, lp involved and unknown r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokenAmountIn = pool.TokenAmount{ + Token: "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", + Amount: new(big.Int).Mul(bignumber.BONE, big.NewInt(3)), + } + tokenOut = "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca" + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.CalcAmountOut(pool.CalcAmountOutParams{ + TokenAmountIn: tokenAmountIn, + TokenOut: tokenOut, + }) + assert.Nil(t, err) + + assert.Equal(t, "114875306101", result.TokenAmountOut.Amount.String()) + + // simulator: -114875306101 + // contract: -114875306101 + }) +} + +func TestVelocoreExecute(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, all token involved, lp token included, lp token known r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokens = []string{ + "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca", + "0xa219439258ca9da29e9cc4ce5596924745e12b93", + "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", + } + + r = []*big.Int{ + new(big.Int).Mul(big.NewInt(1e4), big.NewInt(-6996)), + unknownBI, + unknownBI, + } + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.velocoreExecute(tokens, r) + assert.Nil(t, err) + + assert.Equal(t, "7", result.R[1].String()) + assert.Equal(t, "908433084452167", result.R[2].String()) + + // contract: [-69960000,7,908433084452167] + }) + + t.Run("2. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, all token involved, lp token included, lp token unknown r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokens = []string{ + "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca", + "0xa219439258ca9da29e9cc4ce5596924745e12b93", + "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", + } + + r = []*big.Int{ + unknownBI, + unknownBI, + new(big.Int).Mul(big.NewInt(1e4), big.NewInt(-6996)), + } + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.velocoreExecute(tokens, r) + assert.Nil(t, err) + + assert.Equal(t, "7", result.R[0].String()) + assert.Equal(t, "0", result.R[1].String()) + + // contract: [7,0,-69960000] + }) +} + +func TestVelocoreExecuteFallback(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, all token involved, lp token included, lp token known r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokens = []string{ + "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca", + "0xa219439258ca9da29e9cc4ce5596924745e12b93", + "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", + } + + r = []*big.Int{ + new(big.Int).Mul(big.NewInt(1e4), big.NewInt(-6996)), + unknownBI, + unknownBI, + } + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.velocoreExecuteFallback(tokens, r) + assert.Nil(t, err) + + assert.Equal(t, "5", result.R[1].String()) + assert.Equal(t, "908433163091179", result.R[2].String()) + + // contract: [-69960000,5,908433163091179] + }) + + t.Run("2. should return correct value", func(t *testing.T) { + desc := "pool 2 tokens, all token involved, lp token included, lp token unknown r" + t.Log(desc) + + // block 659738, linea + + entityPoolStr := `{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","exchange":"velocore-v2-cpmm","type":"velocore-v2-cpmm","timestamp":1697617544,"reserves":["5192296858534827628430578339644164","7774767","1310912514297980345043"],"tokens":[{"address":"0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca","swappable":true},{"address":"0xa219439258ca9da29e9cc4ce5596924745e12b93","swappable":true},{"address":"0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1","swappable":true}],"extra":"{\"fee1e9\":10000000,\"feeMultiplier\":0}","staticExtra":"{\"poolTokenNumber\":3,\"weights\":[2,1,1]}"}` + var entityPool entity.Pool + err := json.Unmarshal([]byte(entityPoolStr), &entityPool) + assert.Nil(t, err) + + var ( + tokens = []string{ + "0x515ac85ef7d21b5033a0ad71b194d4c52661b8ca", + "0xa219439258ca9da29e9cc4ce5596924745e12b93", + "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", + } + + r = []*big.Int{ + unknownBI, + unknownBI, + new(big.Int).Mul(bigint1e9, big.NewInt(-6996)), + } + ) + + simulator, err := NewPoolSimulator(entityPool) + assert.Nil(t, err) + + result, err := simulator.velocoreExecuteFallback(tokens, r) + assert.Nil(t, err) + + assert.Equal(t, "538774", result.R[0].String()) + assert.Equal(t, "0", result.R[1].String()) + + // contract: [538774,0,-6996000000000] + }) +} diff --git a/pkg/liquidity-source/velocore-v2/cpmm/pool_simulator.go b/pkg/liquidity-source/velocore-v2/cpmm/pool_simulator.go new file mode 100644 index 000000000..25221d3b3 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/pool_simulator.go @@ -0,0 +1,959 @@ +package cpmm + +import ( + "encoding/json" + "errors" + "math/big" + "strings" + + "github.com/KyberNetwork/blockchain-toolkit/integer" + "github.com/KyberNetwork/blockchain-toolkit/number" + "github.com/holiman/uint256" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/math" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/math/sd59x18" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" +) + +var ( + ErrInvalidToken = errors.New("invalid token") + ErrInvalidTokenGrowth = errors.New("invalid token growth") + ErrInvalidR = errors.New("invalid r") + ErrNotFoundR = errors.New("r not found") + ErrNonPositiveAmountOut = errors.New("non positive amount out") +) + +type PoolSimulator struct { + pool.Pool + + poolTokenNumber uint + weights []*big.Int + sumWeight *big.Int + + fee1e9 uint32 + feeMultiplier *big.Int + + isLastWithdrawInTheSameBlock bool + + vault string + nativeTokenIndex int +} + +func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { + var ( + extra Extra + staticExtra StaticExtra + + tokens []string + reserves []*big.Int + ) + + if err := json.Unmarshal([]byte(entityPool.Extra), &extra); err != nil { + return nil, err + } + + if err := json.Unmarshal([]byte(entityPool.StaticExtra), &staticExtra); err != nil { + return nil, err + } + + for idx := 0; idx < len(entityPool.Tokens); idx++ { + tokens = append(tokens, entityPool.Tokens[idx].Address) + reserves = append(reserves, bignumber.NewBig10(entityPool.Reserves[idx])) + } + + info := pool.PoolInfo{ + Address: strings.ToLower(entityPool.Address), + ReserveUsd: entityPool.ReserveUsd, + Exchange: entityPool.Exchange, + Type: entityPool.Type, + Tokens: tokens, + Reserves: reserves, + Checked: true, + } + + return &PoolSimulator{ + Pool: pool.Pool{Info: info}, + poolTokenNumber: staticExtra.PoolTokenNumber, + weights: staticExtra.Weights, + sumWeight: staticExtra.Weights[0], + fee1e9: extra.Fee1e9, + feeMultiplier: extra.FeeMultiplier, + isLastWithdrawInTheSameBlock: false, + vault: staticExtra.Vault, + nativeTokenIndex: staticExtra.NativeTokenIndex, + }, nil +} + +func (p *PoolSimulator) CalcAmountOut(params pool.CalcAmountOutParams) (*pool.CalcAmountOutResult, error) { + tokenAmountIn, tokenOut := params.TokenAmountIn, params.TokenOut + + tokens, r := p.newVelocoreExecuteParams(tokenAmountIn, tokenOut) + + result, err := p.velocoreExecute(tokens, r) + if err != nil { + return nil, err + } + + amountOut := integer.Zero() + for i, token := range tokens { + if token == tokenOut { + amountOut = new(big.Int).Neg(result.R[i]) + break + } + } + + if amountOut.Cmp(integer.Zero()) <= 0 { + return nil, ErrNonPositiveAmountOut + } + + swapInfo := SwapInfo{ + IsFeeMultiplierUpdated: result.IsFeeMultiplierUpdated, + FeeMultiplier: result.FeeMultiplier.String(), + } + + return &pool.CalcAmountOutResult{ + TokenAmountOut: &pool.TokenAmount{ + Token: tokenOut, + Amount: amountOut, + }, + Fee: &pool.TokenAmount{}, + Gas: defaultGas.Swap, + SwapInfo: swapInfo, + }, nil +} + +func (p *PoolSimulator) UpdateBalance(params pool.UpdateBalanceParams) { + for idx, token := range p.Info.Tokens { + if token == params.TokenAmountIn.Token { + p.Info.Reserves[idx] = new(big.Int).Add(p.Info.Reserves[idx], params.TokenAmountIn.Amount) + } + + if token == params.TokenAmountOut.Token { + p.Info.Reserves[idx] = new(big.Int).Sub(p.Info.Reserves[idx], params.TokenAmountOut.Amount) + } + } + + swapInfo, ok := params.SwapInfo.(SwapInfo) + if ok && swapInfo.IsFeeMultiplierUpdated { + p.feeMultiplier, _ = new(big.Int).SetString(swapInfo.FeeMultiplier, 10) + p.isLastWithdrawInTheSameBlock = true + } +} + +func (p *PoolSimulator) GetMetaInfo(_ string, _ string) interface{} { + return Meta{ + Vault: p.vault, + NativeTokenIndex: p.nativeTokenIndex, + } +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/src/pools/constant-product/ConstantProductPool.sol#L164 +func (p *PoolSimulator) velocoreExecute(tokens []string, r []*big.Int) (*velocoreExecuteResult, error) { + effectiveFee1e9 := p.getEffectiveFee1e9() + iLp := unknownInt + + // balances of "tokens" + a, err := p.getPoolBalances(tokens) + if err != nil { + return nil, err + } + + // weights of "tokens" + weights := make([]*big.Int, len(tokens)) + for i, token := range tokens { + if p.isLpToken(token) { + weights[i] = p.sumWeight // p.weights[0] + iLp = i + continue + } + + weights[i], _ = p.getTokenWeight(token) + a[i] = new(big.Int).Add(a[i], integer.One()) + } + + var ( + invariantMin, invariantMax *big.Int + k = bignumber.BONE + lpInvolved = iLp != unknownInt + lpUnknown = lpInvolved && (r[iLp].Cmp(unknownBI) == 0) + ) + + // calculate "k" + // "k" is used to split "r" into taxable and non-taxable components + if lpInvolved { + _, invariantMin, invariantMax, err = p.getInvariant() + if err != nil { + return nil, err + } + if lpUnknown { + kw := integer.Zero() + for i := range tokens { + if r[i].Cmp(unknownBI) == 0 { + if i != iLp { + kw = new(big.Int).Add(kw, weights[i]) + } + continue + } + balanceRatio := new(big.Int).Quo( + new(big.Int).Mul(new(big.Int).Add(a[i], r[i]), bignumber.BONE), + a[i], + ) + k = new(big.Int).Add(k, new(big.Int).Mul(weights[i], balanceRatio)) + } + k = new(big.Int).Quo(k, new(big.Int).Sub(p.sumWeight, kw)) + } else { + k = new(big.Int).Quo( + new(big.Int).Mul(bignumber.BONE, new(big.Int).Sub(invariantMax, r[iLp])), + invariantMax, + ) + } + } + + // calculate "requestedGrowth1e18" + // which equals to: + // Pi[ ((b - fee(b - a)) / a) ^ w ] + var ( + requestedGrowth1e18 = bignumber.BONE + sumUnknownWeight = integer.Zero() + sumKnownWeight = integer.Zero() + ) + for i := range tokens { + if r[i].Cmp(unknownBI) == 0 { + if i != iLp { + sumUnknownWeight = new(big.Int).Add(sumUnknownWeight, weights[i]) + } + continue + } + + var tokenGrowth1e18 *big.Int + if i == iLp { + newInvariant := new(big.Int).Sub(invariantMax, r[iLp]) + tokenGrowth1e18 = new(big.Int).Quo( + new(big.Int).Mul(bignumber.BONE, invariantMin), + newInvariant, + ) + } else { + sumKnownWeight = new(big.Int).Add(sumKnownWeight, weights[i]) + b := new(big.Int).Add(a[i], r[i]) + + var ( + aPrime *big.Int + bPrime *big.Int + fee = integer.Zero() + ) + if k.Cmp(bignumber.BONE) > 0 { + aPrime = a[i] + bPrime = new(big.Int).Quo(new(big.Int).Mul(b, bignumber.BONE), k) + } else { + aPrime = new(big.Int).Quo(new(big.Int).Mul(a[i], k), bignumber.BONE) + bPrime = b + } + + if bPrime.Cmp(aPrime) > 0 { + fee = math.Common.CeilDivUnsafe( + uint256.MustFromBig(new(big.Int).Mul(new(big.Int).Sub(bPrime, aPrime), effectiveFee1e9)), + uint256.NewInt(1e9), + ).ToBig() + } + + tokenGrowth1e18 = new(big.Int).Quo( + new(big.Int).Mul(bignumber.BONE, new(big.Int).Sub(b, fee)), + a[i], + ) + } + + oneHundred := big.NewInt(100) + lo := new(big.Int).Quo(bignumber.BONE, oneHundred) // 0.01e18 + hi := new(big.Int).Mul(bignumber.BONE, oneHundred) // 100e18 + if tokenGrowth1e18.Cmp(lo) <= 0 || tokenGrowth1e18.Cmp(hi) >= 0 { + return p.velocoreExecuteFallback(tokens, r) + } + + rpow, err := math.Common.RPow( + uint256.MustFromBig(tokenGrowth1e18), + uint256.MustFromBig(weights[i]), + number.Number_1e18, + ) + if err != nil { + return nil, err + } + requestedGrowth1e18 = new(big.Int).Quo( + new(big.Int).Mul(requestedGrowth1e18, rpow.ToBig()), + bignumber.BONE, + ) + + if requestedGrowth1e18.Cmp(integer.Zero()) <= 0 { + return nil, ErrInvalidTokenGrowth + } + } + + // requestedGrowth1e18 + { + unaccountedFeeAsGrowth1e18 := bignumber.BONE + if k.Cmp(bignumber.BONE) < 0 { + x := new(big.Int).Sub( + bignumber.BONE, + new(big.Int).Quo( + new(big.Int).Mul(new(big.Int).Sub(bignumber.BONE, k), effectiveFee1e9), + bigint1e9, + ), + ) + n := new(big.Int).Sub(new(big.Int).Sub(p.sumWeight, sumUnknownWeight), sumKnownWeight) + u, err := math.Common.RPow( + uint256.MustFromBig(x), + uint256.MustFromBig(n), + number.Number_1e18, + ) + if err != nil { + return nil, err + } + unaccountedFeeAsGrowth1e18 = u.ToBig() + } + + requestedGrowth1e18 = new(big.Int).Quo( + new(big.Int).Mul(requestedGrowth1e18, unaccountedFeeAsGrowth1e18), + bignumber.BONE, + ) + } + + var g_, g *big.Int + + { + w := sumUnknownWeight + if lpUnknown { + w = new(big.Int).Sub(w, p.sumWeight) + } + if w.Cmp(integer.Zero()) == 0 { + return nil, ErrInvalidR + } + + g_, g, err = powReciprocal(requestedGrowth1e18, new(big.Int).Neg(w)) + if err != nil { + return nil, err + } + } + + // calculate unknown "r_i" + for i := range tokens { + if r[i].Cmp(unknownBI) != 0 { + continue + } + + if i != iLp { + bU256, err := math.Common.CeilDiv( + uint256.MustFromBig(new(big.Int).Mul(g, a[i])), + number.Number_1e18, + ) + if err != nil { + return nil, err + } + b := bU256.ToBig() + + var ( + fee = integer.Zero() + aPrime, bPrime *big.Int + ) + if k.Cmp(bignumber.BONE) > 0 { + aPrime = a[i] + _bPrime, err := math.Common.CeilDiv( + uint256.MustFromBig(new(big.Int).Mul(b, bignumber.BONE)), + uint256.MustFromBig(k), + ) + if err != nil { + return nil, err + } + bPrime = _bPrime.ToBig() + } else { + aPrime = new(big.Int).Quo(new(big.Int).Mul(a[i], k), bignumber.BONE) + bPrime = b + } + + if bPrime.Cmp(aPrime) > 0 { + bPrimeMinusAPrime := new(big.Int).Sub(bPrime, aPrime) + + v, err := math.Common.CeilDiv( + uint256.MustFromBig(new(big.Int).Mul(bPrimeMinusAPrime, bigint1e9)), + uint256.MustFromBig(new(big.Int).Sub(bigint1e9, effectiveFee1e9)), + ) + if err != nil { + return nil, err + } + fee = new(big.Int).Sub(v.ToBig(), bPrimeMinusAPrime) + } + + r[i] = new(big.Int).Sub(new(big.Int).Add(b, fee), a[i]) + + continue + } + + // case unknown lp token "r" + + b := new(big.Int).Quo(new(big.Int).Mul(g_, invariantMin), bignumber.BONE) + r[i] = new(big.Int).Neg(new(big.Int).Sub(b, invariantMax)) + } + + var ( + isFeeMultiplierUpdated bool + newFeeMultiplier = integer.Zero() + ) + if lpInvolved && r[iLp].Cmp(integer.Zero()) > 0 { + newFeeMultiplier = p.feeMultiplier + if !p.isLastWithdrawInTheSameBlock { + newFeeMultiplier = bigint1e9 + } + newFeeMultiplier = new(big.Int).Quo( + new(big.Int).Mul(newFeeMultiplier, invariantMax), + new(big.Int).Sub(invariantMax, r[iLp]), + ) + isFeeMultiplierUpdated = true + } + + return &velocoreExecuteResult{ + Tokens: tokens, + R: r, + FeeMultiplier: newFeeMultiplier, + IsFeeMultiplierUpdated: isFeeMultiplierUpdated, + }, nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/src/pools/constant-product/ConstantProductLibrary.sol#L25 +func (p *PoolSimulator) velocoreExecuteFallback(tokens []string, r_ []*big.Int) (*velocoreExecuteResult, error) { + var ( + t = p.Info.Tokens + a = p.Info.Reserves + idx = make([]int, len(tokens)) + w = p.weights + + err error + ) + + fee1e18 := new(big.Int).Mul(big.NewInt(int64(p.fee1e9)), bigint1e9) + if p.isLastWithdrawInTheSameBlock { + fee1e18 = new(big.Int).Quo(new(big.Int).Mul(fee1e18, p.feeMultiplier), bigint1e9) + } + additionalMultiplier := bigint1e9 + + r := make([]*big.Int, len(t)) + for i := range r { + r[i] = integer.Zero() + } + j := 1 + for i, token := range tokens { + if tokens[i] == t[0] { + idx[i] = 0 + r[0] = r_[i] + } else { + for token != t[j] { + j++ + } + idx[i] = j + r[j] = r_[i] + } + } + + for i := 1; i < len(w); i++ { + a[i] = new(big.Int).Add(a[i], integer.One()) + } + + // pre convert + var ( + r_SD59x18 = make([]*sd59x18.SD59x18, len(r)) + a_SD59x18 = make([]*sd59x18.SD59x18, len(a)) + w_SD59x18 = make([]*sd59x18.SD59x18, len(w)) + ) + for i := 0; i < int(p.poolTokenNumber); i++ { + var err error + if r[i].Cmp(unknownBI) != 0 { + if r_SD59x18[i], err = sd59x18.ConvertSD59x18(r[i]); err != nil { + return nil, err + } + } + if a_SD59x18[i], err = sd59x18.ConvertSD59x18(a[i]); err != nil { + return nil, err + } + if w_SD59x18[i], err = sd59x18.ConvertSD59x18(w[i]); err != nil { + return nil, err + } + } + + logA := make([]*sd59x18.SD59x18, len(w)) + + logInvariantMin := sd59x18.Zero + for i := 1; i < len(w); i++ { + logA[i], err = new(sd59x18.SD59x18).Log2(a_SD59x18[i]) + if err != nil { + return nil, err + } + + logInvariantMin, err = sd59x18.NewExpr(logA[i]).Mul(w_SD59x18[i]).Add(logInvariantMin).Result() + if err != nil { + return nil, err + } + } + + logInvariantMin, err = new(sd59x18.SD59x18).Div(logInvariantMin, w_SD59x18[0]) + if err != nil { + return nil, err + } + + var ( + logK = sd59x18.Zero + logGrowth = sd59x18.Zero + sumUnknownWeight = sd59x18.Zero + ) + if r[0].Cmp(unknownBI) == 0 { + kw := integer.Zero() + for i := 1; i < len(w); i++ { + if r[i].Cmp(unknownBI) == 0 { + kw = new(big.Int).Add(kw, w[i]) + continue + } + + ai_add_ri_sd59x18, err := sd59x18.ConvertSD59x18(new(big.Int).Add(a[i], r[i])) + if err != nil { + return nil, err + } + + logK, err = sd59x18.NewExpr(ai_add_ri_sd59x18). + Log2(). + Mul(w_SD59x18[i]). + Sub(logA[i]). + Add(logK).Result() + if err != nil { + return nil, err + } + } + + w0_sub_kw_sd59x18, err := sd59x18.ConvertSD59x18(new(big.Int).Sub(w[0], kw)) + if err != nil { + return nil, err + } + logK, err = new(sd59x18.SD59x18).Div(logK, w0_sub_kw_sd59x18) + if err != nil { + return nil, err + } + sumUnknownWeight = new(sd59x18.SD59x18).Sub(sumUnknownWeight, w_SD59x18[0]) + + } else if r[0].Cmp(integer.Zero()) != 0 { + x, err := sd59x18.NewExpr(logInvariantMin).Exp2().Sub(r_SD59x18[0]).Result() + if err != nil { + return nil, err + } + + one_sd59x18, err := sd59x18.ConvertSD59x18(integer.One()) + if err != nil { + return nil, err + } + + v := new(sd59x18.SD59x18).Ternary( + new(sd59x18.SD59x18).Lt(x, one_sd59x18), + one_sd59x18, + x, + ) + + logK, err = sd59x18.NewExpr(v).Log2().Sub(logInvariantMin).Result() + if err != nil { + return nil, err + } + + if new(sd59x18.SD59x18).Lt(logK, sd59x18.Zero) { + t, err := sd59x18.NewExpr(logK).Neg().Exp2().Result() + if err != nil { + return nil, err + } + + additionalMultiplier = new(big.Int).Quo(sd59x18.IntoInt256(t), bigint1e9) + } + + logGrowth, err = sd59x18.NewExpr(logK).Neg().Mul(w_SD59x18[0]).Result() + if err != nil { + return nil, err + } + } + + k, err := new(sd59x18.SD59x18).Exp2(logK) + if err != nil { + return nil, err + } + + for i := 1; i < len(w); i++ { + if r[i].Cmp(unknownBI) == 0 { + sumUnknownWeight = new(sd59x18.SD59x18).Add(sumUnknownWeight, w_SD59x18[i]) + continue + } + + b, err := sd59x18.ConvertSD59x18(new(big.Int).Add(a[i], r[i])) + if err != nil { + return nil, err + } + + fee := sd59x18.Zero + + aPrime, err := new(sd59x18.SD59x18).Mul( + a_SD59x18[i], + new(sd59x18.SD59x18).Ternary( + new(sd59x18.SD59x18).Gt(logK, sd59x18.Zero), + sd59x18.SD(bignumber.BONE), + k, + ), + ) + if err != nil { + return nil, err + } + + bPrime, err := new(sd59x18.SD59x18).Div( + b, + new(sd59x18.SD59x18).Ternary( + new(sd59x18.SD59x18).Gt(logK, sd59x18.Zero), + k, + sd59x18.SD(bignumber.BONE), + ), + ) + if err != nil { + return nil, err + } + + if new(sd59x18.SD59x18).Gt(bPrime, aPrime) { + fee, err = sd59x18.NewExpr(bPrime).Sub(aPrime).Mul(sd59x18.SD(fee1e18)).Result() + if err != nil { + return nil, err + } + } + + logGrowth, err = sd59x18.NewExpr(b).Sub(fee).Log2().Sub(logA[i]).Mul(w_SD59x18[i]).Add(logGrowth).Result() + if err != nil { + return nil, err + } + } + + logG, err := sd59x18.NewExpr(logGrowth).Neg().Div(sumUnknownWeight).Result() + if err != nil { + return nil, err + } + + for i := 0; i < len(w); i++ { + if r[i].Cmp(unknownBI) != 0 { + continue + } + + if i != 0 { + logB, err := sd59x18.NewExpr(logG).Add(logA[i]).Add(sd59x18.SD(big.NewInt(100000))).Result() + if err != nil { + return nil, err + } + + b, err := new(sd59x18.SD59x18).Exp2(logB) + if err != nil { + return nil, err + } + + aPrime, err := new(sd59x18.SD59x18).Mul( + a_SD59x18[i], + new(sd59x18.SD59x18).Ternary( + new(sd59x18.SD59x18).Gt(logK, sd59x18.Zero), + sd59x18.SD(bignumber.BONE), + k, + ), + ) + if err != nil { + return nil, err + } + + bPrime, err := new(sd59x18.SD59x18).Div( + b, + new(sd59x18.SD59x18).Ternary( + new(sd59x18.SD59x18).Gt(logK, sd59x18.Zero), + k, + sd59x18.SD(bignumber.BONE), + ), + ) + if err != nil { + return nil, err + } + + if new(sd59x18.SD59x18).Gt(bPrime, aPrime) { + b, err = sd59x18.NewExpr(bPrime).Sub(aPrime).Div( + new(sd59x18.SD59x18).Sub(sd59x18.SD(bignumber.BONE), sd59x18.SD(fee1e18)), + ).Add(b).Sub(new(sd59x18.SD59x18).Sub(bPrime, aPrime)).Result() + if err != nil { + return nil, err + } + } + + r[i] = sd59x18.ConvertBI(new(sd59x18.SD59x18).Sub(b, a_SD59x18[i])) + + continue + } + + logB := new(sd59x18.SD59x18).Add(logG, logInvariantMin) + t, err := sd59x18.NewExpr(logB).Exp2().SubExpr( + sd59x18.NewExpr(logInvariantMin).Exp2(), + ).Result() + if err != nil { + return nil, err + } + r[i] = new(big.Int).Neg(sd59x18.ConvertBI(t)) + + if new(sd59x18.SD59x18).Lt(logG, sd59x18.Zero) { + u, err := sd59x18.NewExpr(logG).Neg().Exp2().Result() + if err != nil { + return nil, err + } + additionalMultiplier = new(big.Int).Quo( + sd59x18.IntoInt256(u), + bigint1e9, + ) + } + } + + var ( + isFeeMultiplierUpdated bool + newFeeMultiplier = integer.Zero() + ) + if additionalMultiplier.Cmp(bigint1e9) > 0 { + newFeeMultiplier = additionalMultiplier + if p.isLastWithdrawInTheSameBlock { + newFeeMultiplier = new(big.Int).Quo( + new(big.Int).Mul(additionalMultiplier, p.feeMultiplier), + bigint1e9, + ) + } + isFeeMultiplierUpdated = true + } + for i := range tokens { + r_[i] = r[idx[i]] + } + + return &velocoreExecuteResult{ + Tokens: tokens, + R: r_, + FeeMultiplier: newFeeMultiplier, + IsFeeMultiplierUpdated: isFeeMultiplierUpdated, + }, nil +} + +func (p *PoolSimulator) getEffectiveFee1e9() *big.Int { + effectiveFee1e9 := big.NewInt(int64(p.fee1e9)) + if !p.isLastWithdrawInTheSameBlock { + return effectiveFee1e9 + } + effectiveFee1e9 = new(big.Int).Quo( + new(big.Int).Mul(effectiveFee1e9, p.feeMultiplier), + bigint1e9, + ) + return effectiveFee1e9 +} + +func (p *PoolSimulator) getPoolBalances(tokens []string) ([]*big.Int, error) { + tokenToReserve := make(map[string]*big.Int) + for i, token := range p.Info.Tokens { + tokenToReserve[token] = p.Info.Reserves[i] + } + var balances []*big.Int + for _, token := range tokens { + balance, ok := tokenToReserve[token] + if !ok { + return nil, ErrInvalidToken + } + balances = append(balances, balance) + } + return balances, nil +} + +func (p *PoolSimulator) newVelocoreExecuteParams( + tokenAmountIn pool.TokenAmount, + tokenOut string, +) ([]string, []*big.Int) { + tokens := []string{tokenAmountIn.Token, tokenOut} + amounts := []*big.Int{tokenAmountIn.Amount, unknownBI} + return tokens, amounts +} + +func (p *PoolSimulator) isLpToken(token string) bool { + return token == p.Info.Address +} + +func (p *PoolSimulator) getTokenWeight(token string) (*big.Int, error) { + for i, tok := range p.Info.Tokens { + if tok == token { + return p.weights[i], nil + } + } + return nil, ErrInvalidToken +} + +func (p *PoolSimulator) getInvariant() (*big.Int, *big.Int, *big.Int, error) { + /* + https://docs.velocore.xyz/technical-docs/pool-specifics/generalized-cpmm#calculating-unknown-dimensions-in-a-zero-fee-scenario + + So we have the following equation: + D = [Pi(xi^wi)]^(1/sum(wi)) (i=1..n) + */ + + balances := p.Info.Reserves + if p.poolTokenNumber-lpTokenNumber == 2 && p.weights[1].Cmp(p.weights[2]) == 0 { + prod := new(big.Int).Mul( + new(big.Int).Add(balances[1], integer.One()), + new(big.Int).Add(balances[2], integer.One()), + ) + inv := new(big.Int).Sqrt(prod) + ret0 := balances[0] + invariantMin := inv + invariantMax := inv + if new(big.Int).Mul(inv, inv).Cmp(prod) < 0 { + invariantMax = new(big.Int).Add(inv, integer.One()) + } + return ret0, invariantMin, invariantMax, nil + } + + logInvariant := sd59x18.Zero + for i := 1; i < len(p.weights); i++ { + b, err := sd59x18.ConvertSD59x18(new(big.Int).Add(balances[i], integer.One())) + if err != nil { + return nil, nil, nil, err + } + g, err := new(sd59x18.SD59x18).Log2(b) + if err != nil { + return nil, nil, nil, err + } + + w, err := sd59x18.ConvertSD59x18(p.weights[i]) + if err != nil { + return nil, nil, nil, err + } + + logInvariant, err = sd59x18.NewExpr(g).Mul(w).Add(logInvariant).Result() + if err != nil { + return nil, nil, nil, err + } + } + + sumW, err := sd59x18.ConvertSD59x18(p.sumWeight) + if err != nil { + return nil, nil, nil, err + } + + logInvariant, err = new(sd59x18.SD59x18).Div(logInvariant, sumW) + if err != nil { + return nil, nil, nil, err + } + + inv, err := new(sd59x18.SD59x18).Exp2(logInvariant) + if err != nil { + return nil, nil, nil, err + } + + invMin := sd59x18.ConvertBI(inv) + + var invMax *big.Int + { + x, err := math.Common.CeilDiv( + new(uint256.Int).Mul( + uint256.MustFromBig(invMin), + uint256.NewInt(1e18+1e5), + ), + number.Number_1e18, + ) + if err != nil { + return nil, nil, nil, err + } + + invMax = new(big.Int).Add(x.ToBig(), integer.One()) + } + + return integer.Zero(), invMin, invMax, nil +} + +func powReciprocal(x1e18, n *big.Int) (*big.Int, *big.Int, error) { + if n.Cmp(integer.Zero()) == 0 || x1e18.Cmp(bignumber.BONE) == 0 { + return bignumber.BONE, bignumber.BONE, nil + } + + if n.Cmp(integer.One()) == 0 { + return x1e18, x1e18, nil + } + + if n.Cmp(new(big.Int).Neg(integer.One())) == 0 { + bigint1e18Square := new(big.Int).Mul(bignumber.BONE, bignumber.BONE) + + v := math.Common.CeilDivUnsafe( + uint256.MustFromBig(bigint1e18Square), + uint256.MustFromBig(x1e18), + ) + + return new(big.Int).Quo(bigint1e18Square, x1e18), v.ToBig(), nil + } + + if n.Cmp(integer.Two()) == 0 { + x1e18Mul1e18 := new(big.Int).Mul(x1e18, bignumber.BONE) + s := new(big.Int).Sqrt(x1e18Mul1e18) + if new(big.Int).Mul(s, s).Cmp(x1e18Mul1e18) < 0 { + return s, new(big.Int).Add(s, integer.One()), nil + } + return s, s, nil + } + + if n.Cmp(new(big.Int).Neg(integer.Two())) == 0 { + x1e18Mul1e18 := new(big.Int).Mul(x1e18, bignumber.BONE) + s := new(big.Int).Sqrt(x1e18Mul1e18) + ss := s + if new(big.Int).Mul(s, s).Cmp(x1e18Mul1e18) < 0 { + ss = new(big.Int).Add(s, integer.One()) + } + square1e18 := new(big.Int).Mul(bignumber.BONE, bignumber.BONE) + + v, err := math.Common.CeilDiv( + uint256.MustFromBig(square1e18), + uint256.MustFromBig(s), + ) + if err != nil { + return nil, nil, err + } + + return new(big.Int).Quo(square1e18, ss), v.ToBig(), nil + } + + var raw *big.Int + { + x := sd59x18.SD(x1e18) + n_sd59x18, err := sd59x18.ConvertSD59x18(n) + if err != nil { + return nil, nil, err + } + y, err := new(sd59x18.SD59x18).Div(sd59x18.SD(bignumber.BONE), n_sd59x18) + if err != nil { + return nil, nil, err + } + z, err := new(sd59x18.SD59x18).Pow(x, y) + if err != nil { + return nil, nil, err + } + + raw = sd59x18.IntoInt256(z) + } + + var maxError *big.Int + { + v, err := math.Common.CeilDiv( + new(uint256.Int).Mul( + uint256.MustFromBig(raw), + uint256.NewInt(1e4), + ), + number.Number_1e18, + ) + if err != nil { + return nil, nil, err + } + + maxError = new(big.Int).Add(v.ToBig(), integer.One()) + } + + ret0 := integer.Zero() + if raw.Cmp(maxError) >= 0 { + ret0 = new(big.Int).Sub(raw, maxError) + } + ret1 := new(big.Int).Add(raw, maxError) + return ret0, ret1, nil +} diff --git a/pkg/liquidity-source/velocore-v2/cpmm/pool_tracker.go b/pkg/liquidity-source/velocore-v2/cpmm/pool_tracker.go new file mode 100644 index 000000000..f1688924c --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/pool_tracker.go @@ -0,0 +1,112 @@ +package cpmm + +import ( + "context" + "encoding/json" + "math/big" + "time" + + "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/logger" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" +) + +type PoolTracker struct { + ethrpcClient *ethrpc.Client +} + +func NewPoolTracker( + ethrpcClient *ethrpc.Client, +) (*PoolTracker, error) { + return &PoolTracker{ + ethrpcClient: ethrpcClient, + }, nil +} + +func (d *PoolTracker) GetNewPoolState(ctx context.Context, p entity.Pool, _ pool.GetNewPoolStateParams) (entity.Pool, error) { + logger.WithFields(logger.Fields{ + "pool": p.Address, + "type": DexType, + }).Info("start getting new state of pool") + defer func(s time.Time) { + logger.WithFields(logger.Fields{ + "pool": p.Address, + "type": DexType, + "exec": time.Since(s).String(), + }).Info("finish getting new state of pool") + }(time.Now()) + + var ( + reserves [maxPoolTokenNumber]*big.Int + fee1e9 uint32 + feeMultiplier *big.Int + ) + + req := d.ethrpcClient.R() + + req.AddCall(ðrpc.Call{ + ABI: poolABI, + Target: p.Address, + Method: poolMethodPoolBalances, + Params: nil, + }, []interface{}{&reserves}) + + req.AddCall(ðrpc.Call{ + ABI: poolABI, + Target: p.Address, + Method: poolMethodFee1e9, + Params: nil, + }, []interface{}{&fee1e9}) + + req.AddCall(ðrpc.Call{ + ABI: poolABI, + Target: p.Address, + Method: poolMethodFeeMultiplier, + Params: nil, + }, []interface{}{&feeMultiplier}) + + _, err := req.Aggregate() + if err != nil { + logger.WithFields(logger.Fields{ + "pool": p.Address, + "type": DexType, + }).Error(err.Error()) + return entity.Pool{}, err + } + + var ( + staticExtra StaticExtra + poolReserves entity.PoolReserves + ) + if err := json.Unmarshal([]byte(p.StaticExtra), &staticExtra); err != nil { + logger.WithFields(logger.Fields{ + "pool": p.Address, + "type": DexType, + }).Error(err.Error()) + return entity.Pool{}, err + } + for i := 0; i < int(staticExtra.PoolTokenNumber); i++ { + poolReserves = append(poolReserves, reserves[i].String()) + } + + extra := Extra{ + Fee1e9: fee1e9, + FeeMultiplier: feeMultiplier, + } + extraBytes, err := json.Marshal(extra) + if err != nil { + logger.WithFields(logger.Fields{ + "pool": p.Address, + "type": DexType, + }).Error(err.Error()) + return entity.Pool{}, err + } + + p.Reserves = poolReserves + p.Extra = string(extraBytes) + p.Timestamp = time.Now().Unix() + + return p, nil +} diff --git a/pkg/liquidity-source/velocore-v2/cpmm/pools_list_updater.go b/pkg/liquidity-source/velocore-v2/cpmm/pools_list_updater.go new file mode 100644 index 000000000..d85cd3a20 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/pools_list_updater.go @@ -0,0 +1,290 @@ +package cpmm + +import ( + "context" + "encoding/json" + "errors" + "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" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" +) + +var ErrWETHNotFound = errors.New("WETH not found") + +type PoolsListUpdater struct { + config *Config + ethrpcClient *ethrpc.Client +} + +func NewPoolsListUpdater( + cfg *Config, + ethrpcClient *ethrpc.Client, +) *PoolsListUpdater { + return &PoolsListUpdater{ + config: cfg, + ethrpcClient: ethrpcClient, + } +} + +func (d *PoolsListUpdater) InitPool(_ context.Context) error { + return nil +} + +func (d *PoolsListUpdater) GetNewPools(ctx context.Context, metadataBytes []byte) ([]entity.Pool, []byte, error) { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Info("start get new pools") + + var offset, newPools int + + defer func(s time.Time) { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + "offset": offset, + "newPools": newPools, + "duration": time.Since(s).String(), + }).Info("finish get new pools") + }(time.Now()) + + ctx = util.NewContextWithTimestamp(ctx) + + totalNumberOfPools, err := d.getPoolsLength(ctx) + if err != nil { + return nil, metadataBytes, err + } + + offset, err = d.getOffset(metadataBytes) + if err != nil { + return nil, metadataBytes, err + } + + batchSize := getBatchSize(totalNumberOfPools, d.config.NewPoolLimit, offset) + if batchSize == 0 { + return nil, metadataBytes, nil + } + + poolAddresses, err := d.queryPoolAddresses(ctx, offset, batchSize) + if err != nil { + return nil, metadataBytes, err + } + + pools, err := d.processBatch(ctx, poolAddresses) + if err != nil { + return nil, metadataBytes, err + } + newPools = len(pools) + + newMetadataBytes, err := d.newMetadata(offset + batchSize) + if err != nil { + return nil, metadataBytes, err + } + + return pools, newMetadataBytes, nil +} + +func (d *PoolsListUpdater) processBatch(ctx context.Context, poolAddresses []common.Address) ([]entity.Pool, error) { + var ( + limit = len(poolAddresses) + pools = make([]entity.Pool, 0, len(poolAddresses)) + + tokens = make([][maxPoolTokenNumber]bytes32, limit) + weights = make([][maxPoolTokenNumber]*big.Int, limit) + ) + + req := d.ethrpcClient.R() + for i := 0; i < limit; i++ { + req.AddCall(ðrpc.Call{ + ABI: poolABI, + Target: poolAddresses[i].Hex(), + Method: poolMethodRelevantTokens, + Params: nil, + }, []interface{}{&tokens[i]}) + + req.AddCall(ðrpc.Call{ + ABI: poolABI, + Target: poolAddresses[i].Hex(), + Method: poolMethodTokenWeights, + Params: nil, + }, []interface{}{&weights[i]}) + } + + if _, err := req.Aggregate(); err != nil { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Error(err.Error()) + return nil, err + } + + for i, poolAddress := range poolAddresses { + var ( + p = strings.ToLower(poolAddress.Hex()) + nativeTokenIndex = unknownInt + + poolTokens = []*entity.PoolToken{} + reserves = []string{} + tokenWeights = []*big.Int{} + ) + + for j := 0; j < maxPoolTokenNumber; j++ { + t := tokens[i][j].unwrapToken() + w := weights[i][j] + if t == valueobject.ZeroAddress { + break + } + + if strings.EqualFold(t, valueobject.EtherAddress) { + nativeTokenIndex = j + weth, ok := valueobject.WETHByChainID[d.config.ChainID] + if !ok { + return nil, ErrWETHNotFound + } + t = strings.ToLower(weth) + } + + poolTokens = append(poolTokens, &entity.PoolToken{ + Address: t, + Swappable: true, + }) + tokenWeights = append(tokenWeights, w) + reserves = append(reserves, reserveZero) + } + + staticExtra := StaticExtra{ + Weights: tokenWeights, + PoolTokenNumber: uint(len(poolTokens)), + NativeTokenIndex: nativeTokenIndex, + Vault: d.config.VaultAddress, + } + staticExtraBytes, err := json.Marshal(staticExtra) + if err != nil { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Error(err.Error()) + return nil, err + } + + newPool := entity.Pool{ + Address: p, + ReserveUsd: 0, + AmplifiedTvl: 0, + Exchange: d.config.DexID, + Type: DexType, + Timestamp: time.Now().Unix(), + Reserves: reserves, + Tokens: poolTokens, + StaticExtra: string(staticExtraBytes), + } + pools = append(pools, newPool) + } + + return pools, nil +} + +func (d *PoolsListUpdater) queryPoolAddresses(ctx context.Context, offset int, batchSize int) ([]common.Address, error) { + poolAddresses := make([]common.Address, batchSize) + req := d.ethrpcClient.R() + for j := 0; j < batchSize; j++ { + req.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: factoryMethodPoolList, + Params: []interface{}{big.NewInt(int64(offset + j))}, + }, []interface{}{&poolAddresses[j]}) + } + + resp, err := req.TryAggregate() + if err != nil { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Error(err.Error()) + return nil, err + } + + var ret []common.Address + for i, isSuccess := range resp.Result { + if isSuccess { + ret = append(ret, poolAddresses[i]) + } + } + + return ret, nil +} + +func (d *PoolsListUpdater) getPoolsLength(ctx context.Context) (int, error) { + var l *big.Int + req := d.ethrpcClient.R() + req.AddCall(ðrpc.Call{ + ABI: factoryABI, + Target: d.config.FactoryAddress, + Method: factoryMethodPoolsLength, + Params: nil, + }, []interface{}{&l}) + if _, err := req.Call(); err != nil { + logger.WithFields( + logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Error(err.Error()) + return 0, err + } + return int(l.Uint64()), nil +} + +func (u *PoolsListUpdater) newMetadata(newOffset int) ([]byte, error) { + metadata := Metadata{ + Offset: newOffset, + } + + metadataBytes, err := json.Marshal(metadata) + if err != nil { + logger.WithFields(logger.Fields{ + "dexId": u.config.DexID, + "type": DexType, + }).Error(err.Error()) + return nil, err + } + + return metadataBytes, nil +} + +func (d *PoolsListUpdater) getOffset(metadataBytes []byte) (int, error) { + if len(metadataBytes) == 0 { + return 0, nil + } + + var metadata Metadata + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + logger.WithFields(logger.Fields{ + "dexId": d.config.DexID, + "type": DexType, + }).Error(err.Error()) + return 0, err + } + + return metadata.Offset, nil +} + +func getBatchSize(length int, limit int, offset int) int { + if offset == length { + return 0 + } + + if offset+limit > length { + return length - offset + } + + return limit +} diff --git a/pkg/liquidity-source/velocore-v2/cpmm/types.go b/pkg/liquidity-source/velocore-v2/cpmm/types.go new file mode 100644 index 000000000..ebd0606c7 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/cpmm/types.go @@ -0,0 +1,54 @@ +package cpmm + +import ( + "encoding/hex" + "math/big" + "strings" +) + +type Gas struct { + Swap int64 +} + +type Metadata struct { + Offset int `json:"offset"` +} + +type bytes32 [32]byte + +func (b *bytes32) unwrapToken() string { + last20Bytes := b[12:] + address := "0x" + hex.EncodeToString(last20Bytes) + return strings.ToLower(address) +} + +type StaticExtra struct { + Weights []*big.Int `json:"weights"` + PoolTokenNumber uint `json:"poolTokenNumber"` + NativeTokenIndex int `json:"nativeTokenIndex"` + Vault string `json:"vault"` +} + +type Extra struct { + Fee1e9 uint32 `json:"fee1e9"` + FeeMultiplier *big.Int `json:"feeMultiplier"` +} + +type Meta struct { + Vault string `json:"vault"` + NativeTokenIndex int `json:"nativeTokenIndex"` +} + +type SwapInfo struct { + IsFeeMultiplierUpdated bool `json:"-"` + FeeMultiplier string `json:"-"` +} + +// internal types + +type velocoreExecuteResult struct { + Tokens []string + R []*big.Int + FeeMultiplier *big.Int + IsFeeMultiplierUpdated bool +} diff --git a/pkg/liquidity-source/velocore-v2/math/asm.go b/pkg/liquidity-source/velocore-v2/math/asm.go new file mode 100644 index 000000000..d5fd3d12d --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/asm.go @@ -0,0 +1,70 @@ +package math + +import ( + "github.com/KyberNetwork/blockchain-toolkit/number" + "github.com/holiman/uint256" +) + +var ASM *asm + +type asm struct{} + +func (*asm) Not(u *uint256.Int) *uint256.Int { + return new(uint256.Int).Not(u) +} + +func (*asm) MulMod(x, y, z *uint256.Int) *uint256.Int { + return new(uint256.Int).MulMod(x, y, z) +} + +func (*asm) Lt(x, y *uint256.Int) *uint256.Int { + if x.Lt(y) { + return number.Number_1 + } + return number.Zero +} + +func (*asm) Gt(x, y *uint256.Int) *uint256.Int { + if x.Gt(y) { + return number.Number_1 + } + return number.Zero +} + +func (*asm) Mul(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Mul(x, y) +} + +func (*asm) Sub(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Sub(x, y) +} + +func (*asm) Div(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Div(x, y) +} + +func (*asm) Or(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Or(x, y) +} + +func (*asm) Add(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Add(x, y) +} + +func (*asm) Mod(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Mod(x, y) +} + +func (*asm) Eq(x, y *uint256.Int) *uint256.Int { + if x.Eq(y) { + return number.Number_1 + } + return number.Zero +} + +func (*asm) IsZero(x *uint256.Int) *uint256.Int { + if x.IsZero() { + return number.Number_1 + } + return number.Zero +} diff --git a/pkg/liquidity-source/velocore-v2/math/common.go b/pkg/liquidity-source/velocore-v2/math/common.go new file mode 100644 index 000000000..03bf9b141 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/common.go @@ -0,0 +1,359 @@ +package math + +import ( + "errors" + + "github.com/KyberNetwork/blockchain-toolkit/number" + "github.com/holiman/uint256" +) + +var ( + UNIT_LPOTD = uint256.NewInt(262144) + UNIT = uint256.NewInt(1e18) + UNIT_INVERSE = uint256.MustFromDecimal("78156646155174841979727994598816262306175212592076161876661508869554232690281") +) + +var ( + Err_PRBMath_MulDiv18_Overflow = errors.New("PRBMath_MulDiv18_Overflow") + Err_PRBMath_MulDiv_Overflow = errors.New("PRBMath_MulDiv_Overflow") + + ErrDivideByZero = errors.New("divide by zero") + ErrOverflow = errors.New("overflow") +) + +var Common *common + +type common struct{} + +// https://github.com/velocore/velocore-contracts/blob/master/lib/prb-math/src/Common.sol#L495 +func (*common) MulDiv18(x, y *uint256.Int) (*uint256.Int, error) { + var prod0, prod1 *uint256.Int + { + mm := ASM.MulMod(x, y, ASM.Not(number.Zero)) + prod0 = ASM.Mul(x, y) + prod1 = ASM.Sub( + ASM.Sub(mm, prod0), + ASM.Lt(mm, prod0), + ) + } + + if prod1.IsZero() { + return new(uint256.Int).Div(prod0, UNIT), nil + } + + if prod1.Cmp(UNIT) >= 0 { + return nil, Err_PRBMath_MulDiv18_Overflow + } + + var remainder, result *uint256.Int + { + remainder = ASM.MulMod(x, y, UNIT) + result = ASM.Mul( + ASM.Or( + ASM.Div(ASM.Sub(prod0, remainder), UNIT_LPOTD), + ASM.Mul(ASM.Sub(prod1, ASM.Gt(remainder, prod0)), ASM.Add(ASM.Div(ASM.Sub(number.Zero, UNIT_LPOTD), UNIT_LPOTD), number.Number_1)), + ), + UNIT_INVERSE, + ) + } + + return result, nil +} + +// https://github.com/velocore/velocore-contracts/blob/master/lib/prb-math/src/Common.sol#L387 +func (*common) MulDiv(x, y, denominator *uint256.Int) (*uint256.Int, error) { + var prod0, prod1 *uint256.Int + { + mm := ASM.MulMod(x, y, ASM.Not(number.Zero)) + prod0 = ASM.Mul(x, y) + prod1 = ASM.Sub(ASM.Sub(mm, prod0), ASM.Lt(mm, prod0)) + } + + if prod1.IsZero() { + return new(uint256.Int).Div(prod0, denominator), nil + } + + if prod1.Cmp(denominator) >= 0 { + return nil, Err_PRBMath_MulDiv_Overflow + } + + var remainder *uint256.Int + { + + remainder = ASM.MulMod(x, y, denominator) + + prod1 = ASM.Sub(prod1, ASM.Gt(remainder, prod0)) + prod0 = ASM.Sub(prod0, remainder) + } + + lpotdod := new(uint256.Int).And( + denominator, + new(uint256.Int).Add( + new(uint256.Int).Not(denominator), + number.Number_1, + ), + ) + + var flippedLpotdod *uint256.Int + { + denominator = ASM.Div(denominator, lpotdod) + + prod0 = ASM.Div(prod0, lpotdod) + + flippedLpotdod = ASM.Add( + ASM.Div( + ASM.Sub(number.Zero, lpotdod), + lpotdod, + ), + number.Number_1, + ) + } + + prod0 = new(uint256.Int).Or( + prod0, + new(uint256.Int).Mul(prod1, flippedLpotdod), + ) + + inverse := new(uint256.Int).Exp(new(uint256.Int).Mul(number.Number_3, denominator), number.Number_2) + for i := 0; i < 6; i++ { + inverse = new(uint256.Int).Mul( + inverse, + new(uint256.Int).Sub( + number.Number_2, + new(uint256.Int).Mul( + denominator, + inverse, + ), + ), + ) + } + + result := new(uint256.Int).Mul(prod0, inverse) + + return result, nil +} + +// https://github.com/velocore/velocore-contracts/blob/master/lib/prb-math/src/Common.sol#L54 +func (*common) Exp2(x *uint256.Int) *uint256.Int { + result := uint256.MustFromHex("0x800000000000000000000000000000000000000000000000") + + type pair struct { + X string + Y string + } + const n = 8 + v := [n]string{ + "0xFF00000000000000", + "0xFF000000000000", + "0xFF0000000000", + "0xFF00000000", + "0xFF000000", + "0xFF0000", + "0xFF00", + "0xFF", + } + pairs := [n][n]pair{ + { + {"0x8000000000000000", "0x16A09E667F3BCC909"}, + {"0x4000000000000000", "0x1306FE0A31B7152DF"}, + {"0x2000000000000000", "0x1172B83C7D517ADCE"}, + {"0x1000000000000000", "0x10B5586CF9890F62A"}, + {"0x800000000000000", "0x1059B0D31585743AE"}, + {"0x400000000000000", "0x102C9A3E778060EE7"}, + {"0x200000000000000", "0x10163DA9FB33356D8"}, + {"0x100000000000000", "0x100B1AFA5ABCBED61"}, + }, + { + {"0x80000000000000", "0x10058C86DA1C09EA2"}, + {"0x40000000000000", "0x1002C605E2E8CEC50"}, + {"0x20000000000000", "0x100162F3904051FA1"}, + {"0x10000000000000", "0x1000B175EFFDC76BA"}, + {"0x8000000000000", "0x100058BA01FB9F96D"}, + {"0x4000000000000", "0x10002C5CC37DA9492"}, + {"0x2000000000000", "0x1000162E525EE0547"}, + {"0x1000000000000", "0x10000B17255775C04"}, + }, + { + {"0x800000000000", "0x1000058B91B5BC9AE"}, + {"0x400000000000", "0x100002C5C89D5EC6D"}, + {"0x200000000000", "0x10000162E43F4F831"}, + {"0x100000000000", "0x100000B1721BCFC9A"}, + {"0x80000000000", "0x10000058B90CF1E6E"}, + {"0x40000000000", "0x1000002C5C863B73F"}, + {"0x20000000000", "0x100000162E430E5A2"}, + {"0x10000000000", "0x1000000B172183551"}, + }, + { + {"0x8000000000", "0x100000058B90C0B49"}, + {"0x4000000000", "0x10000002C5C8601CC"}, + {"0x2000000000", "0x1000000162E42FFF0"}, + {"0x1000000000", "0x10000000B17217FBB"}, + {"0x800000000", "0x1000000058B90BFCE"}, + {"0x400000000", "0x100000002C5C85FE3"}, + {"0x200000000", "0x10000000162E42FF1"}, + {"0x100000000", "0x100000000B17217F8"}, + }, + { + {"0x80000000", "0x10000000058B90BFC"}, + {"0x40000000", "0x1000000002C5C85FE"}, + {"0x20000000", "0x100000000162E42FF"}, + {"0x10000000", "0x1000000000B17217F"}, + {"0x8000000", "0x100000000058B90C0"}, + {"0x4000000", "0x10000000002C5C860"}, + {"0x2000000", "0x1000000000162E430"}, + {"0x1000000", "0x10000000000B17218"}, + }, + { + {"0x800000", "0x1000000000058B90C"}, + {"0x400000", "0x100000000002C5C86"}, + {"0x200000", "0x10000000000162E43"}, + {"0x100000", "0x100000000000B1721"}, + {"0x80000", "0x10000000000058B91"}, + {"0x40000", "0x1000000000002C5C8"}, + {"0x20000", "0x100000000000162E4"}, + {"0x10000", "0x1000000000000B172"}, + }, + { + {"0x8000", "0x100000000000058B9"}, + {"0x4000", "0x10000000000002C5D"}, + {"0x2000", "0x1000000000000162E"}, + {"0x1000", "0x10000000000000B17"}, + {"0x800", "0x1000000000000058C"}, + {"0x400", "0x100000000000002C6"}, + {"0x200", "0x10000000000000163"}, + {"0x100", "0x100000000000000B1"}, + }, + { + {"0x80", "0x10000000000000059"}, + {"0x40", "0x1000000000000002C"}, + {"0x20", "0x10000000000000016"}, + {"0x10", "0x1000000000000000B"}, + {"0x8", "0x10000000000000006"}, + {"0x4", "0x10000000000000003"}, + {"0x2", "0x10000000000000001"}, + {"0x1", "0x10000000000000001"}, + }, + } + + for i := 0; i < n; i++ { + vi := uint256.MustFromHex(v[i]) + if !new(uint256.Int).And(x, vi).Gt(number.Zero) { + continue + } + + for j := 0; j < n; j++ { + xj := uint256.MustFromHex(pairs[i][j].X) + if !new(uint256.Int).And(x, xj).Gt(number.Zero) { + continue + } + + yj := uint256.MustFromHex(pairs[i][j].Y) + result = new(uint256.Int).Rsh(new(uint256.Int).Mul(result, yj), 64) + } + } + + result = new(uint256.Int).Mul(result, UNIT) + result = new(uint256.Int).Rsh( + result, + uint(new(uint256.Int).Sub( + uint256.NewInt(191), + new(uint256.Int).Rsh(x, 64), + ).Uint64()), + ) + + return result +} + +// https://github.com/velocore/velocore-contracts/blob/master/lib/prb-math/src/Common.sol#L320 +// +// NOTE: Msb is modified from the original implementation. +func (*common) Msb(x *uint256.Int) *uint256.Int { + l, r := 0, 256 + for r-l > 1 { + m := (l + r) >> 1 + p := new(uint256.Int).Lsh(number.Number_1, uint(m)) + if p.Cmp(x) <= 0 { + l = m + } else { + r = m + } + } + return uint256.NewInt(uint64(l)) +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/src/pools/constant-product/ConstantProductPool.sol#L29 +func (*common) CeilDivUnsafe(x, y *uint256.Int) *uint256.Int { + return new(uint256.Int).Div( + new(uint256.Int).Sub( + new(uint256.Int).Add(x, y), + number.Number_1, + ), y, + ) +} + +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol#L107 +func (*common) CeilDiv(x, y *uint256.Int) (*uint256.Int, error) { + if y.IsZero() { + return nil, ErrDivideByZero + } + + if x.IsZero() { + return number.Zero, nil + } + + return new(uint256.Int).Add( + new(uint256.Int).Div( + new(uint256.Int).Sub(x, number.Number_1), + y, + ), + number.Number_1, + ), nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/src/lib/RPow.sol#L22 +func (*common) RPow(x, n, base *uint256.Int) (*uint256.Int, error) { + if x.IsZero() { + if n.IsZero() { + return base, nil + } + + return number.Zero, nil + } + + z := x + if ASM.Mod(n, number.Number_2).IsZero() { + z = base + } + + half := ASM.Div(base, number.Number_2) + for i := ASM.Div(n, number.Number_2); i.Gt(number.Zero); i = ASM.Div(i, number.Number_2) { + xx := ASM.Mul(x, x) + + if !ASM.Div(xx, x).Eq(x) { + return nil, ErrOverflow + } + + xxRound := ASM.Add(xx, half) + if xxRound.Lt(xx) { + return nil, ErrOverflow + } + + x = ASM.Div(xxRound, base) + if !ASM.Mod(i, number.Number_2).IsZero() { + zx := ASM.Mul(z, x) + + if !x.IsZero() && !ASM.Div(zx, x).Eq(z) { + return nil, ErrOverflow + } + + zxRound := ASM.Add(zx, half) + if zxRound.Lt(zx) { + return nil, ErrOverflow + } + + z = ASM.Div(zxRound, base) + } + } + + return z, nil +} diff --git a/pkg/liquidity-source/velocore-v2/math/common_test.go b/pkg/liquidity-source/velocore-v2/math/common_test.go new file mode 100644 index 000000000..8b20628d5 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/common_test.go @@ -0,0 +1,68 @@ +package math + +import ( + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +func TestRPow(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "2587978906837309703137361322131041" + x := uint256.MustFromDecimal("3145121") + n := uint256.MustFromDecimal("10") + base := uint256.MustFromDecimal("3214") + res, err := Common.RPow(x, n, base) + assert.Nil(t, err) + assert.Equal(t, expected, res.Dec()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "0" + x := uint256.MustFromDecimal("213") + n := uint256.MustFromDecimal("100") + base := uint256.MustFromDecimal("999") + res, err := Common.RPow(x, n, base) + assert.Nil(t, err) + assert.Equal(t, expected, res.Dec()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "3150676750167233152197066722664292267937844764501205787" + x := uint256.MustFromDecimal("951") + n := uint256.MustFromDecimal("59") + base := uint256.MustFromDecimal("123") + res, err := Common.RPow(x, n, base) + assert.Nil(t, err) + assert.Equal(t, expected, res.Dec()) + }) + + t.Run("4. should return correct value", func(t *testing.T) { + expected := "3150676750167233152197066722664292267937844764501205787" + x := uint256.MustFromDecimal("951") + n := uint256.MustFromDecimal("59") + base := uint256.MustFromDecimal("123") + res, err := Common.RPow(x, n, base) + assert.Nil(t, err) + assert.Equal(t, expected, res.Dec()) + }) + + t.Run("5. should return correct value", func(t *testing.T) { + expected := "123" + x := uint256.MustFromDecimal("951") + n := uint256.MustFromDecimal("0") + base := uint256.MustFromDecimal("123") + res, err := Common.RPow(x, n, base) + assert.Nil(t, err) + assert.Equal(t, expected, res.Dec()) + }) + + t.Run("6. should return error", func(t *testing.T) { + x := uint256.MustFromDecimal("32142194234951") + n := uint256.MustFromDecimal("3149143124") + base := uint256.MustFromDecimal("421424") + _, err := Common.RPow(x, n, base) + assert.ErrorIs(t, err, ErrOverflow) + }) +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/casting.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/casting.go new file mode 100644 index 000000000..b0b06ab29 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/casting.go @@ -0,0 +1,11 @@ +package sd59x18 + +import "math/big" + +func SD(x *big.Int) *SD59x18 { + return &SD59x18{value: x} +} + +func IntoInt256(x *SD59x18) *big.Int { + return x.value +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/constant.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/constant.go new file mode 100644 index 000000000..7ec52ab08 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/constant.go @@ -0,0 +1,19 @@ +package sd59x18 + +import "math/big" + +var ( + uUNIT = big.NewInt(1e18) + + uMIN_SD59x18, _ = new(big.Int).SetString("-57896044618658097711785492504343953926634992332820282019728792003956564819968", 10) + + uMAX_SD59x18, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819967", 10) + + uUNIT_SQUARED, _ = new(big.Int).SetString("1000000000000000000000000000000000000", 10) + + uHALF_UNIT, _ = new(big.Int).SetString("500000000000000000", 10) + + uEXP2_MAX_INPUT, _ = new(big.Int).SetString("191999999999999999999", 10) + + Zero = SD(big.NewInt(0)) +) diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/conversion.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/conversion.go new file mode 100644 index 000000000..d44ebd5dd --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/conversion.go @@ -0,0 +1,22 @@ +package sd59x18 + +import "math/big" + +func ConvertSD59x18(x *big.Int) (*SD59x18, error) { + if x.Cmp(new(big.Int).Quo(uMIN_SD59x18, uUNIT)) < 0 { + return nil, Err_PRBMath_SD59x18_Convert_Underflow + } + + if x.Cmp(new(big.Int).Quo(uMAX_SD59x18, uUNIT)) > 0 { + return nil, Err_PRBMath_SD59x18_Convert_Overflow + } + + value := new(big.Int).Mul(x, uUNIT) + + return &SD59x18{value}, nil + +} + +func ConvertBI(x *SD59x18) *big.Int { + return new(big.Int).Quo(x.value, uUNIT) +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/error.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/error.go new file mode 100644 index 000000000..35a62e935 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/error.go @@ -0,0 +1,14 @@ +package sd59x18 + +import "errors" + +var ( + Err_PRBMath_SD59x18_Convert_Underflow = errors.New("PRBMath_SD59x18_Convert_Underflow") + Err_PRBMath_SD59x18_Convert_Overflow = errors.New("PRBMath_SD59x18_Convert_Overflow") + Err_PRBMath_SD59x18_Log_InputTooSmall = errors.New("PRBMath_SD59x18_Log_InputTooSmall") + Err_PRBMath_SD59x18_Exp2_InputTooBig = errors.New("PRBMath_SD59x18_Exp2_InputTooBig") + Err_PRBMath_SD59x18_Mul_InputTooSmall = errors.New("PRBMath_SD59x18_Mul_InputTooSmall") + Err_PRBMath_SD59x18_Mul_Overflow = errors.New("PRBMath_SD59x18_Mul_Overflow") + Err_PRBMath_SD59x18_Div_InputTooSmall = errors.New("PRBMath_SD59x18_Div_InputTooSmall") + Err_PRBMath_SD59x18_Div_Overflow = errors.New("PRBMath_SD59x18_Div_Overflow") +) diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/expr.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/expr.go new file mode 100644 index 000000000..e62ab9b00 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/expr.go @@ -0,0 +1,101 @@ +package sd59x18 + +type Expr struct { + result *SD59x18 + err error +} + +func NewExpr(r *SD59x18) *Expr { + return &Expr{ + result: r, + err: nil, + } +} + +func (e *Expr) Result() (*SD59x18, error) { + return e.result, e.err +} + +func (e *Expr) Add(x *SD59x18) *Expr { + if e.err != nil { + return e + } + + e.result = new(SD59x18).Add(e.result, x) + + return e +} + +func (e *Expr) Mul(x *SD59x18) *Expr { + if e.err != nil { + return e + } + + e.result, e.err = new(SD59x18).Mul(e.result, x) + + return e +} + +func (e *Expr) Log2() *Expr { + if e.err != nil { + return e + } + + e.result, e.err = new(SD59x18).Log2(e.result) + + return e +} + +func (e *Expr) Sub(x *SD59x18) *Expr { + if e.err != nil { + return e + } + + e.result = new(SD59x18).Sub(e.result, x) + + return e +} + +func (e *Expr) Exp2() *Expr { + if e.err != nil { + return e + } + + e.result, e.err = new(SD59x18).Exp2(e.result) + + return e +} + +func (e *Expr) Neg() *Expr { + if e.err != nil { + return e + } + + e.result = new(SD59x18).Sub(Zero, e.result) + + return e +} + +func (e *Expr) Div(x *SD59x18) *Expr { + if e.err != nil { + return e + } + + e.result, e.err = new(SD59x18).Div(e.result, x) + + return e +} + +func (e *Expr) SubExpr(other *Expr) *Expr { + if e.err != nil { + return e + } + + if other.err != nil { + return other + } + + e.result = new(SD59x18).Sub(e.result, other.result) + + return e +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/helper.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/helper.go new file mode 100644 index 000000000..3eb5ccf30 --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/helper.go @@ -0,0 +1,30 @@ +package sd59x18 + +import "math/big" + +func (z *SD59x18) Add(x, y *SD59x18) *SD59x18 { + z.value = new(big.Int).Add(x.value, y.value) + return z +} + +func (z *SD59x18) Sub(x, y *SD59x18) *SD59x18 { + z.value = new(big.Int).Sub(x.value, y.value) + return z +} + +func (z *SD59x18) Lt(x, y *SD59x18) bool { + return x.value.Cmp(y.value) < 0 +} + +func (z *SD59x18) Gt(x, y *SD59x18) bool { + return x.value.Cmp(y.value) > 0 +} + +func (z *SD59x18) Ternary(cond bool, x, y *SD59x18) *SD59x18 { + if cond { + z.value = new(big.Int).Set(x.value) + } else { + z.value = new(big.Int).Set(y.value) + } + return z +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/math.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/math.go new file mode 100644 index 000000000..b0c5d729d --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/math.go @@ -0,0 +1,213 @@ +package sd59x18 + +import ( + "math/big" + + "github.com/KyberNetwork/blockchain-toolkit/integer" + "github.com/holiman/uint256" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/velocore-v2/math" +) + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/lib/prb-math/src/sd59x18/Math.sol#L480 +func (z *SD59x18) Log2(x *SD59x18) (*SD59x18, error) { + xInt := x.value + if xInt.Cmp(integer.Zero()) <= 0 { + return nil, Err_PRBMath_SD59x18_Log_InputTooSmall + } + + var sign *big.Int + if xInt.Cmp(uUNIT) >= 0 { + sign = big.NewInt(1) + } else { + sign = big.NewInt(-1) + + xInt = new(big.Int).Quo(uUNIT_SQUARED, xInt) + } + + var n *big.Int + { + xInt_div_uUNIT := new(big.Int).Quo(xInt, uUNIT) + xInt_div_uUNIT_U256, _ := uint256.FromBig(xInt_div_uUNIT) + nUint256 := math.Common.Msb(xInt_div_uUNIT_U256) + + n = nUint256.ToBig() + } + + resultInt := new(big.Int).Mul(n, uUNIT) + y := new(big.Int).Rsh(xInt, uint(n.Uint64())) + if y.Cmp(uUNIT) == 0 { + z.value = new(big.Int).Mul(resultInt, sign) + return z, nil + } + + doubleUnit := big.NewInt(2e18) + for delta := uHALF_UNIT; delta.Cmp(integer.Zero()) > 0; delta = new(big.Int).Rsh(delta, 1) { + y = new(big.Int).Quo(new(big.Int).Mul(y, y), uUNIT) + if y.Cmp(doubleUnit) >= 0 { + resultInt = new(big.Int).Add(resultInt, delta) + y = new(big.Int).Rsh(y, 1) + } + } + + resultInt = new(big.Int).Mul(resultInt, sign) + z.value = resultInt + + return z, nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/lib/prb-math/src/sd59x18/Math.sol#L201 +func (z *SD59x18) Exp2(x *SD59x18) (*SD59x18, error) { + xInt := x.value + + if xInt.Cmp(integer.Zero()) < 0 { + magic, _ := new(big.Int).SetString("-59794705707972522261", 10) + if xInt.Cmp(magic) < 0 { + z.value = integer.Zero() + return z, nil + } + + t, err := new(SD59x18).Exp2(SD( + new(big.Int).Sub(integer.Zero(), xInt), + )) + if err != nil { + return z, err + } + + z.value = new(big.Int).Quo(uUNIT_SQUARED, t.value) + + return z, nil + } + + if xInt.Cmp(uEXP2_MAX_INPUT) > 0 { + return z, Err_PRBMath_SD59x18_Exp2_InputTooBig + } + + x_192x64, _ := uint256.FromBig(new(big.Int).Quo(new(big.Int).Lsh(xInt, 64), uUNIT)) + z.value = math.Common.Exp2(x_192x64).ToBig() + + return z, nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/lib/prb-math/src/sd59x18/Math.sol#L593 +func (z *SD59x18) Pow(x *SD59x18, y *SD59x18) (*SD59x18, error) { + xInt, yInt := x.value, y.value + + if xInt.Cmp(integer.Zero()) == 0 { + z.value = integer.Zero() + if yInt.Cmp(integer.Zero()) == 0 { + z.value = uUNIT + } + + return z, nil + } + + if xInt.Cmp(uUNIT) == 0 { + z.value = uUNIT + return z, nil + } + + if yInt.Cmp(integer.Zero()) == 0 { + z.value = uUNIT + return z, nil + } + + if yInt.Cmp(uUNIT) == 0 { + z.value = new(big.Int).Set(xInt) + return z, nil + } + + log2, err := new(SD59x18).Log2(x) + if err != nil { + return z, err + } + + mul, err := new(SD59x18).Mul(log2, y) + if err != nil { + return z, err + } + + exp2, err := new(SD59x18).Exp2(mul) + if err != nil { + return z, err + } + + z.value = exp2.value + + return z, nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/lib/prb-math/src/sd59x18/Math.sol#L545 +func (z *SD59x18) Mul(x, y *SD59x18) (*SD59x18, error) { + xInt, yInt := x.value, y.value + + if xInt.Cmp(uMIN_SD59x18) == 0 || yInt.Cmp(uMIN_SD59x18) == 0 { + return z, Err_PRBMath_SD59x18_Mul_InputTooSmall + } + + xAbs := new(big.Int).Abs(xInt) + yAbs := new(big.Int).Abs(yInt) + + resultAbsU256, err := math.Common.MulDiv18( + uint256.MustFromBig(xAbs), + uint256.MustFromBig(yAbs), + ) + if err != nil { + return nil, err + } + resultAbs := resultAbsU256.ToBig() + + if resultAbs.Cmp(uMAX_SD59x18) > 0 { + return z, Err_PRBMath_SD59x18_Mul_Overflow + } + + result := resultAbs + if xInt.Sign() != yInt.Sign() { + result = new(big.Int).Neg(resultAbs) + } + + z.value = result + + return z, nil +} + +// https://github.com/velocore/velocore-contracts/blob/c29678e5acbe5e60fc018e08289b49e53e1492f3/lib/prb-math/src/sd59x18/Math.sol#L121 +func (z *SD59x18) Div(x, y *SD59x18) (*SD59x18, error) { + if y.value.Cmp(integer.Zero()) == 0 { + return nil, Err_PRBMath_SD59x18_Div_Overflow + } + + xInt, yInt := x.value, y.value + + if xInt.Cmp(uMIN_SD59x18) == 0 || yInt.Cmp(uMIN_SD59x18) == 0 { + return nil, Err_PRBMath_SD59x18_Div_InputTooSmall + } + + var ( + xAbs = new(big.Int).Abs(xInt) + yAbs = new(big.Int).Abs(yInt) + ) + + resultAbsU256, err := math.Common.MulDiv( + uint256.MustFromBig(xAbs), + uint256.MustFromBig(uUNIT), + uint256.MustFromBig(yAbs), + ) + if err != nil { + return z, err + } + resultAbs := resultAbsU256.ToBig() + + if resultAbs.Cmp(uMAX_SD59x18) > 0 { + return z, Err_PRBMath_SD59x18_Div_Overflow + } + + result := resultAbs + if xInt.Sign() != yInt.Sign() { + result = new(big.Int).Neg(resultAbs) + } + + z.value = result + + return z, nil +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/math_test.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/math_test.go new file mode 100644 index 000000000..077d13b4e --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/math_test.go @@ -0,0 +1,221 @@ +package sd59x18 + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLog2(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "10275233743784123062" + + x, _ := new(big.Int).SetString("1239234710472810957214", 10) + res, err := new(SD59x18).Log2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "90825107446694031641" + + x, _ := new(big.Int).SetString("2193217491592174921591742914732915624927147914", 10) + res, err := new(SD59x18).Log2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "-59794705707972522245" + + x, _ := new(big.Int).SetString("1", 10) + res, err := new(SD59x18).Log2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("4. should return error", func(t *testing.T) { + x, _ := new(big.Int).SetString("0", 10) + _, err := new(SD59x18).Log2(SD(x)) + assert.ErrorIs(t, err, Err_PRBMath_SD59x18_Log_InputTooSmall) + }) +} + +func TestExp2(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "991501979360783687" + + x, _ := new(big.Int).SetString("-12312442341321497", 10) + res, err := new(SD59x18).Exp2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "1000009852095982467" + + x, _ := new(big.Int).SetString("14213500000000", 10) + res, err := new(SD59x18).Exp2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "745770835772355297" + + x, _ := new(big.Int).SetString("-423195714924214000", 10) + res, err := new(SD59x18).Exp2(SD(x)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("4. should return error", func(t *testing.T) { + x, _ := new(big.Int).SetString("20439174392159174914243", 10) + _, err := new(SD59x18).Exp2(SD(x)) + assert.ErrorIs(t, err, Err_PRBMath_SD59x18_Exp2_InputTooBig) + }) +} + +func TestPow(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "1000000007819231118" + + x, _ := new(big.Int).SetString("13894732914", 10) + y, _ := new(big.Int).SetString("-432198571", 10) + + res, err := new(SD59x18).Pow(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "999999999999999960" + + x, _ := new(big.Int).SetString("1", 10) + y, _ := new(big.Int).SetString("1", 10) + + res, err := new(SD59x18).Pow(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "999999999999995856" + + x, _ := new(big.Int).SetString("1", 10) + y, _ := new(big.Int).SetString("100", 10) + + res, err := new(SD59x18).Pow(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("4. should return error", func(t *testing.T) { + x, _ := new(big.Int).SetString("-1", 10) + y, _ := new(big.Int).SetString("100", 10) + + _, err := new(SD59x18).Pow(SD(x), SD(y)) + assert.ErrorIs(t, err, Err_PRBMath_SD59x18_Log_InputTooSmall) + }) +} + +func TestMul(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "0" + + x, _ := new(big.Int).SetString("-31471942", 10) + y, _ := new(big.Int).SetString("432150392", 10) + + res, err := new(SD59x18).Mul(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "4576679332325557870713639756650141931978623606409093849222065" + + x, _ := new(big.Int).SetString("2139317492174912748921478291478129471294", 10) + y, _ := new(big.Int).SetString("2139317492174912748921478291478129471294", 10) + + res, err := new(SD59x18).Mul(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "-134833209231304" + + x, _ := new(big.Int).SetString("-431421947249147", 10) + y, _ := new(big.Int).SetString("312532104801424214", 10) + + res, err := new(SD59x18).Mul(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("4. should return error", func(t *testing.T) { + x, _ := new(big.Int).SetString("-432108420142414321048230148230153214214", 10) + y, _ := new(big.Int).SetString("213248104820152271042104810482107310532810482014812304214", 10) + + _, err := new(SD59x18).Mul(SD(x), SD(y)) + assert.ErrorIs(t, err, Err_PRBMath_SD59x18_Mul_Overflow) + }) +} + +func TestDiv(t *testing.T) { + t.Run("1. should return correct value", func(t *testing.T) { + expected := "29044803764687059979539950758763" + + x, _ := new(big.Int).SetString("412942439471924712956329174921471924234", 10) + y, _ := new(big.Int).SetString("14217429142144314829147241", 10) + + res, err := new(SD59x18).Div(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("2. should return correct value", func(t *testing.T) { + expected := "-25258852003234736335344272844272844" + + x, _ := new(big.Int).SetString("-314018048104214242121", 10) + y, _ := new(big.Int).SetString("12432", 10) + + res, err := new(SD59x18).Div(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("3. should return correct value", func(t *testing.T) { + expected := "-3476000249353172" + + x, _ := new(big.Int).SetString("-432147293175239147293719471294", 10) + y, _ := new(big.Int).SetString("124323147921423429372921472394124", 10) + + res, err := new(SD59x18).Div(SD(x), SD(y)) + assert.Nil(t, err) + + assert.Equal(t, expected, res.value.String()) + }) + + t.Run("4. should return error", func(t *testing.T) { + x, _ := new(big.Int).SetString("-432147293175239147293719471294", 10) + y, _ := new(big.Int).SetString("0", 10) + _, err := new(SD59x18).Div(SD(x), SD(y)) + assert.ErrorIs(t, err, Err_PRBMath_SD59x18_Div_Overflow) + }) +} diff --git a/pkg/liquidity-source/velocore-v2/math/sd59x18/value_type.go b/pkg/liquidity-source/velocore-v2/math/sd59x18/value_type.go new file mode 100644 index 000000000..24dc04c5c --- /dev/null +++ b/pkg/liquidity-source/velocore-v2/math/sd59x18/value_type.go @@ -0,0 +1,7 @@ +package sd59x18 + +import "math/big" + +type SD59x18 struct { + value *big.Int +} diff --git a/pkg/valueobject/chain.go b/pkg/valueobject/chain.go index 2259a4d1b..b546aa0de 100644 --- a/pkg/valueobject/chain.go +++ b/pkg/valueobject/chain.go @@ -24,6 +24,8 @@ const ( ChainIDArbitrumRinkeby ChainID = 421611 ChainIDEthereumW ChainID = 10001 ChainIDFuji ChainID = 43113 + ChainIDLinea ChainID = 59144 + ChainIDZKSync ChainID = 324 // ChainIDSolana is currently used in case of store price to db, that we should transform token addr into lowercase or not. ChainIDSolana ChainID = 0 diff --git a/pkg/valueobject/weth.go b/pkg/valueobject/weth.go index fb06f2c9d..01b289ebb 100644 --- a/pkg/valueobject/weth.go +++ b/pkg/valueobject/weth.go @@ -21,4 +21,6 @@ var WETHByChainID = map[ChainID]string{ ChainIDOasisEmerald: "0x21C718C22D52d0F3a789b752D4c2fD5908a8A733", ChainIDArbitrumOne: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", ChainIDArbitrumRinkeby: "0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681", + ChainIDLinea: "0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f", + ChainIDZKSync: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91", }