Skip to content

Commit 3ad3082

Browse files
Feat/chain specific overrides relay fee calculator (#997)
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org> Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com>
1 parent ae5519e commit 3ad3082

File tree

2 files changed

+123
-8
lines changed

2 files changed

+123
-8
lines changed

src/relayFeeCalculator/relayFeeCalculator.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ChainIdAsString = string;
4747
export interface CapitalCostConfigOverride {
4848
default: CapitalCostConfig;
4949
routeOverrides?: Record<ChainIdAsString, Record<ChainIdAsString, CapitalCostConfig>>;
50+
destinationChainOverrides?: Record<ChainIdAsString, CapitalCostConfig>;
5051
}
5152
export type RelayCapitalCostConfig = CapitalCostConfigOverride | CapitalCostConfig;
5253
export interface BaseRelayFeeCalculatorConfig {
@@ -189,10 +190,10 @@ export class RelayFeeCalculator {
189190
this.validateCapitalCostsConfig(config.default);
190191
// Iterate over all the route overrides and validate them.
191192
for (const toChainIdRoutes of Object.values(config.routeOverrides || {})) {
192-
for (const override of Object.values(toChainIdRoutes)) {
193-
this.validateCapitalCostsConfig(override);
194-
}
193+
Object.values(toChainIdRoutes).forEach(this.validateCapitalCostsConfig);
195194
}
195+
// Validate destination chain overrides
196+
Object.values(config.destinationChainOverrides || {}).forEach(this.validateCapitalCostsConfig);
196197
return config;
197198
}
198199

@@ -310,10 +311,9 @@ export class RelayFeeCalculator {
310311
// bound to an upper bound. After the kink, the fee % increase will be fixed, and slowly approach the upper bound
311312
// for very large amount inputs.
312313
else {
313-
const config =
314-
isDefined(_originRoute) && isDefined(_destinationRoute)
315-
? tokenCostConfig.routeOverrides?.[_originRoute]?.[_destinationRoute] ?? tokenCostConfig.default
316-
: tokenCostConfig.default;
314+
const destinationChainOverride = tokenCostConfig?.destinationChainOverrides?.[_destinationRoute || ""];
315+
const routeOverride = tokenCostConfig?.routeOverrides?.[_originRoute || ""]?.[_destinationRoute || ""];
316+
const config: CapitalCostConfig = routeOverride ?? destinationChainOverride ?? tokenCostConfig.default;
317317

318318
// Scale amount "y" to 18 decimals.
319319
const y = toBN(_amountToRelay).mul(toBNWei("1", 18 - config.decimals));

test/relayFeeCalculator.test.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
makeCustomTransport,
3030
} from "./utils";
3131
import assert from "assert";
32-
import { TOKEN_SYMBOLS_MAP } from "@across-protocol/constants";
32+
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants";
3333
import { EMPTY_MESSAGE, ZERO_ADDRESS } from "../src/constants";
3434
import { SpokePool } from "@across-protocol/contracts";
3535
import { QueryBase, QueryBase__factory } from "../src/relayFeeCalculator";
@@ -280,6 +280,121 @@ describe("RelayFeeCalculator", () => {
280280
assert.equal(client.capitalFeePercent("0", "ZERO_CUTOFF_WBTC").toString(), Number.MAX_SAFE_INTEGER.toString());
281281
assert.equal(client.capitalFeePercent("0", "WBTC").toString(), Number.MAX_SAFE_INTEGER.toString());
282282
});
283+
284+
it("destinationChainOverrides", () => {
285+
// Create a client with destination chain and route overrides
286+
const token = TOKEN_SYMBOLS_MAP.USDC;
287+
288+
const customCapitalCostsConfig = {
289+
[token.symbol]: {
290+
default: {
291+
lowerBound: toBNWei("0.0003").toString(),
292+
upperBound: toBNWei("0.002").toString(),
293+
cutoff: toBNWei("15").toString(),
294+
decimals: token.decimals,
295+
},
296+
destinationChainOverrides: {
297+
// Override for destination chain ARBITRUM
298+
[CHAIN_IDs.ARBITRUM]: {
299+
lowerBound: toBNWei("0.0005").toString(),
300+
upperBound: toBNWei("0.003").toString(),
301+
cutoff: toBNWei("10").toString(),
302+
decimals: token.decimals,
303+
},
304+
},
305+
routeOverrides: {
306+
// Override for route MAINNET->ARBITRUM
307+
[CHAIN_IDs.MAINNET]: {
308+
[CHAIN_IDs.ARBITRUM]: {
309+
lowerBound: toBNWei("0.0007").toString(),
310+
upperBound: toBNWei("0.004").toString(),
311+
cutoff: toBNWei("8").toString(),
312+
decimals: token.decimals,
313+
},
314+
},
315+
},
316+
},
317+
};
318+
319+
const client = new RelayFeeCalculator({
320+
queries,
321+
capitalCostsConfig: customCapitalCostsConfig,
322+
});
323+
324+
// Get config values for cleaner assertions
325+
const defaultConfig = customCapitalCostsConfig[token.symbol].default;
326+
const chainOverride = customCapitalCostsConfig[token.symbol].destinationChainOverrides[CHAIN_IDs.ARBITRUM];
327+
const routeOverride = customCapitalCostsConfig[token.symbol].routeOverrides[CHAIN_IDs.MAINNET][CHAIN_IDs.ARBITRUM];
328+
329+
// Test using default config (no routes specified)
330+
const defaultFee = client.capitalFeePercent(toBNWei("1", token.decimals), token.symbol);
331+
assert.ok(toBN(defaultFee).gte(toBN(defaultConfig.lowerBound)), "Default fee should be at least the lower bound");
332+
assert.ok(toBN(defaultFee).lt(toBN(defaultConfig.upperBound)), "Default fee should be less than the upper bound");
333+
334+
// Test using destination chain override (only destination specified)
335+
const destinationFee = client.capitalFeePercent(
336+
toBNWei("1", token.decimals),
337+
token.symbol,
338+
undefined,
339+
CHAIN_IDs.ARBITRUM.toString()
340+
);
341+
assert.ok(
342+
toBN(destinationFee).gte(toBN(chainOverride.lowerBound)),
343+
"Destination override fee should be at least its lower bound"
344+
);
345+
assert.ok(
346+
toBN(destinationFee).lt(toBN(chainOverride.upperBound)),
347+
"Destination override fee should be less than its upper bound"
348+
);
349+
350+
// Test using route-specific override (both origin and destination specified)
351+
const routeFee = client.capitalFeePercent(
352+
toBNWei("1", token.decimals),
353+
token.symbol,
354+
CHAIN_IDs.MAINNET.toString(),
355+
CHAIN_IDs.ARBITRUM.toString()
356+
);
357+
assert.ok(
358+
toBN(routeFee).gte(toBN(routeOverride.lowerBound)),
359+
"Route override fee should be at least its lower bound"
360+
);
361+
assert.ok(
362+
toBN(routeFee).lt(toBN(routeOverride.upperBound)),
363+
"Route override fee should be less than its upper bound"
364+
);
365+
366+
// Test fallback to destination chain override when a route is not specifically overridden
367+
const fallbackToDestFee = client.capitalFeePercent(
368+
toBNWei("1", token.decimals),
369+
token.symbol,
370+
CHAIN_IDs.OPTIMISM.toString(),
371+
CHAIN_IDs.ARBITRUM.toString()
372+
);
373+
assert.ok(
374+
toBN(fallbackToDestFee).gte(toBN(chainOverride.lowerBound)),
375+
"Fallback to destination fee should be at least the destination lower bound"
376+
);
377+
assert.ok(
378+
toBN(fallbackToDestFee).lt(toBN(chainOverride.upperBound)),
379+
"Fallback to destination fee should be less than the destination upper bound"
380+
);
381+
382+
// Test fallback to default when route doesn't match and no destination chain override exists
383+
const fallbackToDefaultFee = client.capitalFeePercent(
384+
toBNWei("1", token.decimals),
385+
token.symbol,
386+
CHAIN_IDs.OPTIMISM.toString(),
387+
CHAIN_IDs.POLYGON.toString()
388+
);
389+
assert.ok(
390+
toBN(fallbackToDefaultFee).gte(toBN(defaultConfig.lowerBound)),
391+
"Fallback to default fee should be at least the default lower bound"
392+
);
393+
assert.ok(
394+
toBN(fallbackToDefaultFee).lt(toBN(defaultConfig.upperBound)),
395+
"Fallback to default fee should be less than the default upper bound"
396+
);
397+
});
283398
});
284399

285400
describe("RelayFeeCalculator: Composable Bridging", function () {

0 commit comments

Comments
 (0)