Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: chain watcher #47

Merged
merged 9 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 57 additions & 56 deletions src/addContract.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,63 @@
import {
ActionFn,
Context,
Event,
TransactionEvent,
Log,
} from "@tenderly/actions";
import { BytesLike, ethers } from "ethers";

import type {
ComposableCoW,
ComposableCoWInterface,
ConditionalOrderCreatedEvent,
IConditionalOrder,
} from "./types/ComposableCoW";
import { ComposableCoW__factory } from "./types/factories/ComposableCoW__factory";
MerkleRootSetEvent,
} from "./types/generated/ComposableCoW";
import { ComposableCoW__factory } from "./types/generated/factories/ComposableCoW__factory";

import {
isComposableCowCompatible,
handleExecutionError,
initContext,
writeRegistry,
toChainId,
} from "./utils";
import { ChainContext, Owner, Proof, Registry } from "./model";
import { Owner, Proof, Registry } from "./types/model";
import { ChainWatcher } from "./modes";

/**
* Listens to these events on the `ComposableCoW` contract:
* - `ConditionalOrderCreated`
* - `MerkleRootSet`
* @param context tenderly context
* @param chainWatcher chain watcher
* @param event transaction event
*/
export const addContract: ActionFn = async (context: Context, event: Event) => {
return _addContract(context, event).catch(handleExecutionError);
};
export async function addContract(
chainWatcher: ChainWatcher,
event: ConditionalOrderCreatedEvent
) {
return _addContract(chainWatcher, event).catch(handleExecutionError);
}

const _addContract: ActionFn = async (context: Context, event: Event) => {
const transactionEvent = event as TransactionEvent;
const tx = transactionEvent.hash;
async function _addContract(
context: ChainWatcher,
event: ConditionalOrderCreatedEvent
) {
const composableCow = ComposableCoW__factory.createInterface();

const chainId = toChainId(transactionEvent.network);
const { provider } = await ChainContext.create(context, chainId);
const { registry } = await initContext("addContract", chainId, context);
const { chainContext, registry } = context;
const { provider } = chainContext;

// Process the logs
let hasErrors = false;
let numContractsAdded = 0;
for (const log of transactionEvent.logs) {
// Do not process logs that are not from a `ComposableCoW`-compatible contract
// This is a *normal* case, if the contract is not `ComposableCoW`-compatible
// then we do not need to do anything, and therefore don't flag as an error.
if (!isComposableCowCompatible(await provider.getCode(log.address))) {
continue;
}
const { error, added } = await _registerNewOrder(
tx,
log,
composableCow,
registry
);
if (added) {
numContractsAdded++;
}
hasErrors ||= error;

// Do not process logs that are not from a `ComposableCoW`-compatible contract
// This is a *normal* case, if the contract is not `ComposableCoW`-compatible
// then we do not need to do anything, and therefore don't flag as an error.
if (!isComposableCowCompatible(await provider.getCode(event.address))) {
return;
}
const { error, added } = await _registerNewOrder(
event,
composableCow,
registry
);
if (added) {
numContractsAdded++;
}
hasErrors ||= error;

console.log(`[addContract] Added ${numContractsAdded} contracts`);
hasErrors ||= !(await writeRegistry());
Expand All @@ -73,30 +67,41 @@ const _addContract: ActionFn = async (context: Context, event: Event) => {
"[addContract] Error adding conditional order. Event: " + event
);
}
};
}

export async function _registerNewOrder(
tx: string,
log: Log,
event: ConditionalOrderCreatedEvent | MerkleRootSetEvent,
composableCow: ComposableCoWInterface,
registry: Registry
): Promise<{ error: boolean; added: boolean }> {
let added = false;
try {
// Check if the log is a ConditionalOrderCreated event
if (
log.topics[0] === composableCow.getEventTopic("ConditionalOrderCreated")
event.topics[0] === composableCow.getEventTopic("ConditionalOrderCreated")
) {
const log = event as ConditionalOrderCreatedEvent;
// Decode the log
const [owner, params] = composableCow.decodeEventLog(
"ConditionalOrderCreated",
log.data,
log.topics
) as [string, IConditionalOrder.ConditionalOrderParamsStruct];

// Attempt to add the conditional order to the registry
await add(tx, owner, params, null, log.address, registry);
await add(
log.transactionHash,
owner,
params,
null,
log.address,
registry
);
added = true;
} else if (log.topics[0] == composableCow.getEventTopic("MerkleRootSet")) {
} else if (
event.topics[0] == composableCow.getEventTopic("MerkleRootSet")
) {
const log = event as MerkleRootSetEvent;
const [owner, root, proof] = composableCow.decodeEventLog(
"MerkleRootSet",
log.data,
Expand Down Expand Up @@ -125,7 +130,7 @@ export async function _registerNewOrder(
);
// Attempt to add the conditional order to the registry
await add(
tx,
event.transactionHash,
owner,
decodedOrder[1],
{ merkleRoot: root, path: decodedOrder[0] },
Expand Down Expand Up @@ -156,14 +161,14 @@ export async function _registerNewOrder(
* @param composableCow address of the contract that emitted the event
* @param registry of all conditional orders
*/
export const add = async (
export function add(
tx: string,
owner: Owner,
params: IConditionalOrder.ConditionalOrderParamsStruct,
proof: Proof | null,
composableCow: string,
registry: Registry
) => {
) {
const { handler, salt, staticInput } = params;
if (registry.ownerOrders.has(owner)) {
const conditionalOrders = registry.ownerOrders.get(owner);
Expand Down Expand Up @@ -201,19 +206,15 @@ export const add = async (
new Set([{ tx, params, proof, orders: new Map(), composableCow }])
);
}
};
}

/**
* Flush the conditional orders of an owner that do not have the merkle root set
* @param owner to check for conditional orders to flush
* @param root the merkle root to check against
* @param registry of all conditional orders
*/
export const flush = async (
owner: Owner,
root: BytesLike,
registry: Registry
) => {
export function flush(owner: Owner, root: BytesLike, registry: Registry) {
if (registry.ownerOrders.has(owner)) {
const conditionalOrders = registry.ownerOrders.get(owner);
if (conditionalOrders !== undefined) {
Expand All @@ -228,4 +229,4 @@ export const flush = async (
}
}
}
};
}
59 changes: 29 additions & 30 deletions src/checkForAndPlaceOrder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ActionFn, BlockEvent, Context, Event } from "@tenderly/actions";
import {
Order,
OrderBalance,
Expand All @@ -16,20 +15,18 @@ import {
ComposableCoW__factory,
Multicall3,
Multicall3__factory,
} from "./types";
} from "./types/generated";
import {
LowLevelError,
ORDER_NOT_VALID_SELECTOR,
PROOF_NOT_AUTHED_SELECTOR,
SINGLE_ORDER_NOT_AUTHED_SELECTOR,
formatStatus,
handleExecutionError,
initContext,
parseCustomError,
toChainId,
writeRegistry,
} from "./utils";
import { ChainContext, ConditionalOrder, OrderStatus } from "./model";
import { ChainContext, ConditionalOrder, OrderStatus } from "./types/model";
import { pollConditionalOrder } from "./utils/poll";
import {
PollParams,
Expand All @@ -41,6 +38,7 @@ import {
PollResultUnexpectedError,
SupportedChainId,
} from "@cowprotocol/cow-sdk";
import { ChainWatcher } from "./modes";

const GPV2SETTLEMENT = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41";
const MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11";
Expand All @@ -60,31 +58,36 @@ const ORDER_BOOK_API_HANDLED_ERRORS = [
* @param context tenderly context
* @param event block event
*/
export const checkForAndPlaceOrder: ActionFn = async (
context: Context,
event: Event
) => {
return _checkForAndPlaceOrder(context, event).catch(handleExecutionError);
};
export async function checkForAndPlaceOrder(
context: ChainWatcher,
block: ethers.providers.Block,
blockNumberOverride?: number,
blockTimestampOverride?: number
) {
return _checkForAndPlaceOrder(
context,
block,
blockNumberOverride,
blockTimestampOverride
).catch(handleExecutionError);
}

/**
* Asyncronous version of checkForAndPlaceOrder. It will process all the orders, and will throw an error at the end if there was at least one error
* Asynchronous version of checkForAndPlaceOrder. It will process all the orders, and will throw an error at the end if there was at least one error
*/
const _checkForAndPlaceOrder: ActionFn = async (
context: Context,
event: Event
) => {
const blockEvent = event as BlockEvent;
const { network, blockNumber } = blockEvent;
const chainId = toChainId(network);
const chainContext = await ChainContext.create(context, chainId);
const { registry } = await initContext(
"checkForAndPlaceOrder",
chainId,
context
);
async function _checkForAndPlaceOrder(
context: ChainWatcher,
block: ethers.providers.Block,
blockNumberOverride?: number,
blockTimestampOverride?: number
) {
const { chainContext, registry } = context;
const { chainId } = chainContext;
const { ownerOrders } = registry;

const blockNumber = blockNumberOverride || block.number;
const blockTimestamp = blockTimestampOverride || block.timestamp;

let hasErrors = false;
let ownerCounter = 0;
let orderCounter = 0;
Expand All @@ -93,10 +96,6 @@ const _checkForAndPlaceOrder: ActionFn = async (
console.log(`[checkForAndPlaceOrder] Processing Block ${blockNumber}`);
}

const { timestamp: blockTimestamp } = await chainContext.provider.getBlock(
blockNumber
);

console.log("[checkForAndPlaceOrder] Number of orders: ", registry.numOrders);

for (const [owner, conditionalOrders] of ownerOrders.entries()) {
Expand Down Expand Up @@ -232,7 +231,7 @@ const _checkForAndPlaceOrder: ActionFn = async (
"[checkForAndPlaceOrder] At least one unexpected error processing conditional orders"
);
}
};
}

async function _processConditionalOrder(
owner: string,
Expand Down
Loading