Skip to content

Commit bff2f30

Browse files
authored
Merge pull request #117 from BootNodeDev/custom-rules
Custom rules configuration
2 parents 8f7cae3 + 8200abd commit bff2f30

File tree

17 files changed

+290
-230
lines changed

17 files changed

+290
-230
lines changed

typescript/solver/config/chainMetadata.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ import { z } from "zod";
22

33
import { chainMetadata as defaultChainMetadata } from "@hyperlane-xyz/registry";
44

5-
import { ChainMetadataSchema } from "@hyperlane-xyz/sdk";
65
import type { ChainMap, ChainMetadata } from "@hyperlane-xyz/sdk";
6+
import { ChainMetadataSchema } from "@hyperlane-xyz/sdk";
77

88
import { objMerge } from "@hyperlane-xyz/utils";
99

1010
const customChainMetadata = {
1111
// Example custom configuration
1212
// basesepolia: {
1313
// rpcUrls: [
14-
// ,
1514
// {
1615
// http: "https://base-sepolia-rpc.publicnode.com",
1716
// pagination: {

typescript/solver/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ const main = async () => {
2323

2424
const hyperlane7683Listener =
2525
await solvers["hyperlane7683"].listener.create();
26-
const hyperlane7683Filler =
27-
solvers["hyperlane7683"].filler.create(multiProvider);
26+
const hyperlane7683Filler = solvers["hyperlane7683"].filler.create(
27+
multiProvider,
28+
solvers["hyperlane7683"].rules,
29+
);
2830

2931
hyperlane7683Listener(hyperlane7683Filler);
3032
};

typescript/solver/solvers/BaseFiller.ts

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import {
55
isAllowedIntent,
66
} from "../config/index.js";
77
import type { Logger } from "../logger.js";
8-
9-
type Metadata = {
10-
protocolName: string;
11-
};
8+
import type { BaseMetadata, BuildRules } from "./types.js";
129

1310
export type ParsedArgs = {
1411
orderId: string;
@@ -19,8 +16,8 @@ export type ParsedArgs = {
1916
}>;
2017
};
2118

22-
export type Rule<
23-
TMetadata extends Metadata,
19+
export type BaseRule<
20+
TMetadata extends BaseMetadata,
2421
TParsedArgs extends ParsedArgs,
2522
TIntentData extends unknown,
2623
> = (
@@ -29,20 +26,20 @@ export type Rule<
2926
) => Promise<Result<string>>;
3027

3128
export abstract class BaseFiller<
32-
TMetadata extends Metadata,
29+
TMetadata extends BaseMetadata,
3330
TParsedArgs extends ParsedArgs,
3431
TIntentData extends unknown,
3532
> {
36-
rules: Array<Rule<TMetadata, TParsedArgs, TIntentData>> = [];
33+
rules: Array<BaseRule<TMetadata, TParsedArgs, TIntentData>> = [];
3734

3835
protected constructor(
3936
readonly multiProvider: MultiProvider,
4037
readonly allowBlockLists: GenericAllowBlockLists,
4138
readonly metadata: TMetadata,
4239
readonly log: Logger,
43-
rules?: Array<Rule<TMetadata, TParsedArgs, TIntentData>>,
40+
rulesConfig?: BuildRules<BaseRule<TMetadata, TParsedArgs, TIntentData>>,
4441
) {
45-
if (rules) this.rules = rules;
42+
if (rulesConfig) this.rules = this.buildRules(rulesConfig);
4643
}
4744

4845
create() {
@@ -51,15 +48,26 @@ export abstract class BaseFiller<
5148
originChainName: string,
5249
blockNumber: number,
5350
) => {
54-
const origin = await this.retrieveOriginInfo(parsedArgs, originChainName);
55-
const target = await this.retrieveTargetInfo(parsedArgs);
56-
57-
this.log.info({
58-
msg: "Intent Indexed",
59-
intent: `${this.metadata.protocolName}-${parsedArgs.orderId}`,
60-
origin: origin.join(", "),
61-
target: target.join(", "),
62-
});
51+
try {
52+
const origin = await this.retrieveOriginInfo(
53+
parsedArgs,
54+
originChainName,
55+
);
56+
const target = await this.retrieveTargetInfo(parsedArgs);
57+
58+
this.log.info({
59+
msg: "Intent Indexed",
60+
intent: `${this.metadata.protocolName}-${parsedArgs.orderId}`,
61+
origin: origin.join(", "),
62+
target: target.join(", "),
63+
});
64+
} catch (error) {
65+
this.log.error({
66+
msg: "Failed retrieving origin and target info",
67+
intent: `${this.metadata.protocolName}-${parsedArgs.orderId}`,
68+
error: JSON.stringify(error),
69+
});
70+
}
6371

6472
const intent = await this.prepareIntent(parsedArgs);
6573

@@ -73,7 +81,7 @@ export abstract class BaseFiller<
7381
try {
7482
await this.fill(parsedArgs, data, originChainName, blockNumber);
7583

76-
await this.settleOrder(parsedArgs, data);
84+
await this.settleOrder(parsedArgs, data, originChainName);
7785
} catch (error) {
7886
this.log.error({
7987
msg: `Failed processing intent`,
@@ -137,7 +145,11 @@ export abstract class BaseFiller<
137145
blockNumber: number,
138146
): Promise<void>;
139147

140-
protected async settleOrder(parsedArgs: TParsedArgs, data: TIntentData) {
148+
protected async settleOrder(
149+
parsedArgs: TParsedArgs,
150+
data: TIntentData,
151+
originChainName: string,
152+
) {
141153
return;
142154
}
143155

@@ -159,4 +171,38 @@ export abstract class BaseFiller<
159171
}),
160172
);
161173
}
174+
175+
private buildRules({
176+
base = [],
177+
custom,
178+
}: BuildRules<BaseRule<TMetadata, TParsedArgs, TIntentData>>): Array<
179+
BaseRule<TMetadata, TParsedArgs, TIntentData>
180+
> {
181+
const customRules = [];
182+
183+
if (this.metadata.customRules?.rules.length) {
184+
if (!custom) {
185+
throw new Error(
186+
"Custom rules are specified in metadata, but no corresponding rule functions were provided.",
187+
);
188+
}
189+
190+
for (let i = 0; i < this.metadata.customRules.rules.length; i++) {
191+
const rule = this.metadata.customRules.rules[i];
192+
const ruleFn = custom[rule.name];
193+
194+
if (!ruleFn) {
195+
throw new Error(
196+
`Custom rule "${rule.name}" is specified in metadata but is not provided in the custom rules configuration.`,
197+
);
198+
}
199+
200+
customRules.push(ruleFn(rule.args));
201+
}
202+
}
203+
204+
const keepBaseRules = this.metadata.customRules?.keepBaseRules ?? true;
205+
206+
return keepBaseRules ? [...base, ...customRules] : customRules;
207+
}
162208
}

typescript/solver/solvers/eco/config/metadata.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ const metadata: EcoMetadata = {
88
chainName: "optimismsepolia",
99
},
1010
],
11-
adapters: [
12-
{
13-
address: "0x218FB5210d4eE248f046F3EC8B5Dd1c7Bc0756e5",
14-
chainName: "basesepolia",
15-
},
16-
],
11+
adapters: {
12+
basesepolia: "0x218FB5210d4eE248f046F3EC8B5Dd1c7Bc0756e5",
13+
},
1714
};
1815

1916
EcoMetadataSchema.parse(metadata);

typescript/solver/solvers/eco/filler.ts

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { chainIds, chainIdsToName } from "../../config/index.js";
88
import { Erc20__factory } from "../../typechain/factories/contracts/Erc20__factory.js";
99
import { EcoAdapter__factory } from "../../typechain/factories/eco/contracts/EcoAdapter__factory.js";
1010
import { BaseFiller } from "../BaseFiller.js";
11+
import { BuildRules, RulesMap } from "../types.js";
1112
import {
1213
retrieveOriginInfo,
1314
retrieveTargetInfo,
@@ -17,33 +18,11 @@ import { allowBlockLists, metadata } from "./config/index.js";
1718
import type { EcoMetadata, IntentData, ParsedArgs } from "./types.js";
1819
import { log, withdrawRewards } from "./utils.js";
1920

20-
export type Metadata = {
21-
adapters: { [chainId: string]: EcoMetadata["adapters"][number] };
22-
protocolName: EcoMetadata["protocolName"];
23-
};
24-
2521
export type EcoRule = EcoFiller["rules"][number];
2622

27-
export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
28-
constructor(
29-
multiProvider: MultiProvider,
30-
rules?: BaseFiller<Metadata, ParsedArgs, IntentData>["rules"],
31-
) {
32-
const { adapters, protocolName } = metadata;
33-
const ecoFillerMetadata = {
34-
adapters: adapters.reduce<{
35-
[chainId: string]: EcoMetadata["adapters"][number];
36-
}>(
37-
(acc, adapter) => ({
38-
...acc,
39-
[chainIds[adapter.chainName]]: adapter,
40-
}),
41-
{},
42-
),
43-
protocolName,
44-
};
45-
46-
super(multiProvider, allowBlockLists, ecoFillerMetadata, log, rules);
23+
export class EcoFiller extends BaseFiller<EcoMetadata, ParsedArgs, IntentData> {
24+
constructor(multiProvider: MultiProvider, rules?: BuildRules<EcoRule>) {
25+
super(multiProvider, allowBlockLists, metadata, log, rules);
4726
}
4827

4928
protected retrieveOriginInfo(parsedArgs: ParsedArgs, chainName: string) {
@@ -81,10 +60,12 @@ export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
8160
protected async prepareIntent(
8261
parsedArgs: ParsedArgs,
8362
): Promise<Result<IntentData>> {
84-
const adapter =
85-
this.metadata.adapters[parsedArgs._destinationChain.toString()];
63+
const chainName = this.multiProvider.getChainName(
64+
parsedArgs._destinationChain.toString(),
65+
);
66+
const adapterAddress = this.metadata.adapters[chainName];
8667

87-
if (!adapter) {
68+
if (!adapterAddress) {
8869
return {
8970
error: "No adapter found for destination chain",
9071
success: false,
@@ -94,7 +75,7 @@ export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
9475
try {
9576
await super.prepareIntent(parsedArgs);
9677

97-
return { data: { adapter }, success: true };
78+
return { data: { adapterAddress }, success: true };
9879
} catch (error: any) {
9980
return {
10081
error: error.message ?? "Failed to prepare Eco Intent.",
@@ -117,7 +98,7 @@ export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
11798
msg: "Approving tokens",
11899
protocolName: this.metadata.protocolName,
119100
intentHash: parsedArgs._hash,
120-
adapterAddress: data.adapter.address,
101+
adapterAddress: data.adapterAddress,
121102
});
122103

123104
const erc20Interface = Erc20__factory.createInterface();
@@ -136,25 +117,21 @@ export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
136117
return acc;
137118
}, {});
138119

139-
const signer = this.multiProvider.getSigner(data.adapter.chainName);
120+
const destinationChainId = parsedArgs._destinationChain.toString();
121+
const signer = this.multiProvider.getSigner(destinationChainId);
122+
140123
await Promise.all(
141124
Object.entries(requiredAmountsByTarget).map(
142125
async ([target, requiredAmount]) => {
143126
const erc20 = Erc20__factory.connect(target, signer);
144127

145-
const tx = await erc20.approve(data.adapter.address, requiredAmount);
128+
const tx = await erc20.approve(data.adapterAddress, requiredAmount);
146129
await tx.wait();
147130
},
148131
),
149132
);
150133

151-
const _chainId = parsedArgs._destinationChain.toString();
152-
153-
const filler = this.multiProvider.getSigner(_chainId);
154-
const ecoAdapter = EcoAdapter__factory.connect(
155-
data.adapter.address,
156-
filler,
157-
);
134+
const ecoAdapter = EcoAdapter__factory.connect(data.adapterAddress, signer);
158135

159136
const claimantAddress =
160137
await this.multiProvider.getSignerAddress(originChainName);
@@ -190,10 +167,14 @@ export class EcoFiller extends BaseFiller<Metadata, ParsedArgs, IntentData> {
190167
});
191168
}
192169

193-
settleOrder(parsedArgs: ParsedArgs, data: IntentData) {
170+
settleOrder(
171+
parsedArgs: ParsedArgs,
172+
data: IntentData,
173+
originChainName: string,
174+
) {
194175
return withdrawRewards(
195176
parsedArgs,
196-
data.adapter.chainName,
177+
originChainName,
197178
this.multiProvider,
198179
this.metadata.protocolName,
199180
);
@@ -241,13 +222,10 @@ const enoughBalanceOnDestination: EcoRule = async (parsedArgs, context) => {
241222

242223
export const create = (
243224
multiProvider: MultiProvider,
244-
rules?: EcoFiller["rules"],
245-
keepBaseRules = true,
225+
customRules?: RulesMap<EcoRule>,
246226
) => {
247-
const customRules = rules ?? [];
248-
249-
return new EcoFiller(
250-
multiProvider,
251-
keepBaseRules ? [enoughBalanceOnDestination, ...customRules] : customRules,
252-
).create();
227+
return new EcoFiller(multiProvider, {
228+
base: [enoughBalanceOnDestination],
229+
custom: customRules,
230+
}).create();
253231
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * as filler from "./filler.js";
22
export * as listener from "./listener.js";
3+
export * as rules from "./rules/index.js";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

typescript/solver/solvers/eco/types.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,20 @@ import z from "zod";
22
import { chainNames } from "../../config/index.js";
33
import { addressSchema } from "../../config/types.js";
44
import type { IntentCreatedEventObject } from "../../typechain/eco/contracts/IntentSource.js";
5+
import { BaseMetadataSchema } from "../types.js";
56

6-
export const EcoMetadataSchema = z.object({
7-
protocolName: z.string(),
8-
intentSources: z.array(
9-
z.object({
10-
address: addressSchema,
11-
chainName: z.string().refine((name) => chainNames.includes(name), {
12-
message: "Invalid chainName",
13-
}),
14-
}),
15-
),
16-
adapters: z.array(
17-
z.object({
18-
address: addressSchema,
19-
chainName: z.string().refine((name) => chainNames.includes(name), {
20-
message: "Invalid chainName",
21-
}),
7+
export const EcoMetadataSchema = BaseMetadataSchema.extend({
8+
adapters: z.record(
9+
z.string().refine((name) => chainNames.includes(name), {
10+
message: "Invalid chainName",
2211
}),
12+
addressSchema,
2313
),
2414
});
2515

2616
export type EcoMetadata = z.infer<typeof EcoMetadataSchema>;
2717

28-
export type IntentData = { adapter: EcoMetadata["adapters"][number] };
18+
export type IntentData = { adapterAddress: z.infer<typeof addressSchema> };
2919

3020
export type ParsedArgs = IntentCreatedEventObject & {
3121
orderId: string;

0 commit comments

Comments
 (0)