diff --git a/testData/config.json b/testData/config.json index f38d8fe..4f4ee2c 100644 --- a/testData/config.json +++ b/testData/config.json @@ -477,6 +477,27 @@ } ] }, + { + "testName": "FixedPriceLBP-MTK-USDC", + "chainId": "11155111", + "blockNumber": "10434442", + "poolAddress": "0x398a13d3d44e4825f997cc7aee4c7016878ba244", + "poolType": "FIXED_PRICE_LBP", + "swaps": [ + { + "swapKind": 0, + "amountRaw": "10000000", + "tokenIn": "0x80d6d3946ed8a1da4e226aa21ccddc32bd127d1a", + "tokenOut": "0x212f34a319d901f6958e6df2b7d295d5a82635ba" + }, + { + "swapKind": 1, + "amountRaw": "10000000000000000000", + "tokenIn": "0x80d6d3946ed8a1da4e226aa21ccddc32bd127d1a", + "tokenOut": "0x212f34a319d901f6958e6df2b7d295d5a82635ba" + } + ] + }, { "testName": "QuantAMM", "chainId": "1", diff --git a/testData/src/abi/fixedPriceLBPAbi.ts b/testData/src/abi/fixedPriceLBPAbi.ts new file mode 100644 index 0000000..8ab9172 --- /dev/null +++ b/testData/src/abi/fixedPriceLBPAbi.ts @@ -0,0 +1,2010 @@ +export const fixedPriceLBPAbi = [ + { + inputs: [ + { + components: [ + { + internalType: 'string', + name: 'name', + type: 'string', + }, + { + internalType: 'string', + name: 'symbol', + type: 'string', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'projectToken', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'reserveToken', + type: 'address', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'blockProjectTokenSwapsIn', + type: 'bool', + }, + ], + internalType: 'struct LBPCommonParams', + name: 'lbpCommonParams', + type: 'tuple', + }, + { + components: [ + { + internalType: 'contract IVault', + name: 'vault', + type: 'address', + }, + { + internalType: 'address', + name: 'trustedRouter', + type: 'address', + }, + { + internalType: 'string', + name: 'poolVersion', + type: 'string', + }, + ], + internalType: 'struct FactoryParams', + name: 'factoryParams', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'projectTokenRate', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'AddingLiquidityNotAllowed', + type: 'error', + }, + { + inputs: [], + name: 'ECDSAInvalidSignature', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'length', + type: 'uint256', + }, + ], + name: 'ECDSAInvalidSignatureLength', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'ECDSAInvalidSignatureS', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'ERC2612ExpiredSignature', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'ERC2612InvalidSigner', + type: 'error', + }, + { + inputs: [], + name: 'InputLengthMismatch', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'currentNonce', + type: 'uint256', + }, + ], + name: 'InvalidAccountNonce', + type: 'error', + }, + { + inputs: [], + name: 'InvalidBptLockDuration', + type: 'error', + }, + { + inputs: [], + name: 'InvalidBptPercentageToMigrate', + type: 'error', + }, + { + inputs: [], + name: 'InvalidInitializationAmount', + type: 'error', + }, + { + inputs: [], + name: 'InvalidMigrationWeights', + type: 'error', + }, + { + inputs: [], + name: 'InvalidOwner', + type: 'error', + }, + { + inputs: [], + name: 'InvalidProjectToken', + type: 'error', + }, + { + inputs: [], + name: 'InvalidProjectTokenRate', + type: 'error', + }, + { + inputs: [], + name: 'InvalidReserveToken', + type: 'error', + }, + { + inputs: [], + name: 'InvalidShortString', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'resolvedStartTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + ], + name: 'InvalidStartTime', + type: 'error', + }, + { + inputs: [], + name: 'InvalidTokenConfiguration', + type: 'error', + }, + { + inputs: [], + name: 'MigrationRouterRequired', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'OwnableInvalidOwner', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'OwnableUnauthorizedAccount', + type: 'error', + }, + { + inputs: [], + name: 'RemovingLiquidityNotAllowed', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'SenderIsNotVault', + type: 'error', + }, + { + inputs: [ + { + internalType: 'string', + name: 'str', + type: 'string', + }, + ], + name: 'StringTooLong', + type: 'error', + }, + { + inputs: [], + name: 'SwapOfProjectTokenIn', + type: 'error', + }, + { + inputs: [], + name: 'SwapsDisabled', + type: 'error', + }, + { + inputs: [], + name: 'TokenSwapsInUnsupported', + type: 'error', + }, + { + inputs: [], + name: 'TokensMustBeDifferent', + type: 'error', + }, + { + inputs: [], + name: 'UnsupportedOperation', + 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: [], + name: 'EIP712DomainChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferStarted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + 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: '', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'computeBalance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'balances', + type: 'uint256[]', + }, + { + internalType: 'enum Rounding', + name: 'rounding', + type: 'uint8', + }, + ], + name: 'computeInvariant', + outputs: [ + { + internalType: 'uint256', + name: 'invariant', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { + internalType: 'bytes1', + name: 'fields', + type: 'bytes1', + }, + { + internalType: 'string', + name: 'name', + type: 'string', + }, + { + internalType: 'string', + name: 'version', + type: 'string', + }, + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: 'verifyingContract', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'salt', + type: 'bytes32', + }, + { + internalType: 'uint256[]', + name: 'extensions', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'emitApproval', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'emitTransfer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getAggregateFeePercentages', + outputs: [ + { + internalType: 'uint256', + name: 'aggregateSwapFeePercentage', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aggregateYieldFeePercentage', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCurrentLiveBalances', + outputs: [ + { + internalType: 'uint256[]', + name: 'balancesLiveScaled18', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getFixedPriceLBPoolDynamicData', + outputs: [ + { + components: [ + { + internalType: 'uint256[]', + name: 'balancesLiveScaled18', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'staticSwapFeePercentage', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'totalSupply', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'isPoolInitialized', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isPoolPaused', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isPoolInRecoveryMode', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isSwapEnabled', + type: 'bool', + }, + ], + internalType: 'struct FixedPriceLBPoolDynamicData', + name: 'data', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getFixedPriceLBPoolImmutableData', + outputs: [ + { + components: [ + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'decimalScalingFactors', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'projectTokenIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveTokenIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'projectTokenRate', + type: 'uint256', + }, + ], + internalType: 'struct FixedPriceLBPoolImmutableData', + name: 'data', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getHookFlags', + outputs: [ + { + components: [ + { + internalType: 'bool', + name: 'enableHookAdjustedAmounts', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallComputeDynamicSwapFee', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + internalType: 'struct HookFlags', + name: 'hookFlags', + type: 'tuple', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getMaximumInvariantRatio', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getMaximumSwapFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getMigrationParameters', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'migrationRouter', + type: 'address', + }, + { + internalType: 'uint256', + name: 'lockDurationAfterMigration', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'bptPercentageToMigrate', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'migrationWeightProjectToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'migrationWeightReserveToken', + type: 'uint256', + }, + ], + internalType: 'struct MigrationParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getMigrationRouter', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getMinimumInvariantRatio', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getMinimumSwapFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getProjectToken', + outputs: [ + { + internalType: 'contract IERC20', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProjectTokenRate', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRate', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getReserveToken', + outputs: [ + { + internalType: 'contract IERC20', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getStaticSwapFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTokenIndices', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTokenInfo', + outputs: [ + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + components: [ + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'paysYieldFees', + type: 'bool', + }, + ], + internalType: 'struct TokenInfo[]', + name: 'tokenInfo', + type: 'tuple[]', + }, + { + internalType: 'uint256[]', + name: 'balancesRaw', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'lastBalancesLiveScaled18', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTokens', + outputs: [ + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTrustedRouter', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVault', + outputs: [ + { + internalType: 'contract IVault', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'incrementNonce', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'isProjectTokenSwapInBlocked', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isSwapEnabled', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + 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: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'enum AddLiquidityKind', + name: '', + type: 'uint8', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amountsInRaw', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onAfterAddLiquidity', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onAfterInitialize', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'enum RemoveLiquidityKind', + name: '', + type: 'uint8', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amountsOutRaw', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onAfterRemoveLiquidity', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum SwapKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'contract IERC20', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountInScaled18', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutScaled18', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'tokenInBalanceScaled18', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'tokenOutBalanceScaled18', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountCalculatedScaled18', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountCalculatedRaw', + type: 'uint256', + }, + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct AfterSwapParams', + name: '', + type: 'tuple', + }, + ], + name: 'onAfterSwap', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'enum AddLiquidityKind', + name: '', + type: 'uint8', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onBeforeAddLiquidity', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'exactAmountsInScaled18', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onBeforeInitialize', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'enum RemoveLiquidityKind', + name: '', + type: 'uint8', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onBeforeRemoveLiquidity', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum SwapKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'amountGivenScaled18', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'balancesScaled18', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'indexIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'indexOut', + type: 'uint256', + }, + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct PoolSwapParams', + name: '', + type: 'tuple', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'onBeforeSwap', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum SwapKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'amountGivenScaled18', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'balancesScaled18', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'indexIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'indexOut', + type: 'uint256', + }, + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct PoolSwapParams', + name: '', + type: 'tuple', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'onComputeDynamicSwapFeePercentage', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + components: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'paysYieldFees', + type: 'bool', + }, + ], + internalType: 'struct TokenConfig[]', + name: 'tokenConfig', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'bool', + name: 'disableUnbalancedLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'enableAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'enableRemoveLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'enableDonation', + type: 'bool', + }, + ], + internalType: 'struct LiquidityManagement', + name: '', + type: 'tuple', + }, + ], + name: 'onRegister', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum SwapKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'amountGivenScaled18', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'balancesScaled18', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'indexIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'indexOut', + type: 'uint256', + }, + { + internalType: 'address', + name: 'router', + type: 'address', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct PoolSwapParams', + name: 'request', + type: 'tuple', + }, + ], + name: 'onSwap', + outputs: [ + { + internalType: 'uint256', + name: 'amountCalculatedScaled18', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'pendingOwner', + 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: 'amount', + 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: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: '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: '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: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'version', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/testData/src/fixedPriceLBP.ts b/testData/src/fixedPriceLBP.ts new file mode 100644 index 0000000..ef34f86 --- /dev/null +++ b/testData/src/fixedPriceLBP.ts @@ -0,0 +1,138 @@ +import { + PublicClient, + createPublicClient, + http, + type Address, + type Chain, +} from 'viem'; +import { CHAINS } from '@balancer/sdk'; +import { VAULT_V3, vaultExtensionAbi_V3 } from '@balancer/sdk'; +import { fixedPriceLBPAbi } from './abi/fixedPriceLBPAbi'; +import { TransformBigintToString } from './types'; + +export type FixedPriceLBPoolImmutableData = { + tokens: string[]; + scalingFactors: bigint[]; + startTime: bigint; + endTime: bigint; + projectTokenIndex: number; + reserveTokenIndex: number; + projectTokenRate: bigint; +}; + +export type FixedPriceLBPoolDynamicData = { + balancesLiveScaled18: bigint[]; + staticSwapFeePercentage: bigint; + totalSupply: bigint; + isPoolInitialized: boolean; + isPoolPaused: boolean; + isPoolInRecoveryMode: boolean; + isSwapEnabled: boolean; +}; + +export class FixedPriceLBPPool { + client: PublicClient; + vault: Address; + + constructor( + public rpcUrl: string, + public chainId: number, + ) { + this.client = createPublicClient({ + transport: http(this.rpcUrl), + chain: CHAINS[this.chainId] as Chain, + }); + this.vault = VAULT_V3[this.chainId]; + } + + async fetchImmutableData( + address: Address, + blockNumber: bigint, + ): Promise> { + const { + tokens, + decimalScalingFactors, + startTime, + endTime, + projectTokenIndex, + reserveTokenIndex, + projectTokenRate, + } = await this.client.readContract({ + address, + abi: fixedPriceLBPAbi, + functionName: 'getFixedPriceLBPoolImmutableData', + blockNumber, + }); + + return { + tokens: [...tokens], + scalingFactors: decimalScalingFactors.map((sf) => sf.toString()), + startTime: startTime.toString(), + endTime: endTime.toString(), + projectTokenIndex: Number(projectTokenIndex), + reserveTokenIndex: Number(reserveTokenIndex), + projectTokenRate: projectTokenRate.toString(), + }; + } + + async fetchMutableData( + address: Address, + blockNumber: bigint, + ): Promise< + TransformBigintToString< + Omit< + FixedPriceLBPoolDynamicData, + 'staticSwapFeePercentage' + > & { + swapFee: string; + tokenRates: string[]; + currentTimestamp: string; + } + > + > { + const dynamicDataCall = { + address, + abi: fixedPriceLBPAbi, + functionName: 'getFixedPriceLBPoolDynamicData', + } as const; + + const tokenRatesCall = { + address: this.vault, + abi: vaultExtensionAbi_V3, + functionName: 'getPoolTokenRates', + args: [address], + } as const; + + const multicallResult = await this.client.multicall({ + contracts: [dynamicDataCall, tokenRatesCall], + allowFailure: false, + blockNumber, + }); + + const { + balancesLiveScaled18, + staticSwapFeePercentage, + totalSupply, + isPoolInitialized, + isPoolPaused, + isPoolInRecoveryMode, + isSwapEnabled, + } = multicallResult[0] as FixedPriceLBPoolDynamicData; + + const tokenRates = multicallResult[1][1] as bigint[]; + + const { timestamp } = await this.client.getBlock({ blockNumber }); + + return { + balancesLiveScaled18: balancesLiveScaled18.map((b) => b.toString()), + swapFee: staticSwapFeePercentage.toString(), + totalSupply: totalSupply.toString(), + tokenRates: tokenRates.map((rate) => rate.toString()), + isPoolInitialized, + isPoolPaused, + isPoolInRecoveryMode, + isSwapEnabled, + currentTimestamp: timestamp.toString(), + }; + } +} diff --git a/testData/src/getPool.ts b/testData/src/getPool.ts index 9b3b1e9..d1c1c96 100644 --- a/testData/src/getPool.ts +++ b/testData/src/getPool.ts @@ -7,6 +7,7 @@ import { GyroECLPPool } from './gyroECLP'; import { ReClammPool } from './reClamm'; import { LiquidityBootstrappingPool } from './liquidityBootstrappingPool'; import { QuantAmmPool } from './quantAmm'; +import { FixedPriceLBPPool } from './fixedPriceLBP'; import { fetchHookData } from './hooks/fetchHookData'; export async function getPool( @@ -26,6 +27,7 @@ export async function getPool( | ReClammPool | LiquidityBootstrappingPool | QuantAmmPool + | FixedPriceLBPPool > = { WEIGHTED: new WeightedPool(rpcUrl, chainId), STABLE: new StablePool(rpcUrl, chainId), @@ -38,6 +40,7 @@ export async function getPool( RECLAMM: new ReClammPool(rpcUrl, chainId), RECLAMM_V2: new ReClammPool(rpcUrl, chainId), QUANT_AMM_WEIGHTED: new QuantAmmPool(rpcUrl, chainId), + FIXED_PRICE_LBP: new FixedPriceLBPPool(rpcUrl, chainId), }; if (!poolData[poolType]) throw new Error(`getPool: Unsupported pool type: ${poolType}`); diff --git a/testData/testData/11155111-10434442-FixedPriceLBP-MTK-USDC.json b/testData/testData/11155111-10434442-FixedPriceLBP-MTK-USDC.json new file mode 100644 index 0000000..35317cc --- /dev/null +++ b/testData/testData/11155111-10434442-FixedPriceLBP-MTK-USDC.json @@ -0,0 +1,52 @@ +{ + "swaps": [ + { + "swapKind": 0, + "amountRaw": "10000000", + "tokenIn": "0x80d6d3946ed8a1da4e226aa21ccddc32bd127d1a", + "tokenOut": "0x212f34a319d901f6958e6df2b7d295d5a82635ba", + "outputRaw": "9900000000000000000" + }, + { + "swapKind": 1, + "amountRaw": "10000000000000000000", + "tokenIn": "0x80d6d3946ed8a1da4e226aa21ccddc32bd127d1a", + "tokenOut": "0x212f34a319d901f6958e6df2b7d295d5a82635ba", + "outputRaw": "10101011" + } + ], + "pool": { + "chainId": "11155111", + "blockNumber": "10434442", + "poolType": "FIXED_PRICE_LBP", + "poolAddress": "0x398a13d3d44e4825f997cc7aee4c7016878ba244", + "tokens": [ + "0x212F34a319d901F6958e6DF2b7d295D5a82635ba", + "0x80D6d3946ed8A1Da4E226aa21CCdDc32bd127d1A" + ], + "scalingFactors": [ + "1", + "1000000000000" + ], + "startTime": "1773244020", + "endTime": "1773848820", + "projectTokenIndex": 0, + "reserveTokenIndex": 1, + "projectTokenRate": "1000000000000000000", + "balancesLiveScaled18": [ + "10000000000000000000000", + "0" + ], + "swapFee": "10000000000000000", + "totalSupply": "10000000000000000000000", + "tokenRates": [ + "1000000000000000000", + "1000000000000000000" + ], + "isPoolInitialized": true, + "isPoolPaused": false, + "isPoolInRecoveryMode": false, + "isSwapEnabled": true, + "currentTimestamp": "1773340572" + } +} \ No newline at end of file diff --git a/typescript/src/fixedPriceLBP/data.ts b/typescript/src/fixedPriceLBP/data.ts new file mode 100644 index 0000000..77ecd4f --- /dev/null +++ b/typescript/src/fixedPriceLBP/data.ts @@ -0,0 +1,21 @@ +import { BasePoolState } from '@/vault/types'; + +type PoolType = 'FIXED_PRICE_LBP'; + +export type FixedPriceLBPImmutable = { + projectTokenIndex: number; + reserveTokenIndex: number; + projectTokenRate: bigint; + startTime: bigint; + endTime: bigint; +}; + +export type FixedPriceLBPMutable = { + isSwapEnabled: boolean; + currentTimestamp: bigint; +}; + +export type FixedPriceLBPState = BasePoolState & { + poolType: PoolType; +} & FixedPriceLBPImmutable & + FixedPriceLBPMutable; diff --git a/typescript/src/fixedPriceLBP/fixedPriceLBP.ts b/typescript/src/fixedPriceLBP/fixedPriceLBP.ts new file mode 100644 index 0000000..98849bb --- /dev/null +++ b/typescript/src/fixedPriceLBP/fixedPriceLBP.ts @@ -0,0 +1,125 @@ +import { MathSol } from '../utils/math'; +import { toRawUndoRateRoundDown } from '../vault/utils'; +import { + MaxSwapParams, + type PoolBase, + Rounding, + SwapKind, + type SwapParams, +} from '../vault/types'; +import { MAX_UINT256 } from '../constants'; + +import type { FixedPriceLBPState } from './data'; + +export class FixedPriceLBP implements PoolBase { + projectTokenIndex: number; + reserveTokenIndex: number; + projectTokenRate: bigint; + isSwapEnabled: boolean; + + constructor(poolState: FixedPriceLBPState) { + this.projectTokenIndex = poolState.projectTokenIndex; + this.reserveTokenIndex = poolState.reserveTokenIndex; + this.projectTokenRate = poolState.projectTokenRate; + this.isSwapEnabled = poolState.isSwapEnabled; + } + + getMaximumInvariantRatio(): bigint { + return MAX_UINT256; + } + + getMinimumInvariantRatio(): bigint { + return 0n; + } + + getMaxSwapAmount(maxSwapParams: MaxSwapParams): bigint { + const { + balancesLiveScaled18, + indexIn, + indexOut, + tokenRates, + scalingFactors, + swapKind, + } = maxSwapParams; + + if (swapKind === SwapKind.GivenIn) { + // GivenIn: user specifies reserve amount in. + // Max input is limited by output (project) token balance. + // maxIn (scaled18) = projectBalance * projectTokenRate + const maxIn18 = MathSol.mulDownFixed( + balancesLiveScaled18[indexOut], + this.projectTokenRate, + ); + // Convert to raw using input token's scaling/rate + return toRawUndoRateRoundDown( + maxIn18, + scalingFactors[indexIn], + tokenRates[indexIn], + ); + } + + // GivenOut: user specifies project amount out. + // Max output is the project token balance. + const maxOut18 = balancesLiveScaled18[indexOut]; + return toRawUndoRateRoundDown( + maxOut18, + scalingFactors[indexOut], + tokenRates[indexOut], + ); + } + + getMaxSingleTokenAddAmount(): bigint { + throw new Error('UnsupportedOperation'); + } + + getMaxSingleTokenRemoveAmount(): bigint { + throw new Error('UnsupportedOperation'); + } + + onSwap(swapParams: SwapParams): bigint { + if (!this.isSwapEnabled) { + throw new Error('SwapsDisabled'); + } + + if (swapParams.indexIn === this.projectTokenIndex) { + throw new Error('SwapOfProjectTokenIn'); + } + + if (swapParams.swapKind === SwapKind.GivenIn) { + // Reserve tokens in, project tokens out: amountOut = amountIn / rate + return MathSol.divDownFixed( + swapParams.amountGivenScaled18, + this.projectTokenRate, + ); + } + + // ExactOut: amountIn = amountOut * rate + return MathSol.mulUpFixed( + swapParams.amountGivenScaled18, + this.projectTokenRate, + ); + } + + computeInvariant( + balancesLiveScaled18: bigint[], + rounding: Rounding, + ): bigint { + // inv = projectBalance * rate + reserveBalance + const projectTokenValue = + rounding === Rounding.ROUND_UP + ? MathSol.mulUpFixed( + balancesLiveScaled18[this.projectTokenIndex], + this.projectTokenRate, + ) + : MathSol.mulDownFixed( + balancesLiveScaled18[this.projectTokenIndex], + this.projectTokenRate, + ); + + return projectTokenValue + balancesLiveScaled18[this.reserveTokenIndex]; + } + + computeBalance(): bigint { + throw new Error('UnsupportedOperation'); + } +} diff --git a/typescript/src/fixedPriceLBP/index.ts b/typescript/src/fixedPriceLBP/index.ts new file mode 100644 index 0000000..df9393d --- /dev/null +++ b/typescript/src/fixedPriceLBP/index.ts @@ -0,0 +1,2 @@ +export * from './data'; +export * from './fixedPriceLBP'; diff --git a/typescript/src/index.ts b/typescript/src/index.ts index d78c883..273ed57 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -9,3 +9,4 @@ export * from './reClamm'; export * from './reClammV2'; export * from './quantAmm'; export * from './liquidityBootstrapping'; +export * from './fixedPriceLBP'; diff --git a/typescript/src/vault/types.ts b/typescript/src/vault/types.ts index 135e299..16849b4 100644 --- a/typescript/src/vault/types.ts +++ b/typescript/src/vault/types.ts @@ -3,6 +3,7 @@ import { StableState } from '@/stable'; import { WeightedState } from '@/weighted'; import { ReClammState } from '@/reClamm'; import { QuantAmmState } from '@/quantAmm'; +import { FixedPriceLBPState } from '@/fixedPriceLBP'; /** * State of a pool. Note - rates, fees, totalSupply use scaled 18. @@ -27,7 +28,8 @@ export type PoolState = | StableState | GyroECLPState | ReClammState - | QuantAmmState; + | QuantAmmState + | FixedPriceLBPState; export enum SwapKind { GivenIn = 0, diff --git a/typescript/src/vault/vault.ts b/typescript/src/vault/vault.ts index f74307c..50a4b7c 100644 --- a/typescript/src/vault/vault.ts +++ b/typescript/src/vault/vault.ts @@ -13,6 +13,7 @@ import { ReClamm } from '../reClamm'; import { ReClammV2 } from '../reClammV2'; import { QuantAmm } from '../quantAmm'; import { LiquidityBootstrapping } from '../liquidityBootstrapping'; +import { FixedPriceLBP } from '../fixedPriceLBP'; import { BufferState, erc4626BufferWrapOrUnwrap } from '../buffer'; import { @@ -68,6 +69,7 @@ export class Vault { RECLAMM_V2: ReClammV2, LIQUIDITY_BOOTSTRAPPING: LiquidityBootstrapping, QUANT_AMM_WEIGHTED: QuantAmm, + FIXED_PRICE_LBP: FixedPriceLBP, // custom add liquidity types take precedence over base types ...customPoolClasses, }; diff --git a/typescript/test/fixedPriceLBPPool.test.ts b/typescript/test/fixedPriceLBPPool.test.ts new file mode 100644 index 0000000..f50e692 --- /dev/null +++ b/typescript/test/fixedPriceLBPPool.test.ts @@ -0,0 +1,239 @@ +// pnpm test -- fixedPriceLBPPool.test.ts + +import { describe, expect, test } from 'vitest'; +import { SwapKind, Rounding } from '../src/index'; +import { FixedPriceLBPState } from '../src/fixedPriceLBP/data'; +import { FixedPriceLBP } from '../src/fixedPriceLBP/fixedPriceLBP'; + +describe('fixedPriceLBP pool', () => { + // 1 PROJECT = 0.1 USDC (rate = 0.1e18) + const poolState = { + poolAddress: '0x1234567890abcdef1234567890abcdef12345678', + poolType: 'FIXED_PRICE_LBP' as const, + tokens: ['0xProjectToken', '0xReserveToken'], + scalingFactors: [1n, 1n], + tokenRates: [1000000000000000000n, 1000000000000000000n], + balancesLiveScaled18: [ + 100000000000000000000n, // 100 project tokens + 10000000000000000000n, // 10 reserve tokens + ], + swapFee: 0n, + aggregateSwapFee: 0n, + totalSupply: 1000000000000000000n, + supportsUnbalancedLiquidity: false, + projectTokenIndex: 0, + reserveTokenIndex: 1, + projectTokenRate: 100000000000000000n, // 0.1e18 + startTime: 1000000n, + endTime: 2000000n, + isSwapEnabled: true, + currentTimestamp: 1500000n, + } as FixedPriceLBPState; + + const pool = new FixedPriceLBP(poolState); + + describe('onSwap', () => { + test('exactIn: reserve in, project out', () => { + // Swap 1 reserve token in, expect 10 project tokens out (1 / 0.1 = 10) + const result = pool.onSwap({ + swapKind: SwapKind.GivenIn, + amountGivenScaled18: 1000000000000000000n, // 1e18 + balancesLiveScaled18: poolState.balancesLiveScaled18, + indexIn: 1, // reserve + indexOut: 0, // project + }); + // divDown(1e18, 0.1e18) = 10e18 + expect(result).toEqual(10000000000000000000n); + }); + + test('exactOut: reserve in, project out', () => { + // Want 10 project tokens out, how much reserve in? + const result = pool.onSwap({ + swapKind: SwapKind.GivenOut, + amountGivenScaled18: 10000000000000000000n, // 10e18 + balancesLiveScaled18: poolState.balancesLiveScaled18, + indexIn: 1, // reserve + indexOut: 0, // project + }); + // mulUp(10e18, 0.1e18) = 1e18 + expect(result).toEqual(1000000000000000000n); + }); + + test('exactIn with rate = 4e18 (1 PROJECT = 4 RESERVE)', () => { + const highRateState = { + ...poolState, + projectTokenRate: 4000000000000000000n, // 4e18 + } as FixedPriceLBPState; + const highRatePool = new FixedPriceLBP(highRateState); + + // Swap 4 reserve tokens in → expect 1 project token out + const result = highRatePool.onSwap({ + swapKind: SwapKind.GivenIn, + amountGivenScaled18: 4000000000000000000n, + balancesLiveScaled18: highRateState.balancesLiveScaled18, + indexIn: 1, + indexOut: 0, + }); + expect(result).toEqual(1000000000000000000n); + }); + + test('blocks project token swap in', () => { + expect(() => + pool.onSwap({ + swapKind: SwapKind.GivenIn, + amountGivenScaled18: 1000000000000000000n, + balancesLiveScaled18: poolState.balancesLiveScaled18, + indexIn: 0, // project token as input — blocked + indexOut: 1, + }), + ).toThrow('SwapOfProjectTokenIn'); + }); + + test('blocks swap when disabled', () => { + const disabledState = { + ...poolState, + isSwapEnabled: false, + } as FixedPriceLBPState; + const disabledPool = new FixedPriceLBP(disabledState); + + expect(() => + disabledPool.onSwap({ + swapKind: SwapKind.GivenIn, + amountGivenScaled18: 1000000000000000000n, + balancesLiveScaled18: disabledState.balancesLiveScaled18, + indexIn: 1, + indexOut: 0, + }), + ).toThrow('SwapsDisabled'); + }); + }); + + describe('computeInvariant', () => { + test('round down', () => { + const balances = [ + 50000000000000000000n, // 50 project tokens + 5000000000000000000n, // 5 reserve tokens + ]; + // inv = 50 * 0.1 + 5 = 10 + const invariant = pool.computeInvariant( + balances, + Rounding.ROUND_DOWN, + ); + expect(invariant).toEqual(10000000000000000000n); + }); + + test('round up', () => { + const balances = [ + 50000000000000000000n, + 5000000000000000000n, + ]; + const invariant = pool.computeInvariant( + balances, + Rounding.ROUND_UP, + ); + expect(invariant).toEqual(10000000000000000000n); + }); + + test('rounding difference with non-clean values', () => { + // Use values that produce different results for mulUp vs mulDown + const balances = [3n, 1000000000000000000n]; + const invDown = pool.computeInvariant( + balances, + Rounding.ROUND_DOWN, + ); + const invUp = pool.computeInvariant( + balances, + Rounding.ROUND_UP, + ); + // mulDown(3, 0.1e18) = 0, mulUp(3, 0.1e18) = 1 + expect(invDown).toEqual(1000000000000000000n); + expect(invUp).toEqual(1000000000000000001n); + }); + }); + + describe('computeBalance', () => { + test('throws unsupported', () => { + expect(() => pool.computeBalance([], 0, 0n)).toThrow( + 'UnsupportedOperation', + ); + }); + }); + + describe('getMaxSwapAmount', () => { + test('exactIn returns max reserve input based on project balance', () => { + const maxSwapAmount = pool.getMaxSwapAmount({ + swapKind: SwapKind.GivenIn, + balancesLiveScaled18: poolState.balancesLiveScaled18, + tokenRates: poolState.tokenRates, + scalingFactors: poolState.scalingFactors, + indexIn: 1, + indexOut: 0, + }); + // Project balance = 100e18, rate = 0.1e18 + // maxIn = 100e18 * 0.1e18 / 1e18 = 10e18, then toRaw with sf=1, rate=1 → 10e18 + expect(maxSwapAmount).toEqual(10000000000000000000n); + }); + + test('exactOut returns output token balance in raw', () => { + const maxSwapAmount = pool.getMaxSwapAmount({ + swapKind: SwapKind.GivenOut, + balancesLiveScaled18: poolState.balancesLiveScaled18, + tokenRates: poolState.tokenRates, + scalingFactors: poolState.scalingFactors, + indexIn: 1, + indexOut: 0, + }); + // Project balance = 100e18, toRaw with sf=1, rate=1 → 100e18 + expect(maxSwapAmount).toEqual(100000000000000000000n); + }); + + test('exactIn with 6 decimal reserve token', () => { + const maxSwapAmount = pool.getMaxSwapAmount({ + swapKind: SwapKind.GivenIn, + balancesLiveScaled18: [ + 60000000000000000000n, // project (18-dec, sf=1) + 40000000000000000000n, // reserve (6-dec, sf=1e12) + ], + tokenRates: [1000000000000000000n, 1000000000000000000n], + scalingFactors: [1n, 1000000000000n], + indexIn: 1, // reserve (6-dec) + indexOut: 0, // project (18-dec) + }); + // maxIn18 = 60e18 * 0.1e18 / 1e18 = 6e18 + // toRaw: divDown(6e18, 1e12 * 1e18) = 6e18 * 1e18 / 1e30 = 6000000 (6e6) + expect(maxSwapAmount).toEqual(6000000n); + }); + }); + + describe('invariant ratio bounds', () => { + test('getMaximumInvariantRatio returns MAX_UINT256', () => { + expect(pool.getMaximumInvariantRatio()).toEqual( + 115792089237316195423570985008687907853269984665640564039457584007913129639935n, + ); + }); + + test('getMinimumInvariantRatio returns 0', () => { + expect(pool.getMinimumInvariantRatio()).toEqual(0n); + }); + }); + + describe('unsupported liquidity operations', () => { + test('getMaxSingleTokenAddAmount throws', () => { + expect(() => pool.getMaxSingleTokenAddAmount()).toThrow( + 'UnsupportedOperation', + ); + }); + + test('getMaxSingleTokenRemoveAmount throws', () => { + expect(() => + pool.getMaxSingleTokenRemoveAmount({ + isExactIn: true, + totalSupply: 1000000000000000000n, + tokenOutBalance: 1000000000000000000n, + tokenOutScalingFactor: 1000000000000000000n, + tokenOutRate: 1000000000000000000n, + }), + ).toThrow('UnsupportedOperation'); + }); + }); +}); diff --git a/typescript/test/utils/readTestData.ts b/typescript/test/utils/readTestData.ts index 8bdff36..366d887 100644 --- a/typescript/test/utils/readTestData.ts +++ b/typescript/test/utils/readTestData.ts @@ -8,6 +8,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { QuantAmmState } from '@/quantAmm/quantAmmData'; import { ReClammV2State } from '@/reClammV2'; +import { FixedPriceLBPState } from '@/fixedPriceLBP'; import { type HookState } from '../../src/hooks/types'; import { mapHookState } from './mapHookState'; @@ -51,6 +52,8 @@ type LiquidityBootstrappingPool = PoolBase & LiquidityBootstrappingState; type QuantAmmPool = PoolBase & QuantAmmState; +type FixedPriceLBPPool = PoolBase & FixedPriceLBPState; + type SupportedPools = | WeightedPool | StablePool @@ -59,7 +62,8 @@ type SupportedPools = | ReClammPool | ReClammPoolV2 | LiquidityBootstrappingPool - | QuantAmmPool; + | QuantAmmPool + | FixedPriceLBPPool; type PoolsMap = Map; @@ -357,6 +361,24 @@ function mapPool( lastUpdateTime: BigInt(pool.lastUpdateTime), }; } + if (pool.poolType === 'FIXED_PRICE_LBP') { + return { + ...pool, + scalingFactors: pool.scalingFactors.map((sf) => BigInt(sf)), + swapFee: BigInt(pool.swapFee), + balancesLiveScaled18: pool.balancesLiveScaled18.map((b) => + BigInt(b), + ), + tokenRates: pool.tokenRates.map((r) => BigInt(r)), + totalSupply: BigInt(pool.totalSupply), + aggregateSwapFee: BigInt(pool.aggregateSwapFee ?? '0'), + supportsUnbalancedLiquidity: false, + projectTokenRate: BigInt(pool.projectTokenRate), + startTime: BigInt(pool.startTime), + endTime: BigInt(pool.endTime), + currentTimestamp: BigInt(pool.currentTimestamp), + }; + } console.log(pool); throw new Error('mapPool: Unsupported Pool Type'); }