diff --git a/.env.config.yaml b/.env.config.yaml index 6650c748..b2954ffd 100644 --- a/.env.config.yaml +++ b/.env.config.yaml @@ -7,6 +7,7 @@ writeRpc: $WRITE_RPC subgraph: $SUBGRAPH arbAddress: $ARB_ADDRESS genericArbAddress: $GENERIC_ARB_ADDRESS +balancerArbAddress: $BALANCER_ARB_ADDRESS dispair: $DISPAIR liquidityProviders: $LIQUIDITY_PROVIDERS route: $ROUTE @@ -24,7 +25,7 @@ retries: $RETRIES maxRatio: $MAX_RATIO rpOnly: $RP_ONLY ownerProfile: $OWNER_PROFILE -selfFundOrders: $SELF_FUND_ORDERS +selfFundVaults: $SELF_FUND_ORDERS sgFilter: includeOrders: $INCLUDE_ORDERS excludeOrders: $EXCLUDE_ORDERS diff --git a/config.example.yaml b/config.example.yaml index e831ce57..1aae29de 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -42,6 +42,9 @@ arbAddress: "0x1234...5678" # Generic arb contract address, optional genericArbAddress: "0x1234...5678" +# Balancer arb contract address, optional +balancerArbAddress: "0x1234...5678" + # Dispair contract address, required dispair: "0x1234...5678" diff --git a/package-lock.json b/package-lock.json index 129b0e3c..61f264b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.5.1", "license": "CAL-1.0", "dependencies": { + "@balancer/sdk": "^4.5.2", "@opentelemetry/api": "^1.8.0", "@opentelemetry/core": "^1.22.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.49.1", @@ -230,6 +231,250 @@ "node": ">=6.0.0" } }, + "node_modules/@balancer-labs/balancer-maths": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/@balancer-labs/balancer-maths/-/balancer-maths-0.0.27.tgz", + "integrity": "sha512-aXujyHYENIWTzN2aRoUVBKhmmLxvWBhGcO8I6wsRR6kkSw8SfnXvihFdf48RvfE8YnEfL7Bso+JCyqKPzRcBHQ==", + "license": "MIT", + "engines": { + "node": ">=18.x" + } + }, + "node_modules/@balancer/sdk": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@balancer/sdk/-/sdk-4.5.2.tgz", + "integrity": "sha512-tUaZX1yO8gDBOorg8eR6A39i9cSGu9QR2IJKc+IaJ5xjsHPjpyJwtRC9zFvaWDUuHDcbcV3UUMoGuEa79g8REA==", + "license": "MIT", + "dependencies": { + "@balancer-labs/balancer-maths": "0.0.27", + "@types/big.js": "6.2.2", + "big.js": "6.2.2", + "decimal.js-light": "2.5.1", + "viem": "2.27.0" + }, + "engines": { + "node": ">=18.x" + } + }, + "node_modules/@balancer/sdk/node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==", + "license": "MIT" + }, + "node_modules/@balancer/sdk/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@balancer/sdk/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@balancer/sdk/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@balancer/sdk/node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@balancer/sdk/node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@balancer/sdk/node_modules/@types/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==", + "license": "MIT" + }, + "node_modules/@balancer/sdk/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@balancer/sdk/node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/@balancer/sdk/node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/@balancer/sdk/node_modules/ox": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@balancer/sdk/node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@balancer/sdk/node_modules/viem": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.27.0.tgz", + "integrity": "sha512-pKw2dcwDi6TaWlTzLHYazOgjO1GgbUpE1zdLsLNSiCjHNrMTpL/teL0wVHnJDLiB2tR5CL19LBqefYNtRUkH5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.9", + "ws": "8.18.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@balancer/sdk/node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -7999,8 +8244,7 @@ "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/evp_bytestokey": { "version": "1.0.3", diff --git a/package.json b/package.json index 442ed090..a84cf038 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "vitest": "npm run vitest:logger && vitest run --exclude ./src/logger/index.test.ts" }, "dependencies": { + "@balancer/sdk": "^4.5.2", "@opentelemetry/api": "^1.8.0", "@opentelemetry/core": "^1.22.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.49.1", diff --git a/scripts/downtime.ts b/scripts/downtime.ts index cfabbf5f..dac25097 100644 --- a/scripts/downtime.ts +++ b/scripts/downtime.ts @@ -8,7 +8,7 @@ import { DEFAULT_PAGE_SIZE, getTxsQuery } from "../src/subgraph/query"; config(); -const DURATION = process.env.DURATION ? parseInt(process.env.DURATION) : 30; // 7 days default +const DURATION = process.env.DURATION ? parseInt(process.env.DURATION) : 7; // 7 days default const THRESHOLD = (process.env.THRESHOLD ? parseInt(process.env.THRESHOLD) : 60) * 60 * 1000; // 60 minutes default const subgraphs = JSON.parse(process.env.SUBGRAPHS ?? "{}") as Record; diff --git a/src/common/abis/balancer.ts b/src/common/abis/balancer.ts new file mode 100644 index 00000000..a9af0a83 --- /dev/null +++ b/src/common/abis/balancer.ts @@ -0,0 +1,23 @@ +import { Prettify } from "viem"; +import { batchRouterAbi_V3 } from "@balancer/sdk"; + +/** Keeps the BalancerBatchRouter contract ABIs */ +export namespace BalancerBatchRouterAbi { + /** Balancer BatchRouter v3 contract structs */ + export namespace Structs { + type _swapPathExactAmountInType = (typeof batchRouterAbi_V3)[21]["inputs"][0]; + const _swapPathExactAmountIn = structuredClone( + batchRouterAbi_V3[21].inputs[0], + ) as any as Prettify & { readonly type: "tuple" }>; + (_swapPathExactAmountIn as any).type = "tuple"; + + export const SwapPathExactAmountIn = _swapPathExactAmountIn; + export const SwapPathStep = batchRouterAbi_V3[21].inputs[0].components[1]; + } + + /** BalancerBatchRouter contract primary parsed ABI */ + export namespace Primary { + /** Primary parsed ABI for SushiSwap RouteProcessor3 contract only including processRoute() function */ + export const BatchRouterV3 = batchRouterAbi_V3; + } +} diff --git a/src/common/abis/index.ts b/src/common/abis/index.ts index 385869ad..dde1052a 100644 --- a/src/common/abis/index.ts +++ b/src/common/abis/index.ts @@ -3,6 +3,7 @@ import { RouteProcessorAbi } from "./rp"; import { DeployerAbi } from "./deployer"; import { OrderbookAbi } from "./orderbook"; import { Multicall3Abi } from "./multicall"; +import { BalancerBatchRouterAbi } from "./balancer"; import { ArbitrumNodeInterfaceAbi } from "./arbitrum"; /** Keeps all necesdsary ABIs for rain solver app */ @@ -11,5 +12,6 @@ export namespace ABI { export import Orderbook = OrderbookAbi; export import Multicall3 = Multicall3Abi; export import RouteProcessor = RouteProcessorAbi; + export import BalancerBatchRouter = BalancerBatchRouterAbi; export import ArbitrumNodeInterface = ArbitrumNodeInterfaceAbi; } diff --git a/src/config/yaml.test.ts b/src/config/yaml.test.ts index 15ea713a..153ffd9a 100644 --- a/src/config/yaml.test.ts +++ b/src/config/yaml.test.ts @@ -19,6 +19,7 @@ writeRpc: - url: http://write-rpc.example.com subgraph: ["http://subgraph.example.com"] arbAddress: "0x1111111111111111111111111111111111111111" +balancerArbAddress: "0x3333333333333333333333333333333333333333" dispair: "0x2222222222222222222222222222222222222222" liquidityProviders: - lp1 @@ -72,6 +73,7 @@ sgFilter: writeRpc: [{ url: "http://write-rpc.example.com" }], subgraph: ["http://subgraph.example.com"], arbAddress: "0x1111111111111111111111111111111111111111", + balancerArbAddress: "0x3333333333333333333333333333333333333333", dispair: "0x2222222222222222222222222222222222222222", genericArbAddress: undefined, liquidityProviders: ["lp1", "lp2"], diff --git a/src/config/yaml.ts b/src/config/yaml.ts index 5a12bc83..b145f1e9 100644 --- a/src/config/yaml.ts +++ b/src/config/yaml.ts @@ -36,6 +36,8 @@ export type AppOptions = { dispair: string; /** Generic arb contract address */ genericArbAddress?: string; + /** Balancer arb contract address */ + balancerArbAddress?: string; /** List of subgraph urls */ subgraph: string[]; /** Option to maximize maxIORatio, default is true */ @@ -141,6 +143,11 @@ export namespace AppOptions { "genericArbAddress", true, ), + balancerArbAddress: Validator.resolveAddress( + input.balancerArbAddress, + "balancerArbAddress", + true, + ), liquidityProviders: Validator.resolveLiquidityProviders(input.liquidityProviders), route: Validator.resolveRouteType(input.route), ownerProfile: Validator.resolveOwnerProfile(input.ownerProfile), diff --git a/src/core/modes/balancer/index.test.ts b/src/core/modes/balancer/index.test.ts new file mode 100644 index 00000000..baccbec7 --- /dev/null +++ b/src/core/modes/balancer/index.test.ts @@ -0,0 +1,135 @@ +import { Result } from "../../../common"; +import { SimulationResult } from "../../types"; +import { findBestBalancerTrade } from "./index"; +import { extendObjectWithHeader } from "../../../logger"; +import { describe, it, expect, vi, beforeEach, Mock, assert } from "vitest"; +import { trySimulateTrade, BalancerRouterSimulationHaltReason } from "./simulate"; + +vi.mock("./simulate", async (importOriginal) => ({ + ...(await importOriginal()), + trySimulateTrade: vi.fn(), +})); + +vi.mock("../../../logger", () => ({ + extendObjectWithHeader: vi.fn(), +})); + +vi.mock("sushi/currency", async (importOriginal) => { + return { + ...(await importOriginal()), + Token: class { + constructor(args: any) { + return { ...args }; + } + }, + }; +}); + +describe("Test findBestBalancerTrade", () => { + let mockRainSolver: any; + let orderDetails: any; + let signer: any; + let ethPrice: string; + let toToken: any; + let fromToken: any; + + beforeEach(() => { + vi.clearAllMocks(); + + mockRainSolver = { + state: { + client: { + getBlockNumber: vi.fn().mockResolvedValue(123n), + }, + }, + }; + + orderDetails = { + takeOrder: { quote: { maxOutput: 1000n } }, + }; + + signer = { account: { address: "0xsigner" } }; + ethPrice = "2000"; + toToken = { address: "0xTo", decimals: 18, symbol: "TO" }; + fromToken = { address: "0xFrom", decimals: 18, symbol: "FROM" }; + }); + + it("should return success result if full trade size simulation succeeds", async () => { + const mockSuccessResult = Result.ok({ + type: "balancer", + spanAttributes: { foundOpp: true }, + estimatedProfit: 100n, + oppBlockNumber: 123, + }); + (trySimulateTrade as Mock).mockResolvedValue(mockSuccessResult); + + const result: SimulationResult = await findBestBalancerTrade.call( + mockRainSolver, + orderDetails, + signer, + ethPrice, + toToken, + fromToken, + ); + + assert(result.isOk()); + expect(result.value.spanAttributes.foundOpp).toBe(true); + expect(result.value.estimatedProfit).toBe(100n); + expect(result.value.oppBlockNumber).toBe(123); + expect(result.value.type).toBe("balancer"); + expect(trySimulateTrade).toHaveBeenCalledWith({ + orderDetails, + fromToken, + toToken, + signer, + maximumInputFixed: 1000n, + ethPrice, + isPartial: false, + blockNumber: 123n, + }); + }); + + it("should return error if no route found", async () => { + const mockErrorResult = Result.err({ + reason: BalancerRouterSimulationHaltReason.NoRoute, + spanAttributes: { route: "no-way" }, + noneNodeError: "no route available", + }); + (trySimulateTrade as Mock).mockResolvedValue(mockErrorResult); + + const result: SimulationResult = await findBestBalancerTrade.call( + mockRainSolver, + orderDetails, + signer, + ethPrice, + toToken, + fromToken, + ); + + assert(result.isErr()); + expect(result.error.noneNodeError).toBe("no route available"); + expect(result.error.type).toBe("balancer"); + expect(extendObjectWithHeader).toHaveBeenCalledWith( + expect.any(Object), + { route: "no-way" }, + "full", + ); + }); + + it("should return early if ethPrice is unknown", async () => { + const result: SimulationResult = await findBestBalancerTrade.call( + mockRainSolver, + orderDetails, + signer, + "", + toToken, + fromToken, + ); + + assert(result.isErr()); + expect(result.error.type).toBe("balancer"); + expect(result.error.spanAttributes.error).toBe( + "no route to get price of input token to eth", + ); + }); +}); diff --git a/src/core/modes/balancer/index.ts b/src/core/modes/balancer/index.ts new file mode 100644 index 00000000..b4df1cbf --- /dev/null +++ b/src/core/modes/balancer/index.ts @@ -0,0 +1,65 @@ +import { RainSolver } from "../.."; +import { Pair } from "../../../order"; +import { Token } from "sushi/currency"; +import { Result } from "../../../common"; +import { Attributes } from "@opentelemetry/api"; +import { RainSolverSigner } from "../../../signer"; +import { extendObjectWithHeader } from "../../../logger"; +import { SimulationResult, TradeType } from "../../types"; +import { trySimulateTrade } from "./simulate"; + +/** + * Tries to find the best trade against balancer protocol for the given order, + * it will try to simulate a trade for full trade size (order's max output) + * and if it was not successful it will try again with partial trade size + * @param this - RainSolver instance + * @param orderDetails - The details of the order to be processed + * @param signer - The signer to be used for the trade + * @param ethPrice - The current ETH price + * @param toToken - The token to trade to + * @param fromToken - The token to trade from + */ +export async function findBestBalancerTrade( + this: RainSolver, + orderDetails: Pair, + signer: RainSolverSigner, + ethPrice: string, + toToken: Token, + fromToken: Token, +): Promise { + const spanAttributes: Attributes = {}; + + // exit early if eth price is unknown + if (!ethPrice) { + spanAttributes["error"] = "no route to get price of input token to eth"; + return Result.err({ + type: TradeType.Balancer, + spanAttributes, + }); + } + + const maximumInput = orderDetails.takeOrder.quote!.maxOutput; + const blockNumber = await this.state.client.getBlockNumber(); + + // try simulation for full trade size and return if succeeds + const simResult = await trySimulateTrade.call(this, { + orderDetails, + fromToken, + toToken, + signer, + maximumInputFixed: maximumInput, + ethPrice, + isPartial: false, + blockNumber, + }); + if (simResult.isOk()) { + return simResult; + } + extendObjectWithHeader(spanAttributes, simResult.error.spanAttributes, "full"); + + return Result.err({ + type: TradeType.Balancer, + spanAttributes, + noneNodeError: simResult.error.noneNodeError, + }); +} diff --git a/src/core/modes/balancer/simulate.test.ts b/src/core/modes/balancer/simulate.test.ts new file mode 100644 index 00000000..2a04818d --- /dev/null +++ b/src/core/modes/balancer/simulate.test.ts @@ -0,0 +1,318 @@ +import { dryrun } from "../dryrun"; +import { RainSolver } from "../.."; +import { ONE18 } from "../../../math"; +import { Pair } from "../../../order"; +import { ABI, Result } from "../../../common"; +import { SimulationResult } from "../../types"; +import { encodeFunctionData, encodeAbiParameters, maxUint256 } from "viem"; +import { describe, it, expect, vi, beforeEach, Mock, assert } from "vitest"; +import { BalancerRouterError, BalancerRouterErrorType } from "../../../router/balancer"; +import { + trySimulateTrade, + SimulateBalancerTradeArgs, + BalancerRouterSimulationHaltReason, +} from "./simulate"; + +vi.mock("viem", async (importOriginal) => ({ + ...(await importOriginal()), + encodeFunctionData: vi.fn().mockReturnValue("0xdata"), + encodeAbiParameters: vi.fn().mockReturnValue("0xparams"), +})); + +vi.mock("../rp/utils", () => ({ + estimateProfit: vi.fn().mockReturnValue(123n), +})); + +vi.mock("../../../task", () => ({ + parseRainlang: vi.fn().mockResolvedValue("0xbytecode"), + getBountyEnsureRainlang: vi.fn().mockResolvedValue("rainlang"), +})); + +vi.mock("../dryrun", () => ({ + dryrun: vi.fn(), +})); + +function makeOrderDetails(ratio = 1n * ONE18): Pair { + return { + orderbook: "0xorderbook", + sellTokenDecimals: 18, + buyTokenDecimals: 18, + takeOrder: { struct: {}, quote: { ratio } }, + } as Pair; +} + +describe("Test trySimulateTrade", () => { + let solver: RainSolver; + let args: SimulateBalancerTradeArgs; + + beforeEach(() => { + vi.clearAllMocks(); + solver = { + state: { + gasPrice: 1n, + dataFetcher: { + getCurrentPoolCodeMap: vi.fn().mockReturnValue("mockPcMap"), + }, + chainConfig: { + id: 1, + isSpecialL2: false, + routeProcessors: { "4": "0xprocessor" }, + }, + dispair: { + interpreter: "0xint", + store: "0xstore", + }, + client: {}, + balancerRouter: { + routerAddress: "0xbalancerRouter", + tryQuote: vi.fn(), + visualizeRoute: vi.fn().mockReturnValue(["visualRoute"]), + }, + }, + appOptions: { + arbAddress: "0xarb", + balancerArbAddress: "0xbalancerArb", + gasCoveragePercentage: "0", + maxRatio: false, + route: undefined, + gasLimitMultiplier: 120, + }, + } as any; + args = { + orderDetails: makeOrderDetails(), + signer: { account: { address: "0xsigner" } }, + ethPrice: "1", + toToken: { address: "0xTo", decimals: 18, symbol: "TO" }, + fromToken: { address: "0xFrom", decimals: 18, symbol: "FROM" }, + maximumInputFixed: 10n * ONE18, + blockNumber: 123n, + isPartial: false, + } as any; + }); + + it("should return NoRoute if Router.findBestRoute returns NoWay", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.err( + new BalancerRouterError( + "Failed to fetch balancer routes", + BalancerRouterErrorType.NoRouteFound, + ), + ), + ); + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isErr()); + expect(result.error).toHaveProperty("spanAttributes"); + expect(result.error).toHaveProperty("reason"); + expect(result.error.reason).toBe(BalancerRouterSimulationHaltReason.NoRoute); + expect(result.error.spanAttributes.route).toBe("no-way"); + expect(result.error.type).toBe("balancer"); + }); + + it("should return OrderRatioGreaterThanMarketPrice if price < order ratio", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.ok({ + route: [], + amountOut: 1n * ONE18, + price: 1n * ONE18, + }), + ); + // Set order ratio higher than price + args.orderDetails = makeOrderDetails(2n * ONE18); + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isErr()); + expect(result.error).toHaveProperty("spanAttributes"); + expect(result.error).toHaveProperty("reason"); + expect(result.error.reason).toBe( + BalancerRouterSimulationHaltReason.OrderRatioGreaterThanMarketPrice, + ); + expect(result.error.spanAttributes.error).toBe("Order's ratio greater than market price"); + expect(Array.isArray(result.error.spanAttributes.route)).toBe(true); + expect(result.error.type).toBe("balancer"); + }); + + it("should return NoOpportunity if initial dryrun fails", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.ok({ + route: [], + amountOut: 20n * ONE18, + price: 20n * ONE18, + }), + ); + (dryrun as Mock).mockResolvedValueOnce( + Result.err({ + spanAttributes: { stage: 1 }, + reason: BalancerRouterSimulationHaltReason.NoOpportunity, + }), + ); + args.orderDetails = makeOrderDetails(1n * ONE18); + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isErr()); + expect(result.error).toHaveProperty("spanAttributes"); + expect(result.error).toHaveProperty("reason"); + expect(result.error.reason).toBe(BalancerRouterSimulationHaltReason.NoOpportunity); + expect(result.error.spanAttributes.stage).toBe(1); + expect(result.error.spanAttributes.oppBlockNumber).toBe(123); + expect(result.error.type).toBe("balancer"); + }); + + it("should return ok result if all steps succeed with gasCoveragePercentage 0", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.ok({ + route: ["route"], + amountOut: 20n * ONE18, + price: 2n * ONE18, + }), + ); + (dryrun as Mock).mockResolvedValueOnce( + Result.ok({ + estimation: { gas: 100n, totalGasCost: 200n, gasPrice: 1n }, + estimatedGasCost: 200n, + spanAttributes: {}, + }), + ); + args.orderDetails = makeOrderDetails(1n * ONE18); + solver.appOptions.gasCoveragePercentage = "0"; + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isOk()); + expect(result.value).toHaveProperty("spanAttributes"); + expect(result.value).toHaveProperty("rawtx"); + expect(result.value).toHaveProperty("estimatedGasCost"); + expect(result.value).toHaveProperty("oppBlockNumber"); + expect(result.value).toHaveProperty("estimatedProfit"); + expect(result.value.estimatedProfit).toBe(123n); + expect(result.value.oppBlockNumber).toBe(Number(args.blockNumber)); + expect(result.value.spanAttributes.foundOpp).toBe(true); + expect(result.value.estimatedGasCost).toBe(200n); + expect(result.value.rawtx).toHaveProperty("data", "0xdata"); + expect(result.value.rawtx).toHaveProperty("to", "0xbalancerArb"); + expect(result.value.rawtx).toHaveProperty("gasPrice", 1n); + expect(result.value.type).toBe("balancer"); + + // Assert encodeFunctionData was called correctly + expect(encodeFunctionData).toHaveBeenCalledWith({ + abi: expect.any(Array), // ArbAbi + functionName: "arb3", + args: [ + "0xorderbook", + { + data: "0xparams", + maximumIORatio: 2000000000000000000n, + maximumInput: maxUint256, + minimumInput: 1n, + orders: [{}], + }, + { + evaluable: { + bytecode: "0x", + interpreter: "0xint", + store: "0xstore", + }, + signedContext: [], + }, + ], + }); + + // Assert encodeAbiParameters was called correctly + expect(encodeAbiParameters).toHaveBeenCalledWith( + [{ type: "address" }, ABI.BalancerBatchRouter.Structs.SwapPathExactAmountIn], + ["0xbalancerRouter", "route"], + ); + }); + + it("should return ok result if all steps succeed with gasCoveragePercentage not 0", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.ok({ + route: [], + amountOut: 20n * ONE18, + price: 20n * ONE18, + }), + ); + (dryrun as Mock) + .mockResolvedValueOnce( + Result.ok({ + estimation: { gas: 100n, totalGasCost: 200n, gasPrice: 1n }, + estimatedGasCost: 200n, + spanAttributes: { initial: "data" }, + }), + ) + .mockResolvedValueOnce( + Result.ok({ + estimation: { gas: 150n, totalGasCost: 300n, gasPrice: 1n }, + estimatedGasCost: 300n, + spanAttributes: { final: "data" }, + }), + ); + args.orderDetails = makeOrderDetails(1n * ONE18); + solver.appOptions.gasCoveragePercentage = "100"; + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isOk()); + expect(result.value).toHaveProperty("spanAttributes"); + expect(result.value).toHaveProperty("rawtx"); + expect(result.value).toHaveProperty("estimatedGasCost"); + expect(result.value).toHaveProperty("oppBlockNumber"); + expect(result.value).toHaveProperty("estimatedProfit"); + expect(result.value.estimatedProfit).toBe(123n); + expect(result.value.oppBlockNumber).toBe(Number(args.blockNumber)); + expect(result.value.spanAttributes.foundOpp).toBe(true); + expect(result.value.estimatedGasCost).toBe(300n); + expect(result.value.spanAttributes.initial).toBe("data"); + expect(result.value.spanAttributes.final).toBe("data"); + expect(result.value.rawtx).toHaveProperty("data", "0xdata"); + expect(result.value.rawtx).toHaveProperty("to", "0xbalancerArb"); + expect(result.value.rawtx).toHaveProperty("gasPrice", 1n); + expect(result.value.type).toBe("balancer"); + + // verify called times + expect(encodeFunctionData).toHaveBeenCalledTimes(3); + expect(encodeAbiParameters).toHaveBeenCalledTimes(1); + }); + + it("should return NoOpportunity if final dryrun fails when gasCoveragePercentage is not 0", async () => { + (solver.state.balancerRouter?.tryQuote as Mock).mockReturnValueOnce( + Result.ok({ + route: [], + amountOut: 20n * ONE18, + price: 20n * ONE18, + }), + ); + (dryrun as Mock) + .mockResolvedValueOnce( + Result.ok({ + estimation: { gas: 100n, totalGasCost: 200n, gasPrice: 1n }, + estimatedGasCost: 200n, + spanAttributes: {}, + }), + ) + .mockResolvedValueOnce( + Result.err({ + spanAttributes: { stage: 2 }, + reason: BalancerRouterSimulationHaltReason.NoOpportunity, + }), + ); + args.orderDetails = makeOrderDetails(1n * ONE18); + solver.appOptions.gasCoveragePercentage = "100"; + + const result: SimulationResult = await trySimulateTrade.call(solver, args); + + assert(result.isErr()); + expect(result.error).toHaveProperty("spanAttributes"); + expect(result.error).toHaveProperty("reason"); + expect(result.error.reason).toBe(BalancerRouterSimulationHaltReason.NoOpportunity); + expect(result.error.spanAttributes.stage).toBe(2); + expect(result.error.type).toBe("balancer"); + + // verify encodeFunctionData was called twice (for both dryruns) + expect(encodeFunctionData).toHaveBeenCalledTimes(2); + expect(encodeAbiParameters).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/modes/balancer/simulate.ts b/src/core/modes/balancer/simulate.ts new file mode 100644 index 00000000..76011442 --- /dev/null +++ b/src/core/modes/balancer/simulate.ts @@ -0,0 +1,292 @@ +import { dryrun } from "../dryrun"; +import { RainSolver } from "../.."; +import { Pair } from "../../../order"; +import { Token } from "sushi/currency"; +import { scaleFrom18 } from "../../../math"; +import { estimateProfit } from "../rp/utils"; +import { Result, ABI } from "../../../common"; +import { Attributes } from "@opentelemetry/api"; +import { extendObjectWithHeader } from "../../../logger"; +import { RainSolverSigner, RawTransaction } from "../../../signer"; +import { BalancerRouter, BalancerRouterErrorType } from "../../../router/balancer"; +import { getBountyEnsureRainlang, parseRainlang } from "../../../task"; +import { TakeOrdersConfigType, SimulationResult, TradeType, FailedSimulation } from "../../types"; +import { encodeAbiParameters, encodeFunctionData, formatUnits, maxUint256, parseUnits } from "viem"; + +/** Specifies the reason that balancer trade simulation failed */ +export enum BalancerRouterSimulationHaltReason { + NoOpportunity = 1, + NoRoute = 2, + OrderRatioGreaterThanMarketPrice = 3, + FetchFailed = 4, + SwapQueryFailed = 5, +} + +/** Arguments for simulating balancer protocol trade */ +export type SimulateBalancerTradeArgs = { + /** The bundled order details including tokens, decimals, and take orders */ + orderDetails: Pair; + /** The RainSolverSigner instance used for signing transactions */ + signer: RainSolverSigner; + /** The current ETH price (in 18 decimals) */ + ethPrice: string; + /** The token to be received in the swap */ + toToken: Token; + /** The token to be sold in the swap */ + fromToken: Token; + /** The maximum input amount (in 18 decimals) */ + maximumInputFixed: bigint; + /** The current block number for context */ + blockNumber: bigint; +}; + +/** + * Attempts to find a profitable opportunity (opp) for a given order by simulating a trade against balancer protocol. + * @param this - The RainSolver instance context + * @param args - The arguments for simulating the trade + */ +export async function trySimulateTrade( + this: RainSolver, + args: SimulateBalancerTradeArgs, +): Promise { + const { orderDetails, signer, ethPrice, toToken, fromToken, maximumInputFixed, blockNumber } = + args; + const gasPrice = this.state.gasPrice; + const spanAttributes: Attributes = {}; + + const maximumInput = scaleFrom18(maximumInputFixed, orderDetails.sellTokenDecimals); + spanAttributes["amountIn"] = formatUnits(maximumInputFixed, 18); + + const quoteResult = await this.state.balancerRouter!.tryQuote( + { + tokenIn: fromToken, + tokenOut: toToken, + swapAmount: maximumInput, + }, + signer, + ); + if (quoteResult.isErr()) { + spanAttributes["error"] = quoteResult.error.message; + + let reason = BalancerRouterSimulationHaltReason.NoOpportunity; + if (quoteResult.error.type === BalancerRouterErrorType.NoRouteFound) { + spanAttributes["route"] = "no-way"; + reason = BalancerRouterSimulationHaltReason.NoRoute; + } else if (quoteResult.error.type === BalancerRouterErrorType.FetchFailed) { + reason = BalancerRouterSimulationHaltReason.FetchFailed; + } else if (quoteResult.error.type === BalancerRouterErrorType.SwapQueryFailed) { + reason = BalancerRouterSimulationHaltReason.SwapQueryFailed; + } else { + reason = BalancerRouterSimulationHaltReason.NoOpportunity; + } + + return Result.err({ + type: TradeType.Balancer, + spanAttributes, + reason, + }); + } + + const quote = quoteResult.value; + const amountOut = quote.amountOut; + const price = quote.price; + + spanAttributes["amountOut"] = formatUnits(amountOut, toToken.decimals); + spanAttributes["marketPrice"] = formatUnits(price, 18); + + const routeVisual: string[] = []; + try { + BalancerRouter.visualizeRoute(quote.route, this.state.watchedTokens).forEach((v) => { + routeVisual.push(v); + }); + } catch { + /**/ + } + spanAttributes["route"] = routeVisual; + + // exit early if market price is lower than order quote ratio + if (price < orderDetails.takeOrder.quote!.ratio) { + spanAttributes["error"] = "Order's ratio greater than market price"; + const result = { + type: TradeType.Balancer, + spanAttributes, + reason: BalancerRouterSimulationHaltReason.OrderRatioGreaterThanMarketPrice, + }; + return Result.err(result); + } + + spanAttributes["oppBlockNumber"] = Number(blockNumber); + + const balancerRouter = this.state.balancerRouter!.routerAddress; + const orders = [orderDetails.takeOrder.struct]; + const takeOrdersConfigStruct: TakeOrdersConfigType = { + minimumInput: 1n, + maximumInput: maxUint256, + maximumIORatio: this.appOptions.maxRatio ? maxUint256 : price, + orders, + data: encodeAbiParameters( + [{ type: "address" }, ABI.BalancerBatchRouter.Structs.SwapPathExactAmountIn], + [balancerRouter, quote.route[0]], + ), + }; + + const task = { + evaluable: { + interpreter: this.state.dispair.interpreter as `0x${string}`, + store: this.state.dispair.store as `0x${string}`, + bytecode: (this.appOptions.gasCoveragePercentage === "0" + ? "0x" + : await parseRainlang( + await getBountyEnsureRainlang( + parseUnits(ethPrice, 18), + 0n, + 0n, + signer.account.address, + ), + this.state.client, + this.state.dispair, + )) as `0x${string}`, + }, + signedContext: [], + }; + const rawtx: RawTransaction = { + data: encodeFunctionData({ + abi: ABI.Orderbook.Primary.Arb, + functionName: "arb3", + args: [orderDetails.orderbook as `0x${string}`, takeOrdersConfigStruct, task], + }), + to: this.appOptions.balancerArbAddress as `0x${string}`, + gasPrice, + }; + + // initial dryrun with 0 minimum sender output to get initial + // pass and tx gas cost to calc minimum sender output + const initDryrunResult = await dryrun( + signer, + rawtx, + gasPrice, + this.appOptions.gasLimitMultiplier, + ); + if (initDryrunResult.isErr()) { + spanAttributes["stage"] = 1; + Object.assign(initDryrunResult.error.spanAttributes, spanAttributes); + initDryrunResult.error.reason = BalancerRouterSimulationHaltReason.NoOpportunity; + (initDryrunResult.error as FailedSimulation).type = TradeType.Balancer; + return Result.err(initDryrunResult.error as FailedSimulation); + } + + let { estimation, estimatedGasCost } = initDryrunResult.value; + // include dryrun initial gas estimation in logs + Object.assign(spanAttributes, initDryrunResult.value.spanAttributes); + extendObjectWithHeader( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(this.state.chainConfig.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.initial", + ); + + // repeat the same process with headroom if gas + // coverage is not 0, 0 gas coverage means 0 minimum + // sender output which is already called above + if (this.appOptions.gasCoveragePercentage !== "0") { + const headroom = BigInt((Number(this.appOptions.gasCoveragePercentage) * 1.03).toFixed()); + spanAttributes["gasEst.initial.minBountyExpected"] = ( + (estimatedGasCost * headroom) / + 100n + ).toString(); + task.evaluable.bytecode = (await parseRainlang( + await getBountyEnsureRainlang( + parseUnits(ethPrice, 18), + 0n, + (estimatedGasCost * headroom) / 100n, + signer.account.address, + ), + this.state.client, + this.state.dispair, + )) as `0x${string}`; + rawtx.data = encodeFunctionData({ + abi: ABI.Orderbook.Primary.Arb, + functionName: "arb3", + args: [orderDetails.orderbook as `0x${string}`, takeOrdersConfigStruct, task], + }); + + const finalDryrunResult = await dryrun( + signer, + rawtx, + gasPrice, + this.appOptions.gasLimitMultiplier, + ); + if (finalDryrunResult.isErr()) { + spanAttributes["stage"] = 2; + Object.assign(finalDryrunResult.error.spanAttributes, spanAttributes); + finalDryrunResult.error.reason = BalancerRouterSimulationHaltReason.NoOpportunity; + (finalDryrunResult.error as FailedSimulation).type = TradeType.Balancer; + return Result.err(finalDryrunResult.error as FailedSimulation); + } + + ({ estimation, estimatedGasCost } = finalDryrunResult.value); + // include dryrun final gas estimation in otel logs + Object.assign(spanAttributes, finalDryrunResult.value.spanAttributes); + extendObjectWithHeader( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(this.state.chainConfig.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.final", + ); + + task.evaluable.bytecode = (await parseRainlang( + await getBountyEnsureRainlang( + parseUnits(ethPrice, 18), + 0n, + (estimatedGasCost * BigInt(this.appOptions.gasCoveragePercentage)) / 100n, + signer.account.address, + ), + this.state.client, + this.state.dispair, + )) as `0x${string}`; + rawtx.data = encodeFunctionData({ + abi: ABI.Orderbook.Primary.Arb, + functionName: "arb3", + args: [orderDetails.orderbook as `0x${string}`, takeOrdersConfigStruct, task], + }); + spanAttributes["gasEst.final.minBountyExpected"] = ( + (estimatedGasCost * BigInt(this.appOptions.gasCoveragePercentage)) / + 100n + ).toString(); + } + + // if reached here, it means there was a success and found opp + spanAttributes["foundOpp"] = true; + const result = { + type: TradeType.Balancer, + spanAttributes, + rawtx, + estimatedGasCost, + oppBlockNumber: Number(blockNumber), + estimatedProfit: estimateProfit( + orderDetails, + parseUnits(ethPrice, 18), + price, + maximumInputFixed, + )!, + }; + return Result.ok(result); +} diff --git a/src/core/modes/index.test.ts b/src/core/modes/index.test.ts index 1083e9ac..c1f9d244 100644 --- a/src/core/modes/index.test.ts +++ b/src/core/modes/index.test.ts @@ -1,5 +1,7 @@ +import { RainSolver } from ".."; import { Result } from "../../common"; import { findBestTrade } from "./index"; +import { findBestBalancerTrade } from "./balancer"; import { findBestRouteProcessorTrade } from "./rp"; import { findBestIntraOrderbookTrade } from "./intra"; import { findBestInterOrderbookTrade } from "./inter"; @@ -17,8 +19,12 @@ vi.mock("./inter", () => ({ findBestInterOrderbookTrade: vi.fn(), })); +vi.mock("./balancer", () => ({ + findBestBalancerTrade: vi.fn(), +})); + describe("Test findBestTrade", () => { - let mockRainSolver: any; + let mockRainSolver: RainSolver; let args: any; beforeEach(() => { @@ -27,8 +33,10 @@ describe("Test findBestTrade", () => { mockRainSolver = { appOptions: { rpOnly: false, + balancerArbAddress: "0xBalancerArb", }, - }; + state: {}, + } as any; args = { orderDetails: { @@ -168,6 +176,73 @@ describe("Test findBestTrade", () => { expect(findBestInterOrderbookTrade).not.toHaveBeenCalled(); }); + it("should only call balancer router when balancerRouter is available", async () => { + const mocksolver = structuredClone(mockRainSolver); + (mocksolver.state as any).balancerRouter = {}; + + const rpResult = Result.ok({ + type: "routeProcessor", + spanAttributes: { foundOpp: true }, + estimatedProfit: 100n, + oppBlockNumber: 123, + }); + const intraResult = Result.ok({ + type: "intraOrderbook", + spanAttributes: { foundOpp: true }, + estimatedProfit: 150n, + oppBlockNumber: 123, + }); + const interResult = Result.ok({ + type: "interOrderbook", + spanAttributes: { foundOpp: true }, + estimatedProfit: 120n, + oppBlockNumber: 123, + }); + const balancerResult = Result.ok({ + type: "balancer", + spanAttributes: { foundOpp: true }, + estimatedProfit: 120n, + oppBlockNumber: 123, + }); + + (findBestRouteProcessorTrade as Mock).mockResolvedValue(rpResult); + (findBestIntraOrderbookTrade as Mock).mockResolvedValue(intraResult); + (findBestInterOrderbookTrade as Mock).mockResolvedValue(interResult); + (findBestBalancerTrade as Mock).mockResolvedValue(balancerResult); + + const result = await findBestTrade.call(mocksolver, args); + + assert(result.isOk()); + expect(result.value.estimatedProfit).toBe(150n); // highest profit + expect(result.value.type).toBe("intraOrderbook"); + expect(findBestRouteProcessorTrade).toHaveBeenCalledWith( + args.orderDetails, + args.signer, + args.inputToEthPrice, + args.toToken, + args.fromToken, + ); + expect(findBestIntraOrderbookTrade).toHaveBeenCalledWith( + args.orderDetails, + args.signer, + args.inputToEthPrice, + args.outputToEthPrice, + ); + expect(findBestInterOrderbookTrade).toHaveBeenCalledWith( + args.orderDetails, + args.signer, + args.inputToEthPrice, + args.outputToEthPrice, + ); + expect(findBestBalancerTrade).toHaveBeenCalledWith( + args.orderDetails, + args.signer, + args.inputToEthPrice, + args.toToken, + args.fromToken, + ); + }); + it("should call all modes when rpOnly is false", async () => { mockRainSolver.appOptions.rpOnly = false; diff --git a/src/core/modes/index.ts b/src/core/modes/index.ts index c70031db..1e51aaf4 100644 --- a/src/core/modes/index.ts +++ b/src/core/modes/index.ts @@ -10,6 +10,7 @@ import { findBestRouteProcessorTrade } from "./rp"; import { findBestIntraOrderbookTrade } from "./intra"; import { findBestInterOrderbookTrade } from "./inter"; import { extendObjectWithHeader } from "../../logger"; +import { findBestBalancerTrade } from "./balancer"; /** Arguments for finding the best trade */ export type FindBestTradeArgs = { @@ -52,6 +53,19 @@ export async function findBestTrade( toToken, fromToken, ), + // include balancer trade only if balancer router is available for the operating chain + ...(this.state.balancerRouter && this.appOptions.balancerArbAddress + ? [ + findBestBalancerTrade.call( + this, + orderDetails, + signer, + inputToEthPrice, + toToken, + fromToken, + ), + ] + : []), // include intra and inter orderbook trades types only if rpOnly is false ...(!this.appOptions.rpOnly ? [ diff --git a/src/core/modes/rp/simulate.test.ts b/src/core/modes/rp/simulate.test.ts index 801e99af..8f987fbd 100644 --- a/src/core/modes/rp/simulate.test.ts +++ b/src/core/modes/rp/simulate.test.ts @@ -433,82 +433,3 @@ describe("Test findLargestTradeSize", () => { expect(result).toBeGreaterThan(0n); }); }); - -describe("Test findLargestTradeSize", () => { - let solver: RainSolver; - let fromToken: Token; - let toToken: Token; - let maximumInputFixed: bigint; - - beforeEach(() => { - vi.clearAllMocks(); - solver = { - state: { - gasPrice: 1n, - dataFetcher: { - getCurrentPoolCodeMap: vi.fn().mockReturnValue("mockPcMap"), - }, - chainConfig: { - id: 1, - }, - }, - appOptions: { - route: undefined, - }, - } as any; - fromToken = { address: "0xFrom", decimals: 18 } as any; - toToken = { address: "0xTo", decimals: 18 } as any; - maximumInputFixed = 10n * ONE18; - }); - - it("should return undefined if no valid trade size found (all NoWay)", () => { - (Router.findBestRoute as Mock).mockReturnValue({ status: "NoWay" }); - - const result: bigint | undefined = findLargestTradeSize.call( - solver, - makeOrderDetails(1n * ONE18), - toToken, - fromToken, - maximumInputFixed, - ); - - expect(result).toBeUndefined(); - }); - - it("should return the largest valid trade size when some routes are valid", () => { - (Router.findBestRoute as Mock).mockImplementation(() => { - return { status: "OK", amountOutBI: 4n * ONE18 }; - }); - - const orderDetails = makeOrderDetails(1n * ONE18); - - const result: bigint | undefined = findLargestTradeSize.call( - solver, - orderDetails, - toToken, - fromToken, - maximumInputFixed, - ); - - expect(typeof result).toBe("bigint"); - expect(result).toBe(3999999761581420898n); - }); - - it("should return undefined if all OK routes have price < ratio", () => { - (Router.findBestRoute as Mock).mockImplementation(() => ({ - status: "OK", - amountOutBI: 1n, // price = 1 - })); - const orderDetails = makeOrderDetails(2n * ONE18); // ratio = 2 - - const result: bigint | undefined = findLargestTradeSize.call( - solver, - orderDetails, - toToken, - fromToken, - maximumInputFixed, - ); - - expect(result).toBeUndefined(); - }); -}); diff --git a/src/core/types.ts b/src/core/types.ts index 799e3b14..292fc69f 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -27,6 +27,7 @@ export enum TradeType { RouteProcessor = "routeProcessor", IntraOrderbook = "intraOrderbook", InterOrderbook = "interOrderbook", + Balancer = "balancer", } /** Base type for process order results containing shared fields */ diff --git a/src/error/common.ts b/src/error/common.ts index 543d153c..e9660d97 100644 --- a/src/error/common.ts +++ b/src/error/common.ts @@ -44,7 +44,14 @@ export async function errorSnapshot( if (err.details) message.push(`Details: ${err.details}`); if (typeof org.code === "number") message.push(`RPC Error Code: ${org.code}`); if (typeof org.message === "string") message.push(`RPC Error Msg: ${org.message}`); - if (message.some((v) => v.includes("unknown reason") || v.includes("execution reverted"))) { + if ( + message.some( + (v) => + v.includes("unknown reason") || + v.includes("execution reverted") || + v.includes("custom error"), + ) + ) { const { raw, decoded } = await parseRevertError(err); if (decoded) { message.push("Error Name: " + decoded.name); diff --git a/src/error/decoder.ts b/src/error/decoder.ts index f05806df..6d881661 100644 --- a/src/error/decoder.ts +++ b/src/error/decoder.ts @@ -1,7 +1,14 @@ import axios from "axios"; import assert from "assert"; import { Result } from "../common"; -import { decodeErrorResult, isHex, parseAbiItem } from "viem"; +import { balancerBatchRouterAbiExtended } from "@balancer/sdk"; +import { + isHex, + parseAbiItem, + decodeErrorResult, + toFunctionSelector, + toFunctionSignature, +} from "viem"; import { PANIC_ABI, PANIC_SELECTOR, @@ -14,6 +21,13 @@ import { /** Selector abi/sig cache at runtime */ export const SelectorCache = new Map(); +// set balancer error signatures in the cache as they are not available in the registry +balancerBatchRouterAbiExtended.forEach((abi: any) => { + if (abi.type !== "error") return; + const minimalSig = toFunctionSignature(abi).replace("error ", ""); + SelectorCache.set(toFunctionSelector(minimalSig), [minimalSig]); +}); + /** * Tries to decode the given error data by running through known matching signatures * @param data - the error data diff --git a/src/router/balancer.test.ts b/src/router/balancer.test.ts new file mode 100644 index 00000000..761b5f7f --- /dev/null +++ b/src/router/balancer.test.ts @@ -0,0 +1,1005 @@ +import { Token } from "sushi/currency"; +import { TokenDetails } from "../state"; +import { describe, it, expect, vi, beforeEach, Mock, assert } from "vitest"; +import { + BalancerRouter, + BalancerRouterPath, + BalancerRouterError, + BalancerRouterErrorType, +} from "./balancer"; + +vi.mock("@balancer/sdk", async (importOriginal) => { + return { + ...(await importOriginal()), + BalancerApi: vi.fn().mockImplementation(() => ({ + sorSwapPaths: { + fetchSorSwapPaths: vi.fn(), + }, + })), + }; +}); + +describe("test BalancerRouter", () => { + describe("test init method", () => { + it("should successfully initialize BalancerRouter for supported chain", () => { + const chainId = 1; // Ethereum mainnet - supported by Balancer v3 + + const result = BalancerRouter.init(chainId); + + assert(result.isOk()); + if (result.isOk()) { + const router = result.value; + expect(router).toBeInstanceOf(BalancerRouter); + expect(router.chainId).toBe(chainId); + expect(router.protocolVersion).toBe(3); + expect(router.routeTime).toBe(300_000); + expect(router.routerAddress).toBeDefined(); + expect(router.balancerApi).toBeDefined(); + expect(router.cache).toBeInstanceOf(Map); + expect(router.cache.size).toBe(0); + } + }); + + it("should return error for unsupported chain", () => { + const unsupportedChainId = 999999; // Non-existent chain + + const result = BalancerRouter.init(unsupportedChainId); + + assert(result.isErr()); + if (result.isErr()) { + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.UnsupportedChain); + expect(error.message).toBe( + `Balancer router does not support chain with id: ${unsupportedChainId}`, + ); + expect(error.name).toBe("BalancerRouterError"); + } + }); + }); + + describe("test fetchSortedRoutes method", () => { + let router: BalancerRouter; + let mockBalancerApi: { sorSwapPaths: { fetchSorSwapPaths: Mock } }; + + const mockTokenIn: TokenDetails = { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + symbol: "WETH", + }; + + const mockTokenOut: TokenDetails = { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + symbol: "USDC", + }; + + const mockSwapAmount = 1000000000000000000n; // 1 WETH + + beforeEach(() => { + const routerResult = BalancerRouter.init(1); // Ethereum mainnet + if (routerResult.isOk()) { + router = routerResult.value; + mockBalancerApi = (router as any).balancerApi; + } + }); + + it("should successfully fetch sorted routes", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, // 3000 USDC + isBuffer: [false], + }, + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: "0xintermediatetoken", decimals: 6 }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool2", "0xpool3"], + inputAmountRaw: mockSwapAmount / 2n, + outputAmountRaw: 1500000000n, // 1500 USDC + isBuffer: [false, false], + }, + ]; + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + const result = await router.fetchSortedRoutes({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + const routes = result.value; + expect(routes).toHaveLength(2); + + // check first route + expect(routes[0].tokenIn).toBe(mockTokenIn.address); + expect(routes[0].steps).toHaveLength(1); + expect(routes[0].steps[0].pool).toBe("0xpool1"); + expect(routes[0].steps[0].tokenOut).toBe(mockTokenOut.address); + expect(routes[0].steps[0].isBuffer).toBe(false); + + // check second route (multi-hop) + expect(routes[1].tokenIn).toBe(mockTokenIn.address); + expect(routes[1].steps).toHaveLength(2); + expect(routes[1].steps[0].pool).toBe("0xpool2"); + expect(routes[1].steps[0].tokenOut).toBe("0xintermediatetoken"); + expect(routes[1].steps[1].pool).toBe("0xpool3"); + expect(routes[1].steps[1].tokenOut).toBe(mockTokenOut.address); + + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledWith({ + chainId: 1, + tokenIn: mockTokenIn.address.toLowerCase(), + tokenOut: mockTokenOut.address.toLowerCase(), + swapKind: 0, // SwapKind.GivenIn + swapAmount: expect.any(Object), // TokenAmount instance + useProtocolVersion: 3, + }); + }); + + it("should return NoRouteFound error when no routes available", async () => { + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue([]); + + const result = await router.fetchSortedRoutes({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.NoRouteFound); + expect(error.message).toBe("Found no balancer route for given token pair"); + }); + + it("should return FetchFailed error when API call throws", async () => { + const mockError = new Error("Network error"); + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockRejectedValue(mockError); + + const result = await router.fetchSortedRoutes({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.FetchFailed); + expect(error.message).toBe("Failed to fetch balancer routes"); + expect(error.cause).toBe(mockError); + }); + + it("should handle tokens without symbols", async () => { + const tokenWithoutSymbol = new Token({ + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + chainId: 1, + // no symbol property + }); + + const mockSorPaths = [ + { + tokens: [ + { + address: tokenWithoutSymbol.address, + decimals: tokenWithoutSymbol.decimals, + }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + const result = await router.fetchSortedRoutes({ + tokenIn: tokenWithoutSymbol, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + expect(result.value).toHaveLength(1); + + // verify that the Token constructor was called with "unknownSymbol" + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledWith( + expect.objectContaining({ + tokenIn: tokenWithoutSymbol.address.toLowerCase(), + tokenOut: mockTokenOut.address.toLowerCase(), + }), + ); + }); + + it("should handle buffer pools correctly", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: "0xbufferedtoken", decimals: 6 }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xbufferpool", "0xregularpool"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [true, false], // First pool is buffer + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + const result = await router.fetchSortedRoutes({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + const routes = result.value; + expect(routes[0].steps).toHaveLength(2); + expect(routes[0].steps[0].isBuffer).toBe(true); + expect(routes[0].steps[1].isBuffer).toBe(false); + }); + }); + + describe("test getBestRoute method", () => { + let router: BalancerRouter; + let mockBalancerApi: { sorSwapPaths: { fetchSorSwapPaths: Mock } }; + + const mockTokenIn: TokenDetails = { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + symbol: "WETH", + }; + + const mockTokenOut: TokenDetails = { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + symbol: "USDC", + }; + + const mockSwapAmount = 1000000000000000000n; // 1 WETH + + beforeEach(() => { + const routerResult = BalancerRouter.init(1); // Ethereum mainnet + if (routerResult.isOk()) { + router = routerResult.value; + router.cache.clear(); // clear cache before each test + mockBalancerApi = (router as any).balancerApi; + } + }); + + it("should successfully get best route and cache it", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, // 3000 USDC + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + const result = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + const cachedRoute = result.value; + expect(cachedRoute.route).toHaveLength(1); + expect(cachedRoute.route[0].tokenIn).toBe(mockTokenIn.address); + expect(cachedRoute.route[0].exactAmountIn).toBe(mockSwapAmount); + expect(cachedRoute.route[0].minAmountOut).toBe(3000000000n); + expect(cachedRoute.route[0].steps).toHaveLength(1); + expect(cachedRoute.route[0].steps[0].pool).toBe("0xpool1"); + expect(cachedRoute.price).toBeGreaterThan(0n); + expect(cachedRoute.validUntil).toBeGreaterThan(Date.now()); + + // verify route was cached + const cacheKey = `${mockTokenIn.address.toLowerCase()}/${mockTokenOut.address.toLowerCase()}`; + expect(router.cache.has(cacheKey)).toBe(true); + expect(router.cache.get(cacheKey)).toEqual(cachedRoute); + }); + + it("should return cached route when available and not expired", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + // First call - should fetch from API + const firstResult = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(firstResult.isOk()); + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledTimes(1); + + // Second call - should return from cache + const secondResult = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(secondResult.isOk()); + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledTimes(1); // No additional API call + expect(secondResult.value).toEqual(firstResult.value); + }); + + it("should ignore cache when ignoreCache is true", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + // First call + await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledTimes(1); + + // Second call with ignoreCache: true + await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + ignoreCache: true, + }); + + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledTimes(2); // Additional API call + }); + + it("should fetch new route when cache is expired", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + // manually add expired cache entry + const cacheKey = `${mockTokenIn.address.toLowerCase()}/${mockTokenOut.address.toLowerCase()}`; + const expiredRoute = { + route: [ + { + tokenIn: mockTokenIn.address as `0x${string}`, + exactAmountIn: mockSwapAmount, + minAmountOut: 2000000000n, + steps: [ + { + pool: "0xoldpool" as `0x${string}`, + tokenOut: mockTokenOut.address as `0x${string}`, + isBuffer: false, + }, + ], + }, + ], + altRoutes: [], + validUntil: Date.now() - 1000, // Expired 1 second ago + price: 2000000000000000000000n, + }; + router.cache.set(cacheKey, expiredRoute); + + const result = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + expect(mockBalancerApi.sorSwapPaths.fetchSorSwapPaths).toHaveBeenCalledTimes(1); + expect(result.value.route[0].minAmountOut).toBe(3000000000n); // New route data + expect(result.value.route[0].steps[0].pool).toBe("0xpool1"); // New pool + }); + + it("should return error when fetchSortedRoutes fails", async () => { + const mockError = new Error("Network error"); + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockRejectedValue(mockError); + + const result = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.FetchFailed); + expect(error.cause).toBe(mockError); + }); + + it("should return error when no routes found", async () => { + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue([]); + + const result = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.NoRouteFound); + expect(error.message).toBe("Found no balancer route for given token pair"); + }); + + it("should calculate price correctly for multiple routes", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount / 2n, // 0.5 WETH + outputAmountRaw: 1500000000n, // 1500 USDC + isBuffer: [false], + }, + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: "0xintermediatetoken", decimals: 6 }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool2", "0xpool3"], + inputAmountRaw: mockSwapAmount / 2n, // 0.5 WETH + outputAmountRaw: 1600000000n, // 1600 USDC + isBuffer: [false, false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + + const result = await router.getBestRoute({ + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }); + + assert(result.isOk()); + const cachedRoute = result.value; + + // Should use the first route only + expect(cachedRoute.route[0].minAmountOut).toBe(3100000000n); // 1500 + 1600 = 3100 USDC total + expect(cachedRoute.route[0].exactAmountIn).toBe(mockSwapAmount); + + // Price should be calculated based on total output + const expectedAmountOut18 = 3100000000000000000000n; // 3100 * 10^18 (scaled to 18 decimals) + const expectedAmountIn18 = 1000000000000000000n; // 1 ETH in 18 decimals + const expectedPrice = (expectedAmountOut18 * 1000000000000000000n) / expectedAmountIn18; + expect(cachedRoute.price).toBe(expectedPrice); + }); + }); + + describe("test tryQuote method", () => { + let router: BalancerRouter; + let mockBalancerApi: { sorSwapPaths: { fetchSorSwapPaths: Mock } }; + let mockSigner: any; + + const mockTokenIn: TokenDetails = { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + symbol: "WETH", + }; + + const mockTokenOut: TokenDetails = { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + symbol: "USDC", + }; + + const mockSwapAmount = 1000000000000000000n; // 1 WETH + + beforeEach(() => { + const routerResult = BalancerRouter.init(1); // Ethereum mainnet + if (routerResult.isOk()) { + router = routerResult.value; + router.cache.clear(); // clear cache before each test + mockBalancerApi = (router as any).balancerApi; + } + + // mock signer + mockSigner = { + account: { + address: "0x1234567890123456789012345678901234567890" as `0x${string}`, + }, + simulateContract: vi.fn(), + }; + }); + + it("should successfully get market price with onchain simulation", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, // 3000 USDC + isBuffer: [false], + }, + ]; + + const mockSimulationResult = { + result: [null, null, [3100000000n]], // querySwapExactIn returns [,,[amountOut]] + }; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + mockSigner.simulateContract.mockResolvedValue(mockSimulationResult); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isOk()); + const marketPrice = result.value; + expect(marketPrice.amountOut).toBe(3100000000n); + expect(marketPrice.price).toBeGreaterThan(0n); + expect(marketPrice.route).toHaveLength(1); + expect(marketPrice.route[0].tokenIn).toBe(mockTokenIn.address); + expect(marketPrice.route[0].steps[0].pool).toBe("0xpool1"); + + // verify simulation was called with correct parameters + expect(mockSigner.simulateContract).toHaveBeenCalledWith({ + address: router.routerAddress, + abi: expect.any(Array), + functionName: "querySwapExactIn", + args: [expect.any(Array), mockSigner.account.address, "0x"], + }); + }); + + it("should return cached onchain price when available", async () => { + const onchainPrice = 3200000000000000000000n; + const cachedRoute = { + route: [ + { + tokenIn: mockTokenIn.address as `0x${string}`, + exactAmountIn: mockSwapAmount, + minAmountOut: 3000000000n, + steps: [ + { + pool: "0xpool1" as `0x${string}`, + tokenOut: mockTokenOut.address as `0x${string}`, + isBuffer: false, + }, + ], + }, + ], + altRoutes: [], + validUntil: Date.now() + 60000, + price: 3000000000000000000000n, + onchainPrice, + }; + + // manually add cached route with onchain price + const cacheKey = `${mockTokenIn.address.toLowerCase()}/${mockTokenOut.address.toLowerCase()}`; + router.cache.set(cacheKey, cachedRoute); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isOk()); + const marketPrice = result.value; + expect(marketPrice.price).toBe(onchainPrice); + expect(marketPrice.amountOut).toBe(3000000000n); + expect(marketPrice.route).toEqual(cachedRoute.route); + + // Should not call simulateContract when onchainPrice is cached + expect(mockSigner.simulateContract).not.toHaveBeenCalled(); + }); + + it("should use default address when signer has no account", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + const mockSimulationResult = { + result: [null, null, [3100000000n]], + }; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + mockSigner.simulateContract.mockResolvedValue(mockSimulationResult); + mockSigner.account = undefined; // No account + + const customAddress = "0x9999999999999999999999999999999999999999" as `0x${string}`; + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + customAddress, + ); + + assert(result.isOk()); + expect(mockSigner.simulateContract).toHaveBeenCalledWith({ + address: router.routerAddress, + abi: expect.any(Array), + functionName: "querySwapExactIn", + args: [expect.any(Array), customAddress, "0x"], + }); + }); + + it("should fallback to cached price when simulation fails", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + mockSigner.simulateContract.mockResolvedValue({ + result: [null, null, []], // Invalid simulation result + }); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isOk()); + const marketPrice = result.value; + // Should fallback to cached route price and amount + expect(marketPrice.amountOut).toBe(3000000000n); + expect(marketPrice.price).toBeGreaterThan(0n); + expect(marketPrice.route).toHaveLength(1); + }); + + it("should return error when getBestRoute fails", async () => { + const mockError = new Error("Network error"); + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockRejectedValue(mockError); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.FetchFailed); + expect(error.cause).toBe(mockError); + }); + + it("should return SwapQueryFailed error when simulation throws", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + const simulationError = new Error("Simulation failed"); + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + mockSigner.simulateContract.mockRejectedValue(simulationError); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isErr()); + const error = result.error; + expect(error).toBeInstanceOf(BalancerRouterError); + expect(error.type).toBe(BalancerRouterErrorType.SwapQueryFailed); + expect(error.message).toBe( + "Swap query execution failed for the given route to get market price", + ); + expect(error.cause).toBe(simulationError); + }); + + it("should update cache with onchain price after successful simulation", async () => { + const mockSorPaths = [ + { + tokens: [ + { address: mockTokenIn.address, decimals: mockTokenIn.decimals }, + { address: mockTokenOut.address, decimals: mockTokenOut.decimals }, + ], + pools: ["0xpool1"], + inputAmountRaw: mockSwapAmount, + outputAmountRaw: 3000000000n, + isBuffer: [false], + }, + ]; + + const mockSimulationResult = { + result: [null, null, [3100000000n]], + }; + + mockBalancerApi.sorSwapPaths.fetchSorSwapPaths.mockResolvedValue(mockSorPaths); + mockSigner.simulateContract.mockResolvedValue(mockSimulationResult); + + const result = await router.tryQuote( + { + tokenIn: mockTokenIn, + tokenOut: mockTokenOut, + swapAmount: mockSwapAmount, + }, + mockSigner, + ); + + assert(result.isOk()); + + // verify that onchain price was cached + const cacheKey = `${mockTokenIn.address.toLowerCase()}/${mockTokenOut.address.toLowerCase()}`; + const cachedRoute = router.cache.get(cacheKey); + expect(cachedRoute?.onchainPrice).toBeDefined(); + expect(cachedRoute?.onchainPrice).toBeGreaterThan(0n); + }); + }); + + describe("test visualizeRoute method", () => { + const mockTokenDetails = new Map([ + [ + "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b", + { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + symbol: "WETH", + }, + ], + [ + "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b", + { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + symbol: "USDC", + }, + ], + [ + "0xa2b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b", + { + address: "0xa2b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + symbol: "DAI", + }, + ], + ]); + + it("should visualize multiple routes with correct percentages", () => { + const route: BalancerRouterPath[] = [ + { + tokenIn: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + exactAmountIn: 700000000000000000n, // 0.7 WETH (70%) + minAmountOut: 2100000000n, // 2100 USDC + steps: [ + { + pool: "0xpool1" as `0x${string}`, + tokenOut: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + isBuffer: false, + }, + ], + }, + { + tokenIn: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + exactAmountIn: 300000000000000000n, // 0.3 WETH (30%) + minAmountOut: 900000000n, // 900 USDC + steps: [ + { + pool: "0xpool2" as `0x${string}`, + tokenOut: "0xa2b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, // DAI + isBuffer: true, + }, + { + pool: "0xpool3" as `0x${string}`, + tokenOut: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, // USDC + isBuffer: false, + }, + ], + }, + ]; + + const result = BalancerRouter.visualizeRoute(route, mockTokenDetails); + + expect(result).toHaveLength(2); + expect(result[0]).toBe("70.00% ---> USDC/WETH (pool 0xpool1)"); + expect(result[1]).toBe( + "30.00% ---> DAI/WETH (pool 0xpool2) >> USDC/DAI (pool 0xpool3)", + ); + }); + + it("should handle unknown tokens gracefully", () => { + const route: BalancerRouterPath[] = [ + { + tokenIn: "0xunknowntoken1" as `0x${string}`, + exactAmountIn: 1000000000000000000n, + minAmountOut: 3000000000n, + steps: [ + { + pool: "0xpool1" as `0x${string}`, + tokenOut: "0xunknowntoken2" as `0x${string}`, + isBuffer: false, + }, + ], + }, + ]; + + const result = BalancerRouter.visualizeRoute(route, mockTokenDetails); + + expect(result).toHaveLength(1); + expect(result[0]).toBe("100.00% ---> unknownSymbol/unknownSymbol (pool 0xpool1)"); + }); + }); + + describe("test convertToRoutePaths method", () => { + it("should convert single path to BalancerRouterPath correctly", () => { + const mockPaths = [ + { + tokens: [ + { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + }, + { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + }, + ], + pools: ["0xpool1" as `0x${string}`], + inputAmountRaw: 1000000000000000000n, // 1 WETH + outputAmountRaw: 3000000000n, // 3000 USDC + isBuffer: [false], + protocolVersion: 3 as const, + }, + ]; + + const result = BalancerRouter.convertToRoutePaths(mockPaths); + + expect(result).toHaveLength(1); + expect(result[0].tokenIn).toBe("0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b"); + expect(result[0].exactAmountIn).toBe(1000000000000000000n); + expect(result[0].minAmountOut).toBe(3000000000n); + expect(result[0].steps).toHaveLength(1); + expect(result[0].steps[0].pool).toBe("0xpool1"); + expect(result[0].steps[0].tokenOut).toBe("0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b"); + expect(result[0].steps[0].isBuffer).toBe(false); + }); + + it("should convert multi-hop path to BalancerRouterPath correctly", () => { + const mockPaths = [ + { + tokens: [ + { + address: "0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + }, // WETH + { + address: "0xa2b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 18, + }, // DAI + { + address: "0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b" as `0x${string}`, + decimals: 6, + }, // USDC + ], + pools: ["0xpool1" as `0x${string}`, "0xpool2" as `0x${string}`], + inputAmountRaw: 1000000000000000000n, // 1 WETH + outputAmountRaw: 3000000000n, // 3000 USDC + isBuffer: [false, true], // First pool normal, second is buffer + protocolVersion: 3 as const, + }, + ]; + + const result = BalancerRouter.convertToRoutePaths(mockPaths); + + expect(result).toHaveLength(1); + expect(result[0].tokenIn).toBe("0xa0b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b"); + expect(result[0].exactAmountIn).toBe(1000000000000000000n); + expect(result[0].minAmountOut).toBe(3000000000n); + expect(result[0].steps).toHaveLength(2); + + // First step: WETH -> DAI + expect(result[0].steps[0].pool).toBe("0xpool1"); + expect(result[0].steps[0].tokenOut).toBe("0xa2b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b"); + expect(result[0].steps[0].isBuffer).toBe(false); + + // Second step: DAI -> USDC + expect(result[0].steps[1].pool).toBe("0xpool2"); + expect(result[0].steps[1].tokenOut).toBe("0xa1b86a33e6c0c536b5e9f9de9c2c4b6d5e9c2c4b"); + expect(result[0].steps[1].isBuffer).toBe(true); + }); + }); +}); diff --git a/src/router/balancer.ts b/src/router/balancer.ts new file mode 100644 index 00000000..775ed028 --- /dev/null +++ b/src/router/balancer.ts @@ -0,0 +1,405 @@ +import { PublicClient } from "viem"; +import { ABI, Result } from "../common"; +import { TokenDetails } from "../state"; +import { ONE18, scaleTo18 } from "../math"; +import { RainSolverSigner } from "../signer"; +import { RainSolverBaseError } from "../error"; +import { Token as SushiToken } from "sushi/currency"; +import { + Path, + Token, + ChainId, + SwapKind, + TokenAmount, + BalancerApi, + PathWithAmount, + AddressProvider, + balancerV3Contracts, +} from "@balancer/sdk"; + +// Balancer API URL for fetching routes +export const BALANCER_API_URL = "https://api-v3.balancer.fi/" as const; + +/** Represents a single step in a Balancer route */ +export type BalancerRouterStep = { + pool: `0x${string}`; + tokenOut: `0x${string}`; + isBuffer: boolean; +}; + +/** Represents the path details for a Balancer route */ +export type BalancerRouterPath = { + tokenIn: `0x${string}`; + exactAmountIn: bigint; + minAmountOut: bigint; + steps: BalancerRouterStep[]; +}; + +/** Represents the parameters for fetching Balancer routes from balancer API */ +export type FetchBalancerRoutesParams = { + tokenIn: TokenDetails | SushiToken; + tokenOut: TokenDetails | SushiToken; + swapAmount: bigint; +}; + +/** Represents the parameters for quoting Balancer */ +export type BalancerQuoteParams = { + tokenIn: TokenDetails | SushiToken; + tokenOut: TokenDetails | SushiToken; + swapAmount: bigint; + ignoreCache?: boolean; +}; + +/** Represents a cached Balancer route */ +export type BalancerCachedRoute = { + /** The route paths details used for getting calldata */ + route: BalancerRouterPath[]; + /** The alternative route paths details */ + altRoutes: BalancerRouterPath[]; + /** The timestamp until which the route is valid */ + validUntil: number; + /** The price of the route from balancer API */ + price: bigint; + /** + * The on-chain price of the route, this is usually equal to the + * price or pretty close, as the price might sometimes lag slightly + * due to subgraph lags + */ + onchainPrice?: bigint; +}; + +/** Enumerates the possible error types that can occur within the Balancer Router functionalities */ +export enum BalancerRouterErrorType { + UnsupportedChain, + NoRouteFound, + FetchFailed, + SwapQueryFailed, +} + +/** + * Represents an error type for the Balancer Router functionalities. + * This error class extends the `RainSolverBaseError` error class, with the `type` + * property indicates the specific category of the error, as defined by the + * `BalancerRouterErrorType` enum. The optional `cause` property can be used to + * attach the original error or any relevant context that led to this error. + * + * @example + * ```typescript + * // without cause + * throw new BalancerRouterError("msg", BalancerRouterErrorType); + * + * // with cause + * throw new BalancerRouterError("msg", BalancerRouterErrorType, originalError); + * ``` + */ +export class BalancerRouterError extends RainSolverBaseError { + type: BalancerRouterErrorType; + constructor(message: string, type: BalancerRouterErrorType, cause?: any) { + super(message, cause); + this.type = type; + this.name = "BalancerRouterError"; + } +} + +/** + * The Balancer Router class provides methods to interact with the Balancer protocol, + * including fetching routes from balancer API, getting best route and market price as well + * as managing runtime cache for routes. + */ +export class BalancerRouter { + /** The shared state of the router */ + readonly chainId: number; + /** The address of the router contract */ + readonly routerAddress: `0x${string}`; + /** The protocol version of the balancer */ + readonly protocolVersion = 3 as const; + /** The duration for which a route is considered valid in cache */ + readonly routeTime = 300_000 as const; // 5 minutes in milliseconds + /** The Balancer API instance */ + readonly balancerApi: BalancerApi; + /** The cache for storing previously fetched routes that are valid until specified timestamp */ + readonly cache: Map = new Map(); + + private constructor(chainId: number) { + this.chainId = chainId; + this.routerAddress = AddressProvider.BatchRouter(this.chainId); + this.balancerApi = new BalancerApi(BALANCER_API_URL, this.chainId); + } + + /** + * Initializes the Balancer Router instance. + * @param chainId The chain id of the opperating chain + * @returns A Result containing the Balancer Router instance or an error + */ + static init(chainId: number): Result { + if ( + !balancerV3Contracts.BatchRouter[ + chainId as keyof typeof balancerV3Contracts.BatchRouter + ] + ) { + return Result.err( + new BalancerRouterError( + `Balancer router does not support chain with id: ${chainId}`, + BalancerRouterErrorType.UnsupportedChain, + ), + ); + } + return Result.ok(new BalancerRouter(chainId)); + } + + /** + * Gets the balancer market quote for a token pair by simulating balancer swap query + * @param params The parameters for getting the best Balancer market quote + * @param signer The signer instance + */ + async tryQuote( + params: BalancerQuoteParams, + signer: PublicClient | RainSolverSigner, + address: `0x${string}` = `0x${"1".repeat(40)}`, + ): Promise< + Result< + { price: bigint; route: BalancerRouterPath[]; amountOut: bigint }, + BalancerRouterError + > + > { + try { + const route = await this.getBestRoute(params); + if (route.isErr()) { + return Result.err(route.error); + } + if (typeof route.value.onchainPrice === "bigint") { + return Result.ok({ + price: route.value.onchainPrice, + route: route.value.route, + amountOut: route.value.route[0].minAmountOut, + }); + } + + // fallback to alt routes if the best one fails + const { result, pickedRoute } = await (async () => { + let err: any = undefined; + const allRoutes = [...route.value.route, ...route.value.altRoutes]; + for (let i = 0; i < allRoutes.length + 1; i++) { + try { + const result = await signer.simulateContract({ + address: this.routerAddress, + abi: ABI.BalancerBatchRouter.Primary.BatchRouterV3, + functionName: "querySwapExactIn", + args: [[allRoutes[i]], signer.account?.address ?? address, "0x"], + }); + if (i > 0) { + // swap the routes so that next time the working one is tried first + [route.value.route, route.value.altRoutes[i - 1]] = [ + [allRoutes[i]], + route.value.route[0], + ]; + // move the failed alt route to the end of alt routes + route.value.altRoutes.push(...route.value.altRoutes.splice(i - 1, 1)); + } + return { + result, + pickedRoute: allRoutes[i], + }; + } catch (error) { + if (!err) err = error; + } + } + throw err; + })(); + + const [price, amountOut] = (() => { + try { + const amountOut = result.result[2][0]; + const amountOut18 = scaleTo18(amountOut, params.tokenOut.decimals); + const amountIn18 = scaleTo18(params.swapAmount, params.tokenIn.decimals); + const onchainPrice = (amountOut18 * ONE18) / amountIn18; + route.value.onchainPrice = onchainPrice; + return [onchainPrice, amountOut]; + } catch { + return [route.value.price, route.value.route[0].minAmountOut]; + } + })(); + + return Result.ok({ price, route: [pickedRoute], amountOut }); + } catch (error: any) { + return Result.err( + new BalancerRouterError( + "Swap query execution failed for the given route to get market price", + BalancerRouterErrorType.SwapQueryFailed, + error, + ), + ); + } + } + + /** + * Gets the best Balancer route for a given token pair and swap amount + * @param params The parameters for getting the best Balancer route + */ + async getBestRoute( + params: BalancerQuoteParams, + ): Promise> { + const key = `${params.tokenIn.address.toLowerCase()}/${params.tokenOut.address.toLowerCase()}`; + + // search in cache first and return early if ignoreCache is false + if (!params.ignoreCache) { + const cachedRoute = this.cache.get(key); + if (cachedRoute && cachedRoute.validUntil > Date.now()) { + return Result.ok(cachedRoute); + } + } + + const balancerSortedRoutes = await this.fetchSortedRoutes(params); + if (balancerSortedRoutes.isErr()) { + return Result.err(balancerSortedRoutes.error); + } + + let minAmountOut = 0n; + balancerSortedRoutes.value.forEach((route) => { + minAmountOut += route.minAmountOut; + }); + const amountOut18 = scaleTo18(minAmountOut, params.tokenOut.decimals); + const amountIn18 = scaleTo18(params.swapAmount, params.tokenIn.decimals); + const price = (amountOut18 * ONE18) / amountIn18; + const route = { + route: [ + { + ...balancerSortedRoutes.value[0], + exactAmountIn: params.swapAmount, + minAmountOut, + }, + ], + altRoutes: balancerSortedRoutes.value.slice(1).map((r) => ({ + ...r, + exactAmountIn: params.swapAmount, + minAmountOut, + })), + validUntil: Date.now() + this.routeTime, + price, + }; + this.cache.set(key, route); // store in cache + return Result.ok(route); + } + + /** + * Fetches the best Balancer route for a given token pair and swap amount. + * This method uses the Balancer API to find the best swap paths from available liquidity. + * @param params The parameters for fetching the best Balancer price + * @returns A Result containing the best Balancer route or an error + */ + async fetchSortedRoutes( + params: FetchBalancerRoutesParams, + ): Promise> { + try { + // prepare params + const { tokenIn: _tokenIn, tokenOut: _tokenOut, swapAmount: _swapAmount } = params; + const tokenIn = new Token( + this.chainId, + _tokenIn.address as `0x${string}`, + _tokenIn.decimals, + _tokenIn.symbol ?? "unknownSymbol", + ); + const tokenOut = new Token( + this.chainId, + _tokenOut.address as `0x${string}`, + _tokenOut.decimals, + _tokenOut.symbol ?? "unknownSymbol", + ); + const swapAmount = TokenAmount.fromRawAmount(tokenIn, _swapAmount); + const swapKind = SwapKind.GivenIn; + + // fetch the sorted routes from the Balancer API + const sorPaths = await this.balancerApi.sorSwapPaths.fetchSorSwapPaths({ + chainId: this.chainId, + tokenIn: tokenIn.address.toLowerCase() as `0x${string}`, + tokenOut: tokenOut.address.toLowerCase() as `0x${string}`, + swapKind, + swapAmount, + useProtocolVersion: this.protocolVersion, + }); + + // return err if no route was found + if (sorPaths.length === 0) { + return Result.err( + new BalancerRouterError( + "Found no balancer route for given token pair", + BalancerRouterErrorType.NoRouteFound, + ), + ); + } + + return Result.ok(BalancerRouter.convertToRoutePaths(sorPaths)); + } catch (error) { + return Result.err( + new BalancerRouterError( + "Failed to fetch balancer routes", + BalancerRouterErrorType.FetchFailed, + error, + ), + ); + } + } + + /** + * Method to visualize the routes, returns an array of portioned route paths strings + * @param route: The Balancer route to visualize + * @param cachedTokenDetails: A map of token addresses to their details for symbol lookup + */ + static visualizeRoute( + route: BalancerRouterPath[], + cachedTokenDetails: Map, + ): string[] { + let totalIn = 0n; + route.forEach((path) => (totalIn += path.exactAmountIn)); + return route.map( + (path) => + (Number((path.exactAmountIn * 10_000n) / totalIn) / 100).toFixed(2) + + "% ---> " + + path.steps + .map((step, i) => { + const tokenIn = i === 0 ? path.tokenIn : path.steps[i - 1].tokenOut; + const tokenOutSymbol = + cachedTokenDetails.get(step.tokenOut.toLowerCase())?.symbol ?? + "unknownSymbol"; + const tokenInSymbol = + cachedTokenDetails.get(tokenIn.toLowerCase())?.symbol ?? + "unknownSymbol"; + return tokenOutSymbol + "/" + tokenInSymbol + " (pool " + step.pool + ")"; + }) + .join(" >> "), + ); + } + + /** + * Converts an array of paths to BalancerRouterPath objects used for balancer router swap calldata + * @param paths The paths to convert + */ + static convertToRoutePaths(paths: Path[]): BalancerRouterPath[] { + const pathsWithAmounts = paths.map( + (p) => + new PathWithAmount( + ChainId.MAINNET, + p.tokens, + p.pools, + p.inputAmountRaw, + p.outputAmountRaw, + p.isBuffer, + ), + ); + const swaps = pathsWithAmounts.map((p) => { + return { + tokenIn: p.inputAmount.token.address, + exactAmountIn: p.inputAmount.amount, + minAmountOut: p.outputAmount.amount, + steps: p.pools.map((pool, i) => { + return { + pool, + tokenOut: p.tokens[i + 1].address, + isBuffer: p.isBuffer[i], + }; + }), + }; + }); + return swaps; + } +} diff --git a/src/router/marketPrice.test.ts b/src/router/marketPrice.test.ts index 65db234f..8ad45860 100644 --- a/src/router/marketPrice.test.ts +++ b/src/router/marketPrice.test.ts @@ -60,10 +60,7 @@ describe("Test getMarketPrice", () => { describe("happy", () => { it("should return 1 if from/to tokens are the same", async () => { const result = await getMarketPrice.call(mockSharedState, mockFromToken, mockFromToken); - expect(result).toEqual({ - price: "1", - amountOut: "1", - }); + expect(result).toEqual({ price: "1" }); }); it("should call dataFetcher methods with correct parameters", async () => { @@ -115,10 +112,7 @@ describe("Test getMarketPrice", () => { const result = await getMarketPrice.call(mockSharedState, mockFromToken, mockToToken); expect(result).toHaveProperty("price"); - expect(result).toHaveProperty("amountOut"); expect(typeof result?.price).toBe("string"); - expect(typeof result?.amountOut).toBe("string"); - expect(result?.amountOut).toBe("2000"); expect(result?.price).toBe("2000"); }); diff --git a/src/router/marketPrice.ts b/src/router/marketPrice.ts index 1a27515b..364c4a56 100644 --- a/src/router/marketPrice.ts +++ b/src/router/marketPrice.ts @@ -1,7 +1,7 @@ +import { ONE18 } from "../math"; import { SharedState } from "../state"; import { Token } from "sushi/currency"; import { ChainId, Router } from "sushi"; -import { scaleTo18, ONE18 } from "../math"; import { PoolBlackList, RPoolFilter } from "."; import { formatUnits, parseUnits, maxUint256 } from "viem"; @@ -16,13 +16,10 @@ export async function getMarketPrice( fromToken: Token, toToken: Token, blockNumber?: bigint, -): Promise<{ price: string; amountOut: string } | undefined> { +): Promise<{ price: string } | undefined> { // return early if from and to tokens are the same if (fromToken.address.toLowerCase() === toToken.address.toLowerCase()) { - return { - price: "1", - amountOut: "1", - }; + return { price: "1" }; } const amountIn = parseUnits("1", fromToken.decimals); @@ -42,13 +39,22 @@ export async function getMarketPrice( RPoolFilter, ); if (route.status == "NoWay") { + // try balancer + if (this.balancerRouter) { + const balancerRouteResult = await this.balancerRouter.getBestRoute({ + tokenIn: fromToken, + tokenOut: toToken, + swapAmount: amountIn, + }); + if (balancerRouteResult.isOk()) { + const price = + balancerRouteResult.value.onchainPrice ?? balancerRouteResult.value.price; + return { price: formatUnits(price, 18) }; + } + } return; } else { - const price = scaleTo18(route.amountOutBI, toToken.decimals); - return { - price: formatUnits(price, 18), - amountOut: formatUnits(route.amountOutBI, toToken.decimals), - }; + return { price: formatUnits(route.amountOutBI, toToken.decimals) }; } } catch (error) { return; diff --git a/src/state/index.test.ts b/src/state/index.test.ts index 04bc5e39..8cb5eb18 100644 --- a/src/state/index.test.ts +++ b/src/state/index.test.ts @@ -74,6 +74,7 @@ describe("Test SharedStateConfig tryFromAppOptions", () => { expect(config.initL1GasPrice).toBe(0n); expect(config.transactionGas).toBe("120%"); expect(config.rainSolverTransportConfig).toMatchObject({ timeout: 1000 }); + expect(config.balancerRouter).toBeDefined(); }); it("should throw if iInterpreter contract call fails", async () => { diff --git a/src/state/index.ts b/src/state/index.ts index d929ca11..343705fa 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -13,6 +13,7 @@ import { createPublicClient, PublicClient } from "viem"; import { LiquidityProviders, RainDataFetcher } from "sushi"; import { getMarketPrice, processLiquidityProviders } from "../router"; import { rainSolverTransport, RainSolverTransportConfig } from "../rpc"; +import { BalancerRouter } from "../router/balancer"; /** * Rain dispair contracts, deployer, store and interpreter @@ -65,6 +66,8 @@ export type SharedStateConfig = { rainSolverTransportConfig?: RainSolverTransportConfig; /** RainDataFetcher instance */ dataFetcher: RainDataFetcher; + /** Balancer router instance */ + balancerRouter?: BalancerRouter; }; export namespace SharedStateConfig { export async function tryFromAppOptions(options: AppOptions): Promise { @@ -110,6 +113,13 @@ export namespace SharedStateConfig { client, liquidityProviders, ); + + const balancerRouter = (() => { + const res = BalancerRouter.init(chainId); + if (res.isOk()) return res.value; + else return undefined; + })(); + const config: SharedStateConfig = { client, rpcState, @@ -128,6 +138,7 @@ export namespace SharedStateConfig { store, deployer: options.dispair, }, + balancerRouter, }; // try to get init gas price @@ -177,6 +188,8 @@ export class SharedState { readonly transactionGas?: string; /** RainSolver transport configuration */ readonly rainSolverTransportConfig?: RainSolverTransportConfig; + /** Balancer router instance */ + readonly balancerRouter?: BalancerRouter; /** Current gas price of the operating chain */ gasPrice = 0n; @@ -204,6 +217,7 @@ export class SharedState { this.rpc = config.rpcState; this.writeRpc = config.writeRpcState; this.dataFetcher = config.dataFetcher; + this.balancerRouter = config.balancerRouter; if (typeof config.gasPriceMultiplier === "number") { this.gasPriceMultiplier = config.gasPriceMultiplier; } diff --git a/test/abis/BalancerRouterOrderBookV4ArbOrderTaker.json b/test/abis/BalancerRouterOrderBookV4ArbOrderTaker.json new file mode 100644 index 00000000..50d33eeb --- /dev/null +++ b/test/abis/BalancerRouterOrderBookV4ArbOrderTaker.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"config","type":"tuple","internalType":"struct OrderBookV4ArbConfigV2","components":[{"name":"orderBook","type":"address","internalType":"address"},{"name":"task","type":"tuple","internalType":"struct TaskV1","components":[{"name":"evaluable","type":"tuple","internalType":"struct EvaluableV3","components":[{"name":"interpreter","type":"address","internalType":"contract IInterpreterV3"},{"name":"store","type":"address","internalType":"contract IInterpreterStoreV2"},{"name":"bytecode","type":"bytes","internalType":"bytes"}]},{"name":"signedContext","type":"tuple[]","internalType":"struct SignedContextV1[]","components":[{"name":"signer","type":"address","internalType":"address"},{"name":"context","type":"uint256[]","internalType":"uint256[]"},{"name":"signature","type":"bytes","internalType":"bytes"}]}]},{"name":"implementationData","type":"bytes","internalType":"bytes"}]}],"stateMutability":"nonpayable"},{"type":"fallback","stateMutability":"nonpayable"},{"type":"function","name":"arb3","inputs":[{"name":"orderBook","type":"address","internalType":"contract IOrderBookV4"},{"name":"takeOrders","type":"tuple","internalType":"struct TakeOrdersConfigV3","components":[{"name":"minimumInput","type":"uint256","internalType":"uint256"},{"name":"maximumInput","type":"uint256","internalType":"uint256"},{"name":"maximumIORatio","type":"uint256","internalType":"uint256"},{"name":"orders","type":"tuple[]","internalType":"struct TakeOrderConfigV3[]","components":[{"name":"order","type":"tuple","internalType":"struct OrderV3","components":[{"name":"owner","type":"address","internalType":"address"},{"name":"evaluable","type":"tuple","internalType":"struct EvaluableV3","components":[{"name":"interpreter","type":"address","internalType":"contract IInterpreterV3"},{"name":"store","type":"address","internalType":"contract IInterpreterStoreV2"},{"name":"bytecode","type":"bytes","internalType":"bytes"}]},{"name":"validInputs","type":"tuple[]","internalType":"struct IO[]","components":[{"name":"token","type":"address","internalType":"address"},{"name":"decimals","type":"uint8","internalType":"uint8"},{"name":"vaultId","type":"uint256","internalType":"uint256"}]},{"name":"validOutputs","type":"tuple[]","internalType":"struct IO[]","components":[{"name":"token","type":"address","internalType":"address"},{"name":"decimals","type":"uint8","internalType":"uint8"},{"name":"vaultId","type":"uint256","internalType":"uint256"}]},{"name":"nonce","type":"bytes32","internalType":"bytes32"}]},{"name":"inputIOIndex","type":"uint256","internalType":"uint256"},{"name":"outputIOIndex","type":"uint256","internalType":"uint256"},{"name":"signedContext","type":"tuple[]","internalType":"struct SignedContextV1[]","components":[{"name":"signer","type":"address","internalType":"address"},{"name":"context","type":"uint256[]","internalType":"uint256[]"},{"name":"signature","type":"bytes","internalType":"bytes"}]}]},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"task","type":"tuple","internalType":"struct TaskV1","components":[{"name":"evaluable","type":"tuple","internalType":"struct EvaluableV3","components":[{"name":"interpreter","type":"address","internalType":"contract IInterpreterV3"},{"name":"store","type":"address","internalType":"contract IInterpreterStoreV2"},{"name":"bytecode","type":"bytes","internalType":"bytes"}]},{"name":"signedContext","type":"tuple[]","internalType":"struct SignedContextV1[]","components":[{"name":"signer","type":"address","internalType":"address"},{"name":"context","type":"uint256[]","internalType":"uint256[]"},{"name":"signature","type":"bytes","internalType":"bytes"}]}]}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"iPermit2","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IPermit2"}],"stateMutability":"view"},{"type":"function","name":"iTaskHash","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"onTakeOrders","inputs":[{"name":"inputToken","type":"address","internalType":"address"},{"name":"outputToken","type":"address","internalType":"address"},{"name":"inputAmountSent","type":"uint256","internalType":"uint256"},{"name":"totalOutputAmount","type":"uint256","internalType":"uint256"},{"name":"takeOrdersData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"event","name":"Construct","inputs":[{"name":"sender","type":"address","indexed":false,"internalType":"address"},{"name":"config","type":"tuple","indexed":false,"internalType":"struct OrderBookV4ArbConfigV2","components":[{"name":"orderBook","type":"address","internalType":"address"},{"name":"task","type":"tuple","internalType":"struct TaskV1","components":[{"name":"evaluable","type":"tuple","internalType":"struct EvaluableV3","components":[{"name":"interpreter","type":"address","internalType":"contract IInterpreterV3"},{"name":"store","type":"address","internalType":"contract IInterpreterStoreV2"},{"name":"bytecode","type":"bytes","internalType":"bytes"}]},{"name":"signedContext","type":"tuple[]","internalType":"struct SignedContextV1[]","components":[{"name":"signer","type":"address","internalType":"address"},{"name":"context","type":"uint256[]","internalType":"uint256[]"},{"name":"signature","type":"bytes","internalType":"bytes"}]}]},{"name":"implementationData","type":"bytes","internalType":"bytes"}]}],"anonymous":false},{"type":"error","name":"InvalidSignature","inputs":[{"name":"i","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"NoOrders","inputs":[]},{"type":"error","name":"WrongTask","inputs":[]}],"bytecode":{"object":"0x60c060405260006080526e22d473030f116ddee9f6b43ac78ba360a05234801561002857600080fd5b50604051613a1a380380613a1a83398101604081905261004791610381565b808060016000819055507f584f220334313e589a42a75ac60d4fa6ed5b02329a7255af822a0e4ac9cd5f1d33826040516100829291906105fa565b60405180910390a16020810151516040015151156100c9576020808201516040516100ad920161065a565b60408051601f1981840301815291905280516020909101206080525b505050610674565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b0381118282101715610109576101096100d1565b60405290565b604080519081016001600160401b0381118282101715610109576101096100d1565b604051601f8201601f191681016001600160401b0381118282101715610159576101596100d1565b604052919050565b6001600160a01b038116811461017657600080fd5b50565b60005b8381101561019457818101518382015260200161017c565b50506000910152565b600082601f8301126101ae57600080fd5b81516001600160401b038111156101c7576101c76100d1565b6101da601f8201601f1916602001610131565b8181528460208386010111156101ef57600080fd5b610200826020830160208701610179565b949350505050565b60006001600160401b03821115610221576102216100d1565b5060051b60200190565b600082601f83011261023c57600080fd5b8151602061025161024c83610208565b610131565b82815260059290921b8401810191818101908684111561027057600080fd5b8286015b848110156103765780516001600160401b038082111561029357600080fd5b908801906060828b03601f190112156102ac5760008081fd5b6102b46100e7565b868301516102c181610161565b8152604083810151838111156102d75760008081fd5b8401603f81018d136102e95760008081fd5b888101516102f961024c82610208565b81815260059190911b82018301908a8101908f8311156103195760008081fd5b928401925b828410156103375783518252928b0192908b019061031e565b858c01525050506060840151838111156103515760008081fd5b61035f8d8a8388010161019d565b918301919091525085525050918301918301610274565b509695505050505050565b6000602080838503121561039457600080fd5b82516001600160401b03808211156103ab57600080fd5b90840190606082870312156103bf57600080fd5b6103c76100e7565b82516103d281610161565b815282840151828111156103e557600080fd5b8301604081890312156103f757600080fd5b6103ff61010f565b81518481111561040e57600080fd5b82016060818b03121561042057600080fd5b6104286100e7565b815161043381610161565b81528188015161044281610161565b8189015260408201518681111561045857600080fd5b6104648c82850161019d565b604083015250825250818601518481111561047e57600080fd5b61048a8a82850161022b565b87830152508086840152505060408301519350818411156104aa57600080fd5b6104b68785850161019d565b60408201529695505050505050565b600081518084526104dd816020860160208601610179565b601f01601f19169290920160200192915050565b6000604082516040855260018060a01b03808251166040870152602080830151606083821660608a015260408501519450606060808a015261053660a08a01866104c5565b888401518a82038b86015280518083529196508401925083860190600581901b870185016000805b838110156105e857898303601f19018552865180518a168452888101518985018890528051888601819052908a0190849060808701905b808310156105b55783518252928c019260019290920191908c0190610595565b508e84015193508681038f8801526105cd81856104c5565b9650505050508787019650878501945060018101905061055e565b50909c9b505050505050505050505050565b6001600160a01b0383811682526040602080840182905284519092169083015282015160608083015260009061063360a08401826104f1565b90506040840151603f1984830301608085015261065082826104c5565b9695505050505050565b60208152600061066d60208301846104f1565b9392505050565b60805160a05161335f6106bb6000396000818160cf0152818161029c0152818161037c01526106a001526000818161013b015281816107480152610799015261335f6000f3fe60806040526004361061005a5760003560e01c806369e5049b1161004357806369e5049b146100bd5780637ea0b76a1461011657806393be095e146101295761005a565b806301ffc9a714610068578063059bebe61461009d575b34801561006657600080fd5b005b34801561007457600080fd5b50610088610083366004611ef8565b61016b565b60405190151581526020015b60405180910390f35b3480156100a957600080fd5b506100666100b8366004611f5f565b610250565b3480156100c957600080fd5b506100f17f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610094565b610066610124366004612009565b610735565b34801561013557600080fd5b5061015d7f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610094565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f059bebe60000000000000000000000000000000000000000000000000000000014806101fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f7ea0b76a00000000000000000000000000000000000000000000000000000000145b8061024a57507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60008061025f8385018561218f565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000008116600483015260001960248301529294509092509089169063095ea7b3906044016020604051808303816000875af11580156102fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061032091906122da565b506040517f87517c4500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152838116602483015260448201819052600060648301527f000000000000000000000000000000000000000000000000000000000000000016906387517c4590608401600060405180830381600087803b1580156103c057600080fd5b505af11580156103d4573d6000803e3d6000fd5b505050508773ffffffffffffffffffffffffffffffffffffffff16816000015173ffffffffffffffffffffffffffffffffffffffff1614610476576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f496e76616c696420696e70757420746f6b656e0000000000000000000000000060448201526064015b60405180910390fd5b6020810151805173ffffffffffffffffffffffffffffffffffffffff891691906104a290600190612326565b815181106104b2576104b2612339565b60200260200101516020015173ffffffffffffffffffffffffffffffffffffffff161461053b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f496e76616c6964206f757470757420746f6b656e000000000000000000000000604482015260640161046d565b60408181018790526060820186905280516001808252818301909252839160009190816020015b6105a36040518060800160405280600073ffffffffffffffffffffffffffffffffffffffff1681526020016060815260200160008152602001600081525090565b81526020019060019003908161056257905050905082816000815181106105cc576105cc612339565b602002602001018190525060008060008473ffffffffffffffffffffffffffffffffffffffff1663286f580d8560001960006040518463ffffffff1660e01b815260040161061c93929190612368565b6000604051808303816000875af115801561063b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106639190810190612526565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081166004830152600060248301529396509194509250908e169063095ea7b3906044016020604051808303816000875af1158015610701573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072591906122da565b5050505050505050505050505050565b61073d610a9d565b610746816127c3565b7f0000000000000000000000000000000000000000000000000000000000000000158015906107bb5750806040516020016107819190612911565b604051602081830303815290604052805190602001207f000000000000000000000000000000000000000000000000000000000000000014155b156107f2576040517f8812562900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107ff60608401846129fe565b905060000361083a576040517f9c95219f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061084960608501856129fe565b600081811061085a5761085a612339565b905060200281019061086c9190612a48565b6108769080612a86565b610884906040810190612aba565b61089160608701876129fe565b60008181106108a2576108a2612339565b90506020028101906108b49190612a48565b602001358181106108c7576108c7612339565b6108dd9260206060909202019081019150612b03565b905060006108ee60608601866129fe565b60008181106108ff576108ff612339565b90506020028101906109119190612a48565b61091b9080612a86565b610929906060810190612aba565b61093660608801886129fe565b600081811061094757610947612339565b90506020028101906109599190612a48565b6040013581811061096c5761096c612339565b6109829260206060909202019081019150612b03565b90506109a673ffffffffffffffffffffffffffffffffffffffff8316876000610b10565b6109c973ffffffffffffffffffffffffffffffffffffffff831687600019610b10565b6000808773ffffffffffffffffffffffffffffffffffffffff16630997c4a0886040518263ffffffff1660e01b8152600401610a059190612dd1565b60408051808303816000875af1158015610a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a479190613035565b9092509050610a6e73ffffffffffffffffffffffffffffffffffffffff8516896000610b10565b610a81610a7a876127c3565b8585610cf2565b5050505050610a906001600055565b505050565b505050505050565b600260005403610b09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161046d565b6002600055565b801580610bb057506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff838116602483015284169063dd62ed3e90604401602060405180830381865afa158015610b8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bae9190613059565b155b610c3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e636500000000000000000000606482015260840161046d565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a909084907f095ea7b300000000000000000000000000000000000000000000000000000000906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526110ca565b604080516001808252818301909252600091816020015b6060815260200190600190039081610d09575050604080516003808252608082019092529192506000919060208201606080368337019050506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610db2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dd69190613059565b90508015610dff57610dff73ffffffffffffffffffffffffffffffffffffffff861633836111d9565b60008573ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e709190613072565b60ff169050610e818282600061122f565b83600081518110610e9457610e94612339565b602090810291909101015250506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa158015610f0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f329190613059565b90508015610f5b57610f5b73ffffffffffffffffffffffffffffffffffffffff851633836111d9565b60008473ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fcc9190613072565b60ff169050610fdd8282600061122f565b83600181518110610ff057610ff0612339565b60209081029190910101525047905061100933826112b7565b808260028151811061101d5761101d612339565b60200260200101818152505050808260008151811061103e5761103e612339565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160a0810182526000918101828152606080830193909352608082018390528152602081019190915281526020019060019003908161106057905050905085816000815181106110b5576110b5612339565b6020026020010181905250610a958382611411565b600061112c826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661160a9092919063ffffffff16565b905080516000148061114d57508080602001905181019061114d91906122da565b610a90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f74207375636365656400000000000000000000000000000000000000000000606482015260840161046d565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a909084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401610c8e565b60008260121115611264576012839003600283161561125a576112528582611621565b9150506112b0565b6112528582611672565b60128311156112ad577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee830160018316156112a35761125285826116aa565b61125285826116f8565b50825b9392505050565b80471015611321576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015260640161046d565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d806000811461137b576040519150601f19603f3d011682016040523d82523d6000602084013e611380565b606091505b5050905080610a90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d61792068617665207265766572746564000000000000606482015260840161046d565b336114446040805160a0810182526000918101828152606080830193909352608082018390528152602081019190915290565b60005b83518110156116035783818151811061146257611462612339565b6020026020010151915060008260000151604001515111156115fb578151805160209182015160008681523090935260408320839273ffffffffffffffffffffffffffffffffffffffff169163d6e3357a9187600001516040015160006114cd8d8b6020015161171b565b604080516000815260208101918290527fffffffff0000000000000000000000000000000000000000000000000000000060e089901b169091526115199594939291906024810161308f565b600060405180830381865afa158015611536573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261155e919081019061313f565b80519193509150156115f8578351602001516040517f946aadc600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063946aadc6906115c590889085906004016131a3565b600060405180830381600087803b1580156115df57600080fd5b505af11580156115f3573d6000803e3d6000fd5b505050505b50505b600101611447565b5050505050565b60606116198484600085611a2b565b949350505050565b6000604e82106116435782156116395760001961163c565b60005b905061024a565b50600a81900a828102908381838161165d5761165d6131bc565b041461166b57600019611619565b5092915050565b600a81900a61168181846131eb565b9050604e821061024a5782156116a15761169c82600a6132e6565b6112b0565b60009392505050565b6000604e82106116ce5782156116c15760016116c4565b60005b60ff16905061024a565b600a82900a8084816116e2576116e26131bc565b049150808202841461166b575060010192915050565b6000604e8210156116a15781600a0a8381611715576117156131bc565b046112b0565b60606000825167ffffffffffffffff8111156117395761173961208e565b604051908082528060200260200182016040528015611762578160200160208202803683370190505b50905060008084511161177657600061177c565b83516001015b855160010101905060008167ffffffffffffffff81111561179f5761179f61208e565b6040519080825280602002602001820160405280156117d257816020015b60608152602001906001900390816117bd5790505b50905060006117f7604080516002815233602082015230818301526060810190915290565b82828151811061180957611809612339565b602002602001018190525060005b875181101561186757818060010192505087818151811061183a5761183a612339565b602002602001015183838151811061185457611854612339565b6020908102919091010152600101611817565b50855115611a215780806001019150508382828151811061188a5761188a612339565b602002602001018190525060005b8651811015611a1f576119498782815181106118b6576118b6612339565b6020026020010151600001516119266118f38a85815181106118da576118da612339565b6020026020010151602001518051602090810291012090565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b89848151811061193857611938612339565b602002602001015160400151611b44565b611982576040517f52bf98480000000000000000000000000000000000000000000000000000000081526004810182905260240161046d565b86818151811061199457611994612339565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff168582815181106119c8576119c8612339565b60200260200101818152505081806001019250508681815181106119ee576119ee612339565b602002602001015160200151838381518110611a0c57611a0c612339565b6020908102919091010152600101611898565b505b5095945050505050565b606082471015611abd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c0000000000000000000000000000000000000000000000000000606482015260840161046d565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611ae691906132f2565b60006040518083038185875af1925050503d8060008114611b23576040519150601f19603f3d011682016040523d82523d6000602084013e611b28565b606091505b5091509150611b3987838387611bbf565b979650505050505050565b6000806000611b538585611c5f565b90925090506000816004811115611b6c57611b6c613304565b148015611ba457508573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b80611bb55750611bb5868686611ca4565b9695505050505050565b60608315611c55578251600003611c4e5773ffffffffffffffffffffffffffffffffffffffff85163b611c4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161046d565b5081611619565b6116198383611de3565b6000808251604103611c955760208301516040840151606085015160001a611c8987828585611e27565b94509450505050611c9d565b506000905060025b9250929050565b60008060008573ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b8686604051602401611cdb929190613333565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611d4691906132f2565b600060405180830381855afa9150503d8060008114611d81576040519150601f19603f3d011682016040523d82523d6000602084013e611d86565b606091505b5091509150818015611d9a57506020815110155b8015611bb5575080517f1626ba7e0000000000000000000000000000000000000000000000000000000090611dd89083016020908101908401613059565b149695505050505050565b815115611df35781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046d919061334c565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115611e5e5750600090506003611eef565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611eb2573d6000803e3d6000fd5b5050604051601f19015191505073ffffffffffffffffffffffffffffffffffffffff8116611ee857600060019250925050611eef565b9150600090505b94509492505050565b600060208284031215611f0a57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146112b057600080fd5b73ffffffffffffffffffffffffffffffffffffffff81168114611f5c57600080fd5b50565b60008060008060008060a08789031215611f7857600080fd5b8635611f8381611f3a565b95506020870135611f9381611f3a565b94506040870135935060608701359250608087013567ffffffffffffffff80821115611fbe57600080fd5b818901915089601f830112611fd257600080fd5b813581811115611fe157600080fd5b8a6020828501011115611ff357600080fd5b6020830194508093505050509295509295509295565b60008060006060848603121561201e57600080fd5b833561202981611f3a565b9250602084013567ffffffffffffffff8082111561204657600080fd5b9085019060a0828803121561205a57600080fd5b9092506040850135908082111561207057600080fd5b5084016040818703121561208357600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff811182821017156120e0576120e061208e565b60405290565b6040516060810167ffffffffffffffff811182821017156120e0576120e061208e565b6040805190810167ffffffffffffffff811182821017156120e0576120e061208e565b604051601f8201601f1916810167ffffffffffffffff811182821017156121555761215561208e565b604052919050565b600067ffffffffffffffff8211156121775761217761208e565b5060051b60200190565b8015158114611f5c57600080fd5b60008060408084860312156121a357600080fd5b83356121ae81611f3a565b925060208481013567ffffffffffffffff808211156121cc57600080fd5b90860190608082890312156121e057600080fd5b6121e86120bd565b82356121f381611f3a565b8152828401358281111561220657600080fd5b83019150601f8201891361221957600080fd5b813561222c6122278261215d565b61212c565b8181526060918202840186019186820191908c84111561224b57600080fd5b948701945b838610156122ae5780868e0312156122685760008081fd5b6122706120e6565b863561227b81611f3a565b81528689013561228a81611f3a565b818a0152868a013561229b81612181565b818b015283529485019491870191612250565b508087850152505050604083013560408201526060830135606082015280955050505050509250929050565b6000602082840312156122ec57600080fd5b81516112b081612181565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561024a5761024a6122f7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060808083016080845280875180835260a0925060a08601915060a08160051b8701016020808b016000805b85811015612468578a85037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600187528251805173ffffffffffffffffffffffffffffffffffffffff9081168752858201518688018c905280518c8901819052908701919085908c8a01905b8083101561243c5784518051851683528a81015185168b8401526040908101511515908301529389019360019290920191606090910190612400565b50604085810151908b015260609485015194909901939093525050509583019591830191600101612395565b505050870189905287151560408801528681036060880152600281527f30780000000000000000000000000000000000000000000000000000000000006020820152604081019a9950505050505050505050565b600082601f8301126124cd57600080fd5b815160206124dd6122278361215d565b8083825260208201915060208460051b8701019350868411156124ff57600080fd5b602086015b8481101561251b5780518352918301918301612504565b509695505050505050565b60008060006060848603121561253b57600080fd5b835167ffffffffffffffff8082111561255357600080fd5b61255f878388016124bc565b945060209150818601518181111561257657600080fd5b8601601f8101881361258757600080fd5b80516125956122278261215d565b81815260059190911b8201840190848101908a8311156125b457600080fd5b928501925b828410156125db5783516125cc81611f3a565b825292850192908501906125b9565b60408a01519097509450505050808211156125f557600080fd5b50612602868287016124bc565b9150509250925092565b600082601f83011261261d57600080fd5b813567ffffffffffffffff8111156126375761263761208e565b61264a6020601f19601f8401160161212c565b81815284602083860101111561265f57600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261268d57600080fd5b8135602061269d6122278361215d565b82815260059290921b840181019181810190868411156126bc57600080fd5b8286015b8481101561251b57803567ffffffffffffffff808211156126e057600080fd5b908801906060828b03601f190112156126f95760008081fd5b6127016120e6565b8683013561270e81611f3a565b8152604083810135838111156127245760008081fd5b8401603f81018d136127365760008081fd5b888101356127466122278261215d565b81815260059190911b82018301908a8101908f8311156127665760008081fd5b928401925b828410156127845783358252928b0192908b019061276b565b858c015250505060608401358381111561279e5760008081fd5b6127ac8d8a8388010161260c565b9183019190915250855250509183019183016126c0565b6000604082360312156127d557600080fd5b6127dd612109565b823567ffffffffffffffff808211156127f557600080fd5b81850191506060823603121561280a57600080fd5b6128126120e6565b823561281d81611f3a565b8152602083013561282d81611f3a565b602082015260408301358281111561284457600080fd5b6128503682860161260c565b6040830152508352602085013591508082111561286c57600080fd5b506128793682860161267c565b60208301525092915050565b60005b838110156128a0578181015183820152602001612888565b50506000910152565b600081518084526128c1816020860160208601612885565b601f01601f19169290920160200192915050565b60008151808452602080850194506020840160005b83811015612906578151875295820195908201906001016128ea565b509495945050505050565b60006020808352606084516040808487015273ffffffffffffffffffffffffffffffffffffffff808351166060880152808584015116608088015260408301519250606060a088015261296760c08801846128a9565b925084880151601f19808986030160408a01528482518087528887019150888160051b880101898501945060005b828110156129ed578489830301845285518781511683528b8101518b8d8501526129c18c8501826128d5565b918a0151848303858c01529190506129d981836128a9565b978d0197958d019593505050600101612995565b509c9b505050505050505050505050565b6000808335601e19843603018112612a1557600080fd5b83018035915067ffffffffffffffff821115612a3057600080fd5b6020019150600581901b3603821315611c9d57600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112612a7c57600080fd5b9190910192915050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61833603018112612a7c57600080fd5b6000808335601e19843603018112612ad157600080fd5b83018035915067ffffffffffffffff821115612aec57600080fd5b6020019150606081023603821315611c9d57600080fd5b600060208284031215612b1557600080fd5b81356112b081611f3a565b6000808335601e19843603018112612b3757600080fd5b830160208101925035905067ffffffffffffffff811115612b5757600080fd5b8060051b3603821315611c9d57600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112612b9d57600080fd5b90910192915050565b6000808335601e19843603018112612bbd57600080fd5b830160208101925035905067ffffffffffffffff811115612bdd57600080fd5b803603821315611c9d57600080fd5b818352818160208501375060006020828401015260006020601f19601f840116840101905092915050565b6000808335601e19843603018112612c2e57600080fd5b830160208101925035905067ffffffffffffffff811115612c4e57600080fd5b606081023603821315611c9d57600080fd5b60ff81168114611f5c57600080fd5b8183526000602080850194508260005b85811015612906578135612c9281611f3a565b73ffffffffffffffffffffffffffffffffffffffff16875281830135612cb781612c60565b60ff16878401526040828101359088015260609687019690910190600101612c7f565b818352602080840193600091600585811b830182018585805b89811015612dc257601f19878503018b52612d0e838a612b69565b60608135612d1b81611f3a565b73ffffffffffffffffffffffffffffffffffffffff168652612d3f82890183612b20565b828a8901528083890152608092507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811115612d79578586fd5b881b808289850137870190506040612d9384820185612ba6565b94508389840301828a0152612dab8484018683612bec565b9f8b019f9850505094880194505050600101612cf3565b50919998505050505050505050565b6000602080835260c083018435828501526040828601358186015260608187013581870152612e0281880188612b20565b60a060808901528481865260e08901905060e08260051b8a010195508260005b83811015613008577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208b890301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81863603018112612e8357600080fd5b85018035368290037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018112612eb857600080fd5b60808a5281018035612ec981611f3a565b73ffffffffffffffffffffffffffffffffffffffff80821660808d0152612ef28d840184612b69565b915060a0808d01528135612f0581611f3a565b81166101208d0152818d0135612f1a81611f3a565b166101408c0152612f2d818b0182612ba6565b9150896101608d0152612f456101808d018383612bec565b915050612f548a830183612c17565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80808e85030160c08f0152612f8a848385612c6f565b9350612f988c860186612c17565b93509150808e85030160e08f015250612fb2838383612c6f565b9250505060808201356101008c01528b8301358c8c0152898301358a8c0152612fdd89840184612b20565b935091508a8103898c0152612ff3818484612cda565b9a505050928901925090880190600101612e22565b5050505050505061301c6080860186612ba6565b9250601f198583030160a0860152611bb5828483612bec565b6000806040838503121561304857600080fd5b505080516020909101519092909150565b60006020828403121561306b57600080fd5b5051919050565b60006020828403121561308457600080fd5b81516112b081612c60565b73ffffffffffffffffffffffffffffffffffffffff8716815260006020878184015260c060408401526130c560c08401886128a9565b86606085015283810360808501528086518083528383019150838160051b84010184890160005b8381101561311a57601f198684030185526131088383516128d5565b948701949250908601906001016130ec565b505086810360a088015261312e81896128d5565b9d9c50505050505050505050505050565b6000806040838503121561315257600080fd5b825167ffffffffffffffff8082111561316a57600080fd5b613176868387016124bc565b9350602085015191508082111561318c57600080fd5b50613199858286016124bc565b9150509250929050565b82815260406020820152600061161960408301846128d5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b808202811582820484141761024a5761024a6122f7565b600181815b8085111561323d578160001904821115613223576132236122f7565b8085161561323057918102915b93841c9390800290613207565b509250929050565b6000826132545750600161024a565b816132615750600061024a565b816001811461327757600281146132815761329d565b600191505061024a565b60ff841115613292576132926122f7565b50506001821b61024a565b5060208310610133831016604e8410600b84101617156132c0575081810a61024a565b6132ca8383613202565b80600019048211156132de576132de6122f7565b029392505050565b60006112b08383613245565b60008251612a7c818460208701612885565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82815260406020820152600061161960408301846128a9565b6020815260006112b060208301846128a956","sourceMap":"2371:2001:53:-:0;;;1597:1:51;1560:38;;2616:42:53;2570:89;;2666:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2741:6;2489::52;1716:1:5;1821:7;:22;;;;1729:29:51;1739:10;1751:6;1729:29;;;;;;;:::i;:::-;;;;;;;;1773:11;;;;:21;:30;;;:37;:42;1769:119;;1864:11;;;;;1853:23;;;;;;:::i;:::-;;;;-1:-1:-1;;1853:23:51;;;;;;;;;1843:34;;1853:23;1843:34;;;;1831:46;;1769:119;1605:289;2418:81:52;2666:85:53;2371:2001;;14:127:56;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:253;218:2;212:9;260:4;248:17;;-1:-1:-1;;;;;280:34:56;;316:22;;;277:62;274:88;;;342:18;;:::i;:::-;378:2;371:22;146:253;:::o;404:257::-;476:4;470:11;;;508:17;;-1:-1:-1;;;;;540:34:56;;576:22;;;537:62;534:88;;;602:18;;:::i;666:275::-;737:2;731:9;802:2;783:13;;-1:-1:-1;;779:27:56;767:40;;-1:-1:-1;;;;;822:34:56;;858:22;;;819:62;816:88;;;884:18;;:::i;:::-;920:2;913:22;666:275;;-1:-1:-1;666:275:56:o;946:131::-;-1:-1:-1;;;;;1021:31:56;;1011:42;;1001:70;;1067:1;1064;1057:12;1001:70;946:131;:::o;1082:250::-;1167:1;1177:113;1191:6;1188:1;1185:13;1177:113;;;1267:11;;;1261:18;1248:11;;;1241:39;1213:2;1206:10;1177:113;;;-1:-1:-1;;1324:1:56;1306:16;;1299:27;1082:250::o;1337:509::-;1390:5;1443:3;1436:4;1428:6;1424:17;1420:27;1410:55;;1461:1;1458;1451:12;1410:55;1484:13;;-1:-1:-1;;;;;1509:26:56;;1506:52;;;1538:18;;:::i;:::-;1582:55;1625:2;1606:13;;-1:-1:-1;;1602:27:56;1631:4;1598:38;1582:55;:::i;:::-;1662:2;1653:7;1646:19;1708:3;1701:4;1696:2;1688:6;1684:15;1680:26;1677:35;1674:55;;;1725:1;1722;1715:12;1674:55;1738:77;1812:2;1805:4;1796:7;1792:18;1785:4;1777:6;1773:17;1738:77;:::i;:::-;1833:7;1337:509;-1:-1:-1;;;;1337:509:56:o;1851:198::-;1926:4;-1:-1:-1;;;;;1948:30:56;;1945:56;;;1981:18;;:::i;:::-;-1:-1:-1;2026:1:56;2022:14;2038:4;2018:25;;1851:198::o;2054:2494::-;2134:5;2187:3;2180:4;2172:6;2168:17;2164:27;2154:55;;2205:1;2202;2195:12;2154:55;2234:6;2228:13;2260:4;2284:75;2300:58;2355:2;2300:58;:::i;:::-;2284:75;:::i;:::-;2393:15;;;2479:1;2475:10;;;;2463:23;;2459:32;;;2424:12;;;;2503:15;;;2500:35;;;2531:1;2528;2521:12;2500:35;2567:2;2559:6;2555:15;2579:1940;2595:6;2590:3;2587:15;2579:1940;;;2668:10;;-1:-1:-1;;;;;2735:19:56;;;2732:39;;;2767:1;2764;2757:12;2732:39;2794:24;;;;2866:4;2842:12;;;-1:-1:-1;;2838:26:56;2834:37;2831:127;;;2912:1;2941:2;2937;2930:14;2831:127;2984:22;;:::i;:::-;3048:2;3044;3040:11;3034:18;3065:33;3090:7;3065:33;:::i;:::-;3111:22;;3156:2;3193:11;;;3187:18;3221:16;;;3218:106;;;3278:1;3307:2;3303;3296:14;3218:106;3347:17;;3399:2;3391:11;;3387:21;-1:-1:-1;3377:119:56;;3450:1;3479:2;3475;3468:14;3377:119;3534:2;3530;3526:11;3520:18;3564:76;3580:59;3635:3;3580:59;:::i;3564:76::-;3684:18;;;3783:1;3779:11;;;;3771:20;;3767:29;;;3724:14;;;;3812:17;;;3809:110;;;3871:1;3901:3;3896;3889:16;3809:110;3945:11;;;;3969:167;3987:8;3980:5;3977:19;3969:167;;;4069:12;;4055:27;;4008:14;;;;4108;;;;3969:167;;;4156:14;;;4149:29;-1:-1:-1;;;4221:4:56;4213:13;;4207:20;4243:16;;;4240:109;;;4301:1;4331:3;4326;4319:16;4240:109;4385:60;4441:3;4436:2;4425:8;4421:2;4417:17;4413:26;4385:60;:::i;:::-;4369:14;;;4362:84;;;;-1:-1:-1;4459:18:56;;-1:-1:-1;;4497:12:56;;;;2612;;2579:1940;;;-1:-1:-1;4537:5:56;2054:2494;-1:-1:-1;;;;;;2054:2494:56:o;4553:1917::-;4663:6;4694:2;4737;4725:9;4716:7;4712:23;4708:32;4705:52;;;4753:1;4750;4743:12;4705:52;4780:16;;-1:-1:-1;;;;;4845:14:56;;;4842:34;;;4872:1;4869;4862:12;4842:34;4895:22;;;;4951:4;4933:16;;;4929:27;4926:47;;;4969:1;4966;4959:12;4926:47;4995:22;;:::i;:::-;5047:2;5041:9;5059:33;5084:7;5059:33;:::i;:::-;5101:22;;5154:11;;;5148:18;5178:16;;;5175:36;;;5207:1;5204;5197:12;5175:36;5230:17;;5281:4;5263:16;;;5259:27;5256:47;;;5299:1;5296;5289:12;5256:47;5327:22;;:::i;:::-;5380:2;5374:9;5408:2;5398:8;5395:16;5392:36;;;5424:1;5421;5414:12;5392:36;5447:17;;5498:4;5480:16;;;5476:27;5473:47;;;5516:1;5513;5506:12;5473:47;5544:22;;:::i;:::-;5596:2;5590:9;5608:33;5633:7;5608:33;:::i;:::-;5650:24;;5704:11;;;5698:18;5725:33;5698:18;5725:33;:::i;:::-;5774:16;;;5767:33;5839:4;5831:13;;5825:20;5857:16;;;5854:36;;;5886:1;5883;5876:12;5854:36;5926:55;5973:7;5962:8;5958:2;5954:17;5926:55;:::i;:::-;5919:4;5906:18;;5899:83;-1:-1:-1;5991:24:56;;-1:-1:-1;6046:11:56;;;6040:18;6070:16;;;6067:36;;;6099:1;6096;6089:12;6067:36;6137:82;6211:7;6200:8;6196:2;6192:17;6137:82;:::i;:::-;6132:2;6123:7;6119:16;6112:108;;6252:7;6247:2;6240:5;6236:14;6229:31;;;6299:4;6295:2;6291:13;6285:20;6269:36;;6330:2;6320:8;6317:16;6314:36;;;6346:1;6343;6336:12;6314:36;6384:55;6431:7;6420:8;6416:2;6412:17;6384:55;:::i;:::-;6377:4;6366:16;;6359:81;6370:5;4553:1917;-1:-1:-1;;;;;;4553:1917:56:o;6475:270::-;6516:3;6554:5;6548:12;6581:6;6576:3;6569:19;6597:76;6666:6;6659:4;6654:3;6650:14;6643:4;6636:5;6632:16;6597:76;:::i;:::-;6727:2;6706:15;-1:-1:-1;;6702:29:56;6693:39;;;;6734:4;6689:50;;6475:270;-1:-1:-1;;6475:270:56:o;6750:2011::-;6799:3;6827:4;6866:5;6860:12;6893:4;6888:3;6881:17;6934:1;6930;6925:3;6921:11;6917:19;6993:2;6978:12;6972:19;6968:28;6961:4;6956:3;6952:14;6945:52;7016:4;7075:2;7061:12;7057:21;7051:28;7098:4;7154:2;7138:14;7134:23;7127:4;7122:3;7118:14;7111:47;7213:4;7199:12;7195:23;7189:30;7167:52;;7250:4;7244:3;7239;7235:13;7228:27;7277:47;7319:3;7314;7310:13;7294:14;7277:47;:::i;:::-;7361:14;;;7355:21;7406:15;;;7392:12;;;7385:37;7472:21;;7502;;;7264:60;;-1:-1:-1;7634:23:56;;;-1:-1:-1;7541:14:56;;;;7595:1;7591:14;;;7580:26;;7576:35;;7675:1;;7706:1029;7722:6;7717:3;7714:15;7706:1029;;;7793:16;;;-1:-1:-1;;7789:30:56;7775:45;;7843:13;;7926:9;;7922:18;;7909:32;;7982:11;;;7976:18;8014:13;;;8007:25;;;8093:21;;7883:13;;;8127:24;;;8216:23;;;;8263:1;;8183:3;8173:14;;;8277:209;8293:8;8288:3;8285:17;8277:209;;;8370:15;;8356:30;;8455:17;;;;8321:1;8312:11;;;;;8412:14;;;;8277:209;;;8281:3;8535:2;8531;8527:11;8521:18;8499:40;;8585:4;8578:5;8574:16;8569:2;8563:4;8559:13;8552:39;8612;8645:5;8629:14;8612:39;:::i;:::-;8604:47;;;;;;8686:2;8678:6;8674:15;8664:25;;8722:2;8715:5;8711:14;8702:23;;7748:1;7743:3;7739:11;7732:18;;7706:1029;;;-1:-1:-1;8751:4:56;;6750:2011;-1:-1:-1;;;;;;;;;;;;6750:2011:56:o;8766:755::-;-1:-1:-1;;;;;9041:15:56;;;9023:34;;9093:2;9088;9073:18;;;9066:30;;;9136:13;;9132:22;;;9112:18;;;9105:50;9190:15;;9184:22;9244:4;9222:20;;;9215:34;8966:4;;9272:59;9003:3;9311:19;;9184:22;9272:59;:::i;:::-;9258:73;;9380:2;9372:6;9368:15;9362:22;9453:2;9449:7;9437:9;9429:6;9425:22;9421:36;9415:3;9404:9;9400:19;9393:65;9475:40;9508:6;9492:14;9475:40;:::i;:::-;9467:48;8766:755;-1:-1:-1;;;;;;8766:755:56:o;9526:255::-;9703:2;9692:9;9685:21;9666:4;9723:52;9771:2;9760:9;9756:18;9748:6;9723:52;:::i;:::-;9715:60;9526:255;-1:-1:-1;;;9526:255:56:o;:::-;2371:2001:53;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x60806040526004361061005a5760003560e01c806369e5049b1161004357806369e5049b146100bd5780637ea0b76a1461011657806393be095e146101295761005a565b806301ffc9a714610068578063059bebe61461009d575b34801561006657600080fd5b005b34801561007457600080fd5b50610088610083366004611ef8565b61016b565b60405190151581526020015b60405180910390f35b3480156100a957600080fd5b506100666100b8366004611f5f565b610250565b3480156100c957600080fd5b506100f17f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610094565b610066610124366004612009565b610735565b34801561013557600080fd5b5061015d7f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610094565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f059bebe60000000000000000000000000000000000000000000000000000000014806101fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f7ea0b76a00000000000000000000000000000000000000000000000000000000145b8061024a57507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60008061025f8385018561218f565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000008116600483015260001960248301529294509092509089169063095ea7b3906044016020604051808303816000875af11580156102fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061032091906122da565b506040517f87517c4500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152838116602483015260448201819052600060648301527f000000000000000000000000000000000000000000000000000000000000000016906387517c4590608401600060405180830381600087803b1580156103c057600080fd5b505af11580156103d4573d6000803e3d6000fd5b505050508773ffffffffffffffffffffffffffffffffffffffff16816000015173ffffffffffffffffffffffffffffffffffffffff1614610476576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f496e76616c696420696e70757420746f6b656e0000000000000000000000000060448201526064015b60405180910390fd5b6020810151805173ffffffffffffffffffffffffffffffffffffffff891691906104a290600190612326565b815181106104b2576104b2612339565b60200260200101516020015173ffffffffffffffffffffffffffffffffffffffff161461053b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f496e76616c6964206f757470757420746f6b656e000000000000000000000000604482015260640161046d565b60408181018790526060820186905280516001808252818301909252839160009190816020015b6105a36040518060800160405280600073ffffffffffffffffffffffffffffffffffffffff1681526020016060815260200160008152602001600081525090565b81526020019060019003908161056257905050905082816000815181106105cc576105cc612339565b602002602001018190525060008060008473ffffffffffffffffffffffffffffffffffffffff1663286f580d8560001960006040518463ffffffff1660e01b815260040161061c93929190612368565b6000604051808303816000875af115801561063b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106639190810190612526565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081166004830152600060248301529396509194509250908e169063095ea7b3906044016020604051808303816000875af1158015610701573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072591906122da565b5050505050505050505050505050565b61073d610a9d565b610746816127c3565b7f0000000000000000000000000000000000000000000000000000000000000000158015906107bb5750806040516020016107819190612911565b604051602081830303815290604052805190602001207f000000000000000000000000000000000000000000000000000000000000000014155b156107f2576040517f8812562900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107ff60608401846129fe565b905060000361083a576040517f9c95219f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061084960608501856129fe565b600081811061085a5761085a612339565b905060200281019061086c9190612a48565b6108769080612a86565b610884906040810190612aba565b61089160608701876129fe565b60008181106108a2576108a2612339565b90506020028101906108b49190612a48565b602001358181106108c7576108c7612339565b6108dd9260206060909202019081019150612b03565b905060006108ee60608601866129fe565b60008181106108ff576108ff612339565b90506020028101906109119190612a48565b61091b9080612a86565b610929906060810190612aba565b61093660608801886129fe565b600081811061094757610947612339565b90506020028101906109599190612a48565b6040013581811061096c5761096c612339565b6109829260206060909202019081019150612b03565b90506109a673ffffffffffffffffffffffffffffffffffffffff8316876000610b10565b6109c973ffffffffffffffffffffffffffffffffffffffff831687600019610b10565b6000808773ffffffffffffffffffffffffffffffffffffffff16630997c4a0886040518263ffffffff1660e01b8152600401610a059190612dd1565b60408051808303816000875af1158015610a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a479190613035565b9092509050610a6e73ffffffffffffffffffffffffffffffffffffffff8516896000610b10565b610a81610a7a876127c3565b8585610cf2565b5050505050610a906001600055565b505050565b505050505050565b600260005403610b09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161046d565b6002600055565b801580610bb057506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff838116602483015284169063dd62ed3e90604401602060405180830381865afa158015610b8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bae9190613059565b155b610c3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e636500000000000000000000606482015260840161046d565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a909084907f095ea7b300000000000000000000000000000000000000000000000000000000906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526110ca565b604080516001808252818301909252600091816020015b6060815260200190600190039081610d09575050604080516003808252608082019092529192506000919060208201606080368337019050506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610db2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dd69190613059565b90508015610dff57610dff73ffffffffffffffffffffffffffffffffffffffff861633836111d9565b60008573ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e709190613072565b60ff169050610e818282600061122f565b83600081518110610e9457610e94612339565b602090810291909101015250506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa158015610f0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f329190613059565b90508015610f5b57610f5b73ffffffffffffffffffffffffffffffffffffffff851633836111d9565b60008473ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fa8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fcc9190613072565b60ff169050610fdd8282600061122f565b83600181518110610ff057610ff0612339565b60209081029190910101525047905061100933826112b7565b808260028151811061101d5761101d612339565b60200260200101818152505050808260008151811061103e5761103e612339565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160a0810182526000918101828152606080830193909352608082018390528152602081019190915281526020019060019003908161106057905050905085816000815181106110b5576110b5612339565b6020026020010181905250610a958382611411565b600061112c826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661160a9092919063ffffffff16565b905080516000148061114d57508080602001905181019061114d91906122da565b610a90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f74207375636365656400000000000000000000000000000000000000000000606482015260840161046d565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a909084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401610c8e565b60008260121115611264576012839003600283161561125a576112528582611621565b9150506112b0565b6112528582611672565b60128311156112ad577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee830160018316156112a35761125285826116aa565b61125285826116f8565b50825b9392505050565b80471015611321576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015260640161046d565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d806000811461137b576040519150601f19603f3d011682016040523d82523d6000602084013e611380565b606091505b5050905080610a90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d61792068617665207265766572746564000000000000606482015260840161046d565b336114446040805160a0810182526000918101828152606080830193909352608082018390528152602081019190915290565b60005b83518110156116035783818151811061146257611462612339565b6020026020010151915060008260000151604001515111156115fb578151805160209182015160008681523090935260408320839273ffffffffffffffffffffffffffffffffffffffff169163d6e3357a9187600001516040015160006114cd8d8b6020015161171b565b604080516000815260208101918290527fffffffff0000000000000000000000000000000000000000000000000000000060e089901b169091526115199594939291906024810161308f565b600060405180830381865afa158015611536573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261155e919081019061313f565b80519193509150156115f8578351602001516040517f946aadc600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063946aadc6906115c590889085906004016131a3565b600060405180830381600087803b1580156115df57600080fd5b505af11580156115f3573d6000803e3d6000fd5b505050505b50505b600101611447565b5050505050565b60606116198484600085611a2b565b949350505050565b6000604e82106116435782156116395760001961163c565b60005b905061024a565b50600a81900a828102908381838161165d5761165d6131bc565b041461166b57600019611619565b5092915050565b600a81900a61168181846131eb565b9050604e821061024a5782156116a15761169c82600a6132e6565b6112b0565b60009392505050565b6000604e82106116ce5782156116c15760016116c4565b60005b60ff16905061024a565b600a82900a8084816116e2576116e26131bc565b049150808202841461166b575060010192915050565b6000604e8210156116a15781600a0a8381611715576117156131bc565b046112b0565b60606000825167ffffffffffffffff8111156117395761173961208e565b604051908082528060200260200182016040528015611762578160200160208202803683370190505b50905060008084511161177657600061177c565b83516001015b855160010101905060008167ffffffffffffffff81111561179f5761179f61208e565b6040519080825280602002602001820160405280156117d257816020015b60608152602001906001900390816117bd5790505b50905060006117f7604080516002815233602082015230818301526060810190915290565b82828151811061180957611809612339565b602002602001018190525060005b875181101561186757818060010192505087818151811061183a5761183a612339565b602002602001015183838151811061185457611854612339565b6020908102919091010152600101611817565b50855115611a215780806001019150508382828151811061188a5761188a612339565b602002602001018190525060005b8651811015611a1f576119498782815181106118b6576118b6612339565b6020026020010151600001516119266118f38a85815181106118da576118da612339565b6020026020010151602001518051602090810291012090565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b89848151811061193857611938612339565b602002602001015160400151611b44565b611982576040517f52bf98480000000000000000000000000000000000000000000000000000000081526004810182905260240161046d565b86818151811061199457611994612339565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff168582815181106119c8576119c8612339565b60200260200101818152505081806001019250508681815181106119ee576119ee612339565b602002602001015160200151838381518110611a0c57611a0c612339565b6020908102919091010152600101611898565b505b5095945050505050565b606082471015611abd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c0000000000000000000000000000000000000000000000000000606482015260840161046d565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611ae691906132f2565b60006040518083038185875af1925050503d8060008114611b23576040519150601f19603f3d011682016040523d82523d6000602084013e611b28565b606091505b5091509150611b3987838387611bbf565b979650505050505050565b6000806000611b538585611c5f565b90925090506000816004811115611b6c57611b6c613304565b148015611ba457508573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b80611bb55750611bb5868686611ca4565b9695505050505050565b60608315611c55578251600003611c4e5773ffffffffffffffffffffffffffffffffffffffff85163b611c4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161046d565b5081611619565b6116198383611de3565b6000808251604103611c955760208301516040840151606085015160001a611c8987828585611e27565b94509450505050611c9d565b506000905060025b9250929050565b60008060008573ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b8686604051602401611cdb929190613333565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611d4691906132f2565b600060405180830381855afa9150503d8060008114611d81576040519150601f19603f3d011682016040523d82523d6000602084013e611d86565b606091505b5091509150818015611d9a57506020815110155b8015611bb5575080517f1626ba7e0000000000000000000000000000000000000000000000000000000090611dd89083016020908101908401613059565b149695505050505050565b815115611df35781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046d919061334c565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115611e5e5750600090506003611eef565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611eb2573d6000803e3d6000fd5b5050604051601f19015191505073ffffffffffffffffffffffffffffffffffffffff8116611ee857600060019250925050611eef565b9150600090505b94509492505050565b600060208284031215611f0a57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146112b057600080fd5b73ffffffffffffffffffffffffffffffffffffffff81168114611f5c57600080fd5b50565b60008060008060008060a08789031215611f7857600080fd5b8635611f8381611f3a565b95506020870135611f9381611f3a565b94506040870135935060608701359250608087013567ffffffffffffffff80821115611fbe57600080fd5b818901915089601f830112611fd257600080fd5b813581811115611fe157600080fd5b8a6020828501011115611ff357600080fd5b6020830194508093505050509295509295509295565b60008060006060848603121561201e57600080fd5b833561202981611f3a565b9250602084013567ffffffffffffffff8082111561204657600080fd5b9085019060a0828803121561205a57600080fd5b9092506040850135908082111561207057600080fd5b5084016040818703121561208357600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff811182821017156120e0576120e061208e565b60405290565b6040516060810167ffffffffffffffff811182821017156120e0576120e061208e565b6040805190810167ffffffffffffffff811182821017156120e0576120e061208e565b604051601f8201601f1916810167ffffffffffffffff811182821017156121555761215561208e565b604052919050565b600067ffffffffffffffff8211156121775761217761208e565b5060051b60200190565b8015158114611f5c57600080fd5b60008060408084860312156121a357600080fd5b83356121ae81611f3a565b925060208481013567ffffffffffffffff808211156121cc57600080fd5b90860190608082890312156121e057600080fd5b6121e86120bd565b82356121f381611f3a565b8152828401358281111561220657600080fd5b83019150601f8201891361221957600080fd5b813561222c6122278261215d565b61212c565b8181526060918202840186019186820191908c84111561224b57600080fd5b948701945b838610156122ae5780868e0312156122685760008081fd5b6122706120e6565b863561227b81611f3a565b81528689013561228a81611f3a565b818a0152868a013561229b81612181565b818b015283529485019491870191612250565b508087850152505050604083013560408201526060830135606082015280955050505050509250929050565b6000602082840312156122ec57600080fd5b81516112b081612181565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561024a5761024a6122f7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060808083016080845280875180835260a0925060a08601915060a08160051b8701016020808b016000805b85811015612468578a85037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600187528251805173ffffffffffffffffffffffffffffffffffffffff9081168752858201518688018c905280518c8901819052908701919085908c8a01905b8083101561243c5784518051851683528a81015185168b8401526040908101511515908301529389019360019290920191606090910190612400565b50604085810151908b015260609485015194909901939093525050509583019591830191600101612395565b505050870189905287151560408801528681036060880152600281527f30780000000000000000000000000000000000000000000000000000000000006020820152604081019a9950505050505050505050565b600082601f8301126124cd57600080fd5b815160206124dd6122278361215d565b8083825260208201915060208460051b8701019350868411156124ff57600080fd5b602086015b8481101561251b5780518352918301918301612504565b509695505050505050565b60008060006060848603121561253b57600080fd5b835167ffffffffffffffff8082111561255357600080fd5b61255f878388016124bc565b945060209150818601518181111561257657600080fd5b8601601f8101881361258757600080fd5b80516125956122278261215d565b81815260059190911b8201840190848101908a8311156125b457600080fd5b928501925b828410156125db5783516125cc81611f3a565b825292850192908501906125b9565b60408a01519097509450505050808211156125f557600080fd5b50612602868287016124bc565b9150509250925092565b600082601f83011261261d57600080fd5b813567ffffffffffffffff8111156126375761263761208e565b61264a6020601f19601f8401160161212c565b81815284602083860101111561265f57600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261268d57600080fd5b8135602061269d6122278361215d565b82815260059290921b840181019181810190868411156126bc57600080fd5b8286015b8481101561251b57803567ffffffffffffffff808211156126e057600080fd5b908801906060828b03601f190112156126f95760008081fd5b6127016120e6565b8683013561270e81611f3a565b8152604083810135838111156127245760008081fd5b8401603f81018d136127365760008081fd5b888101356127466122278261215d565b81815260059190911b82018301908a8101908f8311156127665760008081fd5b928401925b828410156127845783358252928b0192908b019061276b565b858c015250505060608401358381111561279e5760008081fd5b6127ac8d8a8388010161260c565b9183019190915250855250509183019183016126c0565b6000604082360312156127d557600080fd5b6127dd612109565b823567ffffffffffffffff808211156127f557600080fd5b81850191506060823603121561280a57600080fd5b6128126120e6565b823561281d81611f3a565b8152602083013561282d81611f3a565b602082015260408301358281111561284457600080fd5b6128503682860161260c565b6040830152508352602085013591508082111561286c57600080fd5b506128793682860161267c565b60208301525092915050565b60005b838110156128a0578181015183820152602001612888565b50506000910152565b600081518084526128c1816020860160208601612885565b601f01601f19169290920160200192915050565b60008151808452602080850194506020840160005b83811015612906578151875295820195908201906001016128ea565b509495945050505050565b60006020808352606084516040808487015273ffffffffffffffffffffffffffffffffffffffff808351166060880152808584015116608088015260408301519250606060a088015261296760c08801846128a9565b925084880151601f19808986030160408a01528482518087528887019150888160051b880101898501945060005b828110156129ed578489830301845285518781511683528b8101518b8d8501526129c18c8501826128d5565b918a0151848303858c01529190506129d981836128a9565b978d0197958d019593505050600101612995565b509c9b505050505050505050505050565b6000808335601e19843603018112612a1557600080fd5b83018035915067ffffffffffffffff821115612a3057600080fd5b6020019150600581901b3603821315611c9d57600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112612a7c57600080fd5b9190910192915050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61833603018112612a7c57600080fd5b6000808335601e19843603018112612ad157600080fd5b83018035915067ffffffffffffffff821115612aec57600080fd5b6020019150606081023603821315611c9d57600080fd5b600060208284031215612b1557600080fd5b81356112b081611f3a565b6000808335601e19843603018112612b3757600080fd5b830160208101925035905067ffffffffffffffff811115612b5757600080fd5b8060051b3603821315611c9d57600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112612b9d57600080fd5b90910192915050565b6000808335601e19843603018112612bbd57600080fd5b830160208101925035905067ffffffffffffffff811115612bdd57600080fd5b803603821315611c9d57600080fd5b818352818160208501375060006020828401015260006020601f19601f840116840101905092915050565b6000808335601e19843603018112612c2e57600080fd5b830160208101925035905067ffffffffffffffff811115612c4e57600080fd5b606081023603821315611c9d57600080fd5b60ff81168114611f5c57600080fd5b8183526000602080850194508260005b85811015612906578135612c9281611f3a565b73ffffffffffffffffffffffffffffffffffffffff16875281830135612cb781612c60565b60ff16878401526040828101359088015260609687019690910190600101612c7f565b818352602080840193600091600585811b830182018585805b89811015612dc257601f19878503018b52612d0e838a612b69565b60608135612d1b81611f3a565b73ffffffffffffffffffffffffffffffffffffffff168652612d3f82890183612b20565b828a8901528083890152608092507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811115612d79578586fd5b881b808289850137870190506040612d9384820185612ba6565b94508389840301828a0152612dab8484018683612bec565b9f8b019f9850505094880194505050600101612cf3565b50919998505050505050505050565b6000602080835260c083018435828501526040828601358186015260608187013581870152612e0281880188612b20565b60a060808901528481865260e08901905060e08260051b8a010195508260005b83811015613008577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208b890301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81863603018112612e8357600080fd5b85018035368290037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018112612eb857600080fd5b60808a5281018035612ec981611f3a565b73ffffffffffffffffffffffffffffffffffffffff80821660808d0152612ef28d840184612b69565b915060a0808d01528135612f0581611f3a565b81166101208d0152818d0135612f1a81611f3a565b166101408c0152612f2d818b0182612ba6565b9150896101608d0152612f456101808d018383612bec565b915050612f548a830183612c17565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80808e85030160c08f0152612f8a848385612c6f565b9350612f988c860186612c17565b93509150808e85030160e08f015250612fb2838383612c6f565b9250505060808201356101008c01528b8301358c8c0152898301358a8c0152612fdd89840184612b20565b935091508a8103898c0152612ff3818484612cda565b9a505050928901925090880190600101612e22565b5050505050505061301c6080860186612ba6565b9250601f198583030160a0860152611bb5828483612bec565b6000806040838503121561304857600080fd5b505080516020909101519092909150565b60006020828403121561306b57600080fd5b5051919050565b60006020828403121561308457600080fd5b81516112b081612c60565b73ffffffffffffffffffffffffffffffffffffffff8716815260006020878184015260c060408401526130c560c08401886128a9565b86606085015283810360808501528086518083528383019150838160051b84010184890160005b8381101561311a57601f198684030185526131088383516128d5565b948701949250908601906001016130ec565b505086810360a088015261312e81896128d5565b9d9c50505050505050505050505050565b6000806040838503121561315257600080fd5b825167ffffffffffffffff8082111561316a57600080fd5b613176868387016124bc565b9350602085015191508082111561318c57600080fd5b50613199858286016124bc565b9150509250929050565b82815260406020820152600061161960408301846128d5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b808202811582820484141761024a5761024a6122f7565b600181815b8085111561323d578160001904821115613223576132236122f7565b8085161561323057918102915b93841c9390800290613207565b509250929050565b6000826132545750600161024a565b816132615750600061024a565b816001811461327757600281146132815761329d565b600191505061024a565b60ff841115613292576132926122f7565b50506001821b61024a565b5060208310610133831016604e8410600b84101617156132c0575081810a61024a565b6132ca8383613202565b80600019048211156132de576132de6122f7565b029392505050565b60006112b08383613245565b60008251612a7c818460208701612885565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82815260406020820152600061161960408301846128a9565b6020815260006112b060208301846128a956","sourceMap":"2371:2001:53:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2533:290:52;;;;;;;;;;-1:-1:-1;2533:290:52;;;;;:::i;:::-;;:::i;:::-;;;612:14:56;;605:22;587:41;;575:2;560:18;2533:290:52;;;;;;;;2802:1511:53;;;;;;;;;;-1:-1:-1;2802:1511:53;;;;;:::i;:::-;;:::i;2570:89::-;;;;;;;;;;;;;;;;;;2001:42:56;1989:55;;;1971:74;;1959:2;1944:18;2570:89:53;1808:243:56;2877:1011:52;;;;;;:::i;:::-;;:::i;1560:38:51:-;;;;;;;;;;;;;;;;;;3045:25:56;;;3033:2;3018:18;1560:38:51;2899:177:56;2533:290:52;2618:4;2642:55;;;2657:40;2642:55;;2641:135;;-1:-1:-1;2715:60:52;;;2730:45;2715:60;2641:135;:175;;;-1:-1:-1;952:25:14;937:40;;;;2780:36:52;2634:182;2533:290;-1:-1:-1;;2533:290:52:o;2802:1511:53:-;3134:23;;3197:94;;;;3221:14;3197:94;:::i;:::-;3357:64;;;;;:26;3392:8;7135:55:56;;3357:64:53;;;7117:74:56;-1:-1:-1;;7207:18:56;;;7200:34;3133:158:53;;-1:-1:-1;3133:158:53;;-1:-1:-1;3357:26:53;;;;;;7090:18:56;;3357:64:53;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;3431:67:53;;;;;:16;7812:15:56;;;3431:67:53;;;7794:34:56;7864:15;;;7844:18;;;7837:43;7896:18;;;7889:43;;;3496:1:53;7948:18:56;;;7941:55;3431:8:53;:16;;;;7705:19:56;;3431:67:53;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3615:10;3589:36;;3597:5;:13;;;3589:36;;;3581:68;;;;;;;8209:2:56;3581:68:53;;;8191:21:56;8248:2;8228:18;;;8221:30;8287:21;8267:18;;;8260:49;8326:18;;3581:68:53;;;;;;;;;3675:11;;;;3687:18;;3667:68;;;;3675:11;3687:22;;3708:1;;3687:22;:::i;:::-;3675:35;;;;;;;;:::i;:::-;;;;;;;:44;;;3667:68;;;3659:101;;;;;;;9068:2:56;3659:101:53;;;9050:21:56;9107:2;9087:18;;;9080:30;9146:22;9126:18;;;9119:50;9186:18;;3659:101:53;8866:344:56;3659:101:53;3839:19;;;;:37;;;3886:18;;;:38;;;3973:30;;4001:1;3973:30;;;;;;;;;3812:15;;-1:-1:-1;;3973:30:53;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3973:30:53;;;;;;;;;;;;;;;;3934:69;;4024:5;4013;4019:1;4013:8;;;;;;;;:::i;:::-;;;;;;:16;;;;4041:31;4074:26;4102:27;4133:14;:26;;;4160:5;-1:-1:-1;;4186:5:53;4133:65;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4133:65:53;;;;;;;;;;;;:::i;:::-;4258:48;;;;;:26;4293:8;7135:55:56;;4258:48:53;;;7117:74:56;-1:-1:-1;7207:18:56;;;7200:34;4040:158:53;;-1:-1:-1;4040:158:53;;-1:-1:-1;4040:158:53;-1:-1:-1;4258:26:53;;;;;;7090:18:56;;4258:48:53;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;3018:1295;;;;;;;2802:1511;;;;;;:::o;2877:1011:52:-;2261:21:5;:19;:21::i;:::-;1900:186:51::1;3053:4:52::0;1900:186:51::1;:::i;:::-;1957:9;:23:::0;;::::1;::::0;:67:::1;;;2018:4;2007:16;;;;;;;;:::i;:::-;;;;;;;;;;;;;1997:27;;;;;;1984:9;:40;;1957:67;1953:116;;;2047:11;;;;;;;;;;;;;;1953:116;3146:17:52::2;;::::0;::::2;:10:::0;:17:::2;:::i;:::-;:24;;3174:1;3146:29:::0;3142:77:::2;;3198:10;;;;;;;;;;;;;;3142:77;3229:24;3256:17;;::::0;::::2;:10:::0;:17:::2;:::i;:::-;3274:1;3256:20;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;:26;::::0;;::::2;:::i;:::-;:38;::::0;::::2;::::0;::::2;::::0;::::2;:::i;:::-;3295:17;;::::0;::::2;:10:::0;:17:::2;:::i;:::-;3313:1;3295:20;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;:33;;;3256:73;;;;;;;:::i;:::-;:79;::::0;::::2;:73;::::0;;::::2;;:79:::0;;::::2;::::0;-1:-1:-1;3256:79:52::2;:::i;:::-;3229:106:::0;-1:-1:-1;3345:25:52::2;3373:17;;::::0;::::2;:10:::0;:17:::2;:::i;:::-;3391:1;3373:20;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;:26;::::0;;::::2;:::i;:::-;:39;::::0;::::2;::::0;::::2;::::0;::::2;:::i;:::-;3413:17;;::::0;::::2;:10:::0;:17:::2;:::i;:::-;3431:1;3413:20;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;:34;;;3373:75;;;;;;;:::i;:::-;:81;::::0;::::2;:75;::::0;;::::2;;:81:::0;;::::2;::::0;-1:-1:-1;3373:81:52::2;:::i;:::-;3345:109:::0;-1:-1:-1;3465:59:52::2;:36;::::0;::::2;3510:9:::0;3522:1:::2;3465:36;:59::i;:::-;3534:75;:36;::::0;::::2;3579:9:::0;-1:-1:-1;;3534:36:52::2;:75::i;:::-;3620:18;3640:19:::0;3663:9:::2;:21;;;3685:10;3663:33;;;;;;;;;;;;;;;:::i;:::-;;::::0;::::2;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3619:77:::0;;-1:-1:-1;3619:77:52;-1:-1:-1;3741:59:52::2;:36;::::0;::::2;3786:9:::0;3798:1:::2;3741:36;:59::i;:::-;3811:70;;3839:4:::0;3811:70:::2;:::i;:::-;3845:16;3863:17;3811:27;:70::i;:::-;3063:825;;;;2292:1:5::1;2303:20:::0;1716:1;2809:7;:22;2629:209;2303:20;2877:1011:52;;;:::o;3937:100::-;;;;;;;:::o;2336:287:5:-;1759:1;2468:7;;:19;2460:63;;;;;;;33666:2:56;2460:63:5;;;33648:21:56;33705:2;33685:18;;;33678:30;33744:33;33724:18;;;33717:61;33795:18;;2460:63:5;33464:355:56;2460:63:5;1759:1;2598:7;:18;2336:287::o;1818:573:9:-;2143:10;;;2142:62;;-1:-1:-1;2159:39:9;;;;;2183:4;2159:39;;;34059:34:56;2159:15:9;34129::56;;;34109:18;;;34102:43;2159:15:9;;;;;33971:18:56;;2159:39:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:44;2142:62;2121:163;;;;;;;34547:2:56;2121:163:9;;;34529:21:56;34586:2;34566:18;;;34559:30;34625:34;34605:18;;;34598:62;34696:24;34676:18;;;34669:52;34738:19;;2121:163:9;34345:418:56;2121:163:9;2321:62;;7147:42:56;7135:55;;2321:62:9;;;7117:74:56;7207:18;;;7200:34;;;2294:90:9;;2314:5;;2344:22;;7090:18:56;;2321:62:9;;;;-1:-1:-1;;2321:62:9;;;;;;;;;;;;;;;;;;;;;;;;;;;2294:19;:90::i;1058:2206:55:-;1200:18;;;1216:1;1200:18;;;;;;;;;1171:26;;1200:18;;;;;;;;;;;;;;;;;-1:-1:-1;;1251:16:55;;;1265:1;1251:16;;;;;;;;;1171:47;;-1:-1:-1;1228:20:55;;1251:16;;;;;;;;;;;-1:-1:-1;;1375:49:55;;;;;1418:4;1375:49;;;1971:74:56;1228:39:55;;-1:-1:-1;1352:20:55;;1375:34;;;;;;1944:18:56;;1375:49:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1352:72;-1:-1:-1;1442:16:55;;1438:118;;1478:63;:37;;;1516:10;1528:12;1478:37;:63::i;:::-;1569:21;1608:16;1593:41;;;:43;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1569:67;;;;1659:214;1710:12;1740:13;1858:1;1659:33;:214::i;:::-;1650:3;1654:1;1650:6;;;;;;;;:::i;:::-;;;;;;;;;;:223;-1:-1:-1;;1993:50:55;;;;;2037:4;1993:50;;;1971:74:56;1969:21:55;;1993:35;;;;;;1944:18:56;;1993:50:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1969:74;-1:-1:-1;2061:17:55;;2057:121;;2098:65;:38;;;2137:10;2149:13;2098:38;:65::i;:::-;2192:22;2232:17;2217:42;;;:44;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2192:69;;;;2284:216;2335:13;2366:14;2485:1;2284:33;:216::i;:::-;2275:3;2279:1;2275:6;;;;;;;;:::i;:::-;;;;;;;;;;:225;-1:-1:-1;2985:21:55;;-1:-1:-1;3020:50:55;3046:10;2985:21;3020:17;:50::i;:::-;3093:10;3084:3;3088:1;3084:6;;;;;;;;:::i;:::-;;;;;;:19;;;;;2521:593;3137:3;3124:7;3132:1;3124:10;;;;;;;;:::i;:::-;;;;;;;;;;:16;3174:15;;;3187:1;3174:15;;;;;;;;;3151:20;;3174:15;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3174:15:55;;;;;;;;;;;;;;;3151:38;;3209:4;3199;3204:1;3199:7;;;;;;;;:::i;:::-;;;;;;:14;;;;3223:34;3243:7;3252:4;3223:19;:34::i;5196:642:9:-;5615:23;5641:69;5669:4;5641:69;;;;;;;;;;;;;;;;;5649:5;5641:27;;;;:69;;;;;:::i;:::-;5615:95;;5728:10;:17;5749:1;5728:22;:56;;;;5765:10;5754:30;;;;;;;;;;;;:::i;:::-;5720:111;;;;;;;35453:2:56;5720:111:9;;;35435:21:56;35492:2;35472:18;;;35465:30;35531:34;35511:18;;;35504:62;35602:12;35582:18;;;35575:40;35632:19;;5720:111:9;35251:406:56;941:175:9;1050:58;;7147:42:56;7135:55;;1050:58:9;;;7117:74:56;7207:18;;;7200:34;;;1023:86:9;;1043:5;;1073:23;;7090:18:56;;1050:58:9;6943:297:56;6376:849:3;6460:7;6530:8;344:2:2;6507:31:3;6503:706;;;344:2:2;6578:31:3;;;594:6:2;6631:21:3;;:25;6627:185;;6687:31;6705:1;6708:9;6687:17;:31::i;:::-;6680:38;;;;;6627:185;6772:21;6780:1;6783:9;6772:7;:21::i;6503:706::-;344:2:2;6836:8:3;:31;6832:377;;;6909:31;;;507:1:2;6962:21:3;;:25;6958:190;;7018:32;7035:1;7038:11;7018:16;:32::i;6958:190::-;7104:25;7114:1;7117:11;7104:9;:25::i;6832:377::-;-1:-1:-1;7193:1:3;6832:377;6376:849;;;;;:::o;2647:312:10:-;2761:6;2736:21;:31;;2728:73;;;;;;;35864:2:56;2728:73:10;;;35846:21:56;35903:2;35883:18;;;35876:30;35942:31;35922:18;;;35915:59;35991:18;;2728:73:10;35662:353:56;2728:73:10;2813:12;2831:9;:14;;2853:6;2831:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2812:52;;;2882:7;2874:78;;;;;;;36432:2:56;2874:78:10;;;36414:21:56;36471:2;36451:18;;;36444:30;36510:34;36490:18;;;36483:62;36581:28;36561:18;;;36554:56;36627:19;;2874:78:10;36230:422:56;4688:935:54;4836:10;4859:18;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4859:18:54;4892:9;4887:730;4911:4;:11;4907:1;:15;4887:730;;;4950:4;4955:1;4950:7;;;;;;;;:::i;:::-;;;;;;;4943:14;;5008:1;4975:4;:14;;;:23;;;:30;:34;4971:636;;;5081:14;;:26;;5135:20;;;;;5030:22;1218:25:42;;;5226:4:54;1256:20:42;;;1324:4;1311:18;;5030:22:54;;5081:32;;;;;5254:4;:14;;;:23;;;5318:1;5342:45;5359:7;5368:4;:18;;;5342:16;:45::i;:::-;5409:16;;;5423:1;5409:16;;;;;;;;;5081:362;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5081:362:54;;;;;;;;;;;;:::i;:::-;5490:13;;5029:414;;-1:-1:-1;5029:414:54;-1:-1:-1;5490:17:54;5486:107;;5531:14;;:20;;;:43;;;;;:24;;;;;;;:43;;5556:9;;5567:6;;5531:43;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5486:107;5011:596;;4971:636;4924:3;;4887:730;;;;4763:860;;4688:935;;:::o;4108:223:10:-;4241:12;4272:52;4294:6;4302:4;4308:1;4311:12;4272:21;:52::i;:::-;4265:59;4108:223;-1:-1:-1;;;;4108:223:10:o;3853:689:3:-;3933:9;817:2:2;3982:9:3;:34;3978:548;;4040:6;;:30;;-1:-1:-1;;4040:30:3;;;4049:1;4040:30;4036:34;;3978:548;;;-1:-1:-1;4252:2:3;:15;;;4289:6;;;;:1;4252:15;4289:6;4252:15;4476:6;;;;:::i;:::-;;:11;:35;;-1:-1:-1;;4476:35:3;;;-1:-1:-1;4490:1:3;4472:39;-1:-1:-1;;3853:689:3:o;2909:688::-;3084:2;:15;;;3123:5;3084:15;3123:1;:5;:::i;:::-;3119:9;;817:2:2;3498:9:3;:34;3494:97;;3552:6;;:28;;3565:15;3571:9;3565:2;:15;:::i;:::-;3552:28;;;3561:1;3548:32;2909:688;-1:-1:-1;;;2909:688:3:o;5491:598::-;5572:9;817:2:2;5621:11:3;:36;5617:456;;5681:6;;:14;;5694:1;5681:14;;;5690:1;5681:14;5677:18;;;;5617:456;;;5746:2;:17;;;;5785:1;5746:17;5785:5;;;;:::i;:::-;;5781:9;;6009:1;6005;:5;6000:1;:10;5996:63;;-1:-1:-1;6039:1:3;6034:6;;5491:598;-1:-1:-1;;5491:598:3:o;4915:207::-;4989:7;817:2:2;5039:11:3;:36;;:66;;5093:11;5087:2;:17;5082:1;:23;;;;;:::i;:::-;;5039:66;;7619:2290:39;7754:18;7812:24;7853:14;:21;7839:36;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;7839:36:39;;7812:63;;8024:21;8098:1;8074:14;:21;:25;:57;;8130:1;8074:57;;;8102:14;:21;8126:1;8102:25;8074:57;8052:11;:18;8048:1;:22;:84;8024:108;;8147:26;8192:13;8176:30;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8147:59;;8220:14;8270:17;3181:4:23;3175:11;;3213:1;3199:16;;2741:10:39;3246:4:23;3235:16;;3228:27;2779:4:39;3275:16:23;;;3268:27;2664:16:39;3321::23;;3308:30;;;3175:11;2625:169:39;8270:17;8252:7;8260:6;8252:15;;;;;;;;:::i;:::-;;;;;;:35;;;;8307:9;8302:140;8326:11;:18;8322:1;:22;8302:140;;;8369:8;;;;;;;8413:11;8425:1;8413:14;;;;;;;;:::i;:::-;;;;;;;8395:7;8403:6;8395:15;;;;;;;;:::i;:::-;;;;;;;;;;:32;8346:3;;8302:140;;;-1:-1:-1;8460:21:39;;:25;8456:1408;;8505:8;;;;;;;8549:7;8531;8539:6;8531:15;;;;;;;;:::i;:::-;;;;;;:25;;;;8580:9;8575:1275;8599:14;:21;8595:1;:25;8575:1275;;;9277:284;9343:14;9358:1;9343:17;;;;;;;;:::i;:::-;;;;;;;:24;;;9397:81;9426:51;9451:14;9466:1;9451:17;;;;;;;;:::i;:::-;;;;;;;:25;;;4172:13:0;;4187:4;4168:24;;;4149:17;;4139:54;;3999:210;9426:51:39;7389:34:12;7189:15;7376:48;;;7444:4;7437:18;;;;7495:4;7479:21;;;7120:396;9397:81:39;9508:14;9523:1;9508:17;;;;;;;;:::i;:::-;;;;;;;:27;;;9277:36;:284::i;:::-;8649:1010;;9617:19;;;;;;;;3045:25:56;;;3018:18;;9617:19:39;2899:177:56;8649:1010:39;9710:14;9725:1;9710:17;;;;;;;;:::i;:::-;;;;;;;:24;;;9694:42;;9681:7;9689:1;9681:10;;;;;;;;:::i;:::-;;;;;;:55;;;;;9758:8;;;;;;;9806:14;9821:1;9806:17;;;;;;;;:::i;:::-;;;;;;;:25;;;9788:7;9796:6;9788:15;;;;;;;;:::i;:::-;;;;;;;;;;:43;8622:3;;8575:1275;;;;8456:1408;-1:-1:-1;9885:7:39;7619:2290;-1:-1:-1;;;;;7619:2290:39:o;5165:446:10:-;5330:12;5387:5;5362:21;:30;;5354:81;;;;;;;41459:2:56;5354:81:10;;;41441:21:56;41498:2;41478:18;;;41471:30;41537:34;41517:18;;;41510:62;41608:8;41588:18;;;41581:36;41634:19;;5354:81:10;41257:402:56;5354:81:10;5446:12;5460:23;5487:6;:11;;5506:5;5513:4;5487:31;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5445:73;;;;5535:69;5562:6;5570:7;5579:10;5591:12;5535:26;:69::i;:::-;5528:76;5165:446;-1:-1:-1;;;;;;;5165:446:10:o;1014:366:13:-;1120:4;1137:17;1156:24;1184:33;1201:4;1207:9;1184:16;:33::i;:::-;1136:81;;-1:-1:-1;1136:81:13;-1:-1:-1;1256:26:13;1247:5;:35;;;;;;;;:::i;:::-;;:58;;;;;1299:6;1286:19;;:9;:19;;;1247:58;1246:127;;;;1322:51;1349:6;1357:4;1363:9;1322:26;:51::i;:::-;1227:146;1014:366;-1:-1:-1;;;;;;1014:366:13:o;7671:628:10:-;7851:12;7879:7;7875:418;;;7906:10;:17;7927:1;7906:22;7902:286;;1702:19;;;;8113:60;;;;;;;42347:2:56;8113:60:10;;;42329:21:56;42386:2;42366:18;;;42359:30;42425:31;42405:18;;;42398:59;42474:18;;8113:60:10;42145:353:56;8113:60:10;-1:-1:-1;8208:10:10;8201:17;;7875:418;8249:33;8257:10;8269:12;8249:7;:33::i;2145:730:12:-;2226:7;2235:12;2263:9;:16;2283:2;2263:22;2259:610;;2599:4;2584:20;;2578:27;2648:4;2633:20;;2627:27;2705:4;2690:20;;2684:27;2301:9;2676:36;2746:25;2757:4;2676:36;2578:27;2627;2746:10;:25::i;:::-;2739:32;;;;;;;;;2259:610;-1:-1:-1;2818:1:12;;-1:-1:-1;2822:35:12;2259:610;2145:730;;;;;:::o;1786:473:13:-;1929:4;1946:12;1960:19;1983:6;:17;;2037:34;;;2073:4;2079:9;2014:75;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;2014:75:13;;;;;;;;;;;;;;;;;;;;;;;;;;;1983:116;;;;2014:75;1983:116;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1945:154;;;;2117:7;:42;;;;;2157:2;2140:6;:13;:19;;2117:42;:134;;;;-1:-1:-1;2175:29:13;;2216:34;;2175:29;;;;;;;;;;;;:::i;:::-;:76;;1786:473;-1:-1:-1;;;;;;1786:473:13:o;8821:540:10:-;8980:17;;:21;8976:379;;9208:10;9202:17;9264:15;9251:10;9247:2;9243:19;9236:44;8976:379;9331:12;9324:20;;;;;;;;;;;:::i;5009:1456:12:-;5097:7;;6021:66;6008:79;;6004:161;;;-1:-1:-1;6119:1:12;;-1:-1:-1;6123:30:12;6103:51;;6004:161;6276:24;;;6259:14;6276:24;;;;;;;;;43436:25:56;;;43509:4;43497:17;;43477:18;;;43470:45;;;;43531:18;;;43524:34;;;43574:18;;;43567:34;;;6276:24:12;;43408:19:56;;6276:24:12;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6276:24:12;;-1:-1:-1;;6276:24:12;;;-1:-1:-1;;6314:20:12;;;6310:101;;6366:1;6370:29;6350:50;;;;;;;6310:101;6429:6;-1:-1:-1;6437:20:12;;-1:-1:-1;5009:1456:12;;;;;;;;:::o;14:332:56:-;72:6;125:2;113:9;104:7;100:23;96:32;93:52;;;141:1;138;131:12;93:52;180:9;167:23;230:66;223:5;219:78;212:5;209:89;199:117;;312:1;309;302:12;639:154;725:42;718:5;714:54;707:5;704:65;694:93;;783:1;780;773:12;694:93;639:154;:::o;798:1005::-;904:6;912;920;928;936;944;997:3;985:9;976:7;972:23;968:33;965:53;;;1014:1;1011;1004:12;965:53;1053:9;1040:23;1072:31;1097:5;1072:31;:::i;:::-;1122:5;-1:-1:-1;1179:2:56;1164:18;;1151:32;1192:33;1151:32;1192:33;:::i;:::-;1244:7;-1:-1:-1;1298:2:56;1283:18;;1270:32;;-1:-1:-1;1349:2:56;1334:18;;1321:32;;-1:-1:-1;1404:3:56;1389:19;;1376:33;1428:18;1458:14;;;1455:34;;;1485:1;1482;1475:12;1455:34;1523:6;1512:9;1508:22;1498:32;;1568:7;1561:4;1557:2;1553:13;1549:27;1539:55;;1590:1;1587;1580:12;1539:55;1630:2;1617:16;1656:2;1648:6;1645:14;1642:34;;;1672:1;1669;1662:12;1642:34;1717:7;1712:2;1703:6;1699:2;1695:15;1691:24;1688:37;1685:57;;;1738:1;1735;1728:12;1685:57;1769:2;1765;1761:11;1751:21;;1791:6;1781:16;;;;;798:1005;;;;;;;;:::o;2056:838::-;2218:6;2226;2234;2287:2;2275:9;2266:7;2262:23;2258:32;2255:52;;;2303:1;2300;2293:12;2255:52;2342:9;2329:23;2361:31;2386:5;2361:31;:::i;:::-;2411:5;-1:-1:-1;2467:2:56;2452:18;;2439:32;2490:18;2520:14;;;2517:34;;;2547:1;2544;2537:12;2517:34;2570:22;;;;2626:3;2608:16;;;2604:26;2601:46;;;2643:1;2640;2633:12;2601:46;2666:2;;-1:-1:-1;2721:2:56;2706:18;;2693:32;;2737:16;;;2734:36;;;2766:1;2763;2756:12;2734:36;-1:-1:-1;2789:24:56;;2847:2;2829:16;;;2825:25;2822:45;;;2863:1;2860;2853:12;2822:45;2886:2;2876:12;;;2056:838;;;;;:::o;3081:184::-;3133:77;3130:1;3123:88;3230:4;3227:1;3220:15;3254:4;3251:1;3244:15;3270:253;3342:2;3336:9;3384:4;3372:17;;3419:18;3404:34;;3440:22;;;3401:62;3398:88;;;3466:18;;:::i;:::-;3502:2;3495:22;3270:253;:::o;3528:::-;3600:2;3594:9;3642:4;3630:17;;3677:18;3662:34;;3698:22;;;3659:62;3656:88;;;3724:18;;:::i;3786:257::-;3858:4;3852:11;;;3890:17;;3937:18;3922:34;;3958:22;;;3919:62;3916:88;;;3984:18;;:::i;4048:334::-;4119:2;4113:9;4175:2;4165:13;;-1:-1:-1;;4161:86:56;4149:99;;4278:18;4263:34;;4299:22;;;4260:62;4257:88;;;4325:18;;:::i;:::-;4361:2;4354:22;4048:334;;-1:-1:-1;4048:334:56:o;4387:195::-;4459:4;4492:18;4484:6;4481:30;4478:56;;;4514:18;;:::i;:::-;-1:-1:-1;4559:1:56;4555:14;4571:4;4551:25;;4387:195::o;4587:118::-;4673:5;4666:13;4659:21;4652:5;4649:32;4639:60;;4695:1;4692;4685:12;4710:2228;4825:6;4833;4864:2;4907;4895:9;4886:7;4882:23;4878:32;4875:52;;;4923:1;4920;4913:12;4875:52;4962:9;4949:23;4981:31;5006:5;4981:31;:::i;:::-;5031:5;-1:-1:-1;5055:2:56;5093:18;;;5080:32;5131:18;5161:14;;;5158:34;;;5188:1;5185;5178:12;5158:34;5211:22;;;;5267:4;5249:16;;;5245:27;5242:47;;;5285:1;5282;5275:12;5242:47;5313:22;;:::i;:::-;5372:2;5359:16;5384:33;5409:7;5384:33;:::i;:::-;5426:24;;5488:11;;;5475:25;5512:16;;;5509:36;;;5541:1;5538;5531:12;5509:36;5564:17;;;-1:-1:-1;5612:4:56;5604:13;;5600:27;-1:-1:-1;5590:55:56;;5641:1;5638;5631:12;5590:55;5677:2;5664:16;5700:72;5716:55;5768:2;5716:55;:::i;:::-;5700:72;:::i;:::-;5806:15;;;5868:4;5907:13;;;5899:22;;5895:31;;;5837:12;;;;5794:3;5938:19;;;5935:39;;;5970:1;5967;5960:12;5935:39;5994:11;;;;6014:728;6030:6;6025:3;6022:15;6014:728;;;6110:2;6104:3;6095:7;6091:17;6087:26;6084:116;;;6154:1;6183:2;6179;6172:14;6084:116;6228:22;;:::i;:::-;6291:3;6278:17;6308:33;6333:7;6308:33;:::i;:::-;6354:24;;6419:12;;;6406:26;6445:33;6406:26;6445:33;:::i;:::-;6498:16;;;6491:33;6565:12;;;6552:26;6591:30;6552:26;6591:30;:::i;:::-;6641:16;;;6634:33;6680:20;;6047:12;;;;6720;;;;6014:728;;;6018:3;6776:5;6771:2;6762:7;6758:16;6751:31;;;;6837:2;6833;6829:11;6816:25;6811:2;6802:7;6798:16;6791:51;6899:4;6895:2;6891:13;6878:27;6871:4;6862:7;6858:18;6851:55;6925:7;6915:17;;;;;;;4710:2228;;;;;:::o;7245:245::-;7312:6;7365:2;7353:9;7344:7;7340:23;7336:32;7333:52;;;7381:1;7378;7371:12;7333:52;7413:9;7407:16;7432:28;7454:5;7432:28;:::i;8355:184::-;8407:77;8404:1;8397:88;8504:4;8501:1;8494:15;8528:4;8525:1;8518:15;8544:128;8611:9;;;8632:11;;;8629:37;;;8646:18;;:::i;8677:184::-;8729:77;8726:1;8719:88;8826:4;8823:1;8816:15;8850:4;8847:1;8840:15;9370:2420;9740:4;9769:3;9810;9799:9;9795:19;9841:3;9830:9;9823:22;9865:6;9900;9894:13;9931:6;9923;9916:22;9957:3;9947:13;;9991:3;9980:9;9976:19;9969:26;;10054:3;10044:6;10041:1;10037:14;10026:9;10022:30;10018:40;10077:4;10116:2;10108:6;10104:15;10137:1;10158;10168:1404;10184:6;10179:3;10176:15;10168:1404;;;10253:22;;;10277:66;10249:95;10237:108;;10368:13;;10520:9;;10446:42;10516:18;;;10501:34;;10574:11;;;10568:18;10606:15;;;10599:27;;;10687:19;;10408:15;;;10719:24;;;10809:21;;;;10446:42;10854:1;;10765:15;;;;10868:430;10884:8;10879:3;10876:17;10868:430;;;10957:15;;11007:9;;11003:18;;10989:33;;11072:11;;;11066:18;11062:27;;11046:14;;;11039:51;11117:4;11181:11;;;11175:18;11168:26;11161:34;11145:14;;;11138:58;11267:17;;;;10912:1;10903:11;;;;;11233:4;11222:16;;;;10868:430;;;-1:-1:-1;11321:4:56;11368:11;;;11362:18;11345:15;;;11338:43;11404:4;11451:11;;;11445:18;11428:15;;;;11421:43;;;;-1:-1:-1;;;11550:12:56;;;;11515:15;;;;10210:1;10201:11;10168:1404;;;-1:-1:-1;;;11588:18:56;;11581:34;;;421:13;;414:21;11663:4;11648:20;;402:34;11707:22;;;11700:4;11685:20;;11678:52;9292:1;9280:14;;9326:4;9319;9310:14;;9303:28;9356:2;9347:12;;11739:45;9370:2420;-1:-1:-1;;;;;;;;;;9370:2420:56:o;11795:677::-;11860:5;11913:3;11906:4;11898:6;11894:17;11890:27;11880:55;;11931:1;11928;11921:12;11880:55;11960:6;11954:13;11986:4;12010:72;12026:55;12078:2;12026:55;:::i;12010:72::-;12104:3;12128:2;12123:3;12116:15;12156:4;12151:3;12147:14;12140:21;;12213:4;12207:2;12204:1;12200:10;12192:6;12188:23;12184:34;12170:48;;12241:3;12233:6;12230:15;12227:35;;;12258:1;12255;12248:12;12227:35;12294:4;12286:6;12282:17;12308:135;12324:6;12319:3;12316:15;12308:135;;;12390:10;;12378:23;;12421:12;;;;12341;;12308:135;;;-1:-1:-1;12461:5:56;11795:677;-1:-1:-1;;;;;;11795:677:56:o;12477:1449::-;12640:6;12648;12656;12709:2;12697:9;12688:7;12684:23;12680:32;12677:52;;;12725:1;12722;12715:12;12677:52;12758:9;12752:16;12787:18;12828:2;12820:6;12817:14;12814:34;;;12844:1;12841;12834:12;12814:34;12867:72;12931:7;12922:6;12911:9;12907:22;12867:72;:::i;:::-;12857:82;;12958:2;12948:12;;13006:2;12995:9;12991:18;12985:25;13035:2;13025:8;13022:16;13019:36;;;13051:1;13048;13041:12;13019:36;13074:24;;13129:4;13121:13;;13117:27;-1:-1:-1;13107:55:56;;13158:1;13155;13148:12;13107:55;13187:2;13181:9;13210:72;13226:55;13278:2;13226:55;:::i;13210:72::-;13316:15;;;13398:1;13394:10;;;;13386:19;;13382:28;;;13347:12;;;;13422:19;;;13419:39;;;13454:1;13451;13444:12;13419:39;13478:11;;;;13498:210;13514:6;13509:3;13506:15;13498:210;;;13587:3;13581:10;13604:31;13629:5;13604:31;:::i;:::-;13648:18;;13531:12;;;;13686;;;;13498:210;;;13778:2;13763:18;;13757:25;13727:5;;-1:-1:-1;13757:25:56;-1:-1:-1;;;;13794:16:56;;;13791:36;;;13823:1;13820;13813:12;13791:36;;13846:74;13912:7;13901:8;13890:9;13886:24;13846:74;:::i;:::-;13836:84;;;12477:1449;;;;;:::o;14241:589::-;14283:5;14336:3;14329:4;14321:6;14317:17;14313:27;14303:55;;14354:1;14351;14344:12;14303:55;14390:6;14377:20;14416:18;14412:2;14409:26;14406:52;;;14438:18;;:::i;:::-;14482:114;14590:4;-1:-1:-1;;14514:4:56;14510:2;14506:13;14502:86;14498:97;14482:114;:::i;:::-;14621:2;14612:7;14605:19;14667:3;14660:4;14655:2;14647:6;14643:15;14639:26;14636:35;14633:55;;;14684:1;14681;14674:12;14633:55;14749:2;14742:4;14734:6;14730:17;14723:4;14714:7;14710:18;14697:55;14797:1;14772:16;;;14790:4;14768:27;14761:38;;;;14776:7;14241:589;-1:-1:-1;;;14241:589:56:o;14835:2574::-;14904:5;14957:3;14950:4;14942:6;14938:17;14934:27;14924:55;;14975:1;14972;14965:12;14924:55;15011:6;14998:20;15037:4;15061:72;15077:55;15129:2;15077:55;:::i;15061:72::-;15167:15;;;15253:1;15249:10;;;;15237:23;;15233:32;;;15198:12;;;;15277:15;;;15274:35;;;15305:1;15302;15295:12;15274:35;15341:2;15333:6;15329:15;15353:2027;15369:6;15364:3;15361:15;15353:2027;;;15455:3;15442:17;15482:18;15532:2;15519:11;15516:19;15513:39;;;15548:1;15545;15538:12;15513:39;15575:24;;;;15706:4;15623:12;;;-1:-1:-1;;15619:85:56;15615:96;15612:186;;;15752:1;15781:2;15777;15770:14;15612:186;15824:22;;:::i;:::-;15895:2;15891;15887:11;15874:25;15912:33;15937:7;15912:33;:::i;:::-;15958:22;;16003:2;16047:11;;;16034:25;16075:16;;;16072:106;;;16132:1;16161:2;16157;16150:14;16072:106;16201:17;;16253:2;16245:11;;16241:21;-1:-1:-1;16231:119:56;;16304:1;16333:2;16329;16322:14;16231:119;16395:2;16391;16387:11;16374:25;16425:73;16441:56;16493:3;16441:56;:::i;16425:73::-;16542:18;;;16641:1;16637:11;;;;16629:20;;16625:29;;;16582:14;;;;16670:17;;;16667:110;;;16729:1;16759:3;16754;16747:16;16667:110;16803:11;;;;16827:174;16845:8;16838:5;16835:19;16827:174;;;16927:19;;16913:34;;16866:14;;;;16973;;;;16827:174;;;17021:14;;;17014:29;-1:-1:-1;;;17093:4:56;17085:13;;17072:27;17115:16;;;17112:109;;;17173:1;17203:3;17198;17191:16;17112:109;17257:49;17302:3;17297:2;17286:8;17282:2;17278:17;17274:26;17257:49;:::i;:::-;17241:14;;;17234:73;;;;-1:-1:-1;17320:18:56;;-1:-1:-1;;17358:12:56;;;;15386;;15353:2027;;17414:1210;17514:9;17573:4;17565:5;17549:14;17545:26;17541:37;17538:57;;;17591:1;17588;17581:12;17538:57;17619:22;;:::i;:::-;17677:5;17664:19;17702:18;17743:2;17735:6;17732:14;17729:34;;;17759:1;17756;17749:12;17729:34;17793:6;17786:5;17782:18;17772:28;;17841:4;17836:2;17820:14;17816:23;17812:34;17809:54;;;17859:1;17856;17849:12;17809:54;17887:22;;:::i;:::-;17946:2;17933:16;17958:33;17983:7;17958:33;:::i;:::-;18000:24;;18069:2;18061:11;;18048:25;18082:33;18048:25;18082:33;:::i;:::-;18144:2;18131:16;;18124:33;18203:4;18195:13;;18182:27;18221:16;;;18218:36;;;18250:1;18247;18240:12;18218:36;18290:51;18326:14;18315:8;18311:2;18307:17;18290:51;:::i;:::-;18283:4;18270:18;;18263:79;-1:-1:-1;18351:24:56;;18424:2;18413:14;;18400:28;;-1:-1:-1;18440:16:56;;;18437:36;;;18469:1;18466;18459:12;18437:36;;18507:81;18573:14;18562:8;18555:5;18551:20;18507:81;:::i;:::-;18502:2;18489:16;;18482:107;-1:-1:-1;18493:7:56;17414:1210;-1:-1:-1;;17414:1210:56:o;18629:250::-;18714:1;18724:113;18738:6;18735:1;18732:13;18724:113;;;18814:11;;;18808:18;18795:11;;;18788:39;18760:2;18753:10;18724:113;;;-1:-1:-1;;18871:1:56;18853:16;;18846:27;18629:250::o;18884:329::-;18925:3;18963:5;18957:12;18990:6;18985:3;18978:19;19006:76;19075:6;19068:4;19063:3;19059:14;19052:4;19045:5;19041:16;19006:76;:::i;:::-;19127:2;19115:15;-1:-1:-1;;19111:88:56;19102:98;;;;19202:4;19098:109;;18884:329;-1:-1:-1;;18884:329:56:o;19218:439::-;19271:3;19309:5;19303:12;19336:6;19331:3;19324:19;19362:4;19391;19386:3;19382:14;19375:21;;19430:4;19423:5;19419:16;19453:1;19463:169;19477:6;19474:1;19471:13;19463:169;;;19538:13;;19526:26;;19572:12;;;;19607:15;;;;19499:1;19492:9;19463:169;;;-1:-1:-1;19648:3:56;;19218:439;-1:-1:-1;;;;;19218:439:56:o;19662:1808::-;19802:4;19831:2;19860;19849:9;19842:21;19882:2;19919:6;19913:13;19945:4;19985;19980:2;19969:9;19965:18;19958:32;20009:42;20112:2;20097:12;20091:19;20087:28;20082:2;20071:9;20067:18;20060:56;20187:2;20181;20167:12;20163:21;20157:28;20153:37;20147:3;20136:9;20132:19;20125:66;20246:4;20232:12;20228:23;20222:30;20200:52;;20289:2;20283:3;20272:9;20268:19;20261:31;20312:53;20360:3;20349:9;20345:19;20329:14;20312:53;:::i;:::-;20301:64;;20414:2;20406:6;20402:15;20396:22;-1:-1:-1;;20566:2:56;20554:9;20549:3;20545:19;20541:28;20534:4;20523:9;20519:20;20512:58;20590:3;20622:14;20616:21;20658:6;20653:3;20646:19;20690:2;20685:3;20681:12;20674:19;;20746:2;20736:6;20733:1;20729:14;20724:3;20720:24;20716:33;20792:2;20776:14;20772:23;20758:37;;20813:1;20823:618;20837:6;20834:1;20831:13;20823:618;;;20920:2;20914:3;20906:6;20902:16;20898:25;20893:3;20886:38;20953:6;20947:13;21003:2;20998;20992:9;20988:18;20980:6;20973:34;21056:2;21052;21048:11;21042:18;21097:2;21092;21084:6;21080:15;21073:27;21127:61;21184:2;21176:6;21172:15;21156:14;21127:61;:::i;:::-;21229:11;;;21223:18;21278:19;;;21261:15;;;21254:44;21223:18;21113:75;-1:-1:-1;21321:40:56;21113:75;21223:18;21321:40;:::i;:::-;21384:15;;;;21419:12;;;;21311:50;-1:-1:-1;;;20859:1:56;20852:9;20823:618;;;-1:-1:-1;21458:6:56;19662:1808;-1:-1:-1;;;;;;;;;;;;19662:1808:56:o;21475:641::-;21605:4;21611:6;21671:11;21658:25;-1:-1:-1;;21750:8:56;21734:14;21730:29;21726:102;21706:18;21702:127;21692:155;;21843:1;21840;21833:12;21692:155;21870:33;;21922:20;;;-1:-1:-1;21965:18:56;21954:30;;21951:50;;;21997:1;21994;21987:12;21951:50;22030:4;22018:17;;-1:-1:-1;22081:1:56;22077:14;;;22061;22057:35;22047:46;;22044:66;;;22106:1;22103;22096:12;22121:393;22224:4;22282:11;22269:25;22372:66;22361:8;22345:14;22341:29;22337:102;22317:18;22313:127;22303:155;;22454:1;22451;22444:12;22303:155;22475:33;;;;;22121:393;-1:-1:-1;;22121:393:56:o;22519:383::-;22612:4;22670:11;22657:25;22760:66;22749:8;22733:14;22729:29;22725:102;22705:18;22701:127;22691:155;;22842:1;22839;22832:12;22907:629;23022:4;23028:6;23088:11;23075:25;-1:-1:-1;;23167:8:56;23151:14;23147:29;23143:102;23123:18;23119:127;23109:155;;23260:1;23257;23250:12;23109:155;23287:33;;23339:20;;;-1:-1:-1;23382:18:56;23371:30;;23368:50;;;23414:1;23411;23404:12;23368:50;23447:4;23435:17;;-1:-1:-1;23506:4:56;23494:17;;23478:14;23474:38;23464:49;;23461:69;;;23526:1;23523;23516:12;23541:247;23600:6;23653:2;23641:9;23632:7;23628:23;23624:32;23621:52;;;23669:1;23666;23659:12;23621:52;23708:9;23695:23;23727:31;23752:5;23727:31;:::i;23793:605::-;23889:5;23896:6;23956:3;23943:17;-1:-1:-1;;24027:8:56;24011:14;24007:29;24003:102;23983:18;23979:127;23969:155;;24120:1;24117;24110:12;23969:155;24148:33;;24252:4;24239:18;;;-1:-1:-1;24200:21:56;;-1:-1:-1;24280:18:56;24269:30;;24266:50;;;24312:1;24309;24302:12;24266:50;24366:6;24363:1;24359:14;24343;24339:35;24332:5;24328:47;24325:67;;;24388:1;24385;24378:12;24403:355;24474:5;24533:3;24520:17;24615:66;24604:8;24588:14;24584:29;24580:102;24560:18;24556:127;24546:155;;24697:1;24694;24687:12;24546:155;24719:33;;;;24403:355;-1:-1:-1;;24403:355:56:o;24763:559::-;24821:5;24828:6;24888:3;24875:17;-1:-1:-1;;24959:8:56;24943:14;24939:29;24935:102;24915:18;24911:127;24901:155;;25052:1;25049;25042:12;24901:155;25080:33;;25184:4;25171:18;;;-1:-1:-1;25132:21:56;;-1:-1:-1;25212:18:56;25201:30;;25198:50;;;25244:1;25241;25234:12;25198:50;25291:6;25275:14;25271:27;25264:5;25260:39;25257:59;;;25312:1;25309;25302:12;25327:325;25415:6;25410:3;25403:19;25467:6;25460:5;25453:4;25448:3;25444:14;25431:43;;25519:1;25512:4;25503:6;25498:3;25494:16;25490:27;25483:38;25385:3;25641:4;-1:-1:-1;;25566:2:56;25558:6;25554:15;25550:88;25545:3;25541:98;25537:109;25530:116;;25327:325;;;;:::o;25657:593::-;25738:5;25745:6;25805:3;25792:17;-1:-1:-1;;25876:8:56;25860:14;25856:29;25852:102;25832:18;25828:127;25818:155;;25969:1;25966;25959:12;25818:155;25997:33;;26101:4;26088:18;;;-1:-1:-1;26049:21:56;;-1:-1:-1;26129:18:56;26118:30;;26115:50;;;26161:1;26158;26151:12;26115:50;26220:4;26212:6;26208:17;26192:14;26188:38;26181:5;26177:50;26174:70;;;26240:1;26237;26230:12;26255:114;26339:4;26332:5;26328:16;26321:5;26318:27;26308:55;;26359:1;26356;26349:12;26374:830;26485:6;26480:3;26473:19;26455:3;26511:4;26540;26535:3;26531:14;26524:21;;26568:5;26591:1;26601:578;26615:6;26612:1;26609:13;26601:578;;;26692:6;26679:20;26712:33;26737:7;26712:33;:::i;:::-;26783:42;26770:56;26758:69;;26868:15;;;26855:29;26897:31;26855:29;26897:31;:::i;:::-;26975:4;26962:18;26948:12;;;26941:40;27004:4;27055:15;;;27042:29;27028:12;;;27021:51;27095:4;27119:12;;;;27154:15;;;;26637:1;26630:9;26601:578;;27209:1709;27346:19;;;27384:4;27404:12;;;;27303:3;;27435:1;27472:14;;;27461:26;;27457:35;;27515:5;27303:3;;27569:1323;27585:6;27580:3;27577:15;27569:1323;;;-1:-1:-1;;27664:5:56;27658:4;27654:16;27650:89;27645:3;27638:102;27763:58;27814:6;27807:5;27763:58;:::i;:::-;27844:4;27889:2;27876:16;27905:33;27930:7;27905:33;:::i;:::-;27977:42;27964:56;27951:70;;28068:85;28141:11;;;28145:2;28068:85;:::i;:::-;28188:2;28183;28177:4;28173:13;28166:25;28226:12;28221:2;28215:4;28211:13;28204:35;28262:3;28252:13;;28298:66;28284:12;28281:84;28278:104;;;28378:1;28375;28368:12;28278:104;28411:21;;;28473:12;28458:13;;;28445:51;28519:19;;;-1:-1:-1;28561:4:56;28616:47;28651:11;;;28655:2;28616:47;:::i;:::-;28578:85;;28717:2;28710:4;28706:2;28702:13;28698:22;28693:2;28687:4;28683:13;28676:45;28742:70;28808:2;28804;28800:11;28784:14;28768;28742:70;:::i;:::-;28870:12;;;;28734:78;-1:-1:-1;;;28835:15:56;;;;-1:-1:-1;;;27611:1:56;27602:11;27569:1323;;;-1:-1:-1;28908:4:56;;27209:1709;-1:-1:-1;;;;;;;;;27209:1709:56:o;28923:4286::-;29089:4;29118:2;29147;29136:9;29129:21;29188:3;29177:9;29173:19;29241:6;29228:20;29223:2;29212:9;29208:18;29201:48;29268:2;29331;29323:6;29319:15;29306:29;29301:2;29290:9;29286:18;29279:57;29355:2;29418;29410:6;29406:15;29393:29;29388:2;29377:9;29373:18;29366:57;29466:93;29555:2;29547:6;29543:15;29535:6;29466:93;:::i;:::-;29596:4;29590:3;29579:9;29575:19;29568:33;29621:6;29651:12;29643:6;29636:28;29695:3;29684:9;29680:19;29673:26;;29764:3;29748:12;29745:1;29741:20;29730:9;29726:36;29722:46;29708:60;;29791:12;29821:1;29831:3053;29845:12;29842:1;29839:19;29831:3053;;;29940:66;29928:9;29920:6;29916:22;29912:95;29907:3;29900:108;30060:6;30047:20;30153:66;30138:12;30122:14;30118:33;30114:106;30094:18;30090:131;30080:159;;30235:1;30232;30225:12;30080:159;30265:37;;30343:19;;30419:14;30415:26;;;30443:66;30411:99;30385:126;;30375:154;;30525:1;30522;30515:12;30375:154;30617:3;30602:19;;30557:32;;30649:21;;30683:33;30649:21;30683:33;:::i;:::-;30739:42;30832:2;30823:7;30819:16;30813:3;30805:6;30801:16;30794:42;30871:70;30937:2;30928:7;30924:16;30915:7;30871:70;:::i;:::-;30849:92;;30980:4;30973;30965:6;30961:17;30954:31;31026:14;31013:28;31054:33;31079:7;31054:33;:::i;:::-;31125:16;;31119:3;31107:16;;31100:42;31183:23;;;31170:37;31220:33;31170:37;31220:33;:::i;:::-;31291:16;31285:3;31273:16;;31266:42;31359:71;31406:23;;;31410:14;31359:71;:::i;:::-;31321:109;;31468:2;31462:3;31454:6;31450:16;31443:28;31495:75;31565:3;31557:6;31553:16;31537:14;31521;31495:75;:::i;:::-;31484:86;;;31621:80;31697:2;31688:7;31684:16;31675:7;31621:80;:::i;:::-;31724:66;31850:2;31841:6;31836:3;31832:16;31828:25;31822:3;31814:6;31810:16;31803:51;31881:85;31962:3;31946:14;31930;31881:85;:::i;:::-;31867:99;;32017:80;32093:2;32084:7;32080:16;32071:7;32017:80;:::i;:::-;31979:118;;;;32160:2;32151:6;32143;32139:19;32135:28;32129:3;32121:6;32117:16;32110:54;;32191:88;32272:6;32256:14;32240;32191:88;:::i;:::-;32177:102;;;;32343:3;32334:7;32330:17;32317:31;32311:3;32303:6;32299:16;32292:57;32410:2;32403:5;32399:14;32386:28;32381:2;32373:6;32369:15;32362:53;32476:2;32469:5;32465:14;32452:28;32447:2;32439:6;32435:15;32428:53;32532:91;32619:2;32612:5;32608:14;32601:5;32532:91;:::i;:::-;32494:129;;;;32672:6;32664;32660:19;32655:2;32647:6;32643:15;32636:44;32703:101;32797:6;32781:14;32765;32703:101;:::i;:::-;32693:111;-1:-1:-1;;;32862:12:56;;;;-1:-1:-1;32827:15:56;;;;29873:1;29866:9;29831:3053;;;29835:3;;;;;;;32931:56;32982:3;32974:6;32970:16;32962:6;32931:56;:::i;:::-;32893:94;;-1:-1:-1;;33041:9:56;33033:6;33029:22;33025:95;33018:4;33007:9;33003:20;32996:125;33138:65;33196:6;33180:14;33164;33138:65;:::i;33214:245::-;33293:6;33301;33354:2;33342:9;33333:7;33329:23;33325:32;33322:52;;;33370:1;33367;33360:12;33322:52;-1:-1:-1;;33393:16:56;;33449:2;33434:18;;;33428:25;33393:16;;33428:25;;-1:-1:-1;33214:245:56:o;34156:184::-;34226:6;34279:2;34267:9;34258:7;34254:23;34250:32;34247:52;;;34295:1;34292;34285:12;34247:52;-1:-1:-1;34318:16:56;;34156:184;-1:-1:-1;34156:184:56:o;34999:247::-;35067:6;35120:2;35108:9;35099:7;35095:23;35091:32;35088:52;;;35136:1;35133;35126:12;35088:52;35168:9;35162:16;35187:29;35210:5;35187:29;:::i;36657:1572::-;37212:42;37204:6;37200:55;37189:9;37182:74;37163:4;37275:2;37313:6;37308:2;37297:9;37293:18;37286:34;37356:3;37351:2;37340:9;37336:18;37329:31;37383:45;37423:3;37412:9;37408:19;37400:6;37383:45;:::i;:::-;37464:6;37459:2;37448:9;37444:18;37437:34;37520:9;37512:6;37508:22;37502:3;37491:9;37487:19;37480:51;37551:6;37586;37580:13;37617:6;37609;37602:22;37652:2;37644:6;37640:15;37633:22;;37711:2;37701:6;37698:1;37694:14;37686:6;37682:27;37678:36;37749:2;37741:6;37737:15;37770:1;37780:322;37794:6;37791:1;37788:13;37780:322;;;-1:-1:-1;;37871:6:56;37863;37859:19;37855:92;37850:3;37843:105;37971:51;38015:6;38006;38000:13;37971:51;:::i;:::-;38080:12;;;;37961:61;-1:-1:-1;38045:15:56;;;;37816:1;37809:9;37780:322;;;37784:3;;38151:9;38143:6;38139:22;38133:3;38122:9;38118:19;38111:51;38179:44;38216:6;38208;38179:44;:::i;:::-;38171:52;36657:1572;-1:-1:-1;;;;;;;;;;;;;36657:1572:56:o;38234:614::-;38363:6;38371;38424:2;38412:9;38403:7;38399:23;38395:32;38392:52;;;38440:1;38437;38430:12;38392:52;38473:9;38467:16;38502:18;38543:2;38535:6;38532:14;38529:34;;;38559:1;38556;38549:12;38529:34;38582:72;38646:7;38637:6;38626:9;38622:22;38582:72;:::i;:::-;38572:82;;38700:2;38689:9;38685:18;38679:25;38663:41;;38729:2;38719:8;38716:16;38713:36;;;38745:1;38742;38735:12;38713:36;;38768:74;38834:7;38823:8;38812:9;38808:24;38768:74;:::i;:::-;38758:84;;;38234:614;;;;;:::o;38853:367::-;39095:6;39084:9;39077:25;39138:2;39133;39122:9;39118:18;39111:30;39058:4;39158:56;39210:2;39199:9;39195:18;39187:6;39158:56;:::i;39225:184::-;39277:77;39274:1;39267:88;39374:4;39371:1;39364:15;39398:4;39395:1;39388:15;39414:168;39487:9;;;39518;;39535:15;;;39529:22;;39515:37;39505:71;;39556:18;;:::i;39587:476::-;39676:1;39713:5;39676:1;39727:330;39748:7;39738:8;39735:21;39727:330;;;39867:4;-1:-1:-1;;39795:77:56;39789:4;39786:87;39783:113;;;39876:18;;:::i;:::-;39926:7;39916:8;39912:22;39909:55;;;39946:16;;;;39909:55;40025:22;;;;39985:15;;;;39727:330;;;39731:3;39587:476;;;;;:::o;40068:866::-;40117:5;40147:8;40137:80;;-1:-1:-1;40188:1:56;40202:5;;40137:80;40236:4;40226:76;;-1:-1:-1;40273:1:56;40287:5;;40226:76;40318:4;40336:1;40331:59;;;;40404:1;40399:130;;;;40311:218;;40331:59;40361:1;40352:10;;40375:5;;;40399:130;40436:3;40426:8;40423:17;40420:43;;;40443:18;;:::i;:::-;-1:-1:-1;;40499:1:56;40485:16;;40514:5;;40311:218;;40613:2;40603:8;40600:16;40594:3;40588:4;40585:13;40581:36;40575:2;40565:8;40562:16;40557:2;40551:4;40548:12;40544:35;40541:77;40538:159;;;-1:-1:-1;40650:19:56;;;40682:5;;40538:159;40729:34;40754:8;40748:4;40729:34;:::i;:::-;40859:6;-1:-1:-1;;40787:79:56;40778:7;40775:92;40772:118;;;40870:18;;:::i;:::-;40908:20;;40068:866;-1:-1:-1;;;40068:866:56:o;40939:131::-;40999:5;41028:36;41055:8;41049:4;41028:36;:::i;41664:287::-;41793:3;41831:6;41825:13;41847:66;41906:6;41901:3;41894:4;41886:6;41882:17;41847:66;:::i;41956:184::-;42008:77;42005:1;41998:88;42105:4;42102:1;42095:15;42129:4;42126:1;42119:15;42503:288;42678:6;42667:9;42660:25;42721:2;42716;42705:9;42701:18;42694:30;42641:4;42741:44;42781:2;42770:9;42766:18;42758:6;42741:44;:::i;42985:219::-;43134:2;43123:9;43116:21;43097:4;43154:44;43194:2;43183:9;43179:18;43171:6;43154:44;:::i","linkReferences":{},"immutableReferences":{"6454":[{"start":315,"length":32},{"start":1864,"length":32},{"start":1945,"length":32}],"6850":[{"start":207,"length":32},{"start":668,"length":32},{"start":892,"length":32},{"start":1696,"length":32}]}},"methodIdentifiers":{"arb3(address,(uint256,uint256,uint256,((address,(address,address,bytes),(address,uint8,uint256)[],(address,uint8,uint256)[],bytes32),uint256,uint256,(address,uint256[],bytes)[])[],bytes),((address,address,bytes),(address,uint256[],bytes)[]))":"7ea0b76a","iPermit2()":"69e5049b","iTaskHash()":"93be095e","onTakeOrders(address,address,uint256,uint256,bytes)":"059bebe6","supportsInterface(bytes4)":"01ffc9a7"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"orderBook\",\"type\":\"address\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IInterpreterV3\",\"name\":\"interpreter\",\"type\":\"address\"},{\"internalType\":\"contract IInterpreterStoreV2\",\"name\":\"store\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"}],\"internalType\":\"struct EvaluableV3\",\"name\":\"evaluable\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"uint256[]\",\"name\":\"context\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct SignedContextV1[]\",\"name\":\"signedContext\",\"type\":\"tuple[]\"}],\"internalType\":\"struct TaskV1\",\"name\":\"task\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"implementationData\",\"type\":\"bytes\"}],\"internalType\":\"struct OrderBookV4ArbConfigV2\",\"name\":\"config\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"i\",\"type\":\"uint256\"}],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoOrders\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongTask\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"orderBook\",\"type\":\"address\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IInterpreterV3\",\"name\":\"interpreter\",\"type\":\"address\"},{\"internalType\":\"contract IInterpreterStoreV2\",\"name\":\"store\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"}],\"internalType\":\"struct EvaluableV3\",\"name\":\"evaluable\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"uint256[]\",\"name\":\"context\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct SignedContextV1[]\",\"name\":\"signedContext\",\"type\":\"tuple[]\"}],\"internalType\":\"struct TaskV1\",\"name\":\"task\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"implementationData\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct OrderBookV4ArbConfigV2\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"Construct\",\"type\":\"event\"},{\"stateMutability\":\"nonpayable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"contract IOrderBookV4\",\"name\":\"orderBook\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"minimumInput\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximumInput\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximumIORatio\",\"type\":\"uint256\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"contract IInterpreterV3\",\"name\":\"interpreter\",\"type\":\"address\"},{\"internalType\":\"contract IInterpreterStoreV2\",\"name\":\"store\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"}],\"internalType\":\"struct EvaluableV3\",\"name\":\"evaluable\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"vaultId\",\"type\":\"uint256\"}],\"internalType\":\"struct IO[]\",\"name\":\"validInputs\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"vaultId\",\"type\":\"uint256\"}],\"internalType\":\"struct IO[]\",\"name\":\"validOutputs\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"}],\"internalType\":\"struct OrderV3\",\"name\":\"order\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"inputIOIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"outputIOIndex\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"uint256[]\",\"name\":\"context\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct SignedContextV1[]\",\"name\":\"signedContext\",\"type\":\"tuple[]\"}],\"internalType\":\"struct TakeOrderConfigV3[]\",\"name\":\"orders\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct TakeOrdersConfigV3\",\"name\":\"takeOrders\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IInterpreterV3\",\"name\":\"interpreter\",\"type\":\"address\"},{\"internalType\":\"contract IInterpreterStoreV2\",\"name\":\"store\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"}],\"internalType\":\"struct EvaluableV3\",\"name\":\"evaluable\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"uint256[]\",\"name\":\"context\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct SignedContextV1[]\",\"name\":\"signedContext\",\"type\":\"tuple[]\"}],\"internalType\":\"struct TaskV1\",\"name\":\"task\",\"type\":\"tuple\"}],\"name\":\"arb3\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"iPermit2\",\"outputs\":[{\"internalType\":\"contract IPermit2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"iTaskHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"inputToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"outputToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"inputAmountSent\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"totalOutputAmount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"takeOrdersData\",\"type\":\"bytes\"}],\"name\":\"onTakeOrders\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"onTakeOrders(address,address,uint256,uint256,bytes)\":{\"params\":{\"inputAmountSent\":\"The amount of `inputToken` that was sent to `IOrderBookV4OrderTaker`.\",\"inputToken\":\"The token that was sent to `IOrderBookV4OrderTaker`.\",\"outputToken\":\"The token that `IOrderBookV4` will attempt to pull from `IOrderBookV4OrderTaker` after this callback returns.\",\"takeOrdersData\":\"The data passed to `takeOrders` by the caller.\",\"totalOutputAmount\":\"The total amount of `outputToken` that `IOrderBookV4` will attempt to pull from `IOrderBookV4OrderTaker` after this callback returns.\"}},\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"}},\"version\":1},\"userdoc\":{\"errors\":{\"InvalidSignature(uint256)\":[{\"notice\":\"Thrown when the ith signature from a list of signed contexts is invalid.\"}],\"NoOrders()\":[{\"notice\":\"Thrown when take orders is called with no orders.\"}],\"WrongTask()\":[{\"notice\":\"Thrown when the task does not match the expected hash.\"}]},\"kind\":\"user\",\"methods\":{\"onTakeOrders(address,address,uint256,uint256,bytes)\":{\"notice\":\"Called by `IOrderBookV4` when `takeOrders` is called with non-zero data, if it caused a non-zero input amount. I.e. if the order(s) taker received some tokens. Input and output directions are relative to the `IOrderBookV4OrderTaker` contract. If the order(s) taker had an active debt from a flash loan then that debt will be paid _before_ calculating any input amounts sent. i.e. the debt is deducted from the input amount before this callback is called.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/concrete/arb/BalancerRouterOrderBookV4ArbOrderTaker.sol\":\"BalancerRouterOrderBookV4ArbOrderTaker\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":9000},\"remappings\":[\":axelar-gmp-sdk-solidity/=lib/sushixswap-v2/lib/axelar-gmp-sdk-solidity/\",\":bytecode/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/bytecode/\",\":caller/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/caller/\",\":codegen/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/codegen/\",\":constants/=lib/rain.interpreter/src/lib/constants/\",\":deprecated/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/\",\":ds-test/=lib/sushixswap-v2/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":eval/=lib/rain.interpreter/src/lib/eval/\",\":extern/=lib/rain.interpreter/src/lib/extern/\",\":forge-gas-snapshot/=lib/sushixswap-v2/lib/forge-gas-snapshot/src/\",\":forge-std/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/forge-std/src/\",\":integrity/=lib/rain.interpreter/src/lib/integrity/\",\":ns/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/ns/\",\":op/=lib/rain.interpreter/src/lib/op/\",\":openzeppelin-contracts/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/\",\":openzeppelin/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/\",\":parse/=lib/rain.interpreter/src/lib/parse/\",\":prb-math/=lib/rain.interpreter/lib/prb-math/src/\",\":rain.datacontract/=lib/rain.interpreter/lib/rain.datacontract/src/\",\":rain.interpreter.interface/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/\",\":rain.interpreter/=lib/rain.interpreter/src/\",\":rain.intorastring/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.intorastring/src/\",\":rain.lib.hash/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.hash/src/\",\":rain.lib.memkv/=lib/rain.interpreter/lib/rain.lib.memkv/src/\",\":rain.lib.typecast/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.typecast/src/\",\":rain.math.binary/=lib/rain.interpreter/lib/rain.sol.codegen/lib/rain.math.binary/src/\",\":rain.math.fixedpoint/=lib/rain.interpreter/lib/rain.math.fixedpoint/src/\",\":rain.math.saturating/=lib/rain.interpreter/lib/rain.math.fixedpoint/lib/rain.math.saturating/src/\",\":rain.metadata/=lib/rain.interpreter/lib/rain.metadata/src/\",\":rain.orderbook.interface/=lib/rain.orderbook.interface/src/\",\":rain.sol.binmaskflag/=lib/rain.interpreter/lib/rain.sol.binmaskflag/src/\",\":rain.sol.codegen/=lib/rain.interpreter/lib/rain.sol.codegen/src/\",\":rain.solmem/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/\",\":rain.string/=lib/rain.interpreter/lib/rain.math.fixedpoint/lib/rain.string/src/\",\":state/=lib/rain.interpreter/src/lib/state/\",\":sushixswap-v2/=lib/sushixswap-v2/\"]},\"sources\":{\"lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.hash/src/LibHashNoAlloc.sol\":{\"keccak256\":\"0x1153c2d2ec53adca83c81c37ac958aab8b4d31e0e01f64f5189b1783375c0347\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://79d10d5165cf0d5cc8b28b76613fc114e74c33fcc2d5632f7e284dace2247927\",\"dweb:/ipfs/QmZEt4BQ6xsfiFNBQ55GoQq8Ats3Hb1v2hPFEWPDeqKrTv\"]},\"lib/rain.interpreter/lib/rain.math.fixedpoint/src/error/ErrScale.sol\":{\"keccak256\":\"0xf078f93e8cf6360008743854983c2c98a43f7f78b95a527473089a22b81c55a0\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://580c4ad750f0ebebdf77f87feb52bce41f2d72fa4bf9221fbe3977837eb6a4d9\",\"dweb:/ipfs/QmeYydyLSLqFT5DfFDgJBEBBeBEhwvsMi3oQ4USXXrQudF\"]},\"lib/rain.interpreter/lib/rain.math.fixedpoint/src/lib/FixedPointDecimalConstants.sol\":{\"keccak256\":\"0x9299cb9b7aeeaa2a60c6e248928bc173750c5361df4fc0e4d9ea94a778fcd6e6\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://3a65a4f0f3b7462cb0b0b6d165d3868010ff19192f53a8133e3e93be3d811d81\",\"dweb:/ipfs/QmSFWpSUH2cAcw7ph2c5rwvqcKWXTJgvabaRjk2P8ZMzcP\"]},\"lib/rain.interpreter/lib/rain.math.fixedpoint/src/lib/LibFixedPointDecimalScale.sol\":{\"keccak256\":\"0xef9a2b240ba686ee4b31c37edfbbab1a3b276db3ad269688fa2c0d1beced6c0b\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://a4e115922faa62d4c3f64c466b9e3d27f53d611c92422828c1920c37bc51acfa\",\"dweb:/ipfs/QmPa5cnqmk8eHjkdSPzegHDN9Q4jWbNV1DHeFKh5LZ6XVk\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol\":{\"keccak256\":\"0x0705a4b1b86d7b0bd8432118f226ba139c44b9dcaba0a6eafba2dd7d0639c544\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c45b821ef9e882e57c256697a152e108f0f2ad6997609af8904cae99c9bd422e\",\"dweb:/ipfs/QmRKCJW6jjzR5UYZcLpGnhEJ75UVbH6EHkEa49sWx2SKng\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol\":{\"keccak256\":\"0xa535a5df777d44e945dd24aa43a11e44b024140fc340ad0dfe42acf4002aade1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://41319e7f621f2dc3733511332c4fd032f8e32ad2aa7fd6f665c19741d9941a34\",\"dweb:/ipfs/QmcYR3bd862GD1Bc7jwrU9bGxrhUu5na1oP964bDCu2id1\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5\",\"dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5a376d3dda2cb70536c0a45c208b29b34ac560c4cb4f513a42079f96ba47d2dd\",\"dweb:/ipfs/QmZQg6gn1sUpM8wHzwNvSnihumUCAhxD119MpXeKp8B9s8\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol\":{\"keccak256\":\"0xb264c03a3442eb37a68ad620cefd1182766b58bee6cec40343480392d6b14d69\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://28879d01fd22c07b44f006612775f8577defbe459cb01685c5e25cd518c91a71\",\"dweb:/ipfs/QmVgfkwv2Fxw6hhTcDUZhE7NkoSKjab3ipM7UaRbt6uXb5\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol\":{\"keccak256\":\"0xabefac93435967b4d36a4fabcbdbb918d1f0b7ae3c3d85bc30923b326c927ed1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9d213d3befca47da33f6db0310826bcdb148299805c10d77175ecfe1d06a9a68\",\"dweb:/ipfs/QmRgCn6SP1hbBkExUADFuDo8xkT4UU47yjNF5FhCeRbQmS\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931\",\"dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f\",\"dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0x809bc3edb4bcbef8263fa616c1b60ee0004b50a8a1bfa164d8f57fd31f520c58\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8b93a1e39a4a19eba1600b92c96f435442db88cac91e315c8291547a2a7bcfe2\",\"dweb:/ipfs/QmTm34KVe6uZBZwq8dZDNWwPcm24qBJdxqL3rPxBJ4LrMv\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol\":{\"keccak256\":\"0x3af3ca86df39aac39a0514c84459d691434a108d2151c8ce9d69f32e315cab80\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://77d1f1cf302cd5e1dfbbb4ce3b281b28e8c52942d4319fce43df2e1b6f02a52d\",\"dweb:/ipfs/QmT6ZXStmK3Knhh9BokeVHQ9HXTBZNgL3Eb1ar1cYg1hWy\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d\",\"dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f\",\"dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c\",\"dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7\",\"dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/error/ErrBytes.sol\":{\"keccak256\":\"0x82df8bd5b6a2343eb41c47f9b58f05adb2f8fd92b146ddb31c0ea697ebe3acd4\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://f95976983e965460330e2a1d00f3ed0e2079e13a67f3364f1175723cd97b6a07\",\"dweb:/ipfs/QmQKhYbVSesLuomYv4XPapPMvLmXNLgSx2QtgK5EJhww5c\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/error/ErrUint256Array.sol\":{\"keccak256\":\"0xfc1739c5c406f71e1f52ebfd163162087e9025b02444ca12ae8844c71dcd1cf5\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://ae2e95f1e6260b18105369c7216e1ebc6a251ffe3548d27ce180f7885c3f6906\",\"dweb:/ipfs/QmPQEzZmQDuzp8R5rp6ABmzGD243qu8R5GX4FbHc8G4CUC\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibBytes.sol\":{\"keccak256\":\"0x46f53ecd0554926ed6ef781485f9c3d3dfe08fa5503021aac7476c28df364f30\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://d6841328d12c44dfc3e966d2372b841f27659b592556411bcb2889ed1a8db6b2\",\"dweb:/ipfs/QmQHUvQre6Jb5VtiKDvwQiff3eLSCPxRkAs8Jy81dnPbdD\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibMemCpy.sol\":{\"keccak256\":\"0x58be272366c53e5db26413754881238127a5ccedc69439d50afbb4c4f9769006\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://49d241b4acc8462d0289eafe645c20493a615c9bef629dab4f049599373df1c7\",\"dweb:/ipfs/QmYLK8fhzz5kjdaQY29yrcgEcGv31gUABvC7eHoChgYwV7\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibPointer.sol\":{\"keccak256\":\"0xdadaac57081823b3a4162aeda93195d1dbe98caf1141f6d9113302909c851307\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://fa6026d3853dd4a58faa0c9ac2dda867c200963a052a4d62e8797cd3a0e29250\",\"dweb:/ipfs/QmPRKmE5tpJKwCpy3rumAMjCqDdGK8UC2r2soB1CNQwfmJ\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibUint256Array.sol\":{\"keccak256\":\"0xbfea88bec2894f0ff0b3f8b3088f4a45f561aff1bc439165bcdb0e15fa0124ea\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://3ef8c1cf899aa39585cded6e6510e637fa5e60f40dae9e39688ecacd41ef9c41\",\"dweb:/ipfs/Qme4nJr2tCfkY9DDiMhcGjuvjy6jFGZvNgCeMD81YACEKR\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/error/ErrBytecode.sol\":{\"keccak256\":\"0x56d9f7a498877655b507ddb4683f4cdbb961bf38375d85c80ab66f8cf0aa9d03\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://0206c9b5a55818d20b2a644c2da0a5cfbbfffb82d4e49bbd0ddd5d60df19ecb8\",\"dweb:/ipfs/QmZuMWg8zqPFDVSWKZnRxrmDpr3YQE7ERSjAFLWnK4iDyd\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterCallerV3.sol\":{\"keccak256\":\"0x17a433a06d82d1fbaa497ab0ab0a3c698f86dcdbf9b3dc6814d2f64b1592bc32\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://eb06d882eab9261c1561a3f087b4eb6b29bbf919f6902859a3464d7ecd738315\",\"dweb:/ipfs/QmacaaExc2aL4xokmcBxKy2nhRYKWkEhb2DV2KNJQbMHxK\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterStoreV2.sol\":{\"keccak256\":\"0xd3fe133132e9688f0590f273b1a9851d17b794d7ed85d5cc7a4cf961220359f3\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://bd74c48f247d80804a1fcaa664b4771cbca471cb5bc29307e585d60fb4162bf1\",\"dweb:/ipfs/QmX8PF8aVeXbKsuaBnvLpsgcyjqxHUZK2wgogmNDzQC8mr\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterV3.sol\":{\"keccak256\":\"0x152209b9afda8a9fbba404563d67566584b95f49f57f321538d66995b81de300\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://84a09751006cd68411da9684379919e6f0a6b7606050392633146374b7d10141\",\"dweb:/ipfs/QmWoST14FWQr8MqGZXruDvJn5FeQrTkKZomSjazcp6msdm\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IParserV2.sol\":{\"keccak256\":\"0x9893dfb4e05bfbf088921c5cca38265388de5ef9e127ec8674a2ce6356152725\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://19bac62db62ad9cb20a9b9790208e124bcfdf89f7deeae25b3cb30f11f680724\",\"dweb:/ipfs/QmUvj4Zcj3b6ACRjcAHRW9geoLfLUXuKydcbMakaxRFPTa\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV1.sol\":{\"keccak256\":\"0x933cbfee07ddc43390326f87034613216352250472577bd890e9589e6206fad4\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://d0f947ca6eb86ae9c03806430f5c3a946882daee9f82e436067b1c42ff554595\",\"dweb:/ipfs/QmV7jK8dK6de1HDsUjKDGfEPwVdNEzDHgVHmVZKyfGv6MQ\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV2.sol\":{\"keccak256\":\"0x3f03703392207a285d41b2871c2d83df103425c1984750cc303628269e6258e5\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://a9bc96c0f288ab7b6ec1a14a42e426ec745f0b1c7fcde9d7052380c64e61c8da\",\"dweb:/ipfs/QmYBobAFw36Jd2VtFtoWNqE8HxJhy3n9Eho1fsK3uBCbwc\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV3.sol\":{\"keccak256\":\"0x231a50d32c6f910d7ae13ae1d83a221ba55eb7962380572c9c354c8a3c5c52bf\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://74b19a6c17ac7fab6edee53218a35154c5fe9ad3769ce8e22bac682dd32df0e8\",\"dweb:/ipfs/QmYiG7i4tNfA4eTynoQVbNixvGepqoAedVz2isiMfxRF8D\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterCallerV1.sol\":{\"keccak256\":\"0x20aebc5f73ffd790da90b8e88d6412f9c3a11041239c669c03e188d0a6be1e3e\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://24f85bcb1d9f52ae01f7127274100ef68640a823bf2b981f267faf71192d8235\",\"dweb:/ipfs/QmPmz7q4rsy9vWXixdG9qBB8fLb1Wjj8LWyq6wyuFQR2HK\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterCallerV2.sol\":{\"keccak256\":\"0x8ae6f8b21d5fba09db20a979e797339d4cc385450d4da8cb4de2331db638caee\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://162b488b86df5c57c8cecd6c02172122ab9d30d70dd135ea151db5d56bf06f62\",\"dweb:/ipfs/Qmf9Kd89mH6PE5hoQ6ahV1Dsi28AFbakeUUw12W9Dp3sHW\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterStoreV1.sol\":{\"keccak256\":\"0xe03c065839e972fde62d73c45acf6fc2e88f42e3a07f4255ef5bfdcd30d3b1f1\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://e766d9cfe9464f722e02320c3936fb0b98b9417663fefba6bac3dbb21007ce82\",\"dweb:/ipfs/QmX88DnazhZw7B3Fxp1iDZXogi2k6dyMPvh2vXpXep11oQ\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterV1.sol\":{\"keccak256\":\"0x8986cb9ba93034751b21245398bc1ef7a55fe10d34b5d4cc4207ee9d39a3e616\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://dc7d8e706e96f0d0e53599582fadd591581484ac36921b0e4c6454c54b568c2b\",\"dweb:/ipfs/QmNczMgGQKWJiDhXVbtXeBjUEtzFtr7k41uPjiU8P6BrHT\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterV2.sol\":{\"keccak256\":\"0x0a9223e817a36a9717b91f977a9fce5e698afd393aa2405979baf53a12339805\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://1537576666b7462485e898ddbd36848be401b3c1e3f8317eca4112d1b21d4e74\",\"dweb:/ipfs/QmZYRLbnctnMKEZ4UYz9oZ9MFoqQy1hex29HrXPVBX5U1g\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IParserV1.sol\":{\"keccak256\":\"0x19e1a217e8c67c671e319dd0e5608d7c496fbfc02363fbb2d08626715625dd55\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://603b3234a5c084acb12d0b7e3909503869f9a6d860f91d86692e007f67aba25b\",\"dweb:/ipfs/QmU7i9uyr9qWFzD2xjn1qjgiPjb8vUjuXA6dHEuGLVkmPj\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/bytecode/LibBytecode.sol\":{\"keccak256\":\"0xdebdca9d3d2a6c1be927f6fa1f28550cd7885aa52abd378ddb65e5f6495ef87c\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://5ca7fe490fe2b1287c3c231ec34daa504b3b36fa6c3e69aacda9737db1e1b548\",\"dweb:/ipfs/QmQozWFz6qFJDiRFS1YgPWCsRZgCCQ3yEzmwTVyiCs7SxY\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/caller/LibContext.sol\":{\"keccak256\":\"0xd2cfeec3582d7b0b2a09573e258230ec4a6de169e60d9078f674a7a09d0c7b2d\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://cf266d36372575856000331ebb8dca7d6025db6365d14f4c876ab55305090aba\",\"dweb:/ipfs/QmYyoYFXryPM2SRMafgUgsBbAXPeCmzLFvzhHGR3i47zHy\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/caller/LibEvaluable.sol\":{\"keccak256\":\"0x922f6df3f98bc94869b9c244ae349e4ecfe0f250937414af7f3699e016b9f57e\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://e4fd2f59466c83a6e5dfe843951880d3f1dbe98cf559746242d942ab20962166\",\"dweb:/ipfs/QmTTD9vj8ph7ocmWFAjPb4D4UiwusCg7iZEPWoRfVtEEu1\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/deprecated/caller/LibEncodedDispatch.sol\":{\"keccak256\":\"0x0d9b888a01c5d508dd3cdfd53823ad56c6e16e06d18ca8b2ac68e82b102cc60a\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://6f854a3243d04aa76169367843a97bec51574f01ddb1e31b271f8aa93e184dce\",\"dweb:/ipfs/QmRc85MSQBbqvCvdcZBHvBrA8FFfVbxYWHbNV2vshmLF95\"]},\"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/ns/LibNamespace.sol\":{\"keccak256\":\"0x5e1f5a0e05fbadf8acfad550ed59d960dd05ebc1d74cfa676f22a26644cc0ee5\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://ee31bfc695ae5dc841379c3164b345bb0951b8b10fd94f300d5bb2de51dd7a1e\",\"dweb:/ipfs/QmT1CqR7Lpj2bEJADgEPgY9cmTqgKLEZm1QLbZxfHEd8PY\"]},\"lib/rain.orderbook.interface/src/interface/IOrderBookV4.sol\":{\"keccak256\":\"0x715c5211361bd469a9ec916710611536d087ae5630a3fc3f80e9802ec43c4596\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://c61433f7de82ac5fbd752470aa1b9358dc70455ba85be87a9056509188f5db21\",\"dweb:/ipfs/QmaGvPxe4xpqkVMdq9mo2moJyBHB9f8wTkcxZotk7tevVH\"]},\"lib/rain.orderbook.interface/src/interface/IOrderBookV4OrderTaker.sol\":{\"keccak256\":\"0x7d197bd91214d318ce6a9ee388228494606ba7bdae36bbc6ec6ed1681ff60bd8\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://73caf415d34e06a83d104cbccf98ae76ccd522e5ae1fb32c2bb18af9bae7ccaa\",\"dweb:/ipfs/QmYbiDgjfjpX6pvjawqhtQpiTkUuX24ncHodheJPtSg4d7\"]},\"lib/rain.orderbook.interface/src/interface/deprecated/v2/IOrderBookV2.sol\":{\"keccak256\":\"0x30c528f1772e9593b5329f1458cab4cce3510a2ccca01696b506af177fffd786\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://9f53c14e5aaab2cd2fbf5b39d07c59acfdc09aff3476345816c2635de2cdd5d3\",\"dweb:/ipfs/QmaomXfmDKdoeYwbMe9cy5anx4fDEMtP4W9Rre5s5DZVE6\"]},\"lib/rain.orderbook.interface/src/interface/deprecated/v3/IOrderBookV3.sol\":{\"keccak256\":\"0xdca3fd0c6106ab98d08682e63e433b74250d1f01e646a5908c97183d9b25130b\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://0f986a8388275a4144068981aa628e46bff221c2d27a3ad597a9c5fc2d4fcb09\",\"dweb:/ipfs/QmR2pxkpY4ddhLb7jdoNAVSJwYHokyGpn5Tzg3Q7YnGYgW\"]},\"lib/rain.orderbook.interface/src/interface/ierc3156/IERC3156FlashBorrower.sol\":{\"keccak256\":\"0xdcd81b1110e9c3c91715ac90aef1da70066bcaba8aeb3dc23df979f7f87644f9\",\"license\":\"CC0-1.0\",\"urls\":[\"bzz-raw://4572efaeb9eb30ad9b766b80cb2a8101a2cec0c9e8c78c7696f4aae710c8e2c9\",\"dweb:/ipfs/QmVsz1gtqWfxTAbsoLPvZBe6PBVXqaWNx6nu7RHr2ghLuK\"]},\"lib/rain.orderbook.interface/src/interface/ierc3156/IERC3156FlashLender.sol\":{\"keccak256\":\"0x119c477281a6ec109e9c729a254b39d7ccc9a9f5baa82fa28d0fa1e3bd2b6ae8\",\"license\":\"CC0-1.0\",\"urls\":[\"bzz-raw://19ec8289a123459659dca0389a650ca6bf6bc12d925ad7a3e61f0b1bfe68d1b6\",\"dweb:/ipfs/QmRZ9U5ECovPFmbRaBTauFjjVQcvPA7VQL7S1zJVEBTMD5\"]},\"lib/rain.orderbook.interface/src/interface/unstable/IOrderBookV4ArbOrderTakerV2.sol\":{\"keccak256\":\"0x271d4a0fdeddc07a88b18fa52f39c840515e572d62a755f7710bbd1b20ebe389\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://64000bb3455809c1eb2a8789023286ad9ccaa2cdfe014cff480e96bb1b7ad6a3\",\"dweb:/ipfs/QmRDgNHR4mYjxvHJUNMaaDZdAnxZ7yDF9kCL3aaSYip6AH\"]},\"lib/sushixswap-v2/src/interfaces/IRouteProcessor.sol\":{\"keccak256\":\"0x5b2289c992f34dd25943db3cbc27ece8b0a1addeb2631f812e7834b7ae3eac37\",\"license\":\"GPL-3.0-or-later\",\"urls\":[\"bzz-raw://f21522058b7fe3c18d2353b06a5ddbf2b62d2aceebf7dd6dfdc51b081452bb3f\",\"dweb:/ipfs/QmVkicGnNWgnX1RfXJWPpQaAPbiAQfFwGWigbPi1wu2WiT\"]},\"src/abstract/OrderBookV4ArbCommon.sol\":{\"keccak256\":\"0xe24cce13b3e21ae36c0aae11380128c5f0ddf542a0298f1a1845a39845e10576\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://5f5d04c151a7e9fe4a659cef55abc4a626bc2ba0e58edbd714dcbadb52f4b396\",\"dweb:/ipfs/QmZHQiYBNdxMJWoMjHnsqoj2YszvFMWrGYqDcodKpwyv98\"]},\"src/abstract/OrderBookV4ArbOrderTaker.sol\":{\"keccak256\":\"0xa419a3d91a8c362dad1bea2761e6e0218c96ff295064aad1f5548444977d7896\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://0995be2430baeccbf24ead130e100f6d19a354b2703d65a3544a9bf516ff4c6c\",\"dweb:/ipfs/QmVaKDPVFe8U4MxnR3vwr7yLPrxo6hzYEud4XZzkHShsMu\"]},\"src/concrete/arb/BalancerRouterOrderBookV4ArbOrderTaker.sol\":{\"keccak256\":\"0x2591b8fcde79fa4a27854fbbb09822d1aa504df69561233223d992d473e06cf2\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://4b2f128a405c55af13e0e5fa0e18db0a7aacc89a2439f3d73ed631eb16e9684b\",\"dweb:/ipfs/QmeQSZhZy49SRHRbtQHWahDo4GsAbUU1pQDv8vskJpao4n\"]},\"src/lib/LibOrderBook.sol\":{\"keccak256\":\"0xa33a7731cde01162cc5d5a1d28b96a17626a6919971620850f92cc32a20342cf\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://9bcbc37dd34292bca2ebdc620b418fbfd8b65039a3d040512f9c8d091ed1f2bf\",\"dweb:/ipfs/QmQEBw7n6QKuzF1BMtxyYcZTk8eK4Hed8QVEwuQtUUJ4AM\"]},\"src/lib/LibOrderBookArb.sol\":{\"keccak256\":\"0xe6ba0ae436fbb48193585beaaac8fc9b367ec9f511281b55e6cbc354c7faae90\",\"license\":\"LicenseRef-DCL-1.0\",\"urls\":[\"bzz-raw://2b69af34b0acdabe929170984243f7a04581df75010d790147b86f72bc6411c2\",\"dweb:/ipfs/QmZAYyTLrfB5w3uintmQp1Au32YyVrknfqcd6hLLvf1RKk\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"struct OrderBookV4ArbConfigV2","name":"config","type":"tuple","components":[{"internalType":"address","name":"orderBook","type":"address"},{"internalType":"struct TaskV1","name":"task","type":"tuple","components":[{"internalType":"struct EvaluableV3","name":"evaluable","type":"tuple","components":[{"internalType":"contract IInterpreterV3","name":"interpreter","type":"address"},{"internalType":"contract IInterpreterStoreV2","name":"store","type":"address"},{"internalType":"bytes","name":"bytecode","type":"bytes"}]},{"internalType":"struct SignedContextV1[]","name":"signedContext","type":"tuple[]","components":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256[]","name":"context","type":"uint256[]"},{"internalType":"bytes","name":"signature","type":"bytes"}]}]},{"internalType":"bytes","name":"implementationData","type":"bytes"}]}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"i","type":"uint256"}],"type":"error","name":"InvalidSignature"},{"inputs":[],"type":"error","name":"NoOrders"},{"inputs":[],"type":"error","name":"WrongTask"},{"inputs":[{"internalType":"address","name":"sender","type":"address","indexed":false},{"internalType":"struct OrderBookV4ArbConfigV2","name":"config","type":"tuple","components":[{"internalType":"address","name":"orderBook","type":"address"},{"internalType":"struct TaskV1","name":"task","type":"tuple","components":[{"internalType":"struct EvaluableV3","name":"evaluable","type":"tuple","components":[{"internalType":"contract IInterpreterV3","name":"interpreter","type":"address"},{"internalType":"contract IInterpreterStoreV2","name":"store","type":"address"},{"internalType":"bytes","name":"bytecode","type":"bytes"}]},{"internalType":"struct SignedContextV1[]","name":"signedContext","type":"tuple[]","components":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256[]","name":"context","type":"uint256[]"},{"internalType":"bytes","name":"signature","type":"bytes"}]}]},{"internalType":"bytes","name":"implementationData","type":"bytes"}],"indexed":false}],"type":"event","name":"Construct","anonymous":false},{"inputs":[],"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"contract IOrderBookV4","name":"orderBook","type":"address"},{"internalType":"struct TakeOrdersConfigV3","name":"takeOrders","type":"tuple","components":[{"internalType":"uint256","name":"minimumInput","type":"uint256"},{"internalType":"uint256","name":"maximumInput","type":"uint256"},{"internalType":"uint256","name":"maximumIORatio","type":"uint256"},{"internalType":"struct TakeOrderConfigV3[]","name":"orders","type":"tuple[]","components":[{"internalType":"struct OrderV3","name":"order","type":"tuple","components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"struct EvaluableV3","name":"evaluable","type":"tuple","components":[{"internalType":"contract IInterpreterV3","name":"interpreter","type":"address"},{"internalType":"contract IInterpreterStoreV2","name":"store","type":"address"},{"internalType":"bytes","name":"bytecode","type":"bytes"}]},{"internalType":"struct IO[]","name":"validInputs","type":"tuple[]","components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"vaultId","type":"uint256"}]},{"internalType":"struct IO[]","name":"validOutputs","type":"tuple[]","components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"vaultId","type":"uint256"}]},{"internalType":"bytes32","name":"nonce","type":"bytes32"}]},{"internalType":"uint256","name":"inputIOIndex","type":"uint256"},{"internalType":"uint256","name":"outputIOIndex","type":"uint256"},{"internalType":"struct SignedContextV1[]","name":"signedContext","type":"tuple[]","components":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256[]","name":"context","type":"uint256[]"},{"internalType":"bytes","name":"signature","type":"bytes"}]}]},{"internalType":"bytes","name":"data","type":"bytes"}]},{"internalType":"struct TaskV1","name":"task","type":"tuple","components":[{"internalType":"struct EvaluableV3","name":"evaluable","type":"tuple","components":[{"internalType":"contract IInterpreterV3","name":"interpreter","type":"address"},{"internalType":"contract IInterpreterStoreV2","name":"store","type":"address"},{"internalType":"bytes","name":"bytecode","type":"bytes"}]},{"internalType":"struct SignedContextV1[]","name":"signedContext","type":"tuple[]","components":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256[]","name":"context","type":"uint256[]"},{"internalType":"bytes","name":"signature","type":"bytes"}]}]}],"stateMutability":"payable","type":"function","name":"arb3"},{"inputs":[],"stateMutability":"view","type":"function","name":"iPermit2","outputs":[{"internalType":"contract IPermit2","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"iTaskHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"address","name":"outputToken","type":"address"},{"internalType":"uint256","name":"inputAmountSent","type":"uint256"},{"internalType":"uint256","name":"totalOutputAmount","type":"uint256"},{"internalType":"bytes","name":"takeOrdersData","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"onTakeOrders"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"onTakeOrders(address,address,uint256,uint256,bytes)":{"params":{"inputAmountSent":"The amount of `inputToken` that was sent to `IOrderBookV4OrderTaker`.","inputToken":"The token that was sent to `IOrderBookV4OrderTaker`.","outputToken":"The token that `IOrderBookV4` will attempt to pull from `IOrderBookV4OrderTaker` after this callback returns.","takeOrdersData":"The data passed to `takeOrders` by the caller.","totalOutputAmount":"The total amount of `outputToken` that `IOrderBookV4` will attempt to pull from `IOrderBookV4OrderTaker` after this callback returns."}},"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"version":1},"userdoc":{"kind":"user","methods":{"onTakeOrders(address,address,uint256,uint256,bytes)":{"notice":"Called by `IOrderBookV4` when `takeOrders` is called with non-zero data, if it caused a non-zero input amount. I.e. if the order(s) taker received some tokens. Input and output directions are relative to the `IOrderBookV4OrderTaker` contract. If the order(s) taker had an active debt from a flash loan then that debt will be paid _before_ calculating any input amounts sent. i.e. the debt is deducted from the input amount before this callback is called."}},"version":1}},"settings":{"remappings":["axelar-gmp-sdk-solidity/=lib/sushixswap-v2/lib/axelar-gmp-sdk-solidity/","bytecode/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/bytecode/","caller/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/caller/","codegen/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/codegen/","constants/=lib/rain.interpreter/src/lib/constants/","deprecated/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/","ds-test/=lib/sushixswap-v2/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/openzeppelin-contracts/lib/erc4626-tests/","eval/=lib/rain.interpreter/src/lib/eval/","extern/=lib/rain.interpreter/src/lib/extern/","forge-gas-snapshot/=lib/sushixswap-v2/lib/forge-gas-snapshot/src/","forge-std/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/forge-std/src/","integrity/=lib/rain.interpreter/src/lib/integrity/","ns/=lib/rain.interpreter/lib/rain.interpreter.interface/src/lib/ns/","op/=lib/rain.interpreter/src/lib/op/","openzeppelin-contracts/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/","openzeppelin/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/","parse/=lib/rain.interpreter/src/lib/parse/","prb-math/=lib/rain.interpreter/lib/prb-math/src/","rain.datacontract/=lib/rain.interpreter/lib/rain.datacontract/src/","rain.interpreter.interface/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/","rain.interpreter/=lib/rain.interpreter/src/","rain.intorastring/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.intorastring/src/","rain.lib.hash/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.hash/src/","rain.lib.memkv/=lib/rain.interpreter/lib/rain.lib.memkv/src/","rain.lib.typecast/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.typecast/src/","rain.math.binary/=lib/rain.interpreter/lib/rain.sol.codegen/lib/rain.math.binary/src/","rain.math.fixedpoint/=lib/rain.interpreter/lib/rain.math.fixedpoint/src/","rain.math.saturating/=lib/rain.interpreter/lib/rain.math.fixedpoint/lib/rain.math.saturating/src/","rain.metadata/=lib/rain.interpreter/lib/rain.metadata/src/","rain.orderbook.interface/=lib/rain.orderbook.interface/src/","rain.sol.binmaskflag/=lib/rain.interpreter/lib/rain.sol.binmaskflag/src/","rain.sol.codegen/=lib/rain.interpreter/lib/rain.sol.codegen/src/","rain.solmem/=lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/","rain.string/=lib/rain.interpreter/lib/rain.math.fixedpoint/lib/rain.string/src/","state/=lib/rain.interpreter/src/lib/state/","sushixswap-v2/=lib/sushixswap-v2/"],"optimizer":{"enabled":true,"runs":9000},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"src/concrete/arb/BalancerRouterOrderBookV4ArbOrderTaker.sol":"BalancerRouterOrderBookV4ArbOrderTaker"},"evmVersion":"paris","libraries":{}},"sources":{"lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.lib.hash/src/LibHashNoAlloc.sol":{"keccak256":"0x1153c2d2ec53adca83c81c37ac958aab8b4d31e0e01f64f5189b1783375c0347","urls":["bzz-raw://79d10d5165cf0d5cc8b28b76613fc114e74c33fcc2d5632f7e284dace2247927","dweb:/ipfs/QmZEt4BQ6xsfiFNBQ55GoQq8Ats3Hb1v2hPFEWPDeqKrTv"],"license":"LicenseRef-DCL-1.0"},"lib/rain.interpreter/lib/rain.math.fixedpoint/src/error/ErrScale.sol":{"keccak256":"0xf078f93e8cf6360008743854983c2c98a43f7f78b95a527473089a22b81c55a0","urls":["bzz-raw://580c4ad750f0ebebdf77f87feb52bce41f2d72fa4bf9221fbe3977837eb6a4d9","dweb:/ipfs/QmeYydyLSLqFT5DfFDgJBEBBeBEhwvsMi3oQ4USXXrQudF"],"license":"LicenseRef-DCL-1.0"},"lib/rain.interpreter/lib/rain.math.fixedpoint/src/lib/FixedPointDecimalConstants.sol":{"keccak256":"0x9299cb9b7aeeaa2a60c6e248928bc173750c5361df4fc0e4d9ea94a778fcd6e6","urls":["bzz-raw://3a65a4f0f3b7462cb0b0b6d165d3868010ff19192f53a8133e3e93be3d811d81","dweb:/ipfs/QmSFWpSUH2cAcw7ph2c5rwvqcKWXTJgvabaRjk2P8ZMzcP"],"license":"LicenseRef-DCL-1.0"},"lib/rain.interpreter/lib/rain.math.fixedpoint/src/lib/LibFixedPointDecimalScale.sol":{"keccak256":"0xef9a2b240ba686ee4b31c37edfbbab1a3b276db3ad269688fa2c0d1beced6c0b","urls":["bzz-raw://a4e115922faa62d4c3f64c466b9e3d27f53d611c92422828c1920c37bc51acfa","dweb:/ipfs/QmPa5cnqmk8eHjkdSPzegHDN9Q4jWbNV1DHeFKh5LZ6XVk"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol":{"keccak256":"0x0705a4b1b86d7b0bd8432118f226ba139c44b9dcaba0a6eafba2dd7d0639c544","urls":["bzz-raw://c45b821ef9e882e57c256697a152e108f0f2ad6997609af8904cae99c9bd422e","dweb:/ipfs/QmRKCJW6jjzR5UYZcLpGnhEJ75UVbH6EHkEa49sWx2SKng"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol":{"keccak256":"0xa535a5df777d44e945dd24aa43a11e44b024140fc340ad0dfe42acf4002aade1","urls":["bzz-raw://41319e7f621f2dc3733511332c4fd032f8e32ad2aa7fd6f665c19741d9941a34","dweb:/ipfs/QmcYR3bd862GD1Bc7jwrU9bGxrhUu5na1oP964bDCu2id1"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305","urls":["bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5","dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol":{"keccak256":"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca","urls":["bzz-raw://5a376d3dda2cb70536c0a45c208b29b34ac560c4cb4f513a42079f96ba47d2dd","dweb:/ipfs/QmZQg6gn1sUpM8wHzwNvSnihumUCAhxD119MpXeKp8B9s8"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol":{"keccak256":"0xb264c03a3442eb37a68ad620cefd1182766b58bee6cec40343480392d6b14d69","urls":["bzz-raw://28879d01fd22c07b44f006612775f8577defbe459cb01685c5e25cd518c91a71","dweb:/ipfs/QmVgfkwv2Fxw6hhTcDUZhE7NkoSKjab3ipM7UaRbt6uXb5"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol":{"keccak256":"0xabefac93435967b4d36a4fabcbdbb918d1f0b7ae3c3d85bc30923b326c927ed1","urls":["bzz-raw://9d213d3befca47da33f6db0310826bcdb148299805c10d77175ecfe1d06a9a68","dweb:/ipfs/QmRgCn6SP1hbBkExUADFuDo8xkT4UU47yjNF5FhCeRbQmS"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa","urls":["bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931","dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/Strings.sol":{"keccak256":"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0","urls":["bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f","dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol":{"keccak256":"0x809bc3edb4bcbef8263fa616c1b60ee0004b50a8a1bfa164d8f57fd31f520c58","urls":["bzz-raw://8b93a1e39a4a19eba1600b92c96f435442db88cac91e315c8291547a2a7bcfe2","dweb:/ipfs/QmTm34KVe6uZBZwq8dZDNWwPcm24qBJdxqL3rPxBJ4LrMv"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol":{"keccak256":"0x3af3ca86df39aac39a0514c84459d691434a108d2151c8ce9d69f32e315cab80","urls":["bzz-raw://77d1f1cf302cd5e1dfbbb4ce3b281b28e8c52942d4319fce43df2e1b6f02a52d","dweb:/ipfs/QmT6ZXStmK3Knhh9BokeVHQ9HXTBZNgL3Eb1ar1cYg1hWy"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol":{"keccak256":"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b","urls":["bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d","dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"],"license":"MIT"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/error/ErrBytes.sol":{"keccak256":"0x82df8bd5b6a2343eb41c47f9b58f05adb2f8fd92b146ddb31c0ea697ebe3acd4","urls":["bzz-raw://f95976983e965460330e2a1d00f3ed0e2079e13a67f3364f1175723cd97b6a07","dweb:/ipfs/QmQKhYbVSesLuomYv4XPapPMvLmXNLgSx2QtgK5EJhww5c"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/error/ErrUint256Array.sol":{"keccak256":"0xfc1739c5c406f71e1f52ebfd163162087e9025b02444ca12ae8844c71dcd1cf5","urls":["bzz-raw://ae2e95f1e6260b18105369c7216e1ebc6a251ffe3548d27ce180f7885c3f6906","dweb:/ipfs/QmPQEzZmQDuzp8R5rp6ABmzGD243qu8R5GX4FbHc8G4CUC"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibBytes.sol":{"keccak256":"0x46f53ecd0554926ed6ef781485f9c3d3dfe08fa5503021aac7476c28df364f30","urls":["bzz-raw://d6841328d12c44dfc3e966d2372b841f27659b592556411bcb2889ed1a8db6b2","dweb:/ipfs/QmQHUvQre6Jb5VtiKDvwQiff3eLSCPxRkAs8Jy81dnPbdD"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibMemCpy.sol":{"keccak256":"0x58be272366c53e5db26413754881238127a5ccedc69439d50afbb4c4f9769006","urls":["bzz-raw://49d241b4acc8462d0289eafe645c20493a615c9bef629dab4f049599373df1c7","dweb:/ipfs/QmYLK8fhzz5kjdaQY29yrcgEcGv31gUABvC7eHoChgYwV7"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibPointer.sol":{"keccak256":"0xdadaac57081823b3a4162aeda93195d1dbe98caf1141f6d9113302909c851307","urls":["bzz-raw://fa6026d3853dd4a58faa0c9ac2dda867c200963a052a4d62e8797cd3a0e29250","dweb:/ipfs/QmPRKmE5tpJKwCpy3rumAMjCqDdGK8UC2r2soB1CNQwfmJ"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/lib/rain.solmem/src/lib/LibUint256Array.sol":{"keccak256":"0xbfea88bec2894f0ff0b3f8b3088f4a45f561aff1bc439165bcdb0e15fa0124ea","urls":["bzz-raw://3ef8c1cf899aa39585cded6e6510e637fa5e60f40dae9e39688ecacd41ef9c41","dweb:/ipfs/Qme4nJr2tCfkY9DDiMhcGjuvjy6jFGZvNgCeMD81YACEKR"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/error/ErrBytecode.sol":{"keccak256":"0x56d9f7a498877655b507ddb4683f4cdbb961bf38375d85c80ab66f8cf0aa9d03","urls":["bzz-raw://0206c9b5a55818d20b2a644c2da0a5cfbbfffb82d4e49bbd0ddd5d60df19ecb8","dweb:/ipfs/QmZuMWg8zqPFDVSWKZnRxrmDpr3YQE7ERSjAFLWnK4iDyd"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterCallerV3.sol":{"keccak256":"0x17a433a06d82d1fbaa497ab0ab0a3c698f86dcdbf9b3dc6814d2f64b1592bc32","urls":["bzz-raw://eb06d882eab9261c1561a3f087b4eb6b29bbf919f6902859a3464d7ecd738315","dweb:/ipfs/QmacaaExc2aL4xokmcBxKy2nhRYKWkEhb2DV2KNJQbMHxK"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterStoreV2.sol":{"keccak256":"0xd3fe133132e9688f0590f273b1a9851d17b794d7ed85d5cc7a4cf961220359f3","urls":["bzz-raw://bd74c48f247d80804a1fcaa664b4771cbca471cb5bc29307e585d60fb4162bf1","dweb:/ipfs/QmX8PF8aVeXbKsuaBnvLpsgcyjqxHUZK2wgogmNDzQC8mr"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IInterpreterV3.sol":{"keccak256":"0x152209b9afda8a9fbba404563d67566584b95f49f57f321538d66995b81de300","urls":["bzz-raw://84a09751006cd68411da9684379919e6f0a6b7606050392633146374b7d10141","dweb:/ipfs/QmWoST14FWQr8MqGZXruDvJn5FeQrTkKZomSjazcp6msdm"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/IParserV2.sol":{"keccak256":"0x9893dfb4e05bfbf088921c5cca38265388de5ef9e127ec8674a2ce6356152725","urls":["bzz-raw://19bac62db62ad9cb20a9b9790208e124bcfdf89f7deeae25b3cb30f11f680724","dweb:/ipfs/QmUvj4Zcj3b6ACRjcAHRW9geoLfLUXuKydcbMakaxRFPTa"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV1.sol":{"keccak256":"0x933cbfee07ddc43390326f87034613216352250472577bd890e9589e6206fad4","urls":["bzz-raw://d0f947ca6eb86ae9c03806430f5c3a946882daee9f82e436067b1c42ff554595","dweb:/ipfs/QmV7jK8dK6de1HDsUjKDGfEPwVdNEzDHgVHmVZKyfGv6MQ"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV2.sol":{"keccak256":"0x3f03703392207a285d41b2871c2d83df103425c1984750cc303628269e6258e5","urls":["bzz-raw://a9bc96c0f288ab7b6ec1a14a42e426ec745f0b1c7fcde9d7052380c64e61c8da","dweb:/ipfs/QmYBobAFw36Jd2VtFtoWNqE8HxJhy3n9Eho1fsK3uBCbwc"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IExpressionDeployerV3.sol":{"keccak256":"0x231a50d32c6f910d7ae13ae1d83a221ba55eb7962380572c9c354c8a3c5c52bf","urls":["bzz-raw://74b19a6c17ac7fab6edee53218a35154c5fe9ad3769ce8e22bac682dd32df0e8","dweb:/ipfs/QmYiG7i4tNfA4eTynoQVbNixvGepqoAedVz2isiMfxRF8D"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterCallerV1.sol":{"keccak256":"0x20aebc5f73ffd790da90b8e88d6412f9c3a11041239c669c03e188d0a6be1e3e","urls":["bzz-raw://24f85bcb1d9f52ae01f7127274100ef68640a823bf2b981f267faf71192d8235","dweb:/ipfs/QmPmz7q4rsy9vWXixdG9qBB8fLb1Wjj8LWyq6wyuFQR2HK"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterCallerV2.sol":{"keccak256":"0x8ae6f8b21d5fba09db20a979e797339d4cc385450d4da8cb4de2331db638caee","urls":["bzz-raw://162b488b86df5c57c8cecd6c02172122ab9d30d70dd135ea151db5d56bf06f62","dweb:/ipfs/Qmf9Kd89mH6PE5hoQ6ahV1Dsi28AFbakeUUw12W9Dp3sHW"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterStoreV1.sol":{"keccak256":"0xe03c065839e972fde62d73c45acf6fc2e88f42e3a07f4255ef5bfdcd30d3b1f1","urls":["bzz-raw://e766d9cfe9464f722e02320c3936fb0b98b9417663fefba6bac3dbb21007ce82","dweb:/ipfs/QmX88DnazhZw7B3Fxp1iDZXogi2k6dyMPvh2vXpXep11oQ"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterV1.sol":{"keccak256":"0x8986cb9ba93034751b21245398bc1ef7a55fe10d34b5d4cc4207ee9d39a3e616","urls":["bzz-raw://dc7d8e706e96f0d0e53599582fadd591581484ac36921b0e4c6454c54b568c2b","dweb:/ipfs/QmNczMgGQKWJiDhXVbtXeBjUEtzFtr7k41uPjiU8P6BrHT"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IInterpreterV2.sol":{"keccak256":"0x0a9223e817a36a9717b91f977a9fce5e698afd393aa2405979baf53a12339805","urls":["bzz-raw://1537576666b7462485e898ddbd36848be401b3c1e3f8317eca4112d1b21d4e74","dweb:/ipfs/QmZYRLbnctnMKEZ4UYz9oZ9MFoqQy1hex29HrXPVBX5U1g"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/interface/deprecated/IParserV1.sol":{"keccak256":"0x19e1a217e8c67c671e319dd0e5608d7c496fbfc02363fbb2d08626715625dd55","urls":["bzz-raw://603b3234a5c084acb12d0b7e3909503869f9a6d860f91d86692e007f67aba25b","dweb:/ipfs/QmU7i9uyr9qWFzD2xjn1qjgiPjb8vUjuXA6dHEuGLVkmPj"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/bytecode/LibBytecode.sol":{"keccak256":"0xdebdca9d3d2a6c1be927f6fa1f28550cd7885aa52abd378ddb65e5f6495ef87c","urls":["bzz-raw://5ca7fe490fe2b1287c3c231ec34daa504b3b36fa6c3e69aacda9737db1e1b548","dweb:/ipfs/QmQozWFz6qFJDiRFS1YgPWCsRZgCCQ3yEzmwTVyiCs7SxY"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/caller/LibContext.sol":{"keccak256":"0xd2cfeec3582d7b0b2a09573e258230ec4a6de169e60d9078f674a7a09d0c7b2d","urls":["bzz-raw://cf266d36372575856000331ebb8dca7d6025db6365d14f4c876ab55305090aba","dweb:/ipfs/QmYyoYFXryPM2SRMafgUgsBbAXPeCmzLFvzhHGR3i47zHy"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/caller/LibEvaluable.sol":{"keccak256":"0x922f6df3f98bc94869b9c244ae349e4ecfe0f250937414af7f3699e016b9f57e","urls":["bzz-raw://e4fd2f59466c83a6e5dfe843951880d3f1dbe98cf559746242d942ab20962166","dweb:/ipfs/QmTTD9vj8ph7ocmWFAjPb4D4UiwusCg7iZEPWoRfVtEEu1"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/deprecated/caller/LibEncodedDispatch.sol":{"keccak256":"0x0d9b888a01c5d508dd3cdfd53823ad56c6e16e06d18ca8b2ac68e82b102cc60a","urls":["bzz-raw://6f854a3243d04aa76169367843a97bec51574f01ddb1e31b271f8aa93e184dce","dweb:/ipfs/QmRc85MSQBbqvCvdcZBHvBrA8FFfVbxYWHbNV2vshmLF95"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/lib/rain.interpreter.interface/src/lib/ns/LibNamespace.sol":{"keccak256":"0x5e1f5a0e05fbadf8acfad550ed59d960dd05ebc1d74cfa676f22a26644cc0ee5","urls":["bzz-raw://ee31bfc695ae5dc841379c3164b345bb0951b8b10fd94f300d5bb2de51dd7a1e","dweb:/ipfs/QmT1CqR7Lpj2bEJADgEPgY9cmTqgKLEZm1QLbZxfHEd8PY"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/src/interface/IOrderBookV4.sol":{"keccak256":"0x715c5211361bd469a9ec916710611536d087ae5630a3fc3f80e9802ec43c4596","urls":["bzz-raw://c61433f7de82ac5fbd752470aa1b9358dc70455ba85be87a9056509188f5db21","dweb:/ipfs/QmaGvPxe4xpqkVMdq9mo2moJyBHB9f8wTkcxZotk7tevVH"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/src/interface/IOrderBookV4OrderTaker.sol":{"keccak256":"0x7d197bd91214d318ce6a9ee388228494606ba7bdae36bbc6ec6ed1681ff60bd8","urls":["bzz-raw://73caf415d34e06a83d104cbccf98ae76ccd522e5ae1fb32c2bb18af9bae7ccaa","dweb:/ipfs/QmYbiDgjfjpX6pvjawqhtQpiTkUuX24ncHodheJPtSg4d7"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/src/interface/deprecated/v2/IOrderBookV2.sol":{"keccak256":"0x30c528f1772e9593b5329f1458cab4cce3510a2ccca01696b506af177fffd786","urls":["bzz-raw://9f53c14e5aaab2cd2fbf5b39d07c59acfdc09aff3476345816c2635de2cdd5d3","dweb:/ipfs/QmaomXfmDKdoeYwbMe9cy5anx4fDEMtP4W9Rre5s5DZVE6"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/src/interface/deprecated/v3/IOrderBookV3.sol":{"keccak256":"0xdca3fd0c6106ab98d08682e63e433b74250d1f01e646a5908c97183d9b25130b","urls":["bzz-raw://0f986a8388275a4144068981aa628e46bff221c2d27a3ad597a9c5fc2d4fcb09","dweb:/ipfs/QmR2pxkpY4ddhLb7jdoNAVSJwYHokyGpn5Tzg3Q7YnGYgW"],"license":"LicenseRef-DCL-1.0"},"lib/rain.orderbook.interface/src/interface/ierc3156/IERC3156FlashBorrower.sol":{"keccak256":"0xdcd81b1110e9c3c91715ac90aef1da70066bcaba8aeb3dc23df979f7f87644f9","urls":["bzz-raw://4572efaeb9eb30ad9b766b80cb2a8101a2cec0c9e8c78c7696f4aae710c8e2c9","dweb:/ipfs/QmVsz1gtqWfxTAbsoLPvZBe6PBVXqaWNx6nu7RHr2ghLuK"],"license":"CC0-1.0"},"lib/rain.orderbook.interface/src/interface/ierc3156/IERC3156FlashLender.sol":{"keccak256":"0x119c477281a6ec109e9c729a254b39d7ccc9a9f5baa82fa28d0fa1e3bd2b6ae8","urls":["bzz-raw://19ec8289a123459659dca0389a650ca6bf6bc12d925ad7a3e61f0b1bfe68d1b6","dweb:/ipfs/QmRZ9U5ECovPFmbRaBTauFjjVQcvPA7VQL7S1zJVEBTMD5"],"license":"CC0-1.0"},"lib/rain.orderbook.interface/src/interface/unstable/IOrderBookV4ArbOrderTakerV2.sol":{"keccak256":"0x271d4a0fdeddc07a88b18fa52f39c840515e572d62a755f7710bbd1b20ebe389","urls":["bzz-raw://64000bb3455809c1eb2a8789023286ad9ccaa2cdfe014cff480e96bb1b7ad6a3","dweb:/ipfs/QmRDgNHR4mYjxvHJUNMaaDZdAnxZ7yDF9kCL3aaSYip6AH"],"license":"LicenseRef-DCL-1.0"},"lib/sushixswap-v2/src/interfaces/IRouteProcessor.sol":{"keccak256":"0x5b2289c992f34dd25943db3cbc27ece8b0a1addeb2631f812e7834b7ae3eac37","urls":["bzz-raw://f21522058b7fe3c18d2353b06a5ddbf2b62d2aceebf7dd6dfdc51b081452bb3f","dweb:/ipfs/QmVkicGnNWgnX1RfXJWPpQaAPbiAQfFwGWigbPi1wu2WiT"],"license":"GPL-3.0-or-later"},"src/abstract/OrderBookV4ArbCommon.sol":{"keccak256":"0xe24cce13b3e21ae36c0aae11380128c5f0ddf542a0298f1a1845a39845e10576","urls":["bzz-raw://5f5d04c151a7e9fe4a659cef55abc4a626bc2ba0e58edbd714dcbadb52f4b396","dweb:/ipfs/QmZHQiYBNdxMJWoMjHnsqoj2YszvFMWrGYqDcodKpwyv98"],"license":"LicenseRef-DCL-1.0"},"src/abstract/OrderBookV4ArbOrderTaker.sol":{"keccak256":"0xa419a3d91a8c362dad1bea2761e6e0218c96ff295064aad1f5548444977d7896","urls":["bzz-raw://0995be2430baeccbf24ead130e100f6d19a354b2703d65a3544a9bf516ff4c6c","dweb:/ipfs/QmVaKDPVFe8U4MxnR3vwr7yLPrxo6hzYEud4XZzkHShsMu"],"license":"LicenseRef-DCL-1.0"},"src/concrete/arb/BalancerRouterOrderBookV4ArbOrderTaker.sol":{"keccak256":"0x2591b8fcde79fa4a27854fbbb09822d1aa504df69561233223d992d473e06cf2","urls":["bzz-raw://4b2f128a405c55af13e0e5fa0e18db0a7aacc89a2439f3d73ed631eb16e9684b","dweb:/ipfs/QmeQSZhZy49SRHRbtQHWahDo4GsAbUU1pQDv8vskJpao4n"],"license":"LicenseRef-DCL-1.0"},"src/lib/LibOrderBook.sol":{"keccak256":"0xa33a7731cde01162cc5d5a1d28b96a17626a6919971620850f92cc32a20342cf","urls":["bzz-raw://9bcbc37dd34292bca2ebdc620b418fbfd8b65039a3d040512f9c8d091ed1f2bf","dweb:/ipfs/QmQEBw7n6QKuzF1BMtxyYcZTk8eK4Hed8QVEwuQtUUJ4AM"],"license":"LicenseRef-DCL-1.0"},"src/lib/LibOrderBookArb.sol":{"keccak256":"0xe6ba0ae436fbb48193585beaaac8fc9b367ec9f511281b55e6cbc354c7faae90","urls":["bzz-raw://2b69af34b0acdabe929170984243f7a04581df75010d790147b86f72bc6411c2","dweb:/ipfs/QmZAYyTLrfB5w3uintmQp1Au32YyVrknfqcd6hLLvf1RKk"],"license":"LicenseRef-DCL-1.0"}},"version":1},"id":53} \ No newline at end of file diff --git a/test/e2e/data.js b/test/e2e/data.js index badd7e4a..5710f58a 100644 --- a/test/e2e/data.js +++ b/test/e2e/data.js @@ -143,14 +143,14 @@ module.exports = [ // unique test for aerodrome slipstream ChainId.BASE, process?.env?.TEST_BASE_RPC, - 16418720, + 34181086, [WNATIVE[ChainId.BASE], USDC[ChainId.BASE]], [ - "0x2B8804c2b652f05F7FDD8e0a02F01eE58F01667E", + "0xd9eDC75a3a797Ec92Ca370F19051BAbebfb2edEe", "0x336307F2d8390035Ba926a61a86b45CA9dC91E57", ], [LiquidityProviders.AerodromeSlipstream], - ["1", "10000"], + ["1", "100"], ], [ ChainId.BSC, diff --git a/test/e2e/e2e.test.js b/test/e2e/e2e.test.js index d1729df2..97eee18f 100644 --- a/test/e2e/e2e.test.js +++ b/test/e2e/e2e.test.js @@ -2,7 +2,7 @@ require("dotenv").config(); const { assert } = require("chai"); const testData = require("./data"); const { RainSolver } = require("../../src/core"); -const { ABI } = require("../../src/common"); +const { ABI, Result } = require("../../src/common"); const { RpcState } = require("../../src/rpc"); const mockServer = require("mockttp").getLocal(); const { sendTx, waitUntilFree, estimateGasCost } = require("../../src/signer/actions"); @@ -33,8 +33,10 @@ const { rainterpreterStoreNPE2Deploy, rainterpreterParserNPE2Deploy, rainterpreterExpressionDeployerNPE2Deploy, + balancerArbDeploy, } = require("../utils"); const { SharedState } = require("../../src/state"); +const balancerHelpers = require("../../src/router/balancer"); // run tests on each network in the provided data for (let i = 0; i < testData.length; i++) { @@ -79,6 +81,11 @@ for (let i = 0; i < testData.length; i++) { config.rpc = [rpc]; const rpcState = new RpcState(config.rpc.map((v) => ({ url: v }))); + const balancerRouter = (() => { + const balancerRouterInit = balancerHelpers.BalancerRouter.init(chainId); + if (balancerRouterInit.isOk()) return balancerRouterInit.value; + else return undefined; + })(); const state = new SharedState({ chainConfig: config, client: {}, @@ -91,18 +98,16 @@ for (let i = 0; i < testData.length; i++) { ownerLimits: {}, quoteGas: 1_000_000n, }, + balancerRouter, }); - const dataFetcherPromise = RainDataFetcher.init( - chainId, - createPublicClient({ - chain: publicClientConfig[chainId].chain, - transport: rainSolverTransport(rpcState, { - retryCountNext: 50, - timeout: 600_000, - }), + const client = createPublicClient({ + chain: publicClientConfig[chainId].chain, + transport: rainSolverTransport(rpcState, { + retryCountNext: 50, + timeout: 600_000, }), - liquidityProviders, - ); + }); + const dataFetcherPromise = RainDataFetcher.init(chainId, client, liquidityProviders); // run tests on each rp version for (let j = 0; j < rpVersions.length; j++) { @@ -1045,6 +1050,286 @@ for (let i = 0; i < testData.length; i++) { testSpan.end(); }); + + it(`should clear orders successfully using balancer router v${rpVersion}`, async function () { + config.rpc = [rpc]; + const viemClient = await viem.getPublicClient(); + state.client = viemClient; + state.client.simulateContract = client.simulateContract; + const dataFetcher = await dataFetcherPromise; + state.dataFetcher = dataFetcher; + dataFetcher.web3Client.transport.retryCount = 3; + const testSpan = tracer.startSpan("test-clearing"); + + // set as the route for POC + balancerHelpers.BalancerRouter.prototype.tryQuote = async function (params) { + return Result.ok({ + route: [ + { + steps: [ + { + pool: "0x88c044fb203b58b12252be7242926b1eeb113b4a", + tokenOut: "0x4200000000000000000000000000000000000006", + isBuffer: false, + }, + ], + tokenIn: params.tokenIn.address, + exactAmountIn: params.swapAmount, + minAmountOut: 0n, + }, + ], + price: 100000000000000000000000n, + amountOut: 100000000000000000000000n, + }); + }; + + // reset network before each test + await helpers.reset(rpc, blockNumber); + // get bot signer + const bot = botAddress + ? (await viem.getTestClient({ account: botAddress })) + .extend(publicActions) + .extend(walletActions) + : ( + await viem.getTestClient({ + account: "0x22025257BeF969A81eDaC0b343ce82d777931327", + }) + ) + .extend(publicActions) + .extend(walletActions); + bot.sendTx = async (tx) => { + return await sendTx(bot, tx); + }; + bot.waitUntilFree = async () => { + return await waitUntilFree(bot); + }; + bot.estimateGasCost = async (tx) => { + return await estimateGasCost(bot, tx); + }; + bot.asWriteSigner = () => bot; + bot.state = state; + bot.impersonateAccount({ + address: botAddress ?? "0x22025257BeF969A81eDaC0b343ce82d777931327", + }); + await network.provider.send("hardhat_setBalance", [ + bot.account.address, + "0x4563918244F40000", + ]); + bot.BALANCE = ethers.BigNumber.from("0x4563918244F40000"); + bot.BOUNTY = []; + + // deploy contracts + const interpreter = await rainterpreterNPE2Deploy(); + const store = await rainterpreterStoreNPE2Deploy(); + const parser = await rainterpreterParserNPE2Deploy(); + const deployer = await rainterpreterExpressionDeployerNPE2Deploy({ + interpreter: interpreter.address, + store: store.address, + parser: parser.address, + }); + const orderbook = !orderbookAddress + ? await deployOrderBookNPE2() + : await ethers.getContractAt(orderbookAbi, orderbookAddress); + + const arb = !arbAddress + ? await arbDeploy(orderbook.address, config.routeProcessors[rpVersion]) + : await ethers.getContractAt(ABI.Orderbook.Primary.Arb, arbAddress); + + const balancerArb = await balancerArbDeploy( + orderbook.address, + config.routeProcessors[rpVersion], + ); + + state.dispair = { + interpreter: interpreter.address, + store: store.address, + deployer: deployer.address, + }; + + // set up tokens contracts and impersonate owners + const owners = []; + for (let i = 0; i < tokens.length; i++) { + tokens[i].contract = await ethers.getContractAt( + ERC20Artifact.abi, + tokens[i].address, + ); + tokens[i].vaultId = ethers.BigNumber.from(randomUint256()); + tokens[i].depositAmount = ethers.utils.parseUnits( + deposits[i] ?? "100", + tokens[i].decimals, + ); + // owners.push( + // (await viem.getTestClient({account: addressesWithBalance[i]})).extend(publicActions).extend(walletActions) + // // await ethers.getImpersonatedSigner(addressesWithBalance[i]) + // ); + owners.push(await ethers.getImpersonatedSigner(addressesWithBalance[i])); + await network.provider.send("hardhat_setBalance", [ + addressesWithBalance[i], + "0x4563918244F40000", + ]); + } + + // bot original token balances + const originalBotTokenBalances = []; + for (const t of tokens) { + originalBotTokenBalances.push(await t.contract.balanceOf(bot.account.address)); + } + + // dposit and add orders for each owner and return + // the deployed orders in format of a sg query. + // all orders have WETH as output and other specified + // tokens as input + let orders = []; + for (let i = 1; i < tokens.length; i++) { + const depositConfigStruct = { + token: tokens[i].address, + vaultId: tokens[i].vaultId, + amount: tokens[i].depositAmount.toString(), + }; + await tokens[i].contract + .connect(owners[i]) + .approve(orderbook.address, depositConfigStruct.amount); + await orderbook + .connect(owners[i]) + .deposit2( + depositConfigStruct.token, + depositConfigStruct.vaultId, + depositConfigStruct.amount, + [], + ); + + // prebuild bytecode: "_ _: 0 max; :;" + const ratio = "0".repeat(64); // 0 + const maxOutput = "f".repeat(64); // max + const bytecode = `0x0000000000000000000000000000000000000000000000000000000000000002${maxOutput}${ratio}0000000000000000000000000000000000000000000000000000000000000015020000000c02020002011000000110000100000000`; + const addOrderConfig = { + evaluable: { + interpreter: interpreter.address, + store: store.address, + bytecode, + }, + nonce: "0x" + "0".repeat(63) + "1", + secret: "0x" + "0".repeat(63) + "1", + validInputs: [ + { + token: tokens[0].address, + decimals: tokens[0].decimals, + vaultId: tokens[0].vaultId, + }, + ], + validOutputs: [ + { + token: tokens[i].address, + decimals: tokens[i].decimals, + vaultId: tokens[i].vaultId, + }, + ], + meta: encodeMeta("some_order"), + }; + const tx = await orderbook.connect(owners[i]).addOrder2(addOrderConfig, []); + orders.push( + await mockSgFromEvent( + await getEventArgs(tx, "AddOrderV2", orderbook), + orderbook, + tokens.map((v) => ({ ...v.contract, knownSymbol: v.symbol })), + ), + ); + } + + // run the clearing process + config.isTest = true; + config.shuffle = false; + config.signer = bot; + config.hops = 2; + config.retries = 1; + config.lps = liquidityProviders; + config.rpVersion = rpVersion; + config.arbAddress = arb.address; + config.balancerArbAddress = balancerArb.address; + config.orderbookAddress = orderbook.address; + config.testBlockNumber = BigInt(blockNumber); + config.testBlockNumberInc = BigInt(blockNumber); // increments during test updating to new block height + config.gasCoveragePercentage = "1"; + config.viemClient = viemClient; + config.dataFetcher = dataFetcher; + config.accounts = []; + config.mainAccount = bot; + config.gasPriceMultiplier = 107; + config.gasLimitMultiplier = 120; + config.dispair = { + interpreter: interpreter.address, + store: store.address, + deployer: deployer.address, + }; + + const orderManager = new OrderManager(state); + await orderManager.addOrders(orders); + orders = orderManager.getNextRoundOrders(false); + + state.gasPrice = await bot.getGasPrice(); + orderManager.getNextRoundOrders = () => orders; + const rainSolver = new RainSolver( + state, + config, + orderManager, + { + mainSigner: bot, + getRandomSigner: () => bot, + }, + // config, + ); + const { results: reports } = await rainSolver.processNextRound(undefined, false); + + // should have cleared correct number of orders + assert.ok(reports.length == tokens.length - 1, "Failed to clear all given orders"); + + // validate each cleared order + let inputProfit = ethers.constants.Zero; + let gasSpent = ethers.constants.Zero; + for (let i = 0; i < reports.length; i++) { + const report = reports[i].value; + assert.equal(report.status, ProcessOrderStatus.FoundOpportunity); + + const pair = `${tokens[0].symbol}/${tokens[i + 1].symbol}`; + const clearedAmount = ethers.BigNumber.from(report.clearedAmount); + const outputVault = await orderbook.vaultBalance( + owners[i + 1].address, + tokens[i + 1].address, + tokens[i + 1].vaultId, + ); + const inputVault = await orderbook.vaultBalance( + owners[0].address, + tokens[0].address, + tokens[0].vaultId, + ); + const botTokenBalance = await tokens[i + 1].contract.balanceOf( + bot.account.address, + ); + + assert.equal(report.tokenPair, pair); + + // should have cleared equal to vault balance or lower + assert.ok( + tokens[i + 1].depositAmount.gte(clearedAmount), + `Did not clear expected amount for: ${pair}`, + ); + assert.ok( + outputVault.eq(tokens[i + 1].depositAmount.sub(clearedAmount)), + `Unexpected current output vault balance: ${pair}`, + ); + assert.ok(inputVault.eq(0), `Unexpected current input vault balance: ${pair}`); + assert.ok( + originalBotTokenBalances[i + 1].eq(botTokenBalance), + `Unexpected current bot ${tokens[i + 1].symbol} balance`, + ); + + // collect all bot's input income (bounty) and gas cost + inputProfit = inputProfit.add(ethers.utils.parseUnits(report.inputTokenIncome)); + gasSpent = gasSpent.add(ethers.utils.parseUnits(report.gasCost.toString())); + } + + testSpan.end(); + }); } }); } diff --git a/test/utils.js b/test/utils.js index 3fe7c809..74f97157 100644 --- a/test/utils.js +++ b/test/utils.js @@ -8,6 +8,7 @@ const RainterpreterParserNPE2Artifact = require("./abis/RainterpreterParserNPE2. const RainterpreterExpressionDeployerNPE2Artifact = require("./abis/RainterpreterExpressionDeployerNPE2.json"); const GenericPoolOrderBookV4ArbOrderTakerArtifact = require("./abis/GenericPoolOrderBookV4ArbOrderTaker.json"); const RouteProcessorOrderBookV4ArbOrderTakerArtifact = require("./abis/RouteProcessorOrderBookV4ArbOrderTaker.json"); +const BalancerRouterOrderBookV4ArbOrderTakerArtifact = require("./abis/BalancerRouterOrderBookV4ArbOrderTaker.json"); /** * Deploys a simple contracts that takes no arguments for deployment @@ -34,6 +35,17 @@ exports.arbDeploy = async (orderbookAddress, rpAddress) => { }); }; +exports.balancerArbDeploy = async (orderbookAddress, rpAddress) => { + return await this.basicDeploy(BalancerRouterOrderBookV4ArbOrderTakerArtifact, { + orderBook: orderbookAddress ?? `0x${"0".repeat(40)}`, + task: { + evaluable: ABI.Orderbook.DefaultArbEvaluable, + signedContext: [], + }, + implementationData: ethers.utils.defaultAbiCoder.encode(["address"], [rpAddress]), + }); +}; + exports.genericArbrbDeploy = async (orderbookAddress) => { return await this.basicDeploy(GenericPoolOrderBookV4ArbOrderTakerArtifact, { orderBook: orderbookAddress ?? `0x${"0".repeat(40)}`,