Skip to content

Commit 85a809e

Browse files
authored
improve(Dataworker): Remove 0-value refunds and 0-value empty-message slow fills (#1957)
* improve(Dataworker): Remove 0-value refunds and 0-value empty-message slow fills Implements optimizations to [UMIP-179](UMAprotocol/UMIPs#606) The goal here is protecting dataworker from performing extra computations where no on-chain state changes can happen, in the case of 0-value refunds and 0-value empty-message slow fills. * WIP * Update src/dataworker/DataworkerUtils.ts * Update DataworkerUtils.ts * Use SDK isZeroValueDeposit
1 parent 38e159d commit 85a809e

File tree

4 files changed

+206
-27
lines changed

4 files changed

+206
-27
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"@across-protocol/constants": "^3.1.25",
1414
"@across-protocol/contracts": "^3.0.19",
15-
"@across-protocol/sdk": "^3.3.31",
15+
"@across-protocol/sdk": "^3.3.32",
1616
"@arbitrum/sdk": "^4.0.2",
1717
"@consensys/linea-sdk": "^0.2.1",
1818
"@defi-wonderland/smock": "^2.3.5",

src/dataworker/DataworkerUtils.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { CONTRACT_ADDRESSES } from "../common/ContractAddresses";
1010
import {
1111
PoolRebalanceLeaf,
12+
Refund,
1213
RelayerRefundLeaf,
1314
RelayerRefundLeafWithGroup,
1415
RunningBalances,
@@ -160,10 +161,13 @@ export function _buildSlowRelayRoot(bundleSlowFillsV3: BundleSlowFills): {
160161
// Append V3 slow fills to the V2 leaf list
161162
Object.values(bundleSlowFillsV3).forEach((depositsForChain) => {
162163
Object.values(depositsForChain).forEach((deposits) => {
163-
deposits.forEach((deposit) => {
164-
const v3SlowFillLeaf = buildV3SlowFillLeaf(deposit, deposit.lpFeePct);
165-
slowRelayLeaves.push(v3SlowFillLeaf);
166-
});
164+
// Do not create slow fill leaves where the amount to transfer would be 0 and the message is empty
165+
deposits
166+
.filter((deposit) => !utils.isZeroValueDeposit(deposit))
167+
.forEach((deposit) => {
168+
const v3SlowFillLeaf = buildV3SlowFillLeaf(deposit, deposit.lpFeePct);
169+
slowRelayLeaves.push(v3SlowFillLeaf);
170+
});
167171
});
168172
});
169173

@@ -233,10 +237,6 @@ export function _buildRelayerRefundRoot(
233237
Object.entries(combinedRefunds).forEach(([_repaymentChainId, refundsForChain]) => {
234238
const repaymentChainId = Number(_repaymentChainId);
235239
Object.entries(refundsForChain).forEach(([l2TokenAddress, refunds]) => {
236-
// We need to sort leaves deterministically so that the same root is always produced from the same loadData
237-
// return value, so sort refund addresses by refund amount (descending) and then address (ascending).
238-
const sortedRefundAddresses = sortRefundAddresses(refunds);
239-
240240
const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
241241
l2TokenAddress,
242242
repaymentChainId,
@@ -255,20 +255,8 @@ export function _buildRelayerRefundRoot(
255255
runningBalances[repaymentChainId][l1TokenCounterpart]
256256
);
257257

258-
// Create leaf for { repaymentChainId, L2TokenAddress }, split leaves into sub-leaves if there are too many
259-
// refunds.
260-
for (let i = 0; i < sortedRefundAddresses.length; i += maxRefundCount) {
261-
relayerRefundLeaves.push({
262-
groupIndex: i, // Will delete this group index after using it to sort leaves for the same chain ID and
263-
// L2 token address
264-
amountToReturn: i === 0 ? amountToReturn : bnZero,
265-
chainId: repaymentChainId,
266-
refundAmounts: sortedRefundAddresses.slice(i, i + maxRefundCount).map((address) => refunds[address]),
267-
leafId: 0, // Will be updated before inserting into tree when we sort all leaves.
268-
l2TokenAddress,
269-
refundAddresses: sortedRefundAddresses.slice(i, i + maxRefundCount),
270-
});
271-
}
258+
const _refundLeaves = _getRefundLeaves(refunds, amountToReturn, repaymentChainId, l2TokenAddress, maxRefundCount);
259+
relayerRefundLeaves.push(..._refundLeaves);
272260
});
273261
});
274262

@@ -325,6 +313,42 @@ export function _buildRelayerRefundRoot(
325313
};
326314
}
327315

316+
export function _getRefundLeaves(
317+
refunds: Refund,
318+
amountToReturn: BigNumber,
319+
repaymentChainId: number,
320+
l2TokenAddress: string,
321+
maxRefundCount: number
322+
): RelayerRefundLeafWithGroup[] {
323+
const nonZeroRefunds = Object.fromEntries(Object.entries(refunds).filter(([, refundAmount]) => refundAmount.gt(0)));
324+
// We need to sort leaves deterministically so that the same root is always produced from the same loadData
325+
// return value, so sort refund addresses by refund amount (descending) and then address (ascending).
326+
const sortedRefundAddresses = sortRefundAddresses(nonZeroRefunds);
327+
328+
const relayerRefundLeaves: RelayerRefundLeafWithGroup[] = [];
329+
330+
// Create leaf for { repaymentChainId, L2TokenAddress }, split leaves into sub-leaves if there are too many
331+
// refunds.
332+
for (let i = 0; i < sortedRefundAddresses.length; i += maxRefundCount) {
333+
const newLeaf = {
334+
groupIndex: i, // Will delete this group index after using it to sort leaves for the same chain ID and
335+
// L2 token address
336+
amountToReturn: i === 0 ? amountToReturn : bnZero,
337+
chainId: repaymentChainId,
338+
refundAmounts: sortedRefundAddresses.slice(i, i + maxRefundCount).map((address) => refunds[address]),
339+
leafId: 0, // Will be updated before inserting into tree when we sort all leaves.
340+
l2TokenAddress,
341+
refundAddresses: sortedRefundAddresses.slice(i, i + maxRefundCount),
342+
};
343+
assert(
344+
newLeaf.refundAmounts.length === newLeaf.refundAddresses.length,
345+
"refund address and amount array lengths mismatch"
346+
);
347+
relayerRefundLeaves.push(newLeaf);
348+
}
349+
return relayerRefundLeaves;
350+
}
351+
328352
/**
329353
* @notice Returns WETH and ETH token addresses for chain if defined, or throws an error if they're not
330354
* in the hardcoded dictionary.

test/DataworkerUtils.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { _buildSlowRelayRoot, _getRefundLeaves } from "../src/dataworker/DataworkerUtils";
2+
import { BundleSlowFills, DepositWithBlock } from "../src/interfaces";
3+
import { BigNumber, bnOne, bnZero, toBNWei, ZERO_ADDRESS } from "../src/utils";
4+
import { repaymentChainId } from "./constants";
5+
import { assert, expect, randomAddress } from "./utils";
6+
7+
describe("RelayerRefund utils", function () {
8+
it("Removes zero value refunds from relayer refund root", async function () {
9+
const recipient1 = randomAddress();
10+
const recipient2 = randomAddress();
11+
const repaymentToken = randomAddress();
12+
const maxRefundsPerLeaf = 2;
13+
const result = _getRefundLeaves(
14+
{
15+
[recipient1]: bnZero,
16+
[recipient2]: bnOne,
17+
},
18+
bnZero,
19+
repaymentChainId,
20+
repaymentToken,
21+
maxRefundsPerLeaf
22+
);
23+
expect(result.length).to.equal(1);
24+
expect(result[0].refundAddresses.length).to.equal(1);
25+
});
26+
it("No more than maxRefundsPerLeaf number of refunds in a leaf", async function () {
27+
const recipient1 = randomAddress();
28+
const recipient2 = randomAddress();
29+
const repaymentToken = randomAddress();
30+
const amountToReturn = bnOne;
31+
const maxRefundsPerLeaf = 1;
32+
const result = _getRefundLeaves(
33+
{
34+
[recipient1]: bnOne,
35+
[recipient2]: bnOne,
36+
},
37+
amountToReturn,
38+
repaymentChainId,
39+
repaymentToken,
40+
maxRefundsPerLeaf
41+
);
42+
expect(result.length).to.equal(2);
43+
// Only the first leaf should have an amount to return.
44+
expect(result[0].groupIndex).to.equal(0);
45+
expect(result[0].amountToReturn).to.equal(amountToReturn);
46+
expect(result[1].groupIndex).to.equal(1);
47+
expect(result[1].amountToReturn).to.equal(0);
48+
});
49+
it("Sorts refunds by amount in descending order", async function () {
50+
const recipient1 = randomAddress();
51+
const recipient2 = randomAddress();
52+
const repaymentToken = randomAddress();
53+
const maxRefundsPerLeaf = 2;
54+
const result = _getRefundLeaves(
55+
{
56+
[recipient1]: bnOne,
57+
[recipient2]: bnOne.mul(2),
58+
},
59+
bnZero,
60+
repaymentChainId,
61+
repaymentToken,
62+
maxRefundsPerLeaf
63+
);
64+
expect(result.length).to.equal(1);
65+
expect(result[0].refundAddresses[0]).to.equal(recipient2);
66+
expect(result[0].refundAddresses[1]).to.equal(recipient1);
67+
});
68+
});
69+
70+
describe("SlowFill utils", function () {
71+
/**
72+
* @notice Returns dummy slow fill leaf that you can insert into a BundleSlowFills object.
73+
* @dev The leaf returned will not actually be executable so its good for testing functions
74+
* that produce but do not execute merkle leaves.
75+
* @param depositId This is used to sort slow fill leaves so allow caller to set.
76+
* @param amountToFill This will be set to the deposit's inputAmount because the slow fill pays out
77+
* inputAmount * (1 - lpFeePct).
78+
* @param lpFeePct Amount to charge on the amountToFill.
79+
* @param message 0-value, empty-message slow fills should be ignored by dataworker so allow caller
80+
* to set this to non-empty to test logic.
81+
* @param originChainId This is used to sort slow fill leaves so allow caller to set.
82+
*/
83+
84+
function createSlowFillLeaf(
85+
depositId: number,
86+
originChainId: number,
87+
amountToFill: BigNumber,
88+
message: string,
89+
_lpFeePct: BigNumber
90+
): DepositWithBlock & { lpFeePct: BigNumber } {
91+
assert(message.slice(0, 2) === "0x", "Need to specify message beginning with 0x");
92+
const destinationChainId = originChainId + 1;
93+
const deposit: DepositWithBlock = {
94+
inputAmount: amountToFill,
95+
inputToken: randomAddress(),
96+
outputAmount: bnOne,
97+
outputToken: randomAddress(),
98+
depositor: randomAddress(),
99+
depositId,
100+
originChainId: 1,
101+
recipient: randomAddress(),
102+
exclusiveRelayer: ZERO_ADDRESS,
103+
exclusivityDeadline: 0,
104+
message,
105+
destinationChainId,
106+
fillDeadline: 0,
107+
quoteBlockNumber: 0,
108+
blockNumber: 0,
109+
transactionHash: "",
110+
logIndex: 0,
111+
transactionIndex: 0,
112+
quoteTimestamp: 0,
113+
fromLiteChain: false,
114+
toLiteChain: false,
115+
};
116+
return {
117+
...deposit,
118+
lpFeePct: _lpFeePct,
119+
};
120+
}
121+
it("Filters out 0-value empty-message slowfills", async function () {
122+
const zeroValueSlowFillLeaf = createSlowFillLeaf(0, 1, bnZero, "0x", bnZero);
123+
const oneWeiSlowFillLeaf = createSlowFillLeaf(1, 1, bnOne, "0x", bnZero);
124+
const zeroValueNonEmptyMessageSlowFillLeaf = createSlowFillLeaf(2, 1, bnZero, "0x12", bnZero);
125+
const bundleSlowFills: BundleSlowFills = {
126+
[zeroValueSlowFillLeaf.destinationChainId]: {
127+
[zeroValueSlowFillLeaf.outputToken]: [
128+
zeroValueSlowFillLeaf,
129+
oneWeiSlowFillLeaf,
130+
zeroValueNonEmptyMessageSlowFillLeaf,
131+
],
132+
},
133+
};
134+
135+
// Should return two out of three leaves, sorted by deposit ID.
136+
const { leaves } = _buildSlowRelayRoot(bundleSlowFills);
137+
expect(leaves.length).to.equal(2);
138+
expect(leaves[0].relayData.depositId).to.equal(1);
139+
expect(leaves[1].relayData.depositId).to.equal(2);
140+
});
141+
it("Applies LP fee to input amount", async function () {
142+
const slowFillLeaf = createSlowFillLeaf(0, 1, toBNWei("4"), "0x", toBNWei("0.25"));
143+
const bundleSlowFills: BundleSlowFills = {
144+
[slowFillLeaf.destinationChainId]: {
145+
[slowFillLeaf.outputToken]: [slowFillLeaf],
146+
},
147+
};
148+
149+
// Should return two out of three leaves, sorted by deposit ID.
150+
const { leaves } = _buildSlowRelayRoot(bundleSlowFills);
151+
expect(leaves.length).to.equal(1);
152+
// updatedOutputAmount should be equal to inputAmount * (1 - lpFee) so 4 * (1 - 0.25) = 3
153+
expect(leaves[0].updatedOutputAmount).to.equal(toBNWei("3"));
154+
});
155+
});

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@
5858
yargs "^17.7.2"
5959
zksync-web3 "^0.14.3"
6060

61-
"@across-protocol/sdk@^3.3.31":
62-
version "3.3.31"
63-
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.31.tgz#6fe622517962f84fa140184ec209529f755d9787"
64-
integrity sha512-C7LGiNC+kKxPfimomlRIuKDBD2u7uH19YRiRJjLBbt0pPbl9L40ockXxYOs7+WSxgCjXnY03micljhNz2wvHyg==
61+
"@across-protocol/sdk@^3.3.32":
62+
version "3.3.32"
63+
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.32.tgz#fa2428df5f9b6cb0392c46f742f11265efa4abb3"
64+
integrity sha512-ADyZQeWxjGAreLoeVQYNiJN4zMmmJ7h6ItgbSjP2+JvZENPaH9t23xCegPIyI0oiVqLrOHOGCJ/yEdX6X3HqpQ==
6565
dependencies:
6666
"@across-protocol/across-token" "^1.0.0"
6767
"@across-protocol/constants" "^3.1.25"

0 commit comments

Comments
 (0)