diff --git a/src/dataworker/Dataworker.ts b/src/dataworker/Dataworker.ts index 294e698c6..d1cdae38f 100644 --- a/src/dataworker/Dataworker.ts +++ b/src/dataworker/Dataworker.ts @@ -1488,6 +1488,7 @@ export class Dataworker { ({ availableLiquidReserves: updatedLiquidReserves, syncedL1Tokens: updatedL1Tokens } = await this._updateExchangeRatesBeforeExecutingHubChainLeaves(mainnetLeaves[0], submitExecution)); await this._executePoolRebalanceLeaves( + spokePoolClients, mainnetLeaves, balanceAllocator, expectedTrees.poolRebalanceTree.tree, @@ -1553,6 +1554,7 @@ export class Dataworker { // Perform similar funding checks for remaining non-mainnet pool rebalance leaves. await this._executePoolRebalanceLeaves( + spokePoolClients, nonHubChainPoolRebalanceLeaves, balanceAllocator, expectedTrees.poolRebalanceTree.tree, @@ -1563,6 +1565,9 @@ export class Dataworker { } async _executePoolRebalanceLeaves( + spokePoolClients: { + [chainId: number]: SpokePoolClient; + }, leaves: PoolRebalanceLeaf[], balanceAllocator: BalanceAllocator, tree: MerkleTree, @@ -1574,12 +1579,16 @@ export class Dataworker { (leaf) => sdkUtils.chainIsArbitrum(leaf.chainId) || sdkUtils.chainIsOrbit(leaf.chainId) ); await forEachAsync(orbitLeaves, async (leaf) => { - const { amount: requiredAmount, token: feeToken } = await this._getRequiredEthForOrbitPoolRebalanceLeaf(leaf); + const { + amount: requiredAmount, + token: feeToken, + holder, + } = await this._getRequiredEthForOrbitPoolRebalanceLeaf(leaf); const success = await balanceAllocator.requestBalanceAllocations([ { tokens: [feeToken], amount: requiredAmount, - holder: this.clients.hubPoolClient.hubPool.address, + holder: holder, chainId: hubPoolChainId, }, ]); @@ -1609,7 +1618,7 @@ export class Dataworker { contract: new Contract(feeToken, ERC20.abi, signer), chainId: hubPoolChainId, method: "transfer", - args: [this.clients.hubPoolClient.hubPool.address, requiredAmount], + args: [holder, requiredAmount], message: `Loaded orbit gas token for message to ${getNetworkName(leaf.chainId)} 📨!`, mrkdwn: `Root hash: ${tree.getHexRoot()}\nLeaf: ${leaf.leafId}\nChain: ${leaf.chainId}`, }); @@ -1617,9 +1626,27 @@ export class Dataworker { } } }); - if (submitExecution) { - leaves.forEach((leaf) => { - const mrkdwn = `Root hash: ${tree.getHexRoot()}\nLeaf: ${leaf.leafId}\nChain: ${leaf.chainId}`; + await forEachAsync(leaves, async (leaf) => { + // Add balances to spoke pool on mainnet since we know it will be sent atomically. + if (leaf.chainId === hubPoolChainId) { + await Promise.all( + leaf.netSendAmounts.map(async (amount, i) => { + if (amount.gt(bnZero)) { + await balanceAllocator.addUsed( + leaf.chainId, + leaf.l1Tokens[i], + spokePoolClients[leaf.chainId].spokePool.address, + amount.mul(-1) + ); + } + }) + ); + } + }); + + leaves.forEach((leaf) => { + const mrkdwn = `Root hash: ${tree.getHexRoot()}\nLeaf: ${leaf.leafId}\nChain: ${leaf.chainId}`; + if (submitExecution) { this.clients.multiCallerClient.enqueueTransaction({ contract: this.clients.hubPoolClient.hubPool, chainId: hubPoolChainId, @@ -1641,8 +1668,10 @@ export class Dataworker { // from relayer refund leaves. canFailInSimulation: leaf.chainId !== hubPoolChainId, }); - }); - } + } else { + this.logger.debug({ at: "Dataworker#_executePoolRebalanceLeaves", message: mrkdwn }); + } + }); } async _updateExchangeRatesBeforeExecutingHubChainLeaves( @@ -2123,7 +2152,7 @@ export class Dataworker { const success = await balanceAllocator.requestBalanceAllocations(balanceRequestsToQuery); if (!success) { this.logger.warn({ - at: "Dataworker#executeRelayerRefundLeaves", + at: "Dataworker#_executeRelayerRefundLeaves", message: "Not executing relayer refund leaf on SpokePool due to lack of funds.", root: relayerRefundTree.getHexRoot(), bundle: rootBundleId, @@ -2174,7 +2203,7 @@ export class Dataworker { canFailInSimulation: leaf.chainId === this.clients.hubPoolClient.chainId, }); } else { - this.logger.debug({ at: "Dataworker#executeRelayerRefundLeaves", message: mrkdwn }); + this.logger.debug({ at: "Dataworker#_executeRelayerRefundLeaves", message: mrkdwn }); } }); } @@ -2286,16 +2315,20 @@ export class Dataworker { async _getRequiredEthForOrbitPoolRebalanceLeaf(leaf: PoolRebalanceLeaf): Promise<{ amount: BigNumber; token: string; + holder: string; }> { // TODO: Make this code more dynamic in the future. For now, hard code custom gas token fees. let relayMessageFee: BigNumber; let token: string; + let holder: string; if (leaf.chainId === CHAIN_IDs.ALEPH_ZERO) { relayMessageFee = toBNWei("0.49"); token = TOKEN_SYMBOLS_MAP.AZERO.addresses[CHAIN_IDs.MAINNET]; + holder = "0x0d57392895Db5aF3280e9223323e20F3951E81B1"; } else { relayMessageFee = toBNWei("0.02"); token = ZERO_ADDRESS; + holder = this.clients.hubPoolClient.hubPool.address; } // For orbit chains, the bot needs enough ETH to pay for each L1 -> L2 message. @@ -2314,6 +2347,7 @@ export class Dataworker { return { amount: requiredAmount, token, + holder, }; } diff --git a/test/Dataworker.executePoolRebalances.ts b/test/Dataworker.executePoolRebalances.ts index 0a74677c8..635e122d3 100644 --- a/test/Dataworker.executePoolRebalances.ts +++ b/test/Dataworker.executePoolRebalances.ts @@ -509,6 +509,7 @@ describe("Dataworker: Execute pool rebalances", async function () { }, ]; await dataworkerInstance._executePoolRebalanceLeaves( + spokePoolClients, leaves, balanceAllocator, buildPoolRebalanceLeafTree(leaves), @@ -522,6 +523,37 @@ describe("Dataworker: Execute pool rebalances", async function () { expect(queuedTransactions[1].method).to.equal("executeRootBundle"); expect(queuedTransactions[1].message).to.match(/chain 137/); }); + it("subtracts used balance for ethereum leaves", async function () { + const leaves: PoolRebalanceLeaf[] = [ + { + chainId: hubPoolClient.chainId, + groupIndex: 0, + bundleLpFees: [toBNWei("1")], + netSendAmounts: [toBNWei("1")], + runningBalances: [toBNWei("1")], + leafId: 0, + l1Tokens: [l1Token_1.address], + }, + ]; + await dataworkerInstance._executePoolRebalanceLeaves( + spokePoolClients, + leaves, + balanceAllocator, + buildPoolRebalanceLeafTree(leaves), + true + ); + + expect(multiCallerClient.transactionCount()).to.equal(1); + const queuedTransactions = multiCallerClient.getQueuedTransactions(hubPoolClient.chainId); + expect(queuedTransactions[0].method).to.equal("executeRootBundle"); + expect( + await balanceAllocator.getUsed( + hubPoolClient.chainId, + l1Token_1.address, + spokePoolClients[hubPoolClient.chainId].spokePool.address + ) + ).to.equal(toBNWei("-1")); + }); it("contains arbitrum leaf", async function () { // Adds one fee per net send amount + one extra if groupIndex = 0 const leaves: PoolRebalanceLeaf[] = [ @@ -549,6 +581,7 @@ describe("Dataworker: Execute pool rebalances", async function () { const expectedFeeLeaf1 = expectedFee.mul(2).add(expectedFee); const expectedFeeLeaf2 = expectedFee.mul(2); await dataworkerInstance._executePoolRebalanceLeaves( + spokePoolClients, leaves, balanceAllocator, buildPoolRebalanceLeafTree(leaves), @@ -571,10 +604,10 @@ describe("Dataworker: Execute pool rebalances", async function () { address: TOKEN_SYMBOLS_MAP.AZERO.addresses[CHAIN_IDs.MAINNET], provider: hubPoolClient.hubPool.signer.provider, }); - azero.balanceOf.whenCalledWith(hubPoolClient.hubPool.address).returns(0); - expect( - await balanceAllocator.getBalance(hubPoolClient.chainId, azero.address, hubPoolClient.hubPool.address) - ).to.equal(0); + // Custom gas token funder for AZERO + const customGasTokenFunder = "0x0d57392895Db5aF3280e9223323e20F3951E81B1"; + azero.balanceOf.whenCalledWith(customGasTokenFunder).returns(0); + expect(await balanceAllocator.getBalance(hubPoolClient.chainId, azero.address, customGasTokenFunder)).to.equal(0); // Adds one fee per net send amount + one extra if groupIndex = 0 const leaves: PoolRebalanceLeaf[] = [ @@ -602,6 +635,7 @@ describe("Dataworker: Execute pool rebalances", async function () { const expectedFeeLeaf1 = expectedFee.mul(2).add(expectedFee); const expectedFeeLeaf2 = expectedFee.mul(2); await dataworkerInstance._executePoolRebalanceLeaves( + spokePoolClients, leaves, balanceAllocator, buildPoolRebalanceLeafTree(leaves), @@ -612,9 +646,9 @@ describe("Dataworker: Execute pool rebalances", async function () { expect(multiCallerClient.transactionCount()).to.equal(4); const queuedTransactions = multiCallerClient.getQueuedTransactions(hubPoolClient.chainId); expect(queuedTransactions[0].method).to.equal("transfer"); - expect(queuedTransactions[0].args).to.deep.equal([hubPoolClient.hubPool.address, expectedFeeLeaf1]); + expect(queuedTransactions[0].args).to.deep.equal([customGasTokenFunder, expectedFeeLeaf1]); expect(queuedTransactions[1].method).to.equal("transfer"); - expect(queuedTransactions[1].args).to.deep.equal([hubPoolClient.hubPool.address, expectedFeeLeaf2]); + expect(queuedTransactions[1].args).to.deep.equal([customGasTokenFunder, expectedFeeLeaf2]); expect(queuedTransactions[2].method).to.equal("executeRootBundle"); expect(queuedTransactions[3].method).to.equal("executeRootBundle"); });