Skip to content

Commit

Permalink
feat(Dataworker): Skip exchangeRateCurrent updates for pooled tokens …
Browse files Browse the repository at this point in the history
…whose liquid reserves wouldn't increase (#1137)

* feat(Dataworker): Skip exchangeRateCurrent updates for pooled tokens whose liquid reserves wouldn't increase

Signed-off-by: nicholaspai <[email protected]>

* Update index.ts

* Update src/dataworker/Dataworker.ts

Co-authored-by: Paul <[email protected]>

* Update Dataworker.ts

* Update Dataworker.executePoolRebalances.ts

---------

Signed-off-by: nicholaspai <[email protected]>
Co-authored-by: Paul <[email protected]>
  • Loading branch information
nicholaspai and pxrl authored Jan 8, 2024
1 parent 6e3bee1 commit fd25b90
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 11 deletions.
41 changes: 35 additions & 6 deletions src/dataworker/Dataworker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1856,7 +1856,7 @@ export class Dataworker {

async _updateExchangeRates(l1Tokens: string[], submitExecution: boolean): Promise<void> {
const syncedL1Tokens: string[] = [];
l1Tokens.forEach((l1Token) => {
await sdk.utils.forEachAsync(l1Tokens, async (l1Token) => {
// Exit early if we already synced this l1 token on this loop
if (syncedL1Tokens.includes(l1Token)) {
return;
Expand All @@ -1874,17 +1874,46 @@ export class Dataworker {
return;
}

// Check how liquidReserves will be affected by the exchange rate update and skip it if it wouldn't increase.
// Updating exchange rate current or sync-ing pooled tokens is used only to potentially increase liquid
// reserves available to the HubPool to execute pool rebalance leaves, particularly fot tokens that haven't
// updated recently. If the liquid reserves would not increase, then we skip the update.
const hubPool = this.clients.hubPoolClient.hubPool;
const multicallInput = [
hubPool.interface.encodeFunctionData("pooledTokens", [l1Token]),
hubPool.interface.encodeFunctionData("sync", [l1Token]),
hubPool.interface.encodeFunctionData("pooledTokens", [l1Token]),
];
const multicallOutput = await hubPool.callStatic.multicall(multicallInput);
const currentPooledTokens = hubPool.interface.decodeFunctionResult("pooledTokens", multicallOutput[0]);
const updatedPooledTokens = hubPool.interface.decodeFunctionResult("pooledTokens", multicallOutput[2]);
const liquidReservesDelta = updatedPooledTokens.liquidReserves.sub(currentPooledTokens.liquidReserves);

// If the delta is positive, then the update will increase liquid reserves and
// at this point, we want to update the liquid reserves to make more available
// for executing a pool rebalance leaf.
const chainId = this.clients.hubPoolClient.chainId;
const tokenSymbol = this.clients.hubPoolClient.getTokenInfo(chainId, l1Token)?.symbol;

if (liquidReservesDelta.lte(0)) {
this.logger.debug({
at: "Dataworker#_updateExchangeRates",
message: `Skipping exchange rate update for ${tokenSymbol} because liquid reserves would not increase`,
currentPooledTokens,
updatedPooledTokens,
liquidReservesDelta,
});
return;
}

if (submitExecution) {
const chainId = this.clients.hubPoolClient.chainId;
this.clients.multiCallerClient.enqueueTransaction({
contract: this.clients.hubPoolClient.hubPool,
contract: hubPool,
chainId,
method: "exchangeRateCurrent",
args: [l1Token],
message: "Updated exchange rate ♻️!",
mrkdwn: `Updated exchange rate for l1 token: ${
this.clients.hubPoolClient.getTokenInfo(chainId, l1Token)?.symbol
}`,
mrkdwn: `Updated exchange rate for l1 token: ${tokenSymbol}`,
unpermissioned: true,
});
}
Expand Down
120 changes: 115 additions & 5 deletions test/Dataworker.executePoolRebalances.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { HubPoolClient, MultiCallerClient, SpokePoolClient } from "../src/clients";
import { MAX_UINT_VAL } from "../src/utils";
import { MAX_UINT_VAL, toBNWei } from "../src/utils";
import {
MAX_L1_TOKENS_PER_POOL_REBALANCE_LEAF,
MAX_REFUNDS_PER_RELAYER_REFUND_LEAF,
amountToDeposit,
} from "./constants";
import { setupDataworker } from "./fixtures/Dataworker.Fixture";
import { Contract, SignerWithAddress, buildDeposit, buildFillForRepaymentChain, ethers, expect } from "./utils";
import {
Contract,
FakeContract,
SignerWithAddress,
buildDeposit,
buildFillForRepaymentChain,
ethers,
expect,
smock,
toBN,
zeroAddress,
} from "./utils";

// Tested
import { BalanceAllocator } from "../src/clients/BalanceAllocator";
import { spokePoolClientsToProviders } from "../src/common";
import { Dataworker } from "../src/dataworker/Dataworker";
import { MockHubPoolClient } from "./mocks/MockHubPoolClient";

// Set to arbitrum to test that the dataworker sends ETH to the HubPool to test L1 --> Arbitrum message transfers.
const destinationChainId = 42161;
Expand Down Expand Up @@ -81,9 +93,10 @@ describe("Dataworker: Execute pool rebalances", async function () {
await updateAllClients();
await dataworkerInstance.executePoolRebalanceLeaves(spokePoolClients, new BalanceAllocator(providers));

// Should be 4 transactions: 1 for `exchangeRateCurrent`, 1 for the to chain, 1 for the from chain, and 1 for the extra ETH sent to cover
// arbitrum gas fees.Should NOT be 3 since not enough time has passed since the last lp fee update.
expect(multiCallerClient.transactionCount()).to.equal(4);
// Should be 3 transactions: 1 for the to chain, 1 for the from chain, and 1 for the extra ETH sent to cover
// arbitrum gas fees. exchangeRateCurrent isn't updated because liquidReserves wouldn't increase after calling
// sync() on the spoke pool.
expect(multiCallerClient.transactionCount()).to.equal(3);
await multiCallerClient.executeTransactionQueue();

// TEST 3:
Expand Down Expand Up @@ -120,4 +133,101 @@ describe("Dataworker: Execute pool rebalances", async function () {
pendingBundle = await hubPool.rootBundleProposal();
expect(pendingBundle.unclaimedPoolRebalanceLeafCount).to.equal(0);
});
describe("_updateExchangeRates", function () {
let mockHubPoolClient: MockHubPoolClient, fakeHubPool: FakeContract;
beforeEach(async function () {
fakeHubPool = await smock.fake(hubPool.interface, { address: hubPool.address });
mockHubPoolClient = new MockHubPoolClient(hubPoolClient.logger, fakeHubPool, hubPoolClient.configStoreClient);
mockHubPoolClient.setTokenInfoToReturn({ address: l1Token_1.address, decimals: 18, symbol: "TEST" });
dataworkerInstance.clients.hubPoolClient = mockHubPoolClient;
});
it("exits early if we recently synced l1 token", async function () {
mockHubPoolClient.currentTime = 10_000;
mockHubPoolClient.setLpTokenInfo(l1Token_1.address, 10_000);
await dataworkerInstance._updateExchangeRates([l1Token_1.address], true);
expect(multiCallerClient.transactionCount()).to.equal(0);
});
it("exits early if liquid reserves wouldn't increase for token post-update", async function () {
// Last update was at time 0, current time is at 10_000, so definitely past the update threshold
mockHubPoolClient.currentTime = 10_000;
mockHubPoolClient.setLpTokenInfo(l1Token_1.address, 0);

// Hardcode multicall output such that it looks like liquid reserves stayed the same
fakeHubPool.multicall.returns([
hubPool.interface.encodeFunctionResult("pooledTokens", [
zeroAddress, // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBN(0), // liquid reserves
toBN(0), // unaccumulated fees
]),
zeroAddress, // sync output
hubPool.interface.encodeFunctionResult("pooledTokens", [
zeroAddress, // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBN(0), // liquid reserves, equal to "current" reserves
toBN(0), // unaccumulated fees
]),
]);

await dataworkerInstance._updateExchangeRates([l1Token_1.address], true);
expect(multiCallerClient.transactionCount()).to.equal(0);

// Add test when liquid reserves decreases
fakeHubPool.multicall.returns([
hubPool.interface.encodeFunctionResult("pooledTokens", [
zeroAddress, // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBNWei(1), // liquid reserves
toBN(0), // unaccumulated fees
]),
zeroAddress, // sync output
hubPool.interface.encodeFunctionResult("pooledTokens", [
zeroAddress, // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBNWei(1).sub(1), // liquid reserves, less than "current" reserves
toBN(0), // unaccumulated fees
]),
]);

await dataworkerInstance._updateExchangeRates([l1Token_1.address], true);
expect(multiCallerClient.transactionCount()).to.equal(0);
});
it("submits update if liquid reserves would increase for token post-update and last update was old enough", async function () {
// Last update was at time 0, current time is at 10_000, so definitely past the update threshold
mockHubPoolClient.currentTime = 10_000;
mockHubPoolClient.setLpTokenInfo(l1Token_1.address, 0);

// Hardcode multicall output such that it looks like liquid reserves increased
fakeHubPool.multicall.returns([
hubPool.interface.encodeFunctionResult("pooledTokens", [
"0x0000000000000000000000000000000000000000", // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBNWei(1), // liquid reserves
toBN(0), // unaccumulated fees
]),
"0x0000000000000000000000000000000000000000",
hubPool.interface.encodeFunctionResult("pooledTokens", [
"0x0000000000000000000000000000000000000000", // lp token address
true, // enabled
0, // last lp fee update
toBN(0), // utilized reserves
toBNWei(1).add(1), // liquid reserves, higher than "current" reserves
toBN(0), // unaccumulated fees
]),
]);

await dataworkerInstance._updateExchangeRates([l1Token_1.address], true);
expect(multiCallerClient.transactionCount()).to.equal(1);
});
});
});
3 changes: 3 additions & 0 deletions test/mocks/MockHubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ export class MockHubPoolClient extends clients.mocks.MockHubPoolClient {
0
);
}
setLpTokenInfo(l1Token: string, lastLpFeeUpdate: number): void {
this.lpTokens[l1Token] = { lastLpFeeUpdate };
}
}

0 comments on commit fd25b90

Please sign in to comment.