UNPKG

@f5i23q999d/cow-sdk

Version:

<p align="center"> <img width="400" src="https://github.com/cowprotocol/cow-sdk/raw/main/docs/images/CoW.png" /> </p>

296 lines 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConditionalOrder = void 0; const ethers_1 = require("ethers"); const utils_1 = require("./utils.js"); const types_1 = require("./types.js"); const contracts_1 = require("./contracts.js"); const utils_2 = require("../utils.js"); /** * An abstract base class from which all conditional orders should inherit. * * This class provides some basic functionality to help with handling conditional orders, * such as: * - Validating the conditional order * - Creating a human-readable string representation of the conditional order * - Serializing the conditional order for use with the `IConditionalOrder` struct * - Getting any dependencies for the conditional order * - Getting the off-chain input for the conditional order * * **NOTE**: Instances of conditional orders have an `id` property that is a `keccak256` hash of * the serialized conditional order. */ class ConditionalOrder { handler; salt; data; staticInput; hasOffChainInput; /** * A constructor that provides some basic validation for the conditional order. * * This constructor **MUST** be called by any class that inherits from `ConditionalOrder`. * * **NOTE**: The salt is optional and will be randomly generated if not provided. * @param handler The address of the handler for the conditional order. * @param salt A 32-byte string used to salt the conditional order. * @param data The data of the order * @param hasOffChainInput Whether the conditional order has off-chain input. * @throws If the handler is not a valid ethereum address. * @throws If the salt is not a valid 32-byte string. */ constructor(params) { const { handler, salt = ethers_1.utils.keccak256(ethers_1.utils.randomBytes(32)), data, hasOffChainInput = false } = params; // Verify input to the constructor // 1. Verify that the handler is a valid ethereum address if (!ethers_1.ethers.utils.isAddress(handler)) { throw new Error(`Invalid handler: ${handler}`); } // 2. Verify that the salt is a valid 32-byte string usable with ethers if (!ethers_1.ethers.utils.isHexString(salt) || ethers_1.ethers.utils.hexDataLength(salt) !== 32) { throw new Error(`Invalid salt: ${salt}`); } this.handler = handler; this.salt = salt; this.data = data; this.staticInput = this.transformDataToStruct(data); this.hasOffChainInput = hasOffChainInput; } /** * Get the context dependency for the conditional order. * * This is used when calling `createWithContext` or `setRootWithContext` on a ComposableCoW-enabled Safe. * @returns The context dependency. */ get context() { return undefined; } assertIsValid() { const isValidResult = this.isValid(); if (!(0, utils_1.getIsValidResult)(isValidResult)) { throw new Error(`Invalid order: ${isValidResult.reason}`); } } /** * Get the calldata for creating the conditional order. * * This will automatically determine whether or not to use `create` or `createWithContext` based on the * order type's context dependency. * * **NOTE**: By default, this will cause the create to emit the `ConditionalOrderCreated` event. * @returns The calldata for creating the conditional order. */ get createCalldata() { this.assertIsValid(); const context = this.context; const composableCow = (0, contracts_1.getComposableCowInterface)(); const paramsStruct = { handler: this.handler, salt: this.salt, staticInput: this.encodeStaticInput(), }; if (context) { // Create (with context) const contextArgsAbi = context.factoryArgs ? ethers_1.utils.defaultAbiCoder.encode(context.factoryArgs.argsType, context.factoryArgs.args) : '0x'; return composableCow.encodeFunctionData('createWithContext', [ paramsStruct, context.address, contextArgsAbi, true, ]); } else { // Create return composableCow.encodeFunctionData('create', [paramsStruct, true]); } } /** * Get the calldata for removing a conditional order that was created as a single order. * @returns The calldata for removing the conditional order. */ get removeCalldata() { this.assertIsValid(); return (0, contracts_1.getComposableCowInterface)().encodeFunctionData('remove', [this.id]); } /** * Calculate the id of the conditional order (which also happens to be the key used for `ctx` in the ComposableCoW contract). * * This is a `keccak256` hash of the serialized conditional order. * @returns The id of the conditional order. */ get id() { return ethers_1.utils.keccak256(this.serialize()); } /** * The context key of the order (bytes32(0) if a merkle tree is used, otherwise H(params)) with which to lookup the cabinet * * The context, relates to the 'ctx' in the contract: https://github.com/cowprotocol/composable-cow/blob/c7fb85ab10c05e28a1632ba97a1749fb261fcdfb/src/interfaces/IConditionalOrder.sol#L38 */ get ctx() { return this.isSingleOrder ? this.id : ethers_1.constants.HashZero; } /** * Get the `leaf` of the conditional order. This is the data that is used to create the merkle tree. * * For the purposes of this library, the `leaf` is the `ConditionalOrderParams` struct. * @returns The `leaf` of the conditional order. * @see ConditionalOrderParams */ get leaf() { return { handler: this.handler, salt: this.salt, staticInput: this.encodeStaticInput(), }; } /** * Calculate the id of the conditional order. * @param leaf The `leaf` representing the conditional order. * @returns The id of the conditional order. * @see ConditionalOrderParams */ static leafToId(leaf) { return ethers_1.utils.keccak256((0, utils_1.encodeParams)(leaf)); } /** * If the conditional order has off-chain input, return it! * * **NOTE**: This should be overridden by any conditional order that has off-chain input. * @returns The off-chain input. */ get offChainInput() { return '0x'; } /** * A helper function for generically serializing a conditional order's static input. * * @param orderDataTypes ABI types for the order's data struct. * @param data The order's data struct. * @returns An ABI-encoded representation of the order's data struct. */ encodeStaticInputHelper(orderDataTypes, staticInput) { return ethers_1.utils.defaultAbiCoder.encode(orderDataTypes, [staticInput]); } /** * Poll a conditional order to see if it is tradeable. * * @param owner The owner of the conditional order. * @param p The proof and parameters. * @param chain Which chain to use for the ComposableCoW contract. * @param provider An RPC provider for the chain. * @param offChainInputFn A function, if provided, that will return the off-chain input for the conditional order. * @throws If the conditional order is not tradeable. * @returns The tradeable `GPv2Order.Data` struct and the `signature` for the conditional order. */ async poll(params) { const { chainId, owner, provider, orderBookApi } = params; const composableCow = (0, contracts_1.getComposableCow)(chainId, provider); try { const isValid = this.isValid(); // Do a validation first if (!(0, utils_1.getIsValidResult)(isValid)) { return { result: types_1.PollResultCode.DONT_TRY_AGAIN, reason: `InvalidConditionalOrder. Reason: ${isValid.reason}`, }; } // Let the concrete Conditional Order decide about the poll result const pollResult = await this.pollValidate(params); if (pollResult) { return pollResult; } // Check if the owner authorized the order const isAuthorized = await this.isAuthorized(params); if (!isAuthorized) { return { result: types_1.PollResultCode.DONT_TRY_AGAIN, reason: `NotAuthorized: Order ${this.id} is not authorized for ${owner} on chain ${chainId}`, }; } // Lastly, try to get the tradeable order and signature const [order, signature] = await composableCow.getTradeableOrderWithSignature(owner, this.leaf, this.offChainInput, []); const orderUid = await (0, utils_2.computeOrderUid)(chainId, owner, (0, utils_1.fromStructToOrder)(order)); // Check if the order is already in the order book const isOrderInOrderbook = await orderBookApi .getOrder(orderUid) .then(() => true) .catch(() => false); // Let the concrete Conditional Order decide about the poll result (in the case the order is already in the orderbook) if (isOrderInOrderbook) { const pollResult = await this.handlePollFailedAlreadyPresent(orderUid, order, params); if (pollResult) { return pollResult; } return { result: types_1.PollResultCode.TRY_NEXT_BLOCK, reason: 'Order already in orderbook', }; } return { result: types_1.PollResultCode.SUCCESS, order, signature, }; } catch (error) { return { result: types_1.PollResultCode.UNEXPECTED_ERROR, error: error, }; } } /** * Checks if the owner authorized the conditional order. * * @param params owner context, to be able to check if the order is authorized * @returns true if the owner authorized the order, false otherwise. */ isAuthorized(params) { const { chainId, owner, provider } = params; const composableCow = (0, contracts_1.getComposableCow)(chainId, provider); return composableCow.callStatic.singleOrders(owner, this.id); } /** * Checks the value in the cabinet for a given owner and chain * * @param params owner context, to be able to check the cabinet */ cabinet(params) { const { chainId, owner, provider } = params; const composableCow = (0, contracts_1.getComposableCow)(chainId, provider); return composableCow.callStatic.cabinet(owner, this.ctx); } /** * A helper function for generically deserializing a conditional order. * @param s The ABI-encoded `IConditionalOrder.Params` struct to deserialize. * @param handler Address of the handler for the conditional order. * @param orderDataTypes ABI types for the order's data struct. * @param callback A callback function that takes the deserialized data struct and the salt and returns an instance of the class. * @returns An instance of the conditional order class. */ static deserializeHelper(s, handler, orderDataTypes, callback) { try { // First, decode the `IConditionalOrder.Params` struct const { handler: recoveredHandler, salt, staticInput } = (0, utils_1.decodeParams)(s); // Second, verify that the recovered handler is the correct handler if (!(recoveredHandler == handler)) throw new Error('HandlerMismatch'); // Third, decode the data struct const [d] = ethers_1.utils.defaultAbiCoder.decode(orderDataTypes, staticInput); // Create a new instance of the class return callback(d, salt); } catch (e) { if (e.message === 'HandlerMismatch') { throw e; } else { throw new Error('InvalidSerializedConditionalOrder'); } } } } exports.ConditionalOrder = ConditionalOrder; //# sourceMappingURL=ConditionalOrder.js.map