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

Add a way to process events from blocks in the past #76

Merged
merged 12 commits into from
Feb 18, 2025
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ web_modules/

# dotenv environment variable files
.env
.env.testnet
.env.mainnet
.env.development.local
.env.test.local
.env.production.local
Expand Down
4 changes: 4 additions & 0 deletions solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
"run:deployToken": "dotenv-run-script deployToken",
"openOrder": "forge script script/OpenOrder.s.sol:OpenOrder -f $NETWORK --broadcast --verify --slow -vvv",
"run:openOrder": "dotenv-run-script openOrder",
"deployBatchOpen": "forge script script/DeployBatchOpen.s.sol:DeployBatchOpen -f $NETWORK --broadcast --verify --slow -vvv",
"run:deployBatchOpen": "dotenv-run-script deployBatchOpen",
"openBatchOrder": "forge script script/OpenBatchOrder.s.sol:OpenBatchOrder -f $NETWORK --broadcast --verify --slow -vvv",
"run:openBatchOrder": "dotenv-run-script openBatchOrder",
"enrollRouter": "forge script script/EnrollRouter.s.sol:EnrollRouter -f $NETWORK --broadcast --slow -vvv",
"run:enrollRouter": "dotenv-run-script enrollRouter"
}
Expand Down
44 changes: 44 additions & 0 deletions solidity/script/DeployBatchOpen.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.25 <0.9.0;

import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";

import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { Hyperlane7683 } from "../src/Hyperlane7683.sol";
import { OrderData, OrderEncoder } from "../src/libs/OrderEncoder.sol";

import {
OnchainCrossChainOrder
} from "../src/ERC7683/IERC7683.sol";

contract BatchOpen {
Hyperlane7683 public router;
constructor(address _router) {
router = Hyperlane7683(_router);
}

function openOrders(OnchainCrossChainOrder[] memory orders, address token, uint256 total) external {
ERC20(token).transferFrom(msg.sender, address(this), total);
ERC20(token).approve(address(router), total);
for (uint256 i = 0; i < orders.length; i++) {
router.open(orders[i]);
}
}
}

/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting
contract DeployBatchOpen is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PK");
address router = vm.envAddress("ROUTER_ADDRESS");

vm.startBroadcast(deployerPrivateKey);

new BatchOpen(router);

vm.stopBroadcast();
}
}
81 changes: 81 additions & 0 deletions solidity/script/OpenBatchOrder.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.25 <0.9.0;

import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";

import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { Hyperlane7683 } from "../src/Hyperlane7683.sol";
import { OrderData, OrderEncoder } from "../src/libs/OrderEncoder.sol";
import { BatchOpen } from "./DeployBatchOpen.s.sol";

import {
OnchainCrossChainOrder
} from "../src/ERC7683/IERC7683.sol";

/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting
contract OpenBatchOrder is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PK");

vm.startBroadcast(deployerPrivateKey);

address localRouter = vm.envAddress("ROUTER_ADDRESS");
address sender = vm.envAddress("ORDER_SENDER");
address recipient = vm.envAddress("ORDER_RECIPIENT");
address inputToken = vm.envAddress("ITT_INPUT");
address outputToken = vm.envAddress("ITT_OUTPUT");
uint256 amountIn = vm.envUint("AMOUNT_IN");
uint256 amountOut = vm.envUint("AMOUNT_OUT");
uint256 senderNonce = vm.envUint("SENDER_NONCE");
uint32 originDomain = Hyperlane7683(localRouter).localDomain();
uint256 destinationDomain = vm.envUint("DESTINATION_DOMAIN");
uint32 fillDeadline = type(uint32).max;
address batchOpen = vm.envAddress("BATCH_OPEN");

ERC20(inputToken).approve(batchOpen, amountIn * 3);

OrderData memory order = OrderData(
TypeCasts.addressToBytes32(sender),
TypeCasts.addressToBytes32(recipient),
TypeCasts.addressToBytes32(inputToken),
TypeCasts.addressToBytes32(outputToken),
amountIn,
amountOut,
senderNonce,
originDomain,
uint32(destinationDomain),
TypeCasts.addressToBytes32(localRouter),
fillDeadline,
new bytes(0)
);

OnchainCrossChainOrder[] memory onchainOrders = new OnchainCrossChainOrder[](3);

onchainOrders[0] = OnchainCrossChainOrder(
fillDeadline,
OrderEncoder.orderDataType(),
OrderEncoder.encode(order)
);

order.senderNonce = senderNonce + 1;
onchainOrders[1] = OnchainCrossChainOrder(
fillDeadline,
OrderEncoder.orderDataType(),
OrderEncoder.encode(order)
);

order.senderNonce = senderNonce + 2;
onchainOrders[2] = OnchainCrossChainOrder(
fillDeadline,
OrderEncoder.orderDataType(),
OrderEncoder.encode(order)
);

BatchOpen(batchOpen).openOrders(onchainOrders, inputToken, amountIn * 3);

vm.stopBroadcast();
}
}
2 changes: 1 addition & 1 deletion typescript/solver/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
typechain
local.db
**/local.db
10 changes: 8 additions & 2 deletions typescript/solver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ const main = async () => {
log.info("🙍 Intent Solver 📝");
log.info("Starting...");

// // TODO: implement a way to choose different listeners and fillers
const hyperlane7683Listener = solvers["hyperlane7683"].listener.create();
// TODO: implement a way to choose different listeners and fillers
// const ecoListener = solvers["eco"].listener.create();
// const ecoFiller = solvers["eco"].filler.create(multiProvider);

// ecoListener(ecoFiller);

const hyperlane7683Listener =
await solvers["hyperlane7683"].listener.create();
const hyperlane7683Filler =
solvers["hyperlane7683"].filler.create(multiProvider);

Expand Down
1 change: 1 addition & 0 deletions typescript/solver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@hyperlane-xyz/registry": "^9.3.0",
"@hyperlane-xyz/sdk": "^8.6.1",
"@hyperlane-xyz/utils": "^8.6.1",
"@libsql/client": "^0.14.0",
"chalk": "^5.3.0",
"dotenv-flow": "^4.1.0",
"ethers": "^5.7.2",
Expand Down
11 changes: 8 additions & 3 deletions typescript/solver/solvers/BaseFiller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Metadata = {
protocolName: string;
};

type ParsedArgs = {
export type ParsedArgs = {
orderId: string;
senderAddress: string;
recipients: Array<{
Expand Down Expand Up @@ -46,7 +46,11 @@ export abstract class BaseFiller<
}

create() {
return async (parsedArgs: TParsedArgs, originChainName: string) => {
return async (
parsedArgs: TParsedArgs,
originChainName: string,
blockNumber: number,
) => {
const origin = await this.retrieveOriginInfo(parsedArgs, originChainName);
const target = await this.retrieveTargetInfo(parsedArgs);

Expand All @@ -67,7 +71,7 @@ export abstract class BaseFiller<
const { data } = intent;

try {
await this.fill(parsedArgs, data, originChainName);
await this.fill(parsedArgs, data, originChainName, blockNumber);

await this.settleOrder(parsedArgs, data);
} catch (error) {
Expand Down Expand Up @@ -130,6 +134,7 @@ export abstract class BaseFiller<
parsedArgs: TParsedArgs,
data: TIntentData,
originChainName: string,
blockNumber: number,
): Promise<void>;

protected async settleOrder(parsedArgs: TParsedArgs, data: TIntentData) {
Expand Down
94 changes: 74 additions & 20 deletions typescript/solver/solvers/BaseListener.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { Provider } from "@ethersproject/providers";
import { MultiProvider } from "@hyperlane-xyz/sdk";
import type { Contract, Signer } from "ethers";
import type { Contract, EventFilter, Signer } from "ethers";

import { chainMetadata } from "../config/chainMetadata.js";
import type { Logger } from "../logger.js";
import type { TypedEvent, TypedListener } from "../typechain/common.js";
import type { ParsedArgs } from "./BaseFiller.js";

export abstract class BaseListener<
TContract extends Contract,
TEvent extends TypedEvent,
TParsedArgs,
TParsedArgs extends ParsedArgs,
> {
protected constructor(
private readonly contractFactory: {
Expand All @@ -20,40 +21,93 @@ export abstract class BaseListener<
contracts: Array<{
address: string;
chainName: string;
initialBlock?: number;
processedIds?: string[];
}>;
protocolName: string;
},
private readonly log: Logger,
) {}

create() {
return (handler: (args: TParsedArgs, originChainName: string) => void) => {
return async (
handler: (
args: TParsedArgs,
originChainName: string,
blockNumber: number,
) => void,
) => {
const multiProvider = new MultiProvider(chainMetadata);

this.metadata.contracts.forEach(({ address, chainName }) => {
const provider = multiProvider.getProvider(chainName);
const contract = this.contractFactory.connect(address, provider);
const filter = contract.filters[this.eventName]();
this.metadata.contracts.forEach(
async ({ address, chainName, initialBlock, processedIds }) => {
const provider = multiProvider.getProvider(chainName);
const contract = this.contractFactory.connect(address, provider);
const filter = contract.filters[this.eventName]();

const listener: TypedListener<TEvent> = (...args) => {
handler(this.parseEventArgs(args), chainName);
};
const listener: TypedListener<TEvent> = (...args) => {
handler(
this.parseEventArgs(args),
chainName,
args[args.length - 1].blockNumber,
);
};

contract.on(filter, listener);
const latest = (await provider.getBlockNumber()) - 1;
if (initialBlock && latest > initialBlock) {
this.processPrevBlocks(
chainName,
contract,
filter,
initialBlock,
latest,
handler,
processedIds,
);
}

contract.provider.getNetwork().then((network) => {
this.log.info({
msg: "Listener started",
event: this.eventName,
protocol: this.metadata.protocolName,
chainId: network.chainId,
chainName: chainName,
contract.on(filter, listener);

contract.provider.getNetwork().then((network) => {
this.log.info({
msg: "Listener started",
event: this.eventName,
protocol: this.metadata.protocolName,
chainId: network.chainId,
chainName: chainName,
});
});
});
});
},
);
};
}

protected async processPrevBlocks(
chainName: string,
contract: TContract,
filter: EventFilter,
from: number,
to: number,
handler: (
args: TParsedArgs,
originChainName: string,
blockNumber: number,
) => void,
processedIds?: string[],
) {
const pastEvents = await contract.queryFilter(filter, from, to);
for (let event of pastEvents) {
const parsedArgs = this.parseEventArgs((event as TEvent).args);
if (
event.blockNumber === from &&
processedIds?.includes(parsedArgs.orderId)
) {
continue;
}
await handler(parsedArgs, chainName, event.blockNumber);
}
}

protected abstract parseEventArgs(
args: Parameters<TypedListener<TEvent>>,
): TParsedArgs;
Expand Down
1 change: 1 addition & 0 deletions typescript/solver/solvers/eco/config/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const metadata: EcoMetadata = {
{
address: "0x734a3d5a8D691d9b911674E682De5f06517c79ec",
chainName: "optimismsepolia",
initialBlock: 123,
},
],
adapters: [
Expand Down
1 change: 1 addition & 0 deletions typescript/solver/solvers/eco/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const EcoMetadataSchema = z.object({
chainName: z.string().refine((name) => chainNames.includes(name), {
message: "Invalid chainName",
}),
initialBlock: z.number(),
}),
),
adapters: z.array(
Expand Down
6 changes: 6 additions & 0 deletions typescript/solver/solvers/hyperlane7683/config/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const metadata: Hyperlane7683Metadata = {
address: "0x9245A985d2055CeA7576B293Da8649bb6C5af9D0",
chainName: "form",
},

// testnet
// {
// address: "0x6d2175B89315A9EB6c7eA71fDE54Ac0f294aDC34",
Expand All @@ -52,6 +53,11 @@ const metadata: Hyperlane7683Metadata = {
// address: "0x6d2175B89315A9EB6c7eA71fDE54Ac0f294aDC34",
// chainName: "basesepolia",
// },
// {
// address: "0x6d2175B89315A9EB6c7eA71fDE54Ac0f294aDC34",
// chainName: "basesepolia",
// initialBlock: 21491220,
// }
],
};

Expand Down
Loading