@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
JavaScript
"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