diff --git a/README.md b/README.md index 2766c945..27059b70 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,4 @@ INFINITESIMAL=0.000001 Except where indicated otherwise, the code in this repository is licensed GPLv3. -Superluminal Labs Ltd. is the owner of the directories `balancer-sor/src/pools/gyro2Pool/`, `balancer-sor/src/pools/gyro3Pool/` and `balancer-sor/src/pools/gyroEPool/` and any accompanying files contained herein (collectively, these “Software”). Use of these Software is exclusively subject to the [Gyroscope Pool License](./src/pools/gyroEPool/LICENSE), which is available at the provided link (the “Gyroscope Pool License”). These Software are not covered by the General Public License and do not confer any rights to the user other than the limited rights specified in the Gyroscope Pool License. A special hybrid license between Superluminal Labs Ltd and Balancer Labs OÜ governs Superluminal Labs Ltd's use of the Balancer Labs OÜ code [Special License](./src/pools/gyroEPool/GyroscopeBalancerLicense.pdf), which is available at the provided link. By using these Software, you agree to be bound by the terms and conditions of the Gyroscope Pool License. If you do not agree to all terms and conditions of the Gyroscope Pool License, do not use any of these Software. +Superluminal Labs Ltd. is the owner of the directories `balancer-sor/src/pools/gyro2Pool/`, `balancer-sor/src/pools/gyro3Pool/`, `balancer-sor/src/pools/gyroEPool/`, `balancer-sor/src/pools/gyro2V2Pool/`, and `balancer-sor/src/pools/gyroEV2Pool/` and any accompanying files contained herein (collectively, these “Software”). Use of these Software is exclusively subject to the [Gyroscope Pool License](./src/pools/gyroEPool/LICENSE), which is available at the provided link (the “Gyroscope Pool License”). These Software are not covered by the General Public License and do not confer any rights to the user other than the limited rights specified in the Gyroscope Pool License. A special hybrid license between Superluminal Labs Ltd and Balancer Labs OÜ governs Superluminal Labs Ltd's use of the Balancer Labs OÜ code [Special License](./src/pools/gyroEPool/GyroscopeBalancerLicense.pdf), which is available at the provided link. By using these Software, you agree to be bound by the terms and conditions of the Gyroscope Pool License. If you do not agree to all terms and conditions of the Gyroscope Pool License, do not use any of these Software. diff --git a/src/index.ts b/src/index.ts index c6e50400..da0829ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ export { PhantomStablePool } from './pools/phantomStablePool/phantomStablePool'; export { ComposableStablePool } from './pools/composableStable/composableStablePool'; export { LinearPool } from './pools/linearPool/linearPool'; export { Gyro2Pool } from './pools/gyro2Pool/gyro2Pool'; +export { Gyro2V2Pool } from './pools/gyro2V2Pool/gyro2V2Pool'; export { Gyro3Pool } from './pools/gyro3Pool/gyro3Pool'; export { GyroEV2Pool } from './pools/gyroEV2Pool/gyroEV2Pool'; export { FxPool } from './pools/xaveFxPool/fxPool'; diff --git a/src/pools/gyro2V2Pool/GyroscopeBalancerLicense.pdf b/src/pools/gyro2V2Pool/GyroscopeBalancerLicense.pdf new file mode 100644 index 00000000..531fd47e Binary files /dev/null and b/src/pools/gyro2V2Pool/GyroscopeBalancerLicense.pdf differ diff --git a/src/pools/gyro2V2Pool/LICENSE b/src/pools/gyro2V2Pool/LICENSE new file mode 100644 index 00000000..f22ac9e4 --- /dev/null +++ b/src/pools/gyro2V2Pool/LICENSE @@ -0,0 +1,5 @@ +(c) 2022 Superluminal Labs Ltd. All rights reserved. + +All moral, intellectual property, and other rights (including rights to all inventions, codes, designs, and protocols) associated with the software code published by Superluminal Labs Ltd. and residing in this repository (this “Software”) are reserved by its right holder(s) except as otherwise provided in this Gyroscope Pool User Contract. This Software has been published for informational purposes and may be run only with the Balancer automated portfolio manager and trading platform solely for testing and internal evaluation purposes only; no license under patents, copyright, trademark, or any other intellectual property right (other than the limited license to run this Software for testing and internal evaluation) is granted or implied. A special hybrid license between Superluminal Labs Ltd and Balancer Labs OÜ governs Superluminal Labs Ltd.'s use of the Balancer Labs OÜ code [Special License](./GyroscopeBalancerLicense.pdf), which is available at the provided link. + +THE SOFTWARE AND INTELLECTUAL PROPERTY INCLUDED IN THIS SOFTWARE IS PROVIDED BY THE RIGHT HOLDER(S) "AS IS," AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE RIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR INTELLECTUAL PROPERTY (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION), HOWEVER CAUSED OR CLAIMED (WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)), EVEN IF SUCH DAMAGES WERE REASONABLY FORESEEABLE OR THE RIGHT HOLDER(S) WERE ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/pools/gyro2V2Pool/gyro2V2Abi.json b/src/pools/gyro2V2Pool/gyro2V2Abi.json new file mode 100644 index 00000000..5872e82a --- /dev/null +++ b/src/pools/gyro2V2Pool/gyro2V2Abi.json @@ -0,0 +1,1371 @@ +[ + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "contract IERC20", + "name": "token0", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "internalType": "struct ExtensibleWeightedPool2Tokens.NewPoolParams", + "name": "baseParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "sqrtAlpha", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sqrtBeta", + "type": "uint256" + }, + { + "internalType": "address", + "name": "rateProvider0", + "type": "address" + }, + { + "internalType": "address", + "name": "rateProvider1", + "type": "address" + }, + { + "internalType": "address", + "name": "capManager", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "capParams", + "type": "tuple" + }, + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + } + ], + "internalType": "struct Gyro2CLPPool.GyroParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "address", + "name": "configAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "capManager", + "type": "address" + } + ], + "name": "CapManagerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "indexed": false, + "internalType": "struct ICappedLiquidity.CapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "CapParamsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPauseManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPauseManager", + "type": "address" + } + ], + "name": "PauseManagerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "PausedLocally", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "SwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "UnpausedLocally", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "balanceTokenIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceTokenOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "tokenInIsToken0", + "type": "bool" + } + ], + "name": "calculateCurrentValues", + "outputs": [ + { + "internalType": "uint256", + "name": "currentInvariant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "virtualParamIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "virtualParamOut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "capManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "capParams", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauseManager", + "type": "address" + } + ], + "name": "changePauseManager", + "outputs": [], + "stateMutability": "nonpayable", + "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": "amount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "getActionId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActualSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInvariantDivActualSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMiscData", + "outputs": [ + { + "internalType": "int256", + "name": "logInvariant", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logTotalSupply", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "oracleSampleCreationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "oracleIndex", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNormalizedWeights", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSqrtParameters", + "outputs": [ + { + "internalType": "uint256[2]", + "name": "", + "type": "uint256[2]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSwapFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenRates", + "outputs": [ + { + "internalType": "uint256", + "name": "rate0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rate1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVirtualParameters", + "outputs": [ + { + "internalType": "uint256[]", + "name": "virtualParams", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gyroConfig", + "outputs": [ + { + "internalType": "contract IGyroConfig", + "name": "", + "type": "address" + } + ], + "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": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onExitPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onJoinPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "dueProtocolFeeAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IPoolSwapStructs.SwapRequest", + "name": "request", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "balanceTokenIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceTokenOut", + "type": "uint256" + } + ], + "name": "onSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryExit", + "outputs": [ + { + "internalType": "uint256", + "name": "bptIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryJoin", + "outputs": [ + { + "internalType": "uint256", + "name": "bptOut", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rateProvider0", + "outputs": [ + { + "internalType": "contract IRateProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rateProvider1", + "outputs": [ + { + "internalType": "contract IRateProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_capManager", + "type": "address" + } + ], + "name": "setCapManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "setCapParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "setSwapFeePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/pools/gyro2V2Pool/gyro2V2Math/gyro2V2MathHelpers.ts b/src/pools/gyro2V2Pool/gyro2V2Math/gyro2V2MathHelpers.ts new file mode 100644 index 00000000..441351f0 --- /dev/null +++ b/src/pools/gyro2V2Pool/gyro2V2Math/gyro2V2MathHelpers.ts @@ -0,0 +1,15 @@ +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { WeiPerEther as ONE } from '@ethersproject/constants'; +import { mulDown } from '../../gyroHelpers/gyroSignedFixedPoint'; + +export function normalizeBalances( + balances: BigNumber[], + decimals: number[], + tokenRates: BigNumber[] +): BigNumber[] { + const scalingFactors = decimals.map((d) => parseFixed('1', d)); + + return balances.map((bal, index) => + mulDown(bal.mul(ONE).div(scalingFactors[index]), tokenRates[index]) + ); +} diff --git a/src/pools/gyro2V2Pool/gyro2V2Pool.ts b/src/pools/gyro2V2Pool/gyro2V2Pool.ts new file mode 100644 index 00000000..b8d77a76 --- /dev/null +++ b/src/pools/gyro2V2Pool/gyro2V2Pool.ts @@ -0,0 +1,539 @@ +import { getAddress } from '@ethersproject/address'; +import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; +import { formatFixed, BigNumber } from '@ethersproject/bignumber'; +import { BigNumber as OldBigNumber, bnum, ZERO } from '../../utils/bignumber'; + +import { + PoolBase, + PoolPairBase, + PoolTypes, + SubgraphToken, + SwapTypes, + SubgraphPoolBase, +} from '../../types'; +import { isSameAddress, safeParseFixed } from '../../utils'; +import { mulDown, divDown } from '../gyroHelpers/gyroSignedFixedPoint'; +import { + _calculateInvariant, + _calcOutGivenIn, + _calcInGivenOut, + _findVirtualParams, + _calculateNewSpotPrice, + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, +} from '../gyro2Pool/gyro2Math'; +import { _reduceFee, _addFee } from '../gyroHelpers/helpers'; +import { SWAP_LIMIT_FACTOR } from '../gyroHelpers/constants'; +import { universalNormalizedLiquidity } from '../liquidity'; + +import { normalizeBalances } from './gyro2V2Math/gyro2V2MathHelpers'; + +type Gyro2V2PoolPairData = PoolPairBase & { + // NB we follow a different approach than for the gyroE[V2]Pool here, where the pool pair data contains everything we need to know and we can forget about whether token-in is token 0 or 1. + sqrtAlpha: BigNumber; + sqrtBeta: BigNumber; + tokenRates: BigNumber[]; +}; + +export type Gyro2PoolToken = Pick< + SubgraphToken, + 'address' | 'balance' | 'decimals' +>; + +export class Gyro2V2Pool implements PoolBase { + poolType: PoolTypes = PoolTypes.Gyro2; + id: string; + address: string; + tokensList: string[]; + tokens: Gyro2PoolToken[]; + swapFee: BigNumber; + totalShares: BigNumber; + sqrtAlpha: BigNumber; + sqrtBeta: BigNumber; + tokenRates: BigNumber[]; + + static fromPool(pool: SubgraphPoolBase): Gyro2V2Pool { + if (!pool.sqrtAlpha || !pool.sqrtBeta) + throw new Error( + 'Pool missing Gyro2 sqrtAlpha and/or sqrtBeta params' + ); + + if (!pool.tokenRates) + throw new Error('Gyro2V2 Pool missing tokenRates'); + + return new Gyro2V2Pool( + pool.id, + pool.address, + pool.swapFee, + pool.totalShares, + pool.tokens as Gyro2PoolToken[], + pool.tokensList, + pool.sqrtAlpha, + pool.sqrtBeta, + pool.tokenRates + ); + } + + constructor( + id: string, + address: string, + swapFee: string, + totalShares: string, + tokens: Gyro2PoolToken[], + tokensList: string[], + sqrtAlpha: string, + sqrtBeta: string, + tokenRates: string[] + ) { + this.id = id; + this.address = address; + this.swapFee = safeParseFixed(swapFee, 18); + this.totalShares = safeParseFixed(totalShares, 18); + this.tokens = tokens; + this.tokensList = tokensList; + this.sqrtAlpha = safeParseFixed(sqrtAlpha, 18); + this.sqrtBeta = safeParseFixed(sqrtBeta, 18); + this.tokenRates = [ + safeParseFixed(tokenRates[0], 18), + safeParseFixed(tokenRates[1], 18), + ]; + } + + parsePoolPairData(tokenIn: string, tokenOut: string): Gyro2V2PoolPairData { + const tokenInIndex = this.tokens.findIndex( + (t) => getAddress(t.address) === getAddress(tokenIn) + ); + if (tokenInIndex < 0) throw 'Pool does not contain tokenIn'; + const tI = this.tokens[tokenInIndex]; + const balanceIn = tI.balance; + const decimalsIn = tI.decimals; + + const tokenOutIndex = this.tokens.findIndex( + (t) => getAddress(t.address) === getAddress(tokenOut) + ); + if (tokenOutIndex < 0) throw 'Pool does not contain tokenOut'; + const tO = this.tokens[tokenOutIndex]; + const balanceOut = tO.balance; + const decimalsOut = tO.decimals; + + const tokenInIsToken0 = tokenInIndex === 0; + + const poolPairData: Gyro2V2PoolPairData = { + id: this.id, + address: this.address, + poolType: this.poolType, + tokenIn: tokenIn, + tokenOut: tokenOut, + decimalsIn: Number(decimalsIn), + decimalsOut: Number(decimalsOut), + balanceIn: safeParseFixed(balanceIn, decimalsIn), + balanceOut: safeParseFixed(balanceOut, decimalsOut), + swapFee: this.swapFee, + sqrtAlpha: tokenInIsToken0 + ? this.sqrtAlpha + : divDown(ONE, this.sqrtBeta), + sqrtBeta: tokenInIsToken0 + ? this.sqrtBeta + : divDown(ONE, this.sqrtAlpha), + tokenRates: tokenInIsToken0 + ? this.tokenRates + : [this.tokenRates[1], this.tokenRates[0]], + }; + + return poolPairData; + } + + getNormalizedLiquidity(poolPairData: Gyro2V2PoolPairData): OldBigNumber { + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) + ); + } + + getLimitAmountSwap( + poolPairData: Gyro2V2PoolPairData, + swapType: SwapTypes + ): OldBigNumber { + if (swapType === SwapTypes.SwapExactIn) { + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const maxAmountInAssetInPool = mulDown( + invariant, + divDown(ONE, poolPairData.sqrtAlpha).sub( + divDown(ONE, poolPairData.sqrtBeta) + ) + ); // x+ = L * (1/sqrtAlpha - 1/sqrtBeta) + const limitAmountIn = divDown( + maxAmountInAssetInPool.sub(normalizedBalances[0]), + poolPairData.tokenRates[0] + ); + const limitAmountInPlusSwapFee = divDown( + limitAmountIn, + ONE.sub(poolPairData.swapFee) + ); + return bnum( + formatFixed( + mulDown(limitAmountInPlusSwapFee, SWAP_LIMIT_FACTOR), + 18 + ) + ); + } else { + return bnum( + formatFixed( + mulDown(poolPairData.balanceOut, SWAP_LIMIT_FACTOR), + poolPairData.decimalsOut + ) + ); + } + } + + // Updates the balance of a given token for the pool + updateTokenBalanceForPool(token: string, newBalance: BigNumber): void { + // token is BPT + if (isSameAddress(this.address, token)) { + this.updateTotalShares(newBalance); + } else { + // token is underlying in the pool + const T = this.tokens.find((t) => isSameAddress(t.address, token)); + if (!T) throw Error('Pool does not contain this token'); + T.balance = formatFixed(newBalance, T.decimals); + } + } + + updateTotalShares(newTotalShares: BigNumber): void { + this.totalShares = newTotalShares; + } + + _exactTokenInForTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const inAmount = safeParseFixed(amount.toString(), 18); + const inAmountLessFee = _reduceFee(inAmount, poolPairData.swapFee); + const inAmountLessFeeScaled = mulDown( + inAmountLessFee, + poolPairData.tokenRates[0] + ); + + const outAmountScaled = _calcOutGivenIn( + normalizedBalances[0], + normalizedBalances[1], + inAmountLessFeeScaled, + virtualParamIn, + virtualParamOut + ); + const outAmount = divDown( + outAmountScaled, + poolPairData.tokenRates[1] + ); + return bnum(formatFixed(outAmount, 18)); + } catch (error) { + return bnum(0); + } + } + + _tokenInForExactTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const outAmount = safeParseFixed(amount.toString(), 18); + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const outAmountScaled = mulDown( + outAmount, + poolPairData.tokenRates[1] + ); + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const inAmountScaledLessFee = _calcInGivenOut( + normalizedBalances[0], + normalizedBalances[1], + outAmountScaled, + virtualParamIn, + virtualParamOut + ); + const inAmountLessFee = divDown( + inAmountScaledLessFee, + poolPairData.tokenRates[0] + ); + const inAmount = _addFee(inAmountLessFee, poolPairData.swapFee); + + return bnum(formatFixed(inAmount, 18)); + } catch (error) { + return bnum(0); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _calcTokensOutGivenExactBptIn(bptAmountIn: BigNumber): BigNumber[] { + // Missing maths for this + return new Array(this.tokens.length).fill(Zero); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _calcBptOutGivenExactTokensIn(amountsIn: BigNumber[]): BigNumber { + // Missing maths for this + return Zero; + } + + _spotPriceAfterSwapExactTokenInForTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const inAmount = safeParseFixed(amount.toString(), 18); + const inAmountScaled = mulDown( + inAmount, + poolPairData.tokenRates[0] + ); + const inAmountLessFee = _reduceFee(inAmount, poolPairData.swapFee); + const inAmountLessFeeScaled = mulDown( + inAmountLessFee, + poolPairData.tokenRates[0] + ); + const outAmountScaled = _calcOutGivenIn( + normalizedBalances[0], + normalizedBalances[1], + inAmountLessFeeScaled, + virtualParamIn, + virtualParamOut + ); + const newSpotPriceScaled = _calculateNewSpotPrice( + normalizedBalances, + inAmountScaled, + outAmountScaled, + virtualParamIn, + virtualParamOut, + poolPairData.swapFee + ); + const newSpotPrice = divDown( + mulDown(newSpotPriceScaled, poolPairData.tokenRates[1]), + poolPairData.tokenRates[0] + ); + return bnum(formatFixed(newSpotPrice, 18)); + } catch (error) { + return bnum(0); + } + } + + _spotPriceAfterSwapTokenInForExactTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const outAmount = safeParseFixed(amount.toString(), 18); + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const outAmountScaled = mulDown( + outAmount, + poolPairData.tokenRates[1] + ); + const inAmountLessFeeScaled = _calcInGivenOut( + normalizedBalances[0], + normalizedBalances[1], + outAmountScaled, + virtualParamIn, + virtualParamOut + ); + const inAmountScaled = _addFee( + inAmountLessFeeScaled, + poolPairData.swapFee + ); + const newSpotPriceScaled = _calculateNewSpotPrice( + normalizedBalances, + inAmountScaled, + outAmountScaled, + virtualParamIn, + virtualParamOut, + poolPairData.swapFee + ); + const newSpotPrice = divDown( + mulDown(newSpotPriceScaled, poolPairData.tokenRates[1]), + poolPairData.tokenRates[0] + ); + + return bnum(formatFixed(newSpotPrice, 18)); + } catch (error) { + return bnum(0); + } + } + + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const inAmount = safeParseFixed(amount.toString(), 18); + const inAmountLessFee = _reduceFee(inAmount, poolPairData.swapFee); + const inAmountLessFeeScaled = mulDown( + inAmountLessFee, + poolPairData.tokenRates[0] + ); + const outAmountScaled = _calcOutGivenIn( + normalizedBalances[0], + normalizedBalances[1], + inAmountLessFeeScaled, + virtualParamIn, + virtualParamOut + ); + const derivativeScaled = + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + normalizedBalances, + outAmountScaled, + virtualParamOut + ); + const derivative = mulDown( + derivativeScaled, + poolPairData.tokenRates[1] + ); + return bnum(formatFixed(derivative, 18)); + } catch (error) { + return bnum(0); + } + } + + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + poolPairData: Gyro2V2PoolPairData, + amount: OldBigNumber + ): OldBigNumber { + try { + const outAmount = safeParseFixed(amount.toString(), 18); + const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; + const normalizedBalances = normalizeBalances( + balances, + [poolPairData.decimalsIn, poolPairData.decimalsOut], + poolPairData.tokenRates + ); + const invariant = _calculateInvariant( + normalizedBalances, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const [virtualParamIn, virtualParamOut] = _findVirtualParams( + invariant, + poolPairData.sqrtAlpha, + poolPairData.sqrtBeta + ); + const outAmountScaled = mulDown( + outAmount, + poolPairData.tokenRates[1] + ); + const inAmountLessFeeScaled = _calcInGivenOut( + normalizedBalances[0], + normalizedBalances[1], + outAmountScaled, + virtualParamIn, + virtualParamOut + ); + const inAmountScaled = _addFee( + inAmountLessFeeScaled, + poolPairData.swapFee + ); + + const derivativeScaled = + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + normalizedBalances, + inAmountScaled, + outAmountScaled, + virtualParamIn, + virtualParamOut, + poolPairData.swapFee + ); + const rateAdjFactor = divDown( + mulDown(poolPairData.tokenRates[1], poolPairData.tokenRates[1]), + poolPairData.tokenRates[0] + ); + const derivative = mulDown(derivativeScaled, rateAdjFactor); + return bnum(formatFixed(derivative, 18)); + } catch (error) { + return bnum(0); + } + } +} diff --git a/src/pools/index.ts b/src/pools/index.ts index 45db5f49..891dc2dc 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -6,6 +6,7 @@ import { ElementPool } from './elementPool/elementPool'; import { PhantomStablePool } from './phantomStablePool/phantomStablePool'; import { ComposableStablePool } from './composableStable/composableStablePool'; import { Gyro2Pool } from './gyro2Pool/gyro2Pool'; +import { Gyro2V2Pool } from './gyro2V2Pool/gyro2V2Pool'; import { Gyro3Pool } from './gyro3Pool/gyro3Pool'; import { GyroEPool } from './gyroEPool/gyroEPool'; import { GyroEV2Pool } from './gyroEV2Pool/gyroEV2Pool'; @@ -37,6 +38,7 @@ export function parseNewPool( | PhantomStablePool | ComposableStablePool | Gyro2Pool + | Gyro2V2Pool | Gyro3Pool | GyroEPool | GyroEV2Pool @@ -54,6 +56,7 @@ export function parseNewPool( | PhantomStablePool | ComposableStablePool | Gyro2Pool + | Gyro2V2Pool | Gyro3Pool | GyroEPool | GyroEV2Pool @@ -81,8 +84,14 @@ export function parseNewPool( newPool = PhantomStablePool.fromPool(pool); else if (pool.poolType === 'ComposableStable') newPool = ComposableStablePool.fromPool(pool); - else if (pool.poolType === 'Gyro2') newPool = Gyro2Pool.fromPool(pool); - else if (pool.poolType === 'Gyro3') newPool = Gyro3Pool.fromPool(pool); + else if (pool.poolType === 'Gyro2') { + if (pool.poolTypeVersion === 2) { + newPool = Gyro2V2Pool.fromPool(pool); + } else { + newPool = Gyro2Pool.fromPool(pool); + } + } else if (pool.poolType === 'Gyro3') + newPool = Gyro3Pool.fromPool(pool); else if (pool.poolType === 'GyroE') { if (pool.poolTypeVersion === 2) { newPool = GyroEV2Pool.fromPool(pool); diff --git a/src/types.ts b/src/types.ts index daced257..3ce14f9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -120,7 +120,7 @@ export interface SubgraphPoolBase { z?: string; dSq?: string; - // GyroEV2 specific fields + // GyroEV2 and Gyro2V2 specific fields tokenRates?: string[]; // FxPool diff --git a/test/gyro2V2.integration.spec.ts b/test/gyro2V2.integration.spec.ts new file mode 100644 index 00000000..d4976225 --- /dev/null +++ b/test/gyro2V2.integration.spec.ts @@ -0,0 +1,224 @@ +// yarn test:only test/gyro2V2.integration.spec.ts +import dotenv from 'dotenv'; +dotenv.config(); + +import { expect } from 'chai'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { parseFixed } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; +import { Vault__factory } from '@balancer-labs/typechain'; + +import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; +import { setUp } from './testScripts/utils'; + +const networkId = Network.MAINNET; +const jsonRpcUrl = process.env.RPC_URL_MAINNET ?? ''; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new JsonRpcProvider(rpcUrl, networkId); +const blocknumber = 19269440; + +const vault = Vault__factory.connect(vaultAddr, provider); + +const WETH = ADDRESSES[networkId].WETH; +const wSTETH = ADDRESSES[networkId].wSTETH; + +const gyro2V2_WSTETH_WETH: SubgraphPoolBase = { + id: '0xc6853f0539f7d4926c719326d60bd84a752bbb8f00020000000000000000065e', + address: '0xc6853f0539f7d4926c719326d60bd84a752bbb8f', + poolType: 'Gyro2', + poolTypeVersion: 2, + swapFee: '0.0001', + swapEnabled: true, + totalWeight: '0', + totalShares: '0.000026367631539116', + tokensList: [ + '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + ], + tokens: [ + { + address: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + balance: '0.000005508044603265', + decimals: 18, + priceRate: '1', + weight: null, + }, + { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + balance: '0.000019999999999999', + decimals: 18, + priceRate: '1', + weight: null, + }, + ], + sqrtAlpha: '0.998348636499294268', + sqrtBeta: '1.001249219725039286', +}; + +const ROUNDING_ERROR_TOLERANCE = 2; // in wei + +describe('gyro2V2: WETH-wSTETH integration tests', () => { + let sor: SOR; + const funds = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, + }; + + // Setup chain + before(async function () { + sor = await setUp( + networkId, + provider, + [gyro2V2_WSTETH_WETH], + jsonRpcUrl as string, + blocknumber + ); + + await sor.fetchPools(); + }); + context('ExactIn', async () => { + const swapType = SwapTypes.SwapExactIn; + + it('should return no swaps when above limit', async () => { + const tokenIn = WETH.address; + const tokenOut = wSTETH.address; + const swapAmount = parseFixed('1', WETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + expect(swapInfo.swaps.length).to.eq(0); + expect(swapInfo.returnAmount.toString()).to.eq('0'); + }); + it('token > LSD, getSwaps result should match queryBatchSwap', async () => { + const tokenIn = WETH.address; + const tokenOut = wSTETH.address; + const swapAmount = parseFixed('0.000000001', WETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(queryResult[0].toNumber()).to.be.closeTo( + swapInfo.swapAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + expect(queryResult[1].toNumber() * -1).to.be.closeTo( + swapInfo.returnAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + }); + it('LSD > token, getSwaps result should match queryBatchSwap', async () => { + const tokenIn = wSTETH.address; + const tokenOut = WETH.address; + const swapAmount = parseFixed('0.000000001', wSTETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(queryResult[0].toNumber()).to.be.closeTo( + swapInfo.swapAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + expect(queryResult[1].toNumber() * -1).to.be.closeTo( + swapInfo.returnAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + }); + }); + + context('ExactOut', async () => { + const swapType = SwapTypes.SwapExactOut; + + it('should return no swaps when above limit', async () => { + const tokenIn = WETH.address; + const tokenOut = wSTETH.address; + const swapAmount = parseFixed('1', wSTETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + expect(swapInfo.swaps.length).to.eq(0); + expect(swapInfo.returnAmount.toString()).to.eq('0'); + }); + it('token > LSD, getSwaps result should match queryBatchSwap', async () => { + const tokenIn = WETH.address; + const tokenOut = wSTETH.address; + const swapAmount = parseFixed('0.000000001', wSTETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(queryResult[0].toNumber()).to.be.closeTo( + swapInfo.returnAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + expect(queryResult[1].toNumber() * -1).to.be.closeTo( + swapInfo.swapAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + }); + it('LSD > token, getSwaps result should match queryBatchSwap', async () => { + const tokenIn = wSTETH.address; + const tokenOut = WETH.address; + const swapAmount = parseFixed('0.000000001', WETH.decimals); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(queryResult[0].toNumber()).to.be.closeTo( + swapInfo.returnAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + expect(queryResult[1].toNumber() * -1).to.be.closeTo( + swapInfo.swapAmount.toNumber(), + ROUNDING_ERROR_TOLERANCE + ); + }); + }); +}); diff --git a/test/gyro2V2Pool.spec.ts b/test/gyro2V2Pool.spec.ts new file mode 100644 index 00000000..d7bb3dac --- /dev/null +++ b/test/gyro2V2Pool.spec.ts @@ -0,0 +1,217 @@ +// TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register test/gyro2V2Pool.spec.ts + +import 'dotenv/config'; +import { expect } from 'chai'; +import cloneDeep from 'lodash.clonedeep'; +import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { bnum } from '../src/utils/bignumber'; +import { USDC, DAI, sorConfigEth } from './lib/constants'; +import { SwapTypes, SOR, SwapInfo, SubgraphPoolBase } from '../src'; +// Add new PoolType +import { Gyro2V2Pool } from '../src/pools/gyro2V2Pool/gyro2V2Pool'; +// Add new pool test data in Subgraph Schema format +import testPools from './testData/gyro2Pools/gyro2V2TestPool.json'; +import { MockPoolDataService } from './lib/mockPoolDataService'; +import { mockTokenPriceService } from './lib/mockTokenPriceService'; + +describe('Gyro2V2Pool tests USDC > DAI', () => { + const testPool = cloneDeep(testPools).pools[0]; + const pool = Gyro2V2Pool.fromPool(testPool); + + const poolPairData = pool.parsePoolPairData(USDC.address, DAI.address); + + const poolPairData2 = pool.parsePoolPairData(DAI.address, USDC.address); + + context('parsePoolPairData', () => { + it(`should correctly parse USDC > DAI`, async () => { + // Tests that compare poolPairData to known results with correct number scaling, etc, i.e.: + expect(poolPairData.swapFee.toString()).to.eq( + parseFixed(testPool.swapFee, 18).toString() + ); + expect(poolPairData.id).to.eq(testPool.id); + expect(poolPairData.tokenRates[0].toString()).to.eq( + parseFixed(testPool.tokenRates[1], 18).toString() + ); + expect(poolPairData.tokenRates[1].toString()).to.eq( + parseFixed(testPool.tokenRates[0], 18).toString() + ); + }); + + // NB these price bounds are not affected by rate scaling, so they're the same as for gyro2Pool. + it(`should correctly calculate price bounds USDC > DAI`, async () => { + expect( + Number(formatFixed(poolPairData.sqrtAlpha, 18)) + ).to.be.approximately(0.9995003747, 0.00000001); + + expect( + Number(formatFixed(poolPairData.sqrtBeta, 18)) + ).to.be.approximately(1.000500375, 0.00000001); + }); + + it(`should correctly calculate price bounds DAI > USDC`, async () => { + expect( + Number(formatFixed(poolPairData2.sqrtAlpha, 18)) + ).to.be.approximately(0.9994998749, 0.00000001); + + expect( + Number(formatFixed(poolPairData2.sqrtBeta, 18)) + ).to.be.approximately(1.000499875, 0.00000001); + }); + }); + + context('limit amounts', () => { + it(`should correctly calculate limit amounts, USDC > DAI`, async () => { + let amount = pool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactIn + ); + + expect(amount.toString()).to.eq('1865.435197059850834134'); + + amount = pool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactOut + ); + + expect(amount.toString()).to.eq('1231.998768'); + }); + }); + + context('normalized liquidity', () => { + it(`should correctly calculate normalized liquidity, USDC > DAI`, async () => { + const normalizedLiquidity = + pool.getNormalizedLiquidity(poolPairData); + + expect(Number(normalizedLiquidity.toString())).to.be.approximately( + 949690.862560122978692435, + 0.00001 + ); + }); + + it(`should correctly calculate normalized liquidity, DAI > USDC`, async () => { + const normalizedLiquidity = + pool.getNormalizedLiquidity(poolPairData2); + + expect(Number(normalizedLiquidity.toString())).to.be.approximately( + 1424111.581891956376601924, + 0.00001 + ); + }); + }); + + context('Test Swaps', () => { + context('SwapExactIn', () => { + const amountIn = bnum('13.5'); + + it('should correctly calculate amountOut given amountIn', async () => { + const amountOut = pool._exactTokenInForTokenOut( + poolPairData, + amountIn + ); + expect(Number(amountOut.toString())).to.be.approximately( + 8.921618001976369271, + 0.000000000000000001 + ); + // expect(amountOut.toString()).to.eq('8.921618001976369271'); + }); + it('should correctly calculate newSpotPrice', async () => { + const newSpotPrice = + pool._spotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + amountIn + ); + expect(newSpotPrice.toString()).to.eq('1.513185546431756763'); + }); + it('should correctly calculate derivative of spot price function at newSpotPrice', async () => { + const derivative = + pool._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + amountIn + ); + expect(derivative.toString()).to.eq('0.000001052979170973'); + }); + }); + + context('SwapExactOut', () => { + const amountOut = bnum('45.568'); + + it('should correctly calculate amountIn given amountOut', async () => { + const amountIn = pool._tokenInForExactTokenOut( + poolPairData, + amountOut + ); + expect(amountIn.toString()).to.eq('68.953845491508993928'); + }); + it('should correctly calculate newSpotPrice', async () => { + const newSpotPrice = + pool._spotPriceAfterSwapTokenInForExactTokenOut( + poolPairData, + amountOut + ); + expect(Number(newSpotPrice.toString())).to.be.approximately( + 1.513243938739323921, + 0.000000000000000001 + ); + }); + it('should correctly calculate derivative of spot price function at newSpotPrice', async () => { + const derivative = + pool._derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + poolPairData, + amountOut + ); + expect(Number(derivative.toString())).to.be.approximately( + 0.000001593445091924, + 0.000000000000000005 + ); + }); + }); + + context('FullSwap', () => { + it(`Full Swap - swapExactIn, Token>Token`, async () => { + const pools: SubgraphPoolBase[] = cloneDeep(testPools.pools); + const tokenIn = USDC.address; + const tokenOut = DAI.address; + const swapType = SwapTypes.SwapExactIn; + const swapAmt = parseFixed('13.5', 6); + + const gasPrice = parseFixed('30', 9); + const maxPools = 4; + const provider = new JsonRpcProvider(``); + + const sor = new SOR( + provider, + sorConfigEth, + new MockPoolDataService(pools), + mockTokenPriceService + ); + const fetchSuccess = await sor.fetchPools(); + expect(fetchSuccess).to.be.true; + + const swapInfo: SwapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmt, + { gasPrice, maxPools } + ); + + console.log(`Return amt:`); + console.log(swapInfo.returnAmount.toString()); + // This value is hard coded as sanity check if things unexpectedly change. Taken from V2 test run (with extra fee logic added). + // TO DO - expect(swapInfo.returnAmount.toString()).eq('999603'); + expect(swapInfo.swaps.length).eq(1); + expect(swapInfo.swaps[0].amount.toString()).eq( + swapAmt.toString() + ); + expect(swapInfo.swaps[0].poolId).eq(testPools.pools[0].id); + expect( + swapInfo.tokenAddresses[swapInfo.swaps[0].assetInIndex] + ).eq(tokenIn); + expect( + swapInfo.tokenAddresses[swapInfo.swaps[0].assetOutIndex] + ).eq(tokenOut); + }); + }); + }); +}); diff --git a/test/lib/onchainData.ts b/test/lib/onchainData.ts index 8313e77a..6aa07724 100644 --- a/test/lib/onchainData.ts +++ b/test/lib/onchainData.ts @@ -13,6 +13,7 @@ import elementPoolAbi from '../../src/pools/elementPool/ConvergentCurvePool.json import linearPoolAbi from '../../src/pools/linearPool/linearPoolAbi.json'; import fxPoolAbi from '../../src/pools/xaveFxPool/fxPoolAbi.json'; import gyroEV2Abi from '../../src/pools/gyroEV2Pool/gyroEV2Abi.json'; +import gyro2V2Abi from '../../src/pools/gyro2V2Pool/gyro2V2Abi.json'; import { PoolFilter, SubgraphPoolBase, PoolDataService } from '../../src'; import { Multicaller } from './multicaller'; import { Fragment, JsonFragment } from '@ethersproject/abi/lib/fragments'; @@ -39,6 +40,7 @@ export async function getOnChainBalances( ...composableStablePoolAbi, ...fxPoolAbi, ...gyroEV2Abi, + ...gyro2V2Abi, ].map((row) => [row.name, row]) ) ); @@ -161,6 +163,15 @@ export async function getOnChainBalances( pool.address, 'getTokenRates' ); + } else if ( + pool.poolType.toString() === 'Gyro2' && + pool.poolTypeVersion === 2 + ) { + multiPool.call( + `${pool.id}.tokenRates`, + pool.address, + 'getTokenRates' + ); } } }); @@ -336,6 +347,20 @@ export async function getOnChainBalances( ); } + if ( + subgraphPools[index].poolType === 'Gyro2' && + subgraphPools[index].poolTypeVersion == 2 + ) { + if (!Array.isArray(tokenRates) || tokenRates.length !== 2) { + console.error( + `Gyro2V2 pool with missing or invalid tokenRates: ${poolId}` + ); + return; + } + subgraphPools[index].tokenRates = tokenRates.map((rate) => + formatFixed(rate, 18) + ); + } onChainPools.push(subgraphPools[index]); } catch (err) { throw `Issue with pool onchain data: ${err}`; diff --git a/test/testData/gyro2Pools/gyro2V2TestPool.json b/test/testData/gyro2Pools/gyro2V2TestPool.json new file mode 100644 index 00000000..3b37dec8 --- /dev/null +++ b/test/testData/gyro2Pools/gyro2V2TestPool.json @@ -0,0 +1,49 @@ +{ + "tradeInfo": { + "SwapType": "n/a", + "TokenIn": "n/a", + "TokenOut": "n/a", + "NoPools": 1, + "SwapAmount": "n/a", + "GasPrice": "n/a", + "SwapAmountDecimals": "n/a", + "ReturnAmountDecimals": "n/a" + }, + "pools": [ + { + "id": "0xebfed10e11dc08fcda1af1fda146945e8710f22e0000000000000000000000ff", + "address": "0xebfed10e11dc08fcda1af1fda146945e8710f22e", + "swapFee": "0.009", + "tokens": [ + { + "address": "0x6b175474e89094c44da98b954eedeac495271d0f", + "balance": "1232", + "decimals": 18, + "symbol": "DAI", + "weight": null, + "priceRate": "1" + }, + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "balance": "1000", + "decimals": 6, + "weight": null, + "priceRate": "1", + "symbol": "USDC" + } + ], + "tokensList": [ + "0x6b175474e89094c44da98b954eedeac495271d0f", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + ], + "totalWeight": "15", + "totalShares": "100", + "poolType": "Gyro2", + "poolTypeVersion": 2, + "swapEnabled": true, + "sqrtAlpha": "0.9994998749", + "sqrtBeta": "1.000499875", + "tokenRates": ["1.5", "1"] + } + ] +} diff --git a/test/testScripts/utils.ts b/test/testScripts/utils.ts index b9cae3aa..d5ddb0be 100644 --- a/test/testScripts/utils.ts +++ b/test/testScripts/utils.ts @@ -239,7 +239,7 @@ export const setUp = async ( provider, pools, }); - class CoingeckoTokenPriceService implements TokenPriceService { + class MockTokenPriceService implements TokenPriceService { constructor(private readonly chainId: number) {} async getNativeAssetPriceInToken( // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -249,16 +249,14 @@ export const setUp = async ( } } - // Use coingecko to fetch token price information. Used to calculate cost of additonal swaps/hops. - const coingeckoTokenPriceService = new CoingeckoTokenPriceService( - networkId - ); + // Mock token price information. Used to calculate cost of additonal swaps/hops. + const mockTokenPriceService = new MockTokenPriceService(networkId); return new SOR( provider, SOR_CONFIG[networkId], onChainPoolDataService, - coingeckoTokenPriceService + mockTokenPriceService ); }; diff --git a/tsconfig.json b/tsconfig.json index 42f46e40..b0009957 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,8 @@ "src/pools/linearPool/linearPoolAbi.json", "src/pools/composableStable/ComposableStable.json", "src/pools/xaveFxPool/fxPoolAbi.json", - "src/pools/gyroEV2Pool/gyroEV2Abi.json" + "src/pools/gyroEV2Pool/gyroEV2Abi.json", + "src/pools/gyro2V2Pool/gyro2V2Abi.json" ], "files": ["hardhat.config.ts"] } diff --git a/yarn.lock b/yarn.lock index 74c11b8b..61d24cf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4156,9 +4156,9 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0"