packages/contracts-core/contracts/libs/stack/GasData.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {Number} from "./Number.sol";
/// GasData in encoded data with "basic information about gas prices" for some chain.
type GasData is uint96;
using GasDataLib for GasData global;
/// ChainGas is encoded data with given chain's "basic information about gas prices".
type ChainGas is uint128;
using GasDataLib for ChainGas global;
/// Library for encoding and decoding GasData and ChainGas structs.
/// # GasData
/// `GasData` is a struct to store the "basic information about gas prices", that could
/// be later used to approximate the cost of a message execution, and thus derive the
/// minimal tip values for sending a message to the chain.
/// > - `GasData` is supposed to be cached by `GasOracle` contract, allowing to store the
/// > approximates instead of the exact values, and thus save on storage costs.
/// > - For instance, if `GasOracle` only updates the values on +- 10% change, having an
/// > 0.4% error on the approximates would be acceptable.
/// `GasData` is supposed to be included in the Origin's state, which are synced across
/// chains using Agent-signed snapshots and attestations.
/// ## GasData stack layout (from highest bits to lowest)
///
/// | Position | Field | Type | Bytes | Description |
/// | ---------- | ------------ | ------ | ----- | --------------------------------------------------- |
/// | (012..010] | gasPrice | uint16 | 2 | Gas price for the chain (in Wei per gas unit) |
/// | (010..008] | dataPrice | uint16 | 2 | Calldata price (in Wei per byte of content) |
/// | (008..006] | execBuffer | uint16 | 2 | Tx fee safety buffer for message execution (in Wei) |
/// | (006..004] | amortAttCost | uint16 | 2 | Amortized cost for attestation submission (in Wei) |
/// | (004..002] | etherPrice | uint16 | 2 | Chain's Ether Price / Mainnet Ether Price (in BWAD) |
/// | (002..000] | markup | uint16 | 2 | Markup for the message execution (in BWAD) |
/// > See Number.sol for more details on `Number` type and BWAD (binary WAD) math.
///
/// ## ChainGas stack layout (from highest bits to lowest)
///
/// | Position | Field | Type | Bytes | Description |
/// | ---------- | ------- | ------ | ----- | ---------------- |
/// | (016..004] | gasData | uint96 | 12 | Chain's gas data |
/// | (004..000] | domain | uint32 | 4 | Chain's domain |
library GasDataLib {
/// @dev Amount of bits to shift to gasPrice field
uint96 private constant SHIFT_GAS_PRICE = 10 * 8;
/// @dev Amount of bits to shift to dataPrice field
uint96 private constant SHIFT_DATA_PRICE = 8 * 8;
/// @dev Amount of bits to shift to execBuffer field
uint96 private constant SHIFT_EXEC_BUFFER = 6 * 8;
/// @dev Amount of bits to shift to amortAttCost field
uint96 private constant SHIFT_AMORT_ATT_COST = 4 * 8;
/// @dev Amount of bits to shift to etherPrice field
uint96 private constant SHIFT_ETHER_PRICE = 2 * 8;
/// @dev Amount of bits to shift to gasData field
uint128 private constant SHIFT_GAS_DATA = 4 * 8;
// ═════════════════════════════════════════════════ GAS DATA ══════════════════════════════════════════════════════
/// @notice Returns an encoded GasData struct with the given fields.
/// @param gasPrice_ Gas price for the chain (in Wei per gas unit)
/// @param dataPrice_ Calldata price (in Wei per byte of content)
/// @param execBuffer_ Tx fee safety buffer for message execution (in Wei)
/// @param amortAttCost_ Amortized cost for attestation submission (in Wei)
/// @param etherPrice_ Ratio of Chain's Ether Price / Mainnet Ether Price (in BWAD)
/// @param markup_ Markup for the message execution (in BWAD)
function encodeGasData(
Number gasPrice_,
Number dataPrice_,
Number execBuffer_,
Number amortAttCost_,
Number etherPrice_,
Number markup_
) internal pure returns (GasData) {
// Number type wraps uint16, so could safely be casted to uint96
// forgefmt: disable-next-item
return GasData.wrap(
uint96(Number.unwrap(gasPrice_)) << SHIFT_GAS_PRICE |
uint96(Number.unwrap(dataPrice_)) << SHIFT_DATA_PRICE |
uint96(Number.unwrap(execBuffer_)) << SHIFT_EXEC_BUFFER |
uint96(Number.unwrap(amortAttCost_)) << SHIFT_AMORT_ATT_COST |
uint96(Number.unwrap(etherPrice_)) << SHIFT_ETHER_PRICE |
uint96(Number.unwrap(markup_))
);
}
/// @notice Wraps padded uint256 value into GasData struct.
function wrapGasData(uint256 paddedGasData) internal pure returns (GasData) {
// Casting to uint96 will truncate the highest bits, which is the behavior we want
return GasData.wrap(uint96(paddedGasData));
}
/// @notice Returns the gas price, in Wei per gas unit.
function gasPrice(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data) >> SHIFT_GAS_PRICE));
}
/// @notice Returns the calldata price, in Wei per byte of content.
function dataPrice(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data) >> SHIFT_DATA_PRICE));
}
/// @notice Returns the tx fee safety buffer for message execution, in Wei.
function execBuffer(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data) >> SHIFT_EXEC_BUFFER));
}
/// @notice Returns the amortized cost for attestation submission, in Wei.
function amortAttCost(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data) >> SHIFT_AMORT_ATT_COST));
}
/// @notice Returns the ratio of Chain's Ether Price / Mainnet Ether Price, in BWAD math.
function etherPrice(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data) >> SHIFT_ETHER_PRICE));
}
/// @notice Returns the markup for the message execution, in BWAD math.
function markup(GasData data) internal pure returns (Number) {
// Casting to uint16 will truncate the highest bits, which is the behavior we want
return Number.wrap(uint16(GasData.unwrap(data)));
}
// ════════════════════════════════════════════════ CHAIN DATA ═════════════════════════════════════════════════════
/// @notice Returns an encoded ChainGas struct with the given fields.
/// @param gasData_ Chain's gas data
/// @param domain_ Chain's domain
function encodeChainGas(GasData gasData_, uint32 domain_) internal pure returns (ChainGas) {
// GasData type wraps uint96, so could safely be casted to uint128
return ChainGas.wrap(uint128(GasData.unwrap(gasData_)) << SHIFT_GAS_DATA | uint128(domain_));
}
/// @notice Wraps padded uint256 value into ChainGas struct.
function wrapChainGas(uint256 paddedChainGas) internal pure returns (ChainGas) {
// Casting to uint128 will truncate the highest bits, which is the behavior we want
return ChainGas.wrap(uint128(paddedChainGas));
}
/// @notice Returns the chain's gas data.
function gasData(ChainGas data) internal pure returns (GasData) {
// Casting to uint96 will truncate the highest bits, which is the behavior we want
return GasData.wrap(uint96(ChainGas.unwrap(data) >> SHIFT_GAS_DATA));
}
/// @notice Returns the chain's domain.
function domain(ChainGas data) internal pure returns (uint32) {
// Casting to uint32 will truncate the highest bits, which is the behavior we want
return uint32(ChainGas.unwrap(data));
}
/// @notice Returns the hash for the list of ChainGas structs.
function snapGasHash(ChainGas[] memory snapGas) internal pure returns (bytes32 snapGasHash_) {
// Use assembly to calculate the hash of the array without copying it
// ChainGas takes a single word of storage, thus ChainGas[] is stored in the following way:
// 0x00: length of the array, in words
// 0x20: first ChainGas struct
// 0x40: second ChainGas struct
// And so on...
// solhint-disable-next-line no-inline-assembly
assembly {
// Find the location where the array data starts, we add 0x20 to skip the length field
let loc := add(snapGas, 0x20)
// Load the length of the array (in words).
// Shifting left 5 bits is equivalent to multiplying by 32: this converts from words to bytes.
let len := shl(5, mload(snapGas))
// Calculate the hash of the array
snapGasHash_ := keccak256(loc, len)
}
}
}