// 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) } } }