diff --git a/contracts/HubPool.sol b/contracts/HubPool.sol index ca876e344..6642cc4c0 100644 --- a/contracts/HubPool.sol +++ b/contracts/HubPool.sol @@ -163,7 +163,7 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { event RootBundleCanceled(address indexed disputer, uint256 requestTime, bytes disputedAncillaryData); modifier noActiveRequests() { - require(rootBundleProposal.unclaimedPoolRebalanceLeafCount == 0, "proposal has unclaimed leafs"); + require(!_activeRequest(), "proposal has unclaimed leafs"); _; } @@ -666,12 +666,15 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { function _sync(address l1Token) internal { // Check if the l1Token balance of the contract is greater than the liquidReserves. If it is then the bridging // action from L2 -> L1 has concluded and the local accounting can be updated. - uint256 l1TokenBalance = IERC20(l1Token).balanceOf(address(this)); - if (l1TokenBalance > pooledTokens[l1Token].liquidReserves) { + // Note: this calculation must take into account the bond when it's acting on the bond token and there's an + // active request. + uint256 balance = IERC20(l1Token).balanceOf(address(this)); + uint256 balanceSansBond = l1Token == address(bondToken) && _activeRequest() ? balance - bondAmount : balance; + if (balanceSansBond > pooledTokens[l1Token].liquidReserves) { // Note the numerical operation below can send utilizedReserves to negative. This can occur when tokens are // dropped onto the contract, exceeding the liquidReserves. - pooledTokens[l1Token].utilizedReserves -= int256(l1TokenBalance - pooledTokens[l1Token].liquidReserves); - pooledTokens[l1Token].liquidReserves = l1TokenBalance; + pooledTokens[l1Token].utilizedReserves -= int256(balanceSansBond - pooledTokens[l1Token].liquidReserves); + pooledTokens[l1Token].liquidReserves = balanceSansBond; } } @@ -722,6 +725,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { emit SpokePoolAdminFunctionTriggered(chainId, functionData); } + function _activeRequest() internal view returns (bool) { + return rootBundleProposal.unclaimedPoolRebalanceLeafCount != 0; + } + // If functionCallStackOriginatesFromOutsideThisContract is true then this was called by the callback function // by dropping ETH onto the contract. In this case, deposit the ETH into WETH. This would happen if ETH was sent // over the optimism bridge, for example. If false then this was set as a result of unwinding LP tokens, with the diff --git a/test/HubPool.PooledTokenSynchronization.ts b/test/HubPool.PooledTokenSynchronization.ts index 361305bdb..83e4698d4 100644 --- a/test/HubPool.PooledTokenSynchronization.ts +++ b/test/HubPool.PooledTokenSynchronization.ts @@ -36,6 +36,12 @@ describe("HubPool Pooled Token Synchronization", function () { await hubPool .connect(dataWorker) .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayRoot); + + // Bond being paid in should not impact liquid reserves. + await hubPool.exchangeRateCurrent(weth.address); // force state sync (calls sync internally). + expect((await hubPool.pooledTokens(weth.address)).liquidReserves).to.equal(consts.amountToLp); + + // Counters should move once the root bundle is executed. await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1); await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); expect((await hubPool.pooledTokens(weth.address)).liquidReserves).to.equal(consts.amountToLp.sub(tokensSendToL2)); diff --git a/test/gas-analytics/HubPool.RefundExecution.ts b/test/gas-analytics/HubPool.RefundExecution.ts index ac86a3aad..707dccdd7 100644 --- a/test/gas-analytics/HubPool.RefundExecution.ts +++ b/test/gas-analytics/HubPool.RefundExecution.ts @@ -125,7 +125,7 @@ describe("Gas Analytics: HubPool Relayer Refund Execution", function () { 1, // poolRebalanceLeafCount. There is exactly one leaf in the bundle. tree.getHexRoot(), // poolRebalanceRoot. Generated from the merkle tree constructed before. consts.mockRelayerRefundRoot, // Not relevant for this test. - consts.mockSlowRelayFulfillmentRoot // Not relevant for this test. + consts.mockSlowRelayRoot // Not relevant for this test. ); console.log(`proposeRootBundle-gasUsed: ${(await initiateTxn.wait()).gasUsed}`); @@ -152,7 +152,7 @@ describe("Gas Analytics: HubPool Relayer Refund Execution", function () { REFUND_CHAIN_COUNT, // poolRebalanceLeafCount. Execute all leaves tree.getHexRoot(), // poolRebalanceRoot. Generated from the merkle tree constructed before. consts.mockRelayerRefundRoot, // Not relevant for this test. - consts.mockSlowRelayFulfillmentRoot // Not relevant for this test. + consts.mockSlowRelayRoot // Not relevant for this test. ); console.log(`proposeRootBundle-gasUsed: ${(await initiateTxn.wait()).gasUsed}`);