Skip to content

Conversation

@crispymangoes
Copy link

@crispymangoes crispymangoes commented Dec 23, 2025

  • Allows Proxy.sol to withdraw tokens from it
  • Allows VersionedVerifierResolver to withdraw tokens from it
  • Adds a zero address check to the fee token handler lib.
  • Changes MAX_GAS_BUFFER_TO_UPDATE_STATE into a variable that can be changed

@crispymangoes crispymangoes requested a review from a team as a code owner December 23, 2025 19:43

/// @notice Gas buffer to update state.
// Example, 5k for updating the state + 5k for the event and misc costs.
uint64 internal s_maxGasBufferToUpdateState;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably fine as uint32 right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think that will work even for chains that have wild opcode prices. Fixed 4be9538


uint256 gasLeft = gasleft();
if (gasLeft <= MAX_GAS_BUFFER_TO_UPDATE_STATE) {
if (gasLeft <= s_maxGasBufferToUpdateState) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

caching this like
uint256 gasBuffer = s_maxGasBufferToUpdateState;

saves 136 gas

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice catch, fixed! 4be9538

}

/// @notice Returns the max gas buffer to update state.
function getmaxGasBufferToUpdateState() external view virtual returns (uint32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function getmaxGasBufferToUpdateState() external view virtual returns (uint32) {
function getMaxGasBufferToUpdateState() external view virtual returns (uint32) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 28192ad

kylesmartin
kylesmartin previously approved these changes Dec 29, 2025

/// @notice Gas buffer to update state.
// Example, 5k for updating the state + 5k for the event and misc costs.
uint32 internal s_maxGasBufferToUpdateState;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it immutable to save the setting functions and the gas for reading. That allows us to set it to a different value for chains with strange gas behaviour, and changing gas costs are extremely rare. With 1.7, swapping out a single offramp is possible so even if we need to deploy a new one it should be fine.

}

/// @dev Internal method that allows for reuse in constructor.
function _setFeeAggregator(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can move this fee aggregator code to the FeeTokenHandler? Since it is duplicated

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cant do storage changes in a lib


/// @notice Test fee withdrawal from all components after ccipSend
function test_FeeWithdrawal_AfterCcipSend() public {
vm.pauseGasMetering();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a gas test so no need to pause/resume gas metering

data: "",
tokenAmounts: new Client.EVMTokenAmount[](1),
feeToken: s_sourceFeeToken,
extraArgs: ExtraArgsCodec._encodeGenericExtraArgsV3(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just provide "" instead

uint256 actualOnRampBalance = IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp));

// Only proceed if there's actually a balance to withdraw
if (actualOnRampBalance > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already checked that it's greater than before, so it must be non-zero

// Only proceed if there's actually a balance to withdraw
if (actualOnRampBalance > 0) {
uint256 aggregatorBalanceBeforeOnRamp = IERC20(s_sourceFeeToken).balanceOf(s_feeAggregator);
vm.stopPrank();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this prank switch once before we do any of these withdrawals


// Only proceed if there's actually a balance to withdraw
if (actualOnRampBalance > 0) {
uint256 aggregatorBalanceBeforeOnRamp = IERC20(s_sourceFeeToken).balanceOf(s_feeAggregator);
Copy link
Collaborator

@RensR RensR Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can keep this between contracts so we don't have to do a pre-post for every one, just a post and we have a pre already. Just update the pre to the post after each contract is checked

// Use actual balance withdrawn, not the calculated difference
uint256 actualWithdrawn = aggregatorBalanceAfterOnRamp - aggregatorBalanceBeforeOnRamp;
assertEq(actualWithdrawn, actualOnRampBalance, "OnRamp fees should be withdrawn to aggregator");
totalFeesWithdrawn += actualWithdrawn;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check against receipts?

uint256 poolFeeBalance = end.tokenPoolBalance - initial.tokenPoolBalance;

// If pool received fees from the transaction, test withdrawing them
// Otherwise, manually add fees to test the withdrawal mechanism
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just ensure it always has fees in the setup no? This should be deterministic

}

/// @notice Test that verifier fees go to resolver (proxy), not implementation
function test_VerifierFeeIssue_FeesGoToResolverNotImplementation() public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you already test this in the test above? Can probably remove this one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah you are right, I think this test came about when I was confirming it was an issue, but the main test is sufficiently covering this .

}

/// @notice Test PAL compatibility: permissionless vs onlyOwner
function test_PALCompatibility_PermissionlessVsOnlyOwner() public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already test this in the first test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah good point, will remove

import {BaseTest} from "../../BaseTest.t.sol";

contract OffRamp_constructor is BaseTest {
uint32 internal constant DEFAULT_MAX_GAS_BUFFER_TO_UPDATE_STATE = 5000 + 5000 + 2000;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated a few places


import {TokenPoolHelper} from "../helpers/TokenPoolHelper.sol";
import {MockVerifier} from "../mocks/MockVerifier.sol";

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rm newline here and line 15


import {OnRampSetup} from "../onRamp/OnRamp/OnRampSetup.t.sol";

import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is first party, not third party. It should go in the higher section

@github-actions
Copy link

github-actions bot commented Jan 3, 2026

Metric fix-executor-proxy-and-resolver-fee-handling develop
Coverage 69.4% 69.0%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants