Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,942 changes: 1,942 additions & 0 deletions docs/contracts/05-relaygasused.md

Large diffs are not rendered by default.

Binary file added docs/contracts/images/combined_candlestick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/constant_model_err.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/cubic_candlestick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/l1_calldatasize_gas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/l1_err.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/l2_calldatasize_gas.png
Copy link
Contributor

Choose a reason for hiding this comment

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

is it linear though? (also L1)

Copy link
Member Author

Choose a reason for hiding this comment

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

The total gas on L2 is made up of L1 and L2 gas. In the experiment, the gas on L2 is strongly linear, despite the fact that memory expansion should still occur, but its effect on L2 is insignificant.
However, the behavior of L1 gas on L2 is linear, but the maximum error can be large due to the unknown behavior of the compression algorithm and the calldata itself.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/l2_err.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/linear_candlestick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/post_calibration_err.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/quadratic_candlestick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/contracts/images/rmse_vs_ratio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ build_info = true
extra_output = ["storageLayout"]
evm_version = 'shanghai'
solc_version = '0.8.28'
block_gas_limit = 2000000000

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
1 change: 1 addition & 0 deletions script/SetupGasPriceTunnelRouter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ contract Executor is Script {
IVault(proxyVaultAddr),
msg.sender,
100000,
14000,
300000,
gasPrice,
keccak256(bytes(sourceChainId)),
Expand Down
1 change: 1 addition & 0 deletions script/SetupPriorityFeeTunnelRouter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ contract Executor is Script {
IVault(proxyVaultAddr),
msg.sender,
100000,
14000,
300000,
priorityFee,
keccak256(bytes(sourceChainId)),
Expand Down
8 changes: 0 additions & 8 deletions src/interfaces/ITunnelRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,6 @@ interface ITunnelRouter {
*/
event CallbackGasLimitSet(uint256 callbackGasLimit);

/**
* @notice Emitted when the additional gas used is set.
*
* @param additionalGasUsed The additional gas estimated for relaying the message;
* does not include the gas cost for executing the target contract.
*/
event AdditionalGasUsedSet(uint256 additionalGasUsed);

/**
* @notice Emitted after the message is relayed to the target contract
* to indicate the result of the process.
Expand Down
55 changes: 30 additions & 25 deletions src/router/BaseTunnelRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,20 @@ import "../libraries/PacketDecoder.sol";
import "../libraries/Originator.sol";

import "./ErrorHandler.sol";
import "./L1RouterGasCalculator.sol";

abstract contract BaseTunnelRouter is
Initializable,
Ownable2StepUpgradeable,
PausableUpgradeable,
AccessControlUpgradeable,
ITunnelRouter,
ErrorHandler
ErrorHandler,
L1RouterGasCalculator
{
using PacketDecoder for bytes;

ITssVerifier public tssVerifier;
IVault public vault;

// Additional gas estimated for relaying the message;
// does not include the gas cost for executing the target contract.
uint256 public additionalGasUsed;
// The maximum gas limit can be used when calling the target contract.
uint256 public callbackGasLimit;
// The hash of the source chain ID.
Expand Down Expand Up @@ -60,7 +57,8 @@ abstract contract BaseTunnelRouter is
ITssVerifier tssVerifier_,
IVault vault_,
address initialOwner,
uint256 additionalGasUsed_,
uint256 packedAdditionalGasFuncCoeffs,
uint256 maxCalldataBytes_,
uint256 callbackGasLimit_,
bytes32 sourceChainIdHash_,
bytes32 targetChainIdHash_
Expand All @@ -71,24 +69,27 @@ abstract contract BaseTunnelRouter is
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, initialOwner);
_grantRole(GAS_FEE_UPDATER_ROLE, initialOwner);
__L1RouterGasCalculator_init(
packedAdditionalGasFuncCoeffs,
maxCalldataBytes_
); // UPDATED

tssVerifier = tssVerifier_;
vault = vault_;
sourceChainIdHash = sourceChainIdHash_;
targetChainIdHash = targetChainIdHash_;

_setAdditionalGasUsed(additionalGasUsed_);
_setCallbackGasLimit(callbackGasLimit_);
}

/**
* @dev Sets the additionalGasUsed being used in relaying message.
* @param additionalGasUsed_ The new additional gas used amount.
* @dev Sets the packedCoeffs being used in relaying message.
* @param packedCoeffs The new packed value [c2|c1|c0] (fixed-point 1e18 lanes).
*/
function setAdditionalGasUsed(
uint256 additionalGasUsed_
function setPackedAdditionalGasFuncCoeffs(
uint256 packedCoeffs
) external onlyOwner {
_setAdditionalGasUsed(additionalGasUsed_);
_setPackedAdditionalGasFuncCoeffs(packedCoeffs);
}

/**
Expand Down Expand Up @@ -141,6 +142,9 @@ abstract contract BaseTunnelRouter is
revert InvalidSequence(tunnelDetail.sequence + 1, packet.sequence);
}

// update the sequence.
tunnelDetails[originatorHash_].sequence = packet.sequence;

// verify signature.
bool isValid = tssVerifier.verify(
keccak256(message),
Expand All @@ -151,21 +155,25 @@ abstract contract BaseTunnelRouter is
revert InvalidSignature();
}

// update the sequence.
tunnelDetails[originatorHash_].sequence = packet.sequence;

// forward the message to the target contract.
uint256 gasLeft = gasleft();
uint256 beginGasleft = gasleft();
(bool isSuccess, ) = _callWithCustomErrorHandling(
tunnelDetail.targetAddr,
callbackGasLimit,
abi.encodeWithSelector(IPacketConsumer.process.selector, tssMessage)
);
uint256 targetGasUsed = beginGasleft - gasleft();

emit MessageProcessed(originatorHash_, packet.sequence, isSuccess);

// charge a fee from the target contract and send to caller.
uint256 fee = _routerFee(gasLeft - gasleft() + additionalGasUsed);
uint256 calldataSize;
assembly {
calldataSize := calldatasize()
}
uint256 fee = _routerFee(
targetGasUsed + _additionalGasForCalldata(calldataSize)
);
vault.collectFee(originatorHash_, msg.sender, fee);

// deactivate the target contract if the remaining balance is under the threshold.
Expand Down Expand Up @@ -222,7 +230,10 @@ abstract contract BaseTunnelRouter is
* @dev See {ITunnelRouter-minimumBalanceThreshold}.
*/
function minimumBalanceThreshold() public view override returns (uint256) {
return _routerFee(additionalGasUsed + callbackGasLimit);
return
_routerFee(
callbackGasLimit + _additionalGasForCalldata(maxCalldataBytes)
);
}

/**
Expand Down Expand Up @@ -349,12 +360,6 @@ abstract contract BaseTunnelRouter is
emit CallbackGasLimitSet(callbackGasLimit_);
}

/// @dev Sets additionalGasUsed and emit an event.
function _setAdditionalGasUsed(uint256 additionalGasUsed_) internal {
additionalGasUsed = additionalGasUsed_;
emit AdditionalGasUsedSet(additionalGasUsed_);
}

/// @dev Grants `GAS_FEE_UPDATER_ROLE` to `accounts`
function grantGasFeeUpdater(address[] calldata accounts) external onlyRole(DEFAULT_ADMIN_ROLE) {
for (uint256 i = 0; i < accounts.length; i++) {
Expand Down
10 changes: 7 additions & 3 deletions src/router/GasPriceTunnelRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ contract GasPriceTunnelRouter is BaseTunnelRouter {
ITssVerifier tssVerifier_,
IVault vault_,
address initialOwner,
uint256 additionalGas_,
uint256 packedAdditionalGasFuncCoeffs,
uint256 maxCalldataBytes_,
uint256 callbackGasLimit_,
uint256 gasPrice_,
bytes32 sourceChainIdHash_,
Expand All @@ -29,7 +30,8 @@ contract GasPriceTunnelRouter is BaseTunnelRouter {
tssVerifier_,
vault_,
initialOwner,
additionalGas_,
packedAdditionalGasFuncCoeffs,
maxCalldataBytes_,
callbackGasLimit_,
sourceChainIdHash_,
targetChainIdHash_
Expand All @@ -51,7 +53,9 @@ contract GasPriceTunnelRouter is BaseTunnelRouter {
emit SetGasFee(gasFee_);
}

function _routerFee(uint256 gasUsed) internal view virtual override returns (uint256) {
function _routerFee(
uint256 gasUsed
) internal view virtual override returns (uint256) {
uint256 effectiveGasPrice = Math.min(tx.gasprice, gasFee.gasPrice);
return effectiveGasPrice * gasUsed;
}
Expand Down
134 changes: 134 additions & 0 deletions src/router/L1RouterGasCalculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

/**
* @title L1RouterGasCalculator
* @notice Owns and evaluates a quadratic model for base gas as a function of calldata size.
*
* packedAdditionalGasFuncCoeffs layout (fixed-point 1e18):
* packed = [ c2 | c1 | c0 ], each ci is uint80 (0..2^80-1) representing ci/1e18
*
* f(x) = (c2*x^2 + c1*x + c0) / 1e18, where x = calldata size in bytes.
*
*/
abstract contract L1RouterGasCalculator is
Initializable,
Ownable2StepUpgradeable
{
// ----- packing constants -----
uint256 internal constant COEFF_BITS = 80;
uint256 internal constant COEFF_MASK = (uint256(1) << COEFF_BITS) - 1;
uint256 internal constant FP_SCALE = 1e18;
uint256 internal constant SHIFT_C1 = COEFF_BITS;
uint256 internal constant SHIFT_C2 = COEFF_BITS * 2;

/// @dev packed coefficients: [c2 (80b) | c1 (80b) | c0 (80b)]
uint256 public packedAdditionalGasFuncCoeffs;

/// @dev maximum calldata length (bytes) the model will accept.
uint256 public maxCalldataBytes;

/**
* @notice Emitted when the packed coefficients are updated.
* @param packedCoeffs The new packed value [c2|c1|c0] (fixed-point 1e18 lanes).
*/
event PackedAdditionalGasFuncCoeffsSet(uint256 packedCoeffs);

/**
* @notice Emitted when the maximum supported calldata length is updated.
* @param maxBytes New maximum calldata bytes accepted by the model.
*/
event MaxCalldataBytesSet(uint256 maxBytes);

error CoefficientOutOfRange(); // any ci > 2^80-1
error CalldataSizeTooLarge(uint256 got, uint256 maxAllowed);

function __L1RouterGasCalculator_init(
uint256 packedCoeffs,
uint256 maxBytes
) internal onlyInitializing {
_setPackedAdditionalGasFuncCoeffs(packedCoeffs);
_setMaxCalldataBytes(maxBytes);
}

/// @notice Pack 3×80-bit fixed-point (1e18) coefficients into one uint256.
function packCoeffs(
uint256 c2,
uint256 c1,
uint256 c0
) public pure returns (uint256 packedCoeffs) {
if (c2 > COEFF_MASK || c1 > COEFF_MASK || c0 > COEFF_MASK) {
revert CoefficientOutOfRange();
}
unchecked {
packedCoeffs = (c2 << SHIFT_C2) | (c1 << SHIFT_C1) | c0;
}
}

/// @notice Unpack a provided packed value into its (c2, c1, c0) lanes.
function unpackCoeffs(
uint256 packedCoeffs
) public pure returns (uint256 c2, uint256 c1, uint256 c0) {
unchecked {
c2 = (packedCoeffs >> SHIFT_C2) & COEFF_MASK;
c1 = (packedCoeffs >> SHIFT_C1) & COEFF_MASK;
c0 = packedCoeffs & COEFF_MASK;
}
}

/// @notice View the currently stored coefficients.
function currentCoeffs()
public
view
returns (uint256 c2, uint256 c1, uint256 c0)
{
return unpackCoeffs(packedAdditionalGasFuncCoeffs);
}

/// @dev Store a new packed coefficient triple and emit.
function _setPackedAdditionalGasFuncCoeffs(uint256 packedCoeffs) internal {
packedAdditionalGasFuncCoeffs = packedCoeffs;
emit PackedAdditionalGasFuncCoeffsSet(packedCoeffs);
}

/// @notice Owner: set the maximum accepted calldata bytes.
function setMaxCalldataBytes(uint256 maxBytes) external onlyOwner {
_setMaxCalldataBytes(maxBytes);
}

/// @dev Internal setter with event.
function _setMaxCalldataBytes(uint256 maxBytes) internal {
maxCalldataBytes = maxBytes;
emit MaxCalldataBytesSet(maxBytes);
}

/**
* @dev Evaluate baseGas(x) in *gas units* for a calldata length `x` (bytes).
* Reverts if `x` exceeds `maxCalldataBytes`.
* Returns the quadratic (c2*x^2 + c1*x + c0)/1e18 using the stored coefficients.
*
* @param x Calldata size in bytes (i.e., `calldatasize()` when called from a router).
*/
function _additionalGasForCalldata(
uint256 x
) internal view returns (uint256 y) {
if (x > maxCalldataBytes)
revert CalldataSizeTooLarge(x, maxCalldataBytes);
(uint256 c2, uint256 c1, uint256 c0) = unpackCoeffs(
packedAdditionalGasFuncCoeffs
);
unchecked {
y = (c2 * x * x + c1 * x + c0) / FP_SCALE;
}
}

/// @notice Public preview of baseGas(x) using the stored coefficients.
function additionalGasForCalldata(
uint256 x
) external view returns (uint256 y) {
return _additionalGasForCalldata(x);
}
}
15 changes: 11 additions & 4 deletions src/router/PriorityFeeTunnelRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ contract PriorityFeeTunnelRouter is BaseTunnelRouter {
ITssVerifier tssVerifier_,
IVault vault_,
address initialOwner,
uint256 additionalGas_,
uint256 packedAdditionalGasFuncCoeffs,
uint256 maxCalldataBytes_,
uint256 callbackGasLimit_,
uint256 priorityFee_,
bytes32 sourceChainIdHash_,
Expand All @@ -28,7 +29,8 @@ contract PriorityFeeTunnelRouter is BaseTunnelRouter {
tssVerifier_,
vault_,
initialOwner,
additionalGas_,
packedAdditionalGasFuncCoeffs,
maxCalldataBytes_,
callbackGasLimit_,
sourceChainIdHash_,
targetChainIdHash_
Expand All @@ -50,8 +52,13 @@ contract PriorityFeeTunnelRouter is BaseTunnelRouter {
emit SetGasFee(gasFee_);
}

function _routerFee(uint256 gasUsed) internal view virtual override returns (uint256) {
uint256 effectiveGasPrice = Math.min(tx.gasprice, gasFee.priorityFee + block.basefee);
function _routerFee(
uint256 gasUsed
) internal view virtual override returns (uint256) {
uint256 effectiveGasPrice = Math.min(
tx.gasprice,
gasFee.priorityFee + block.basefee
);
return effectiveGasPrice * gasUsed;
}
}
3 changes: 2 additions & 1 deletion test/PacketConsumer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ contract PacketConsumerTest is Test, Constants {
tssVerifier,
vault,
address(this),
75000,
75000 * 1e18,
14000,
50000,
1,
0x0e1ac2c4a50a82aa49717691fc1ae2e5fa68eff45bd8576b0f2be7a0850fa7c6,
Expand Down
1 change: 1 addition & 0 deletions test/PacketConsumerMultipleTunnel.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ contract PacketConsumerMultipleTunnelTest is Test, Constants {
vault,
address(this),
75000,
14000,
100000,
1,
keccak256("bandchain"),
Expand Down
Loading