-
Notifications
You must be signed in to change notification settings - Fork 1
fix: emit generic metas as a rain meta document, not a bare cbor map #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d2ef1b8
fix: emit generic metas as a rain meta document, not a bare cbor map
0e05d51
Merge commit '4a688094b279a33f531c9001623f3adb588020ca' into 2026-08-…
thedavidmeister 9d33396
Merge remote-tracking branch 'origin/main' into check272
f13c056
test: send the rust-built calldata to a real metaboard
4cc9380
ci: git-clean regenerates the calldata fixture and diffs it
675248c
ci: satisfy the legal and static lanes for the new files
a451323
refactor: the fixture writer is a writer, not a self-checking test
6cd7ecc
style: cargo fmt the fixture writer
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| //! The seam between the two halves of this repo: rust builds `emitMeta` | ||
| //! calldata, solidity's `IMetaBoardV1_2` decides whether it is acceptable. | ||
| //! Each half was tested against its own idea of the bytes and neither against | ||
| //! the other, which is how `generate_emit_meta_calldata` came to build a bare | ||
| //! cbor map that `LibMeta.checkMetaUnhashedV1` reverts `NotRainMetaV1` on. | ||
| //! | ||
| //! This writes the calldata rust actually produces to a committed fixture. | ||
| //! `test/lib/EmitCalldataFixture.t.sol` reads that fixture and sends it to a | ||
| //! real `TestMetaBoard`, so the contract itself is what says the bytes are | ||
| //! acceptable rather than an assertion here restating what the encoder did. | ||
| //! | ||
| //! Regenerate with `BLESS=1 cargo test -p rain-metadata --test | ||
| //! emit_calldata_fixture`. Without `BLESS` the test asserts the committed | ||
| //! fixture still matches, so CI fails if the two drift apart. | ||
|
|
||
| use std::{fs, path::PathBuf}; | ||
|
|
||
| use rain_metadata::{ | ||
| ContentEncoding, ContentLanguage, ContentType, KnownMagic, RainMetaDocumentV1Item, | ||
| generate_dotrain_source_emit_tx_data, generate_emit_meta_calldata, | ||
| }; | ||
|
|
||
| fn fixture_path() -> PathBuf { | ||
| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../test/fixtures/emit-calldata.json") | ||
| } | ||
|
|
||
| /// A meta item with every optional field defaulted, so the fixture pins the | ||
| /// shortest encoding rather than an unusually decorated one. | ||
| fn plain_item(content: &str) -> RainMetaDocumentV1Item { | ||
| RainMetaDocumentV1Item { | ||
| payload: serde_bytes::ByteBuf::from(content.as_bytes().to_vec()), | ||
| magic: KnownMagic::DotrainSourceV1, | ||
| content_type: ContentType::OctetStream, | ||
| content_encoding: ContentEncoding::None, | ||
| content_language: ContentLanguage::None, | ||
| schema: None, | ||
| } | ||
| } | ||
|
|
||
| /// Every case solidity should be handed. Keyed by name so a failure names the | ||
| /// case rather than an index. | ||
| fn cases() -> Vec<(String, String)> { | ||
| let mut out = Vec::new(); | ||
|
|
||
| let generic = generate_emit_meta_calldata(plain_item("emit calldata fixture")).unwrap(); | ||
| out.push(( | ||
| "generic_item".to_string(), | ||
| alloy::hex::encode_prefixed(generic), | ||
| )); | ||
|
|
||
| let dotrain = generate_dotrain_source_emit_tx_data("#main _ _: int-add(1 2);").unwrap(); | ||
| out.push(("dotrain_source".to_string(), dotrain.calldata)); | ||
|
|
||
| out | ||
| } | ||
|
|
||
| #[test] | ||
| fn emit_calldata_fixture_is_current() { | ||
| let map: serde_json::Map<String, serde_json::Value> = cases() | ||
| .into_iter() | ||
| .map(|(name, calldata)| (name, serde_json::Value::String(calldata))) | ||
| .collect(); | ||
| let generated = serde_json::to_string_pretty(&serde_json::Value::Object(map)).unwrap() + "\n"; | ||
|
|
||
| let path = fixture_path(); | ||
| if std::env::var("BLESS").is_ok() { | ||
| fs::create_dir_all(path.parent().unwrap()).unwrap(); | ||
| fs::write(&path, &generated).unwrap(); | ||
| return; | ||
| } | ||
|
|
||
| let committed = fs::read_to_string(&path).unwrap_or_else(|error| { | ||
| panic!( | ||
| "{} is missing ({error}). Regenerate with BLESS=1 cargo test -p rain-metadata --test emit_calldata_fixture", | ||
| path.display() | ||
| ) | ||
| }); | ||
|
|
||
| assert_eq!( | ||
| committed, generated, | ||
| "committed emit calldata is stale. Regenerate with BLESS=1 cargo test -p rain-metadata --test emit_calldata_fixture" | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "dotrain_source": "0x37480e2a3fd2b7b238e68674f97d936e0e457eae32fe6b647984644c1162c9885502f68600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000049ff0a89c674ee7874a3005818236d61696e205f205f3a20696e742d61646428312032293b011bffa15ef0fc4370990278186170706c69636174696f6e2f6f637465742d73747265616d0000000000000000000000000000000000000000000000", | ||
| "generic_item": "0x37480e2aa8a816410fb3aa58c6c2440b66b741d975394347626bd243dc08c6444ae9a29f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000045ff0a89c674ee7874a30055656d69742063616c6c646174612066697874757265011bffa15ef0fc4370990278186170706c69636174696f6e2f6f637465742d73747265616d000000000000000000000000000000000000000000000000000000" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // SPDX-License-Identifier: LicenseRef-DCL-1.0 | ||
| // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd | ||
| pragma solidity =0.8.25; | ||
|
|
||
| import {Test, Vm} from "forge-std-1.16.2/src/Test.sol"; | ||
| import {IMetaV1_2} from "src/interface/unstable/IMetaV1_2.sol"; | ||
| import {TestMetaBoard} from "test/concrete/TestMetaBoard.sol"; | ||
|
|
||
| /// @title EmitCalldataFixtureTest | ||
| /// @notice The rust half of this repo builds `emitMeta` calldata and the | ||
| /// solidity half decides whether it is acceptable, and until now nothing tested | ||
| /// that seam: each half was checked against its own idea of the bytes. That is | ||
| /// how `generate_emit_meta_calldata` came to build a bare cbor map, which | ||
| /// `LibMeta.checkMetaUnhashedV1` reverts `NotRainMetaV1` on, while both test | ||
| /// suites stayed green. | ||
| /// | ||
| /// `crates/cli/tests/emit_calldata_fixture.rs` writes the calldata rust | ||
| /// actually produces to `test/fixtures/emit-calldata.json` and fails if the | ||
| /// committed copy is stale. This sends that calldata to a real metaboard, so | ||
| /// what accepts or rejects it is the contract rather than an assertion | ||
| /// restating what the encoder did. | ||
| contract EmitCalldataFixtureTest is Test { | ||
| TestMetaBoard internal metaBoard; | ||
|
|
||
| function setUp() external { | ||
| metaBoard = new TestMetaBoard(); | ||
| } | ||
|
|
||
| /// Every entry in the fixture is calldata a metaboard accepts, and emits | ||
| /// verbatim as a single `MetaV1_2`. A case that reverts fails here rather | ||
| /// than in production. | ||
| /// @param key The fixture key naming the rust producer under test. | ||
| function _checkFixtureCase(string memory key) internal { | ||
| string memory json = vm.readFile("test/fixtures/emit-calldata.json"); | ||
| bytes memory callData = vm.parseJsonBytes(json, string.concat(".", key)); | ||
|
|
||
| vm.recordLogs(); | ||
| (bool success,) = address(metaBoard).call(callData); | ||
| assertTrue(success, string.concat("metaboard rejected calldata for ", key)); | ||
|
|
||
| Vm.Log[] memory logs = vm.getRecordedLogs(); | ||
| assertEq(logs.length, 1, string.concat("log count for ", key)); | ||
| assertEq(logs[0].topics[0], IMetaV1_2.MetaV1_2.selector, string.concat("topic for ", key)); | ||
|
|
||
| // The emitted meta is the calldata's own meta argument, carried | ||
| // verbatim. Decoding the log against the calldata rather than against a | ||
| // literal keeps this test about the seam and not about the encoding. | ||
| (bytes32 subjectArg, bytes memory metaArg) = abi.decode(_args(callData), (bytes32, bytes)); | ||
| (address sender, bytes32 subject, bytes memory meta) = | ||
| abi.decode(logs[0].data, (address, bytes32, bytes)); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| assertEq(sender, address(this), string.concat("sender for ", key)); | ||
| assertEq(subject, subjectArg, string.concat("subject for ", key)); | ||
| assertEq(meta, metaArg, string.concat("meta for ", key)); | ||
| } | ||
|
|
||
| /// The abi encoded arguments, with the four byte selector dropped. | ||
| /// callData Whole calldata as the fixture carries it. | ||
| /// out The arguments alone, decodable as `(bytes32, bytes)`. | ||
| function _args(bytes memory callData) internal pure returns (bytes memory out) { | ||
| out = new bytes(callData.length - 4); | ||
| for (uint256 i = 0; i < out.length; i++) { | ||
| out[i] = callData[i + 4]; | ||
| } | ||
| } | ||
|
|
||
| /// `generate_emit_meta_calldata`, the generic producer. | ||
| function testGenericItemCalldataIsAcceptable() external { | ||
| _checkFixtureCase("generic_item"); | ||
| } | ||
|
|
||
| /// `generate_dotrain_source_emit_tx_data`, the dotrain producer. | ||
| function testDotrainSourceCalldataIsAcceptable() external { | ||
| _checkFixtureCase("dotrain_source"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.