UNPKG

@catalabs/catalyst-sdk

Version:
558 lines 24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RouterArguments = exports.gasCostTable = void 0; const ethers_1 = require("ethers"); const evm_1 = require("../evm"); const route_enum_1 = require("./enums/route.enum"); const abi = ethers_1.AbiCoder.defaultAbiCoder(); const routerCommands = { LOCALSWAP: (0x00).toString(16).padStart(2, '0'), SENDASSET: (0x01).toString(16).padStart(2, '0'), PERMIT2_TRANSFER_FROM: (0x02).toString(16).padStart(2, '0'), PERMIT2_PERMIT_BATCH: (0x03).toString(16).padStart(2, '0'), PERMIT2_TRANSFER_FROM_BATCH: (0x1e).toString(16).padStart(2, '0'), SWEEP: (0x04).toString(16).padStart(2, '0'), TRANSFER: (0x05).toString(16).padStart(2, '0'), PAY_PORTION: (0x06).toString(16).padStart(2, '0'), PERMIT2_PERMIT: (0x07).toString(16).padStart(2, '0'), WRAP_GAS: (0x08).toString(16).padStart(2, '0'), UNWRAP_GAS: (0x09).toString(16).padStart(2, '0'), WITHDRAW_EQUAL: (0x0a).toString(16).padStart(2, '0'), WITHDRAW_MIXED: (0x0b).toString(16).padStart(2, '0'), DEPOSIT_MIXED: (0x0c).toString(16).padStart(2, '0'), SENDLIQUIDITY: (0x0d).toString(16).padStart(2, '0'), ALLOW_CANCEL: (0x0e).toString(16).padStart(2, '0'), BALANCE_CHECK_ERC20: (0x0f).toString(16).padStart(2, '0'), TRANSFER_FROM: (0x1f).toString(16).padStart(2, '0'), }; const RouteDescription = 'tuple(bytes32, bytes, bytes, tuple(uint48, uint48, address, uint96, uint96, uint64), uint64)'; const PermitSingle = 'tuple(tuple(address, uint160, uint48, uint48), address, uint256)'; const PermitBatch = 'tuple(tuple(address, uint160, uint48, uint48)[], address, uint256)'; const AllowanceTransferDetailsArray = 'tuple(address, address, uint160, address)[]'; const encodeTable = { LOCALSWAP: ['address', 'address', 'address', 'uint256', 'uint256'], SENDASSET: [ 'address', RouteDescription, 'address', 'uint8', 'uint256', 'uint256', 'address', 'uint256', 'uint16', ], PERMIT2_TRANSFER_FROM: ['address', 'address', 'uint160'], PERMIT2_PERMIT_BATCH: [PermitBatch, 'bytes'], PERMIT2_TRANSFER_FROM_BATCH: [AllowanceTransferDetailsArray], SWEEP: ['address', 'address', 'uint256'], TRANSFER: ['address', 'address', 'uint256'], PAY_PORTION: ['address', 'address', 'uint256'], PERMIT2_PERMIT: [PermitSingle, 'bytes'], WRAP_GAS: ['address', 'uint256'], UNWRAP_GAS: ['address', 'uint256'], WITHDRAW_EQUAL: ['address', 'uint256', 'uint256[]'], WITHDRAW_MIXED: ['address', 'uint256', 'uint256[]', 'uint256[]'], DEPOSIT_MIXED: ['address', 'address[]', 'uint256[]', 'uint256'], SENDLIQUIDITY: [ 'address', RouteDescription, 'uint256', 'uint256[2]', 'address', 'uint256', ], ALLOW_CANCEL: ['address', 'bytes32'], TRANSFER_FROM: ['address', 'address', 'uint160'], }; const TX_TRANSFER_COST = 65000n - 21000n; exports.gasCostTable = { LOCALSWAP: BigInt('123549') - BigInt('24929') - BigInt('27450'), SENDASSET: BigInt('216540') - BigInt(TX_TRANSFER_COST), PERMIT2_TRANSFER_FROM: TX_TRANSFER_COST, PERMIT2_PERMIT_BATCH: TX_TRANSFER_COST * 3n, PERMIT2_TRANSFER_FROM_BATCH: TX_TRANSFER_COST * 3n, SWEEP: TX_TRANSFER_COST, TRANSFER: TX_TRANSFER_COST, PAY_PORTION: TX_TRANSFER_COST, PERMIT2_PERMIT: BigInt('10000'), WRAP_GAS: BigInt('52807'), UNWRAP_GAS: BigInt('35392') - BigInt('20805'), WITHDRAW_EQUAL: BigInt('140270'), WITHDRAW_MIXED: BigInt('141467'), DEPOSIT_MIXED: BigInt('119056'), SENDLIQUIDITY: BigInt('181294'), ALLOW_CANCEL: BigInt('2000'), BALANCE_CHECK_ERC20: BigInt('2000'), TRANSFER_FROM: BigInt('65000') - BigInt('10000'), ROUTE: BigInt('21000') + BigInt('4786'), RECEIVE: BigInt('21000') + BigInt('113957') + BigInt('210000'), ACK: BigInt('21000') + BigInt('89876') + BigInt('210000'), }; const UNITS = '0xUNITS'; const GAS = 'GAS'; const EXEC_ABI = [ 'function execute(bytes calldata commands, bytes[] calldata inputs)', ]; const execInterface = new ethers_1.ethers.Interface(EXEC_ABI); class RouterArguments { static MSG_SENDER = '0x0000000000000000000000000000000000000001'; static ADDRESS_THIS = '0x0000000000000000000000000000000000000002'; static BALANCE_THIS = BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819968'); static ROUTER = evm_1.EVM_ROUTER_ADDRESS; WGAS; constructor(options = {}) { const { WGAS = 'WGAS' } = options; this.WGAS = WGAS; } params = [[], []]; gasCost = exports.gasCostTable['ROUTE']; routerContains = {}; static getIncentiveCost(routingIncentive) { return (routingIncentive.maxGasAck * routingIncentive.priceOfAckGas + routingIncentive.maxGasDelivery * routingIncentive.priceOfDeliveryGas + routingIncentive.messageVerifyGasCost); } ensureRouterEmpty() { for (const key in this.routerContains) { if (this.routerContains[key] > 0n) { throw new Error(`Router contains ${this.routerContains[key].toString()} of ${key}. It should be empty`); } } } modifyRouterContains(asset, amount) { if (this.routerContains[asset] === undefined) { this.routerContains[asset] = 0n; } if (amount === RouterArguments.BALANCE_THIS) { this.routerContains[asset] = BigInt(2) ** 256n - 1n; return; } if (amount === RouterArguments.BALANCE_THIS * -1n) { this.routerContains[asset] = 0n; return; } this.routerContains[asset] = this.routerContains[asset] + amount; } getParams(checkRouterIsEmpty = true) { if (checkRouterIsEmpty) { this.ensureRouterEmpty(); } return ['0x' + this.params[0].join(''), this.params[1]]; } getCalldata() { return (RouterArguments.ROUTER.replace('0x', '') + execInterface.encodeFunctionData('execute', this.getParams()).slice(10)); } getGasCostEstimate(options = {}) { const { additionalMargin = 0.01, remote = false } = options; const resolution = 1000000n; const additionalGas = remote ? exports.gasCostTable['RECEIVE'] : 0n; const totalGasCost = this.gasCost + additionalGas; const marginWithScalar = (totalGasCost * BigInt(Number(resolution) * additionalMargin)) / resolution; return totalGasCost + marginWithScalar; } static splitRoute(fromAsset, route) { const [vaults, tokens] = route; const chainSwitch = tokens.indexOf(UNITS) + 1; if (chainSwitch === 0) { const fromRoute = { fromAsset: fromAsset, routeDetails: route, }; const crossRoute = { fromAsset: '', routeDetails: [[], []], }; const toRoute = { fromAsset: fromAsset, routeDetails: [[], []], }; return [fromRoute, crossRoute, toRoute]; } const fromRoute = { fromAsset: fromAsset, routeDetails: [ vaults.slice(0, chainSwitch - 1), tokens.slice(0, chainSwitch - 1), ], }; const crossRoute = { fromAsset: [fromAsset, ...tokens][chainSwitch - 1], routeDetails: [ vaults.slice(chainSwitch - 1, chainSwitch + 1), tokens.slice(chainSwitch - 1, chainSwitch + 1), ], }; const toRoute = { fromAsset: tokens[chainSwitch], routeDetails: [ vaults.slice(chainSwitch + 1), tokens.slice(chainSwitch + 1), ], }; return [fromRoute, crossRoute, toRoute]; } wrapGas(to = RouterArguments.ADDRESS_THIS, amount = RouterArguments.BALANCE_THIS) { const commands = [routerCommands['WRAP_GAS']]; const encodedParams = [abi.encode(encodeTable['WRAP_GAS'], [to, amount])]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(GAS, amount * -1n); if (to === RouterArguments.ADDRESS_THIS) { this.modifyRouterContains(this.WGAS, amount); } this.gasCost += exports.gasCostTable['WRAP_GAS']; this.params = this.combineRouteParams(this.params, routeParams); } unwrapGas(to, minOut = 0n) { const commands = [routerCommands['UNWRAP_GAS']]; const encodedParams = [abi.encode(encodeTable['UNWRAP_GAS'], [to, minOut])]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(this.WGAS, RouterArguments.BALANCE_THIS * -1n); if (to === RouterArguments.ADDRESS_THIS) { this.modifyRouterContains(GAS, RouterArguments.BALANCE_THIS); } this.gasCost += exports.gasCostTable['UNWRAP_GAS']; this.params = this.combineRouteParams(this.params, routeParams); } localroute(fromAsset, route, minOut) { const commands = route[0].map((_) => routerCommands['LOCALSWAP']); const parameters = []; const [vaults, assets] = route; let pastAsset = fromAsset; for (let i = 0; i < vaults.length; i++) { const toVault = vaults[i]; const toAsset = assets[i]; parameters.push([ toVault, pastAsset, toAsset, RouterArguments.BALANCE_THIS, i === vaults.length - 1 ? minOut : 0n, ]); this.modifyRouterContains(pastAsset, RouterArguments.BALANCE_THIS * -1n); this.modifyRouterContains(toAsset, RouterArguments.BALANCE_THIS); pastAsset = toAsset; } const encodedParams = parameters.map((param) => abi.encode(encodeTable['LOCALSWAP'], param)); const routeParams = [commands, encodedParams]; this.gasCost += exports.gasCostTable['LOCALSWAP']; this.params = this.combineRouteParams(this.params, routeParams); } sendAsset(fromAsset, toAssetIndex, fromVault, toVault, channelId, minOut, transferTo, gasTokensToSend, routingIncentive, deadline = 0n, underwritingIncentive = 0n, target_data = '', revert_to = RouterArguments.MSG_SENDER) { const toVaultEncoded = RouterArguments.addressEncodeHelper(toVault); const transferToEncoded = RouterArguments.addressEncodeHelper(transferTo); const commands = [routerCommands['SENDASSET']]; const encodedParams = [ abi.encode(encodeTable['SENDASSET'], [ fromVault, [ channelId, toVaultEncoded, transferToEncoded, [ routingIncentive.maxGasDelivery, routingIncentive.maxGasAck, routingIncentive.refundGasTo, routingIncentive.priceOfDeliveryGas, routingIncentive.priceOfAckGas, routingIncentive.targetDelta, ], deadline, ], fromAsset, toAssetIndex, RouterArguments.BALANCE_THIS, minOut, revert_to, gasTokensToSend, underwritingIncentive, ]) + target_data, ]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(GAS, gasTokensToSend * -1n); this.modifyRouterContains(fromAsset, RouterArguments.BALANCE_THIS * -1n); this.gasCost += exports.gasCostTable['SENDASSET']; this.params = this.combineRouteParams(this.params, routeParams); } deposit(vault, tokens, tokenAmounts, minOut) { const commands = [routerCommands['DEPOSIT_MIXED']]; const encodedParams = [ abi.encode(encodeTable['DEPOSIT_MIXED'], [ vault, tokens, tokenAmounts, minOut, ]), ]; const routeParams = [commands, encodedParams]; for (const token of tokens) { this.modifyRouterContains(token, RouterArguments.BALANCE_THIS * -1n); } this.modifyRouterContains(vault, RouterArguments.BALANCE_THIS); this.gasCost += exports.gasCostTable['DEPOSIT_MIXED']; this.params = this.combineRouteParams(this.params, routeParams); } withdrawEqual(vault, amountVaultTokens, minOuts) { const commands = [routerCommands['WITHDRAW_EQUAL']]; const encodedParams = [ abi.encode(encodeTable['WITHDRAW_EQUAL'], [ vault, amountVaultTokens, minOuts, ]), ]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(vault, amountVaultTokens * -1n); this.gasCost += exports.gasCostTable['WITHDRAW_EQUAL']; this.params = this.combineRouteParams(this.params, routeParams); } withdrawMixed(vault, amountVaultTokens, withdrawRatios, minOuts) { const commands = [routerCommands['WITHDRAW_MIXED']]; const encodedParams = [ abi.encode(encodeTable['WITHDRAW_MIXED'], [ vault, amountVaultTokens, withdrawRatios, minOuts, ]), ]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(vault, amountVaultTokens * -1n); this.gasCost += exports.gasCostTable['WITHDRAW_MIXED']; this.params = this.combineRouteParams(this.params, routeParams); } sweep(token, target, minOut = 0n) { const commands = [routerCommands['SWEEP']]; const encodedParams = [ abi.encode(encodeTable['SWEEP'], [token, target, minOut]), ]; const routeParams = [commands, encodedParams]; if (target !== RouterArguments.ADDRESS_THIS) { this.modifyRouterContains(token, RouterArguments.BALANCE_THIS * -1n); } this.gasCost += exports.gasCostTable['SWEEP']; this.params = this.combineRouteParams(this.params, routeParams); } transferFrom(token, amount) { const commands = [routerCommands['TRANSFER_FROM']]; const encodedParams = [ abi.encode(encodeTable['TRANSFER_FROM'], [ token, RouterArguments.ADDRESS_THIS, amount, ]), ]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(token, amount); this.gasCost += exports.gasCostTable['TRANSFER_FROM']; this.params = this.combineRouteParams(this.params, routeParams); } liquiditySwap(fromVault, toVault, channelId, minOuts, transferTo, gasTokensToSend, routingIncentive, amount = RouterArguments.BALANCE_THIS, deadline = 0n, target_data = '', revert_to = RouterArguments.MSG_SENDER) { const commands = [routerCommands['SENDLIQUIDITY']]; const toVaultEncoded = RouterArguments.addressEncodeHelper(toVault); const transferToEncoded = RouterArguments.addressEncodeHelper(transferTo); const encodedParams = [ abi.encode(encodeTable['SENDLIQUIDITY'], [ fromVault, [ channelId, toVaultEncoded, transferToEncoded, [ routingIncentive.maxGasDelivery, routingIncentive.maxGasAck, routingIncentive.refundGasTo, routingIncentive.priceOfDeliveryGas, routingIncentive.priceOfAckGas, routingIncentive.targetDelta, ], deadline, ], amount, minOuts, revert_to, gasTokensToSend, ]) + target_data, ]; const routeParams = [commands, encodedParams]; this.modifyRouterContains(GAS, gasTokensToSend * -1n); this.modifyRouterContains(fromVault, amount * -1n); this.gasCost += exports.gasCostTable['SENDLIQUIDITY']; this.params = this.combineRouteParams(this.params, routeParams); } permit2Permit(permitSingle, signature, order = route_enum_1.CombineRouteParamsOrder.Last) { const commands = [routerCommands['PERMIT2_PERMIT']]; const encodedParams = [ abi.encode(encodeTable['PERMIT2_PERMIT'], [ [ [ permitSingle.details.token, permitSingle.details.amount, permitSingle.details.expiration, permitSingle.details.nonce, ], permitSingle.spender, permitSingle.sigDeadline, ], signature, ]), ]; const routeParams = [commands, encodedParams]; this.gasCost += exports.gasCostTable['PERMIT2_PERMIT']; this.params = this.combineRouteParams(this.params, routeParams, order); } permit2PermitBatch(permitBatch, signature, order = route_enum_1.CombineRouteParamsOrder.Last) { const commands = [routerCommands['PERMIT2_PERMIT_BATCH']]; const encodedParams = [ abi.encode(encodeTable['PERMIT2_PERMIT_BATCH'], [ [ permitBatch.details.map((permit) => [ permit.token, permit.amount, permit.expiration, permit.nonce, ]), permitBatch.spender, permitBatch.sigDeadline, ], signature, ]), ]; const routeParams = [commands, encodedParams]; this.gasCost += exports.gasCostTable['PERMIT2_PERMIT_BATCH']; this.params = this.combineRouteParams(this.params, routeParams, order); } permit2TransferFrom(token, amount, order = route_enum_1.CombineRouteParamsOrder.Last) { const commands = [routerCommands['PERMIT2_TRANSFER_FROM']]; const encodedParams = [ abi.encode(encodeTable['PERMIT2_TRANSFER_FROM'], [ token, RouterArguments.ADDRESS_THIS, amount, ]), ]; const routeParams = [commands, encodedParams]; if (order !== route_enum_1.CombineRouteParamsOrder.First) { this.modifyRouterContains(token, amount); } this.gasCost += exports.gasCostTable['PERMIT2_TRANSFER_FROM']; this.params = this.combineRouteParams(this.params, routeParams, order); } permit2TransferFromBatch(assets, owner, order = route_enum_1.CombineRouteParamsOrder.Last) { const assetsWithFromAndTo = assets.map(({ token, amount }) => ({ token, amount, from: owner, to: RouterArguments.ROUTER, })); const commands = [routerCommands['PERMIT2_TRANSFER_FROM_BATCH']]; const encodedParams = [ abi.encode(encodeTable['PERMIT2_TRANSFER_FROM_BATCH'], [ assetsWithFromAndTo.map((asset) => [ asset.from, asset.to, asset.amount, asset.token, ]), ]), ]; const routeParams = [commands, encodedParams]; for (const asset of assets) { const { token, amount } = asset; if (order !== route_enum_1.CombineRouteParamsOrder.First) { this.modifyRouterContains(token, amount); } } this.gasCost += exports.gasCostTable['PERMIT2_TRANSFER_FROM_BATCH']; this.params = this.combineRouteParams(this.params, routeParams, order); } transferWithOptionalPermit(asset, permitData, order = route_enum_1.CombineRouteParamsOrder.Last) { if (order === route_enum_1.CombineRouteParamsOrder.First) { this.permit2TransferFrom(asset.token, asset.amount, order); if (permitData) { this.permit2Permit(permitData.permit, permitData.signature, order); } return; } if (permitData) { this.permit2Permit(permitData.permit, permitData.signature, order); } this.permit2TransferFrom(asset.token, asset.amount, order); } transferWithOptionalPermitBatch(assets, owner, permitBatchData, order = route_enum_1.CombineRouteParamsOrder.Last) { if (assets.length === 0) { return; } if (order === route_enum_1.CombineRouteParamsOrder.First) { this.permit2TransferFromBatch(assets, owner, order); if (permitBatchData) { this.permit2PermitBatch(permitBatchData.permit, permitBatchData.signature, order); } return; } if (permitBatchData) { this.permit2PermitBatch(permitBatchData.permit, permitBatchData.signature, order); } this.permit2TransferFromBatch(assets, owner, order); } transferWithPermitForDepositWithLiquiditySwaps(assets, owner, gasUsage, permitBatchData) { this.transferWithOptionalPermitBatch(assets, owner, permitBatchData, route_enum_1.CombineRouteParamsOrder.First); const estimatedGasUsedOnLocal = this.getGasCostEstimate(); const toCallParameters = this.getParams(); const estimatedRefund = (gasUsage.estimatedGasUsedOnRemote * 2n) / 12n; return { executionInstructions: { commands: toCallParameters[0], inputs: toCallParameters[1], gas: { ...gasUsage, estimatedGasUsedOnLocal: estimatedGasUsedOnLocal, estimatedRefundOnAck: estimatedRefund, }, }, }; } transferWithPermitForWithdrawWithLiquiditySwap(asset, gasUsage, permitData) { this.transferWithOptionalPermit(asset, permitData, route_enum_1.CombineRouteParamsOrder.First); const estimatedGasUsedOnLocal = this.getGasCostEstimate(); const toCallParameters = this.getParams(); const estimatedRefund = (gasUsage.estimatedGasUsedOnRemote * 2n) / 12n; return { executionInstructions: { commands: toCallParameters[0], inputs: toCallParameters[1], gas: { ...gasUsage, estimatedGasUsedOnLocal: estimatedGasUsedOnLocal, estimatedRefundOnAck: estimatedRefund, }, }, }; } static addressEncodeHelper(address) { if (address.length === 20 * 2 + 1 * 2) { return ('0x140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + address.replace('0x', '')); } else if (address.length === 65) { return address; } throw new Error(`The address, ${address}, is incorrectly provided`); } combineRouteParams(firstParams, lastParams, order = route_enum_1.CombineRouteParamsOrder.Last) { if (order === route_enum_1.CombineRouteParamsOrder.First) { return [ [...lastParams[0], ...firstParams[0]], [...lastParams[1], ...firstParams[1]], ]; } return [ [...firstParams[0], ...lastParams[0]], [...firstParams[1], ...lastParams[1]], ]; } } exports.RouterArguments = RouterArguments; //# sourceMappingURL=router.utils.js.map