// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import {ByteString, CallData, MemView, MemViewLib} from "../../../contracts/libs/memory/ByteString.sol"; import {BaseMessage, BaseMessageLib, Tips, TipsLib} from "../../../contracts/libs/memory/BaseMessage.sol"; import {ChainGas, GasData, GasDataLib} from "../../../contracts/libs/stack/GasData.sol"; import {Header, HeaderLib, MessageFlag} from "../../../contracts/libs/stack/Header.sol"; import {Message, MessageLib} from "../../../contracts/libs/memory/Message.sol"; import {Number, NumberLib} from "../../../contracts/libs/stack/Number.sol"; import {Receipt, ReceiptLib} from "../../../contracts/libs/memory/Receipt.sol"; import {Request, RequestLib} from "../../../contracts/libs/stack/Request.sol"; import { Snapshot, SnapshotLib, SNAPSHOT_MAX_STATES, State, StateLib } from "../../../contracts/libs/memory/Snapshot.sol"; import {Attestation, AttestationLib} from "../../../contracts/libs/memory/Attestation.sol"; struct RawHeader { uint8 flag; uint32 origin; uint32 nonce; uint32 destination; uint32 optimisticPeriod; } using CastLib for RawHeader global; struct RawRequest { uint96 gasDrop; uint64 gasLimit; uint32 version; } using CastLib for RawRequest global; struct RawTips { uint64 summitTip; uint64 attestationTip; uint64 executionTip; uint64 deliveryTip; } using CastLib for RawTips global; struct RawTipsProof { bytes32 headerHash; bytes32 bodyHash; } using CastLib for RawTips global; // RawReceipt name is already taken in forge-std struct RawExecReceipt { uint32 origin; uint32 destination; bytes32 messageHash; bytes32 snapshotRoot; uint8 stateIndex; address attNotary; address firstExecutor; address finalExecutor; } struct RawReceiptTips { RawExecReceipt re; RawTips tips; RawTipsProof rtp; } using CastLib for RawExecReceipt global; struct RawCallData { bytes4 selector; bytes args; } using CastLib for RawCallData global; struct RawManagerCall { uint32 origin; uint256 proofMaturity; RawCallData callData; } using CastLib for RawManagerCall global; struct RawBaseMessage { RawTips tips; bytes32 sender; bytes32 recipient; RawRequest request; bytes content; } using CastLib for RawBaseMessage global; struct RawMessage { RawHeader header; bytes body; } using CastLib for RawMessage global; struct RawNumber { uint16 number; } using CastLib for RawNumber global; struct RawGasData { RawNumber gasPrice; RawNumber dataPrice; RawNumber execBuffer; RawNumber amortAttCost; RawNumber etherPrice; RawNumber markup; } using CastLib for RawGasData global; struct RawGasData256 { uint256 gasPrice; uint256 dataPrice; uint256 execBuffer; uint256 amortAttCost; uint256 etherPrice; uint256 markup; } using CastLib for RawGasData256 global; struct RawChainGas { uint32 domain; RawGasData gasData; } using CastLib for RawChainGas global; struct RawState { bytes32 root; uint32 origin; uint32 nonce; uint40 blockNumber; uint40 timestamp; RawGasData gasData; } using CastLib for RawState global; struct RawStateIndex { uint8 stateIndex; uint256 statesAmount; } using CastLib for RawStateIndex global; struct RawSnapshot { RawState[] states; } using CastLib for RawSnapshot global; struct RawAttestation { bytes32 snapRoot; bytes32 dataHash; uint32 nonce; uint40 blockNumber; uint40 timestamp; // Merged into dataHash bytes32 _agentRoot; bytes32 _snapGasHash; } using CastLib for RawAttestation global; // solhint-disable no-empty-blocks // solhint-disable ordering library CastLib { using AttestationLib for bytes; using ByteString for bytes; using BaseMessageLib for bytes; using MessageLib for bytes; using ReceiptLib for bytes; using SnapshotLib for bytes; using StateLib for bytes; /// @notice Prevents this contract from being included in the coverage report function testCastLib() external {} // ══════════════════════════════════════════════════ MESSAGE ══════════════════════════════════════════════════════ function formatMessage(RawMessage memory rm) internal pure returns (bytes memory msgPayload) { return MessageLib.formatMessage(rm.header.castToHeader(), rm.body); } function castToMessage(RawMessage memory rm) internal pure returns (Message ptr) { ptr = rm.formatMessage().castToMessage(); } function encodeHeader(RawHeader memory rh) internal pure returns (uint136 encodedHeader) { encodedHeader = Header.unwrap(rh.castToHeader()); } function castToHeader(RawHeader memory rh) internal pure returns (Header header) { // Explicit revert when out of range require(rh.flag <= uint8(type(MessageFlag).max), "Flag out of range"); header = HeaderLib.encodeHeader({ flag_: MessageFlag(rh.flag), origin_: rh.origin, nonce_: rh.nonce, destination_: rh.destination, optimisticPeriod_: rh.optimisticPeriod }); } function boundFlag(RawHeader memory rh) internal pure { rh.flag = rh.flag % (uint8(type(MessageFlag).max) + 1); } function encodeRequest(RawRequest memory rr) internal pure returns (uint192 encodedReq) { encodedReq = Request.unwrap(rr.castToRequest()); } function castToRequest(RawRequest memory rr) internal pure returns (Request request) { request = RequestLib.encodeRequest({gasDrop_: rr.gasDrop, gasLimit_: rr.gasLimit, version_: rr.version}); } function boundRequest(RawRequest memory rr, uint96 maxGasDrop, uint64 maxGasLimit) internal pure { require(maxGasDrop != 0, "maxGasDrop can't be 0"); require(maxGasLimit != 0, "maxGasLimit can't be 0"); rr.gasDrop = rr.gasDrop % maxGasDrop; rr.gasLimit = rr.gasLimit % maxGasLimit; } function encodeTips(RawTips memory rt) internal pure returns (uint256 encodedTips) { encodedTips = Tips.unwrap(rt.castToTips()); } function boundTips(RawTips memory rt, uint64 maxTipValue) internal pure { require(maxTipValue != 0, "maxTipValue can't be 0"); rt.summitTip = rt.summitTip % maxTipValue; rt.attestationTip = rt.attestationTip % maxTipValue; rt.executionTip = rt.executionTip % maxTipValue; rt.deliveryTip = rt.deliveryTip % maxTipValue; } function floorTips(RawTips memory rt, uint64 minTipValue) internal pure { rt.summitTip = minTipValue + uint64(rt.summitTip % (2 ** 64 - minTipValue)); rt.attestationTip = minTipValue + uint64(rt.attestationTip % (2 ** 64 - minTipValue)); rt.executionTip = minTipValue + uint64(rt.executionTip % (2 ** 64 - minTipValue)); rt.deliveryTip = minTipValue + uint64(rt.deliveryTip % (2 ** 64 - minTipValue)); } function cloneTips(RawTips memory rt) internal pure returns (RawTips memory crt) { crt.summitTip = rt.summitTip; crt.attestationTip = rt.attestationTip; crt.executionTip = rt.executionTip; crt.deliveryTip = rt.deliveryTip; } function castToTips(RawTips memory rt) internal pure returns (Tips tips) { tips = TipsLib.encodeTips({ summitTip_: rt.summitTip, attestationTip_: rt.attestationTip, executionTip_: rt.executionTip, deliveryTip_: rt.deliveryTip }); } function getMessageHash(RawTips memory rt, RawTipsProof memory rtp) internal pure returns (bytes32) { bytes32 baseMessageLeaf = keccak256(bytes.concat(rt.castToTips().leaf(), rtp.bodyHash)); return keccak256(bytes.concat(rtp.headerHash, baseMessageLeaf)); } function formatBaseMessage(RawBaseMessage memory rbm) internal pure returns (bytes memory bmPayload) { bmPayload = BaseMessageLib.formatBaseMessage({ tips_: rbm.tips.castToTips(), sender_: rbm.sender, recipient_: rbm.recipient, request_: rbm.request.castToRequest(), content_: rbm.content }); } function castToBaseMessage(RawBaseMessage memory rbm) internal pure returns (BaseMessage ptr) { ptr = rbm.formatBaseMessage().castToBaseMessage(); } function getTipsProof(RawHeader memory rh, RawBaseMessage memory rbm) internal pure returns (RawTipsProof memory rtp) { rtp.headerHash = rh.castToHeader().leaf(); rtp.bodyHash = rbm.castToBaseMessage().bodyLeaf(); } // ══════════════════════════════════════════════ MANAGER MESSAGE ══════════════════════════════════════════════════ function formatCallData(RawCallData memory rcd) internal pure returns (bytes memory cdPayload) { // Explicit revert when args are not taking whole amount of words require(rcd.args.length % 32 == 0, "Args don't take exactly N words"); cdPayload = abi.encodePacked(rcd.selector, rcd.args); } function castToCallData(RawCallData memory rcd) internal pure returns (CallData ptr) { ptr = rcd.formatCallData().castToCallData(); } function callPayload(RawManagerCall memory rsc) internal view returns (bytes memory scPayload) { // Add (msgOrigin, proofMaturity) as the first two args scPayload = rsc.callData.castToCallData().addPrefix(abi.encode(rsc.origin, rsc.proofMaturity)); } // ═════════════════════════════════════════════════ RECEIPT ═════════════════════════════════════════════════════ function formatReceipt(RawExecReceipt memory re) internal pure returns (bytes memory) { return ReceiptLib.formatReceipt( re.origin, re.destination, re.messageHash, re.snapshotRoot, re.stateIndex, re.attNotary, re.firstExecutor, re.finalExecutor ); } function castToReceipt(RawExecReceipt memory re) internal pure returns (Receipt) { return re.formatReceipt().castToReceipt(); } function modifyReceipt(RawExecReceipt memory re, uint256 mask) internal pure returns (RawExecReceipt memory mrb) { // Don't modify the destination, message hash mrb.destination = re.destination; mrb.messageHash = re.messageHash; // Make sure at least one value is modified, valid mask values are [1 .. 63] mask = 1 + (mask % 63); mrb.origin = re.origin ^ uint32(mask & 1); mrb.snapshotRoot = re.snapshotRoot ^ bytes32(mask & 2); mrb.stateIndex = re.stateIndex ^ uint8(mask & 4); mrb.attNotary = address(uint160(re.attNotary) ^ uint160(mask & 8)); mrb.firstExecutor = address(uint160(re.firstExecutor) ^ uint160(mask & 16)); mrb.finalExecutor = address(uint160(re.finalExecutor) ^ uint160(mask & 32)); } // ═════════════════════════════════════════════════ GAS DATA ══════════════════════════════════════════════════════ function encodeNumber(RawNumber memory rn) internal pure returns (Number) { return Number.wrap(rn.number); } function round(uint256 num) internal pure returns (uint256) { return NumberLib.decompress(NumberLib.compress(num)); } function round(RawGasData256 memory rgd256) internal pure { rgd256.gasPrice = round(rgd256.gasPrice); rgd256.dataPrice = round(rgd256.dataPrice); rgd256.execBuffer = round(rgd256.execBuffer); rgd256.amortAttCost = round(rgd256.amortAttCost); rgd256.etherPrice = round(rgd256.etherPrice); rgd256.markup = round(rgd256.markup); } function compress(RawGasData256 memory rdg256) internal pure returns (RawGasData memory rgd) { rgd.gasPrice.number = Number.unwrap(NumberLib.compress(rdg256.gasPrice)); rgd.dataPrice.number = Number.unwrap(NumberLib.compress(rdg256.dataPrice)); rgd.execBuffer.number = Number.unwrap(NumberLib.compress(rdg256.execBuffer)); rgd.amortAttCost.number = Number.unwrap(NumberLib.compress(rdg256.amortAttCost)); rgd.etherPrice.number = Number.unwrap(NumberLib.compress(rdg256.etherPrice)); rgd.markup.number = Number.unwrap(NumberLib.compress(rdg256.markup)); } function decompress(RawGasData memory rgd) internal pure returns (RawGasData256 memory rdg256) { rdg256.gasPrice = Number.wrap(rgd.gasPrice.number).decompress(); rdg256.dataPrice = Number.wrap(rgd.dataPrice.number).decompress(); rdg256.execBuffer = Number.wrap(rgd.execBuffer.number).decompress(); rdg256.amortAttCost = Number.wrap(rgd.amortAttCost.number).decompress(); rdg256.etherPrice = Number.wrap(rgd.etherPrice.number).decompress(); rdg256.markup = Number.wrap(rgd.markup.number).decompress(); } function encodeGasData(RawGasData memory rgd) internal pure returns (uint96 encodedGasData) { return GasData.unwrap(rgd.castToGasData()); } function castToGasData(RawGasData memory rgd) internal pure returns (GasData) { return GasDataLib.encodeGasData({ gasPrice_: rgd.gasPrice.encodeNumber(), dataPrice_: rgd.dataPrice.encodeNumber(), execBuffer_: rgd.execBuffer.encodeNumber(), amortAttCost_: rgd.amortAttCost.encodeNumber(), etherPrice_: rgd.etherPrice.encodeNumber(), markup_: rgd.markup.encodeNumber() }); } function encodeChainGas(RawChainGas memory rcg) internal pure returns (uint128 encodedChainGas) { return ChainGas.unwrap(rcg.castToChainGas()); } function castToChainGas(RawChainGas memory rcg) internal pure returns (ChainGas) { return GasDataLib.encodeChainGas({domain_: rcg.domain, gasData_: rcg.gasData.castToGasData()}); } // ═══════════════════════════════════════════════════ STATE ═══════════════════════════════════════════════════════ function formatState(RawState memory rs) internal pure returns (bytes memory state) { state = StateLib.formatState({ root_: rs.root, origin_: rs.origin, nonce_: rs.nonce, blockNumber_: rs.blockNumber, timestamp_: rs.timestamp, gasData_: rs.gasData.castToGasData() }); } function castToState(RawState memory rs) internal pure returns (State ptr) { ptr = rs.formatState().castToState(); } function modifyState(RawState memory rs, uint256 mask) internal pure returns (RawState memory mrs) { // Make sure at least one value is modified, valid mask values are [1 .. 2047] mask = 1 + (mask % 2047); mrs.root = rs.root ^ bytes32(mask & 1); mrs.origin = rs.origin ^ uint32(mask & 2); mrs.nonce = rs.nonce ^ uint32(mask & 4); mrs.blockNumber = rs.blockNumber ^ uint32(mask & 8); mrs.timestamp = rs.timestamp ^ uint32(mask & 16); mrs.gasData.gasPrice.number = rs.gasData.gasPrice.number ^ uint16(mask & 32); mrs.gasData.dataPrice.number = rs.gasData.dataPrice.number ^ uint16(mask & 64); mrs.gasData.execBuffer.number = rs.gasData.execBuffer.number ^ uint16(mask & 128); mrs.gasData.amortAttCost.number = rs.gasData.amortAttCost.number ^ uint16(mask & 256); mrs.gasData.etherPrice.number = rs.gasData.etherPrice.number ^ uint16(mask & 512); mrs.gasData.markup.number = rs.gasData.markup.number ^ uint16(mask & 1024); } function boundStateIndex(RawStateIndex memory rsi) internal pure { // [1 .. SNAPSHOT_MAX_STATES] range rsi.statesAmount = 1 + rsi.statesAmount % SNAPSHOT_MAX_STATES; // [0 .. statesAmount) range rsi.stateIndex = uint8(rsi.stateIndex % rsi.statesAmount); } // ═════════════════════════════════════════════════ SNAPSHOT ══════════════════════════════════════════════════════ function formatStates(RawSnapshot memory rawSnap) internal pure returns (bytes[] memory states) { states = new bytes[](rawSnap.states.length); for (uint256 i = 0; i < rawSnap.states.length; ++i) { states[i] = rawSnap.states[i].formatState(); } } function castToRawAttestation( RawSnapshot memory rawSnap, bytes32 agentRoot, uint32 nonce, uint40 blockNumber, uint40 timestamp ) internal view returns (RawAttestation memory ra) { Snapshot snapshot = rawSnap.castToSnapshot(); ra.snapRoot = snapshot.calculateRoot(); ra._agentRoot = agentRoot; ra._snapGasHash = rawSnap.snapGasHash(); ra.setDataHash(); ra.nonce = nonce; ra.blockNumber = blockNumber; ra.timestamp = timestamp; } function formatSnapshot(RawSnapshot memory rawSnap) internal view returns (bytes memory snapshot) { State[] memory states = new State[](rawSnap.states.length); for (uint256 i = 0; i < rawSnap.states.length; ++i) { states[i] = rawSnap.states[i].castToState(); } snapshot = SnapshotLib.formatSnapshot(states); } function castToSnapshot(RawSnapshot memory rawSnap) internal view returns (Snapshot ptr) { ptr = rawSnap.formatSnapshot().castToSnapshot(); } function snapGas(RawSnapshot memory rawSnap) internal view returns (uint256[] memory snapGas_) { ChainGas[] memory chainData = rawSnap.castToSnapshot().snapGas(); snapGas_ = new uint256[](chainData.length); for (uint256 i = 0; i < snapGas_.length; ++i) { snapGas_[i] = ChainGas.unwrap(chainData[i]); } } function snapGasHash(RawSnapshot memory rawSnap) internal view returns (bytes32) { return GasDataLib.snapGasHash(rawSnap.castToSnapshot().snapGas()); } // ════════════════════════════════════════════════ ATTESTATION ════════════════════════════════════════════════════ function formatAttestation(RawAttestation memory ra) internal pure returns (bytes memory attestation) { attestation = AttestationLib.formatAttestation({ snapRoot_: ra.snapRoot, dataHash_: ra.dataHash, nonce_: ra.nonce, blockNumber_: ra.blockNumber, timestamp_: ra.timestamp }); } function castToAttestation(RawAttestation memory ra) internal pure returns (Attestation ptr) { ptr = ra.formatAttestation().castToAttestation(); } function setDataHash(RawAttestation memory ra) internal pure { ra.dataHash = keccak256(bytes.concat(ra._agentRoot, ra._snapGasHash)); } function modifyAttestation(RawAttestation memory ra, uint256 mask) internal pure returns (bool isEqual, RawAttestation memory mra) { // Don't modify the nonce mra.nonce = ra.nonce; // Check if at least one value was modified by checking last 5 bits isEqual = mask & 31 == 0; mra.snapRoot = ra.snapRoot ^ bytes32(mask & 1); mra._agentRoot = ra._agentRoot ^ bytes32(mask & 2); mra._snapGasHash = ra._snapGasHash ^ bytes32(mask & 4); mra.blockNumber = ra.blockNumber ^ uint40(mask & 8); mra.timestamp = ra.timestamp ^ uint40(mask & 16); mra.setDataHash(); } }