From 9ad3b454c06e59116be586ee4dd294af4763ded3 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 31 Jul 2026 20:35:45 +0000 Subject: [PATCH 1/9] Retire legacy STAKE and UNSTAKE attestations Change-Id: I6295e0e573de1a523870927964b07419518febd3 --- contracts/sysio.msgch/src/sysio.msgch.cpp | 41 ++++++++++++++----- contracts/sysio.msgch/sysio.msgch.abi | 8 ---- .../sysio.opp.common/opp_table_types.hpp | 11 +---- contracts/sysio.uwrit/sysio.uwrit.abi | 8 ---- contracts/tests/sysio.dispatch_tests.cpp | 13 +++--- contracts/tests/sysio.msgch_chain_tests.cpp | 8 ++-- contracts/tests/sysio.msgch_tests.cpp | 27 ++++++++++++ etc/schema/opp_entity_diagram-gen.puml | 14 ------- etc/schema/opp_entity_diagram-gen.svg | 2 +- etc/schema/opp_entity_diagram.puml | 19 --------- libraries/opp/include/sysio/opp/opp.hpp | 2 - .../sysio/opp/attestations/attestations.proto | 13 ++---- .../opp/proto/sysio/opp/types/types.proto | 9 +++- .../protoc-gen-solidity/src/generator/enum.ts | 26 ++++++++++-- .../src/generator/index.ts | 2 +- .../tools/protoc-gen-solidity/src/plugin.ts | 21 +++++++++- .../protoc-gen-solidity/tests/enum.test.ts | 38 +++++++++++++++++ 17 files changed, 165 insertions(+), 97 deletions(-) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index a9cf372453..143195e22a 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace sysio { @@ -79,6 +80,18 @@ constexpr size_t ATTESTATION_OVERHEAD_BYTES = 24; /// + payload preamble, and a safety margin for `zpp::bits` length prefixes. constexpr size_t ENVELOPE_BASELINE_BYTES = 512; +/// Retired pre-launch attestation wire slots. They remain recognizable here +/// only so an upgraded contract can tombstone READY rows queued by the prior +/// implementation instead of forwarding them or blocking envelope creation. +constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; +constexpr int32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; + +bool is_retired_staking_attestation(AttestationType type) { + const auto value = magic_enum::enum_integer(type); + return value == RETIRED_STAKE_ATTESTATION_VALUE || + value == RETIRED_UNSTAKE_ATTESTATION_VALUE; +} + using namespace sysio::msgch_svm_terminal_budget; static_assert(svm_hard_dynamic_account_budget() == 16, @@ -125,8 +138,6 @@ std::optional estimate_svm_dynamic_accounts(AttestationType type, return SVM_DYNAMIC_ACCOUNTS_RESERVE_EFFECT_WORST_CASE; case AT::ATTESTATION_TYPE_UNSPECIFIED: - case AT::ATTESTATION_TYPE_STAKE: - case AT::ATTESTATION_TYPE_UNSTAKE: case AT::ATTESTATION_TYPE_PRETOKEN_PURCHASE: case AT::ATTESTATION_TYPE_PRETOKEN_YIELD: case AT::ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE: @@ -799,8 +810,8 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) /// in `evalcons` after a consensus envelope has been unpacked. Dispatch is /// best-effort — silently no-ops on unknown / out-of-scope types so the /// inbound stream can keep flowing even when the depot hasn't yet wired up -/// every handler (e.g. the deferred STAKE / UNSTAKE / STAKE_UPDATE staking -/// lifecycle types). +/// every active handler (for example, STAKE_UPDATE from the separate staking +/// track). Retired STAKE / UNSTAKE wire values also land on this no-op path. void dispatch_attestation(name self, uint64_t attestation_id, AttestationType type, const std::vector& data, @@ -925,12 +936,10 @@ void dispatch_attestation(name self, uint64_t attestation_id, // opening a sysio.chalg dispute vote, not by inbound challenge attestations. break; - case AttestationType::ATTESTATION_TYPE_STAKE: - case AttestationType::ATTESTATION_TYPE_UNSTAKE: case AttestationType::ATTESTATION_TYPE_STAKE_UPDATE: case AttestationType::ATTESTATION_TYPE_STAKE_RESULT: - // Validator-staking lifecycle; depot-side handlers land in a later - // task alongside liqEth / liqsol-token wiring. + // Post-launch validator-staking lifecycle; depot-side handlers land + // alongside liqEth / liqsol-token wiring. break; // Outbound-only types (depot emits these, never receives them inbound) @@ -1694,8 +1703,19 @@ void msgch::buildenv(uint64_t chain_code) { for (auto it = status_idx.lower_bound( static_cast(AttestationStatus::ATTESTATION_STATUS_READY)); it != status_idx.end() && - it->status == AttestationStatus::ATTESTATION_STATUS_READY; ++it) { - if (it->chain_code != chain_code) continue; + it->status == AttestationStatus::ATTESTATION_STATUS_READY; ) { + // Upgrade tombstone: legacy builds could persist STAKE / UNSTAKE rows. + // Erase them before destination-specific estimation so neither the SVM + // terminal-account gate nor an outpost decoder can be blocked by a + // protocol value that no longer has a generated enum/message type. + if (is_retired_staking_attestation(it->type)) { + it = status_idx.erase(std::move(it)); + continue; + } + if (it->chain_code != chain_code) { + ++it; + continue; + } opp::AttestationEntry entry; entry.type = it->type; @@ -1703,6 +1723,7 @@ void msgch::buildenv(uint64_t chain_code) { entry.data = it->data; candidate_entries.push_back(std::move(entry)); candidate_ids.push_back(it->id); + ++it; } if (candidate_entries.empty()) return; diff --git a/contracts/sysio.msgch/sysio.msgch.abi b/contracts/sysio.msgch/sysio.msgch.abi index 1a423908ef..d220efdb8a 100644 --- a/contracts/sysio.msgch/sysio.msgch.abi +++ b/contracts/sysio.msgch/sysio.msgch.abi @@ -582,14 +582,6 @@ "name": "ATTESTATION_TYPE_OPERATOR_ACTION", "value": 2001 }, - { - "name": "ATTESTATION_TYPE_STAKE", - "value": 3001 - }, - { - "name": "ATTESTATION_TYPE_UNSTAKE", - "value": 3002 - }, { "name": "ATTESTATION_TYPE_PRETOKEN_PURCHASE", "value": 3004 diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp index 4abc09f303..41562122d8 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp @@ -298,15 +298,8 @@ DataStream& operator>>(DataStream& ds, ReserveBalanceSheet& t) { return ds >> t.chain_code >> t.reserves; } -// PretokenStakeChange (deprecated; pre-launch only) -template -DataStream& operator<<(DataStream& ds, const PretokenStakeChange& t) { - return ds << t.actor << t.amount << t.index_at_mint << t.index_at_burn; -} -template -DataStream& operator>>(DataStream& ds, PretokenStakeChange& t) { - return ds >> t.actor >> t.amount >> t.index_at_mint >> t.index_at_burn; -} +// PretokenStakeChange DataStream operators were removed with the retired +// pre-launch STAKE / UNSTAKE lifecycle (enum slots 3001 and 3002). // PretokenPurchase (deprecated; pre-launch only) template diff --git a/contracts/sysio.uwrit/sysio.uwrit.abi b/contracts/sysio.uwrit/sysio.uwrit.abi index 95bc74caca..72dca74379 100644 --- a/contracts/sysio.uwrit/sysio.uwrit.abi +++ b/contracts/sysio.uwrit/sysio.uwrit.abi @@ -588,14 +588,6 @@ "name": "ATTESTATION_TYPE_OPERATOR_ACTION", "value": 2001 }, - { - "name": "ATTESTATION_TYPE_STAKE", - "value": 3001 - }, - { - "name": "ATTESTATION_TYPE_UNSTAKE", - "value": 3002 - }, { "name": "ATTESTATION_TYPE_PRETOKEN_PURCHASE", "value": 3004 diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index cbadc48e61..2dae3bb57e 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -140,18 +140,19 @@ std::vector encode_envelope_with_attestations( constexpr size_t MAX_ENVELOPE_BYTES = 65'536; /// Encode a decodable envelope whose serialised size is EXACTLY `target_bytes`, padded with a -/// single out-of-scope STAKE attestation (dispatch drops it with no value-bearing effect). Probe +/// single challenge-response attestation (dispatch drops it with no value-bearing effect). Probe /// once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with /// the pad shrunk by that overhead: at sizes near the 64 KiB envelope cap every nested length /// prefix and the `data_size` varint sit in the same 3-byte width band (16 KiB .. 2 MiB), so the /// second pass lands exactly on target — the final REQUIRE pins it. std::vector encode_envelope_padded_to(uint32_t epoch_index, size_t target_bytes) { auto probe = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_STAKE, std::string(target_bytes, 'x')); + epoch_index, sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, + std::string(target_bytes, 'x')); BOOST_REQUIRE_GT(probe.size(), target_bytes); const size_t overhead = probe.size() - target_bytes; auto padded = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_STAKE, + epoch_index, sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, std::string(target_bytes - overhead, 'x')); BOOST_REQUIRE_EQUAL(target_bytes, padded.size()); return padded; @@ -889,9 +890,11 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat bootstrap_for_dispatch(); const auto eth_code = fc::slug_name{"ETH"}.value; + constexpr auto retired_stake_attestation = + static_cast(3001); auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, + retired_stake_attestation, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); @@ -1228,7 +1231,7 @@ BOOST_FIXTURE_TEST_CASE(deliver_duplicate_from_same_operator_reverts, sysio_disp const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, + sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); diff --git a/contracts/tests/sysio.msgch_chain_tests.cpp b/contracts/tests/sysio.msgch_chain_tests.cpp index e0f403d85b..e53aec573f 100644 --- a/contracts/tests/sysio.msgch_chain_tests.cpp +++ b/contracts/tests/sysio.msgch_chain_tests.cpp @@ -358,9 +358,9 @@ class sysio_msgch_chain_tester : public tester { // -- Inbound envelope builder -- - /// Encode a deliverable envelope carrying one out-of-scope STAKE attestation (dispatch drops - /// the attestation silently; acceptance is still fully observable via `outpcons` and the - /// stored attestation row). The semantic header is derived per the spec — `apply_consensus` + /// Encode a deliverable envelope carrying one out-of-scope CHALLENGE_RESPONSE attestation + /// (dispatch drops the attestation silently; acceptance is still fully observable via + /// `outpcons` and the stored attestation row). The semantic header is derived per the spec — `apply_consensus` /// drops envelopes whose header fields do not recompute or whose message does not continue the /// per-outpost message chain. `prev` (previous_envelope_hash), `prev_message_id`, and /// `env_hash` are raw 32-byte strings (or empty for stream genesis). @@ -375,7 +375,7 @@ class sysio_msgch_chain_tester : public tester { if (!prev.empty()) env.set_previous_envelope_hash(prev); if (!env_hash.empty()) env.set_envelope_hash(env_hash); auto* att = env.add_messages()->mutable_payload()->add_attestations(); - att->set_type(sysio::opp::types::ATTESTATION_TYPE_STAKE); + att->set_type(sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE); att->set_data(att_data); att->set_data_size(static_cast(att_data.size())); oracle::finalize_header(*env.mutable_messages(0), prev_message_id, 1'775'612'516'983ULL); diff --git a/contracts/tests/sysio.msgch_tests.cpp b/contracts/tests/sysio.msgch_tests.cpp index f4630afa05..198ff038f1 100644 --- a/contracts/tests/sysio.msgch_tests.cpp +++ b/contracts/tests/sysio.msgch_tests.cpp @@ -361,6 +361,8 @@ constexpr uint64_t SOL_OUTPOST_ID = "SOL"_s.value; constexpr auto EVM_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_OPERATORS; constexpr auto SWAP_REMIT_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_SWAP_REMIT; constexpr auto UNCOVERED_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_STAKING_REWARD; +/// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. +constexpr uint32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; /// Decode the emitted OPP envelope and count attestations in its single message. uint32_t emitted_attestation_count(const fc::variant& emitted_row) { @@ -404,6 +406,31 @@ BOOST_FIXTURE_TEST_CASE(buildenv_writes_envlog_row, sysio_msgch_envlog_tester) { BOOST_REQUIRE_EQUAL(31337u, row["endpoints"]["end"]["id"]["value"].as_uint64()); } FC_LOG_AND_RETHROW() } +/// READY rows written by a pre-upgrade contract with a retired staking wire +/// value are tombstoned before destination-specific envelope construction. +/// Active rows behind the tombstone still emit normally, so one legacy row +/// cannot strand the queue or reach an outpost decoder. +BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_retired_staking_rows, + sysio_msgch_envlog_tester) { try { + bootstrap_epoch_config(/*retention=*/200); + register_outpost(opp::types::CHAIN_KIND_EVM, 31337); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); + BOOST_REQUIRE_EQUAL(2u, count_ready_attestations(ETH_OUTPOST_ID, 8)); + + BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 8)); + const auto emitted = find_outbound_envelope(); + BOOST_REQUIRE(!emitted.is_null()); + BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); +} FC_LOG_AND_RETHROW() } + /// Eviction at the boundary. Set `retention=2` and one outpost → /// `cap = 1*2*2 = 4`. After 5 buildenv rounds (5 rows inserted), the /// oldest full epoch (`per_epoch = 1*2 = 2` rows) gets evicted; final diff --git a/etc/schema/opp_entity_diagram-gen.puml b/etc/schema/opp_entity_diagram-gen.puml index 5c3286a151..17ee3058c7 100644 --- a/etc/schema/opp_entity_diagram-gen.puml +++ b/etc/schema/opp_entity_diagram-gen.puml @@ -44,11 +44,6 @@ sysio.opp.MessagePayload -- sysio.opp.AttestationEntry -sysio.opp.attestations.PretokenStakeChange -- sysio.opp.types.ChainAddress -sysio.opp.attestations.PretokenStakeChange -- sysio.opp.types.TokenAmount - - - sysio.opp.types.ChainSignature -- sysio.opp.types.ChainAddress sysio.opp.types.ChainSignature -- sysio.opp.types.ChainKeyType @@ -109,13 +104,6 @@ package sysio.opp.attestations { index_at_mint: Long } - class PretokenStakeChange { - actor: sysio.opp.types.ChainAddress - amount: sysio.opp.types.TokenAmount - index_at_mint: Long - index_at_burn: Long - } - class PretokenYield { actor: sysio.opp.types.ChainAddress amount: sysio.opp.types.TokenAmount @@ -156,8 +144,6 @@ package sysio.opp.attestations { package sysio.opp.types { enum AttestationType { ATTESTATION_TYPE_UNSPECIFIED - ATTESTATION_TYPE_STAKE - ATTESTATION_TYPE_UNSTAKE ATTESTATION_TYPE_PRETOKEN_PURCHASE ATTESTATION_TYPE_PRETOKEN_YIELD ATTESTATION_TYPE_RESERVE_BALANCE_SHEET diff --git a/etc/schema/opp_entity_diagram-gen.svg b/etc/schema/opp_entity_diagram-gen.svg index 7482257f86..623ce2c7d8 100644 --- a/etc/schema/opp_entity_diagram-gen.svg +++ b/etc/schema/opp_entity_diagram-gen.svg @@ -1 +1 @@ -sysiooppattestationstypesMessagePayloadversion: Intattestations: AttestationEntry [*]AttestationEntrytype: sysio.opp.types.AttestationTypedata_size: Intdata: BytesMessageheader: MessageHeaderpayload: MessagePayloadMessageHeaderendpoints: Endpointsmessage_id: Bytesprevious_message_id: Bytesencoding_flags: sysio.opp.types.EncodingFlagspayload_size: Intpayload_checksum: Bytestimestamp: Longheader_checksum: BytesEnvelopeenvelope_hash: Bytesendpoints: Endpointsepoch_timestamp: Longepoch_index: Intepoch_envelope_index: Intmerkle: Bytesprevious_envelope_hash: Bytesstart_message_id: Bytesend_message_id: Bytessignatures: sysio.opp.types.ChainSignature [*]Endpointsstart: sysio.opp.types.ChainIdend: sysio.opp.types.ChainIdOperatorActionaction_type: OperatorAction::ActionTypeactor: sysio.opp.types.ChainAddresstype: sysio.opp.types.OperatorTypestatus: sysio.opp.types.OperatorStatusamount: sysio.opp.types.TokenAmountChainReserveBalanceSheetkind: sysio.opp.types.ChainKindamounts: sysio.opp.types.TokenAmount [*]PretokenPurchaseactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountpretoken_count: Longindex_at_mint: LongReserveDisbursementactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountsignature: sysio.opp.types.ChainSignature [*]StakeUpdateactor: sysio.opp.types.ChainAddressstatus: sysio.opp.types.StakeStatusamount: sysio.opp.types.TokenAmountPretokenStakeChangeactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountindex_at_mint: Longindex_at_burn: LongTimestampedMessagetimestamp: Longmessage: sysio.opp.MessagePretokenYieldactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountindex_at_mint: LongWireTokenPurchaseactor: sysio.opp.types.ChainAddressamounts: sysio.opp.types.TokenAmount [*]ProtocolStatechain_id: sysio.opp.types.ChainIdcurrent_message_id: Bytesprocessed_message_id: Bytesincoming_messages: TimestampedMessage [*]outgoing: sysio.opp.Message [*]OperatorAction::ActionTypeACTION_TYPE_UNKNOWNACTION_TYPE_DEPOSITACTION_TYPE_WITHDRAWOperatorStatusOPERATOR_STATUS_UNKNOWNOPERATOR_STATUS_WARMUPOPERATOR_STATUS_COOLDOWNOPERATOR_STATUS_ACTIVEOPERATOR_STATUS_TERMINATEDOPERATOR_STATUS_SLASHEDTokenAmountkind: TokenKindamount: LongChainAddresskind: ChainKindaddress: BytesOperatorTypeOPERATOR_TYPE_UNKNOWNOPERATOR_TYPE_PRODUCEROPERATOR_TYPE_BATCHOPERATOR_TYPE_UNDERWRITEROPERATOR_TYPE_CHALLENGERChainIdkind: ChainKindid: IntChainKindCHAIN_KIND_UNKNOWNCHAIN_KIND_WIRECHAIN_KIND_ETHEREUMCHAIN_KIND_SOLANACHAIN_KIND_SUITokenKindTOKEN_KIND_WIRETOKEN_KIND_ETHTOKEN_KIND_LIQETHTOKEN_KIND_SOLTOKEN_KIND_LIQSOLChainSignatureactor: ChainAddresskey_type: ChainKeyTypesignature: BytesStakeStatusSTAKE_STATUS_UNKNOWNSTAKE_STATUS_WARMUPSTAKE_STATUS_COOLDOWNSTAKE_STATUS_ACTIVESTAKE_STATUS_TERMINATEDSTAKE_STATUS_SLASHEDChainKeyTypeCHAIN_KEY_TYPE_UNKNOWNCHAIN_KEY_TYPE_WIRECHAIN_KEY_TYPE_WIRE_BLSCHAIN_KEY_TYPE_ETHEREUMCHAIN_KEY_TYPE_SOLANACHAIN_KEY_TYPE_SUIAttestationTypeATTESTATION_TYPE_UNSPECIFIEDATTESTATION_TYPE_STAKEATTESTATION_TYPE_UNSTAKEATTESTATION_TYPE_PRETOKEN_PURCHASEATTESTATION_TYPE_PRETOKEN_YIELDATTESTATION_TYPE_RESERVE_BALANCE_SHEETATTESTATION_TYPE_STAKE_UPDATEATTESTATION_TYPE_NATIVE_YIELD_REWARDATTESTATION_TYPE_WIRE_TOKEN_PURCHASEATTESTATION_TYPE_OPERATOR_REG_DEREGATTESTATION_TYPE_CHALLENGE_RESPONSEATTESTATION_TYPE_SLASH_OPERATOREncodingFlagsendianness: Endiannesshash_algorithm: HashAlgorithmlength_encoding: LengthEncodingEndiannessENDIANNESS_BIGENDIANNESS_LITTLEHashAlgorithmHASH_ALGORITHM_KECCAK256HASH_ALGORITHM_SHA256HASH_ALGORITHM_RESERVED_1HASH_ALGORITHM_RESERVED_2LengthEncodingLENGTH_ENCODING_VARUINTLENGTH_ENCODING_UINT32 +sysiooppattestationstypesMessagePayloadversion: Intattestations: AttestationEntry [*]AttestationEntrytype: sysio.opp.types.AttestationTypedata_size: Intdata: BytesMessageheader: MessageHeaderpayload: MessagePayloadMessageHeaderendpoints: Endpointsmessage_id: Bytesprevious_message_id: Bytesencoding_flags: sysio.opp.types.EncodingFlagspayload_size: Intpayload_checksum: Bytestimestamp: Longheader_checksum: BytesEnvelopeenvelope_hash: Bytesendpoints: Endpointsepoch_timestamp: Longepoch_index: Intepoch_envelope_index: Intmerkle: Bytesprevious_envelope_hash: Bytesstart_message_id: Bytesend_message_id: Bytessignatures: sysio.opp.types.ChainSignature [*]Endpointsstart: sysio.opp.types.ChainIdend: sysio.opp.types.ChainIdOperatorActionaction_type: OperatorAction::ActionTypeactor: sysio.opp.types.ChainAddresstype: sysio.opp.types.OperatorTypestatus: sysio.opp.types.OperatorStatusamount: sysio.opp.types.TokenAmountChainReserveBalanceSheetkind: sysio.opp.types.ChainKindamounts: sysio.opp.types.TokenAmount [*]PretokenPurchaseactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountpretoken_count: Longindex_at_mint: LongReserveDisbursementactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountsignature: sysio.opp.types.ChainSignature [*]StakeUpdateactor: sysio.opp.types.ChainAddressstatus: sysio.opp.types.StakeStatusamount: sysio.opp.types.TokenAmountTimestampedMessagetimestamp: Longmessage: sysio.opp.MessagePretokenYieldactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountindex_at_mint: LongWireTokenPurchaseactor: sysio.opp.types.ChainAddressamounts: sysio.opp.types.TokenAmount [*]ProtocolStatechain_id: sysio.opp.types.ChainIdcurrent_message_id: Bytesprocessed_message_id: Bytesincoming_messages: TimestampedMessage [*]outgoing: sysio.opp.Message [*]OperatorAction::ActionTypeACTION_TYPE_UNKNOWNACTION_TYPE_DEPOSITACTION_TYPE_WITHDRAWOperatorStatusOPERATOR_STATUS_UNKNOWNOPERATOR_STATUS_WARMUPOPERATOR_STATUS_COOLDOWNOPERATOR_STATUS_ACTIVEOPERATOR_STATUS_TERMINATEDOPERATOR_STATUS_SLASHEDTokenAmountkind: TokenKindamount: LongChainAddresskind: ChainKindaddress: BytesOperatorTypeOPERATOR_TYPE_UNKNOWNOPERATOR_TYPE_PRODUCEROPERATOR_TYPE_BATCHOPERATOR_TYPE_UNDERWRITEROPERATOR_TYPE_CHALLENGERChainIdkind: ChainKindid: IntChainKindCHAIN_KIND_UNKNOWNCHAIN_KIND_WIRECHAIN_KIND_ETHEREUMCHAIN_KIND_SOLANACHAIN_KIND_SUITokenKindTOKEN_KIND_WIRETOKEN_KIND_ETHTOKEN_KIND_LIQETHTOKEN_KIND_SOLTOKEN_KIND_LIQSOLChainSignatureactor: ChainAddresskey_type: ChainKeyTypesignature: BytesStakeStatusSTAKE_STATUS_UNKNOWNSTAKE_STATUS_WARMUPSTAKE_STATUS_COOLDOWNSTAKE_STATUS_ACTIVESTAKE_STATUS_TERMINATEDSTAKE_STATUS_SLASHEDChainKeyTypeCHAIN_KEY_TYPE_UNKNOWNCHAIN_KEY_TYPE_WIRECHAIN_KEY_TYPE_WIRE_BLSCHAIN_KEY_TYPE_ETHEREUMCHAIN_KEY_TYPE_SOLANACHAIN_KEY_TYPE_SUIAttestationTypeATTESTATION_TYPE_UNSPECIFIEDATTESTATION_TYPE_PRETOKEN_PURCHASEATTESTATION_TYPE_PRETOKEN_YIELDATTESTATION_TYPE_RESERVE_BALANCE_SHEETATTESTATION_TYPE_STAKE_UPDATEATTESTATION_TYPE_NATIVE_YIELD_REWARDATTESTATION_TYPE_WIRE_TOKEN_PURCHASEATTESTATION_TYPE_OPERATOR_REG_DEREGATTESTATION_TYPE_CHALLENGE_RESPONSEATTESTATION_TYPE_SLASH_OPERATOREncodingFlagsendianness: Endiannesshash_algorithm: HashAlgorithmlength_encoding: LengthEncodingEndiannessENDIANNESS_BIGENDIANNESS_LITTLEHashAlgorithmHASH_ALGORITHM_KECCAK256HASH_ALGORITHM_SHA256HASH_ALGORITHM_RESERVED_1HASH_ALGORITHM_RESERVED_2LengthEncodingLENGTH_ENCODING_VARUINTLENGTH_ENCODING_UINT32 \ No newline at end of file diff --git a/etc/schema/opp_entity_diagram.puml b/etc/schema/opp_entity_diagram.puml index c13b42b144..f70dcb566c 100644 --- a/etc/schema/opp_entity_diagram.puml +++ b/etc/schema/opp_entity_diagram.puml @@ -51,8 +51,6 @@ package opp.types #F5F5F5 { enum attestation_type_t <> { reserve_balance_sheet = 0xAA00 - stake = 0x0BB9 - unstake = 0x0BBA pretoken_purchase = 0x0BBB pretoken_yield = 0x0BBE -- @@ -149,21 +147,6 @@ package opp.attestations #FCE4EC { * amounts : token_amount[] } - entity stake { - * actor : chain_address - * amount : token_amount - * pretoken_count : uint256 - * index_at_mint : uint256 - } - - entity unstake { - * unstaker : chain_address - * amount : token_amount - * pretoken_count : uint256 - * index_at_burn : uint256 - - } - entity pretoken_purchase { * actor : chain_address * amount : token_amount @@ -249,8 +232,6 @@ package opp.attestations #FCE4EC { ' -- Assertion entry resolves to concrete payloads -- opp.attestation_entry ..> opp.attestations.reserve_balance_sheet -opp.attestation_entry ..> opp.attestations.stake -opp.attestation_entry ..> opp.attestations.unstake opp.attestation_entry ..> opp.attestations.pretoken_purchase opp.attestation_entry ..> opp.attestations.pretoken_yield opp.attestation_entry ..> opp.attestations.stake_update diff --git a/libraries/opp/include/sysio/opp/opp.hpp b/libraries/opp/include/sysio/opp/opp.hpp index 7224652467..6f1f12e25d 100644 --- a/libraries/opp/include/sysio/opp/opp.hpp +++ b/libraries/opp/include/sysio/opp/opp.hpp @@ -82,8 +82,6 @@ FC_REFLECT_ENUM(sysio::opp::types::ReserveStatus, FC_REFLECT_ENUM(sysio::opp::types::AttestationType, (ATTESTATION_TYPE_UNSPECIFIED) (ATTESTATION_TYPE_OPERATOR_ACTION) - (ATTESTATION_TYPE_STAKE) - (ATTESTATION_TYPE_UNSTAKE) (ATTESTATION_TYPE_PRETOKEN_PURCHASE) (ATTESTATION_TYPE_PRETOKEN_YIELD) (ATTESTATION_TYPE_RESERVE_BALANCE_SHEET) diff --git a/libraries/opp/proto/sysio/opp/attestations/attestations.proto b/libraries/opp/proto/sysio/opp/attestations/attestations.proto index 16e732ce07..19ebeabeb5 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -29,18 +29,13 @@ message ReserveBalanceSheet { repeated sysio.opp.types.ReserveAmount reserves = 4; } +// PretokenStakeChange was removed with the pre-launch STAKE / UNSTAKE +// lifecycle. Attestation enum slots 3001 and 3002 remain retired. + // --------------------------------------------------------------------------- -// Pre-launch specific attestations (DEPRECATED — kept for proto compatibility -// during the deprecation pass) +// Remaining pre-launch specific attestations (DEPRECATED) // --------------------------------------------------------------------------- -message PretokenStakeChange { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount amount = 2; - int64 index_at_mint = 10; - int64 index_at_burn = 11; -} - message PretokenPurchase { sysio.opp.types.ChainAddress actor = 1; sysio.opp.types.TokenAmount amount = 2; diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index eb05ede717..a3b8e93fd5 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -229,10 +229,15 @@ message ReserveAmount { // --------------------------------------------------------------------------- enum AttestationType { + reserved 3001, 3002; + reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; + ATTESTATION_TYPE_UNSPECIFIED = 0; ATTESTATION_TYPE_OPERATOR_ACTION = 2001; // 0x07D1 - ATTESTATION_TYPE_STAKE = 3001; - ATTESTATION_TYPE_UNSTAKE = 3002; + // 3001 was ATTESTATION_TYPE_STAKE — removed; do not reuse. The pre-launch + // stake lifecycle was retired before mainnet launch. + // 3002 was ATTESTATION_TYPE_UNSTAKE — removed; do not reuse. The pre-launch + // unstake lifecycle was retired before mainnet launch. // DEPRECATED — pre-launch only, do not use in new code. ATTESTATION_TYPE_PRETOKEN_PURCHASE = 3004; // DEPRECATED — pre-launch only, do not use in new code. diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts index d0fd5a792b..52c12290fa 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts @@ -6,6 +6,12 @@ export interface EnumValueInfo { number: number } +/** A protobuf enum reservation. Both bounds are inclusive in descriptor.proto. */ +export interface EnumReservedRangeInfo { + start: number + end: number +} + /** Descriptor for a protobuf enum, ready for Solidity codegen. */ export interface EnumDescriptor { /** Simple name (e.g. "Role") */ @@ -14,6 +20,8 @@ export interface EnumDescriptor { fullName: string /** Enum values */ values: EnumValueInfo[] + /** Numeric slots retired with `reserved`; decoded opaquely but never valid. */ + reservedRanges: EnumReservedRangeInfo[] /** Computed smallest unsigned integer type that fits all values */ underlyingType: string } @@ -34,9 +42,12 @@ export interface EnumFieldInfo { /** * Compute the smallest unsigned integer type that can hold all enum values. */ -export function computeUnderlyingType(values: EnumValueInfo[]): string { - if (values.length === 0) return "uint8" - const maxVal = Math.max(0, ...values.map(v => v.number)) +export function computeUnderlyingType( + values: EnumValueInfo[], + reservedRanges: EnumReservedRangeInfo[] = [] +): string { + const maxReserved = reservedRanges.map(range => range.end) + const maxVal = Math.max(0, ...values.map(v => v.number), ...maxReserved) if (maxVal <= 0xff) return "uint8" if (maxVal <= 0xffff) return "uint16" if (maxVal <= 0xffffff) return "uint24" @@ -100,6 +111,15 @@ export function genEnumDefinition(desc: EnumDescriptor): string { for (const val of uniqueValues) { lines.push(` if (_raw == ${val.number}) return ${val.name};`) } + for (const range of desc.reservedRanges) { + const condition = + range.end === range.start + ? `_raw == ${range.start}` + : `_raw >= ${range.start} && _raw <= ${range.end}` + lines.push( + ` if (${condition}) return ${name}.wrap(${underlying}(_raw));` + ) + } lines.push(` revert InvalidEnumValue(_raw);`) lines.push(` }`) diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts index 11c70009b4..977a331818 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts @@ -3,5 +3,5 @@ export { generateRuntime } from "./runtime.js" export type { MessageDescriptor, TypeRegistry } from "./message.js" export type { FieldInfo } from "./field.js" export { PROTO_TYPE_MAP, WireType, resolveSolType, fieldTag } from "./type-map.js" -export type { EnumDescriptor, EnumValueInfo, EnumRegistry, EnumFieldInfo } from "./enum.js" +export type { EnumDescriptor, EnumValueInfo, EnumReservedRangeInfo, EnumRegistry, EnumFieldInfo } from "./enum.js" export { genEnumDefinition, enumLibName, computeUnderlyingType } from "./enum.js" diff --git a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts index 213d369e4a..5f9833cb9d 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts @@ -48,10 +48,17 @@ const EnumValueDescriptorProto = new protobuf.Type("EnumValueDescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) .add(new protobuf.Field("number", 2, "int32", "optional")) +const EnumReservedRange = new protobuf.Type("EnumReservedRange") + .add(new protobuf.Field("start", 1, "int32", "optional")) + .add(new protobuf.Field("end", 2, "int32", "optional")) + const EnumDescriptorProtoMsg = new protobuf.Type("EnumDescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) .add(new protobuf.Field("value", 2, "EnumValueDescriptorProto", "repeated")) + .add(new protobuf.Field("reserved_range", 4, "EnumReservedRange", "repeated")) + .add(new protobuf.Field("reserved_name", 5, "string", "repeated")) .add(EnumValueDescriptorProto) + .add(EnumReservedRange) const DescriptorProto = new protobuf.Type("DescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) @@ -231,11 +238,16 @@ function buildEnumRegistry(protoFiles: any[]): EnumRegistry { name: v.name ?? "", number: v.number ?? 0 })) + const reservedRanges = (e.reserved_range ?? []).map((range: any) => ({ + start: range.start ?? 0, + end: range.end ?? 0 + })) registry.set(fqn, { name, fullName, values, - underlyingType: computeUnderlyingType(values) + reservedRanges, + underlyingType: computeUnderlyingType(values, reservedRanges) }) } } @@ -324,11 +336,16 @@ function extractEnums(protoFile: any, packageName: string): EnumDescriptor[] { name: v.name ?? "", number: v.number ?? 0 })) + const reservedRanges = (e.reserved_range ?? []).map((range: any) => ({ + start: range.start ?? 0, + end: range.end ?? 0 + })) result.push({ name, fullName, values, - underlyingType: computeUnderlyingType(values) + reservedRanges, + underlyingType: computeUnderlyingType(values, reservedRanges) }) } } diff --git a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts index 3ce892b441..569a41e732 100644 --- a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts +++ b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts @@ -57,6 +57,13 @@ describe("computeUnderlyingType", () => { const values: EnumValueInfo[] = [{ name: "A", number: 0x100000000 }] expect(computeUnderlyingType(values)).toBe("uint64") }) + + it("includes reserved slots when selecting the underlying type", () => { + const values: EnumValueInfo[] = [{ name: "UNSPECIFIED", number: 0 }] + expect(computeUnderlyingType(values, [{ start: 3001, end: 3002 }])).toBe( + "uint16" + ) + }) }) describe("enumLibName", () => { @@ -79,6 +86,7 @@ describe("genEnumDefinition", () => { { name: "ADMIN", number: 1 }, { name: "USER", number: 2 } ], + reservedRanges: [], underlyingType: "uint8" } @@ -121,6 +129,7 @@ describe("genEnumDefinition", () => { name: "Status", fullName: "deep.nested.package.Status", values: [{ name: "OK", number: 0 }], + reservedRanges: [], underlyingType: "uint8" } @@ -134,6 +143,7 @@ describe("genEnumDefinition", () => { name: "Empty", fullName: "Empty", values: [], + reservedRanges: [], underlyingType: "uint8" } @@ -159,6 +169,7 @@ describe("genEnumDefinition", () => { { name: "HIGH", number: 100 }, { name: "MEDIUM", number: 50 } ], + reservedRanges: [], underlyingType: "uint8" } @@ -176,6 +187,7 @@ describe("genEnumDefinition", () => { { name: "RUNNING", number: 1 }, { name: "STARTED", number: 1 } ], + reservedRanges: [], underlyingType: "uint8" } @@ -194,10 +206,36 @@ describe("genEnumDefinition", () => { name: "Big", fullName: "Big", values: [{ name: "VAL", number: 0x10000 }], + reservedRanges: [], underlyingType: "uint24" } const result = genEnumDefinition(desc) expect(result).toContain("type Big is uint24;") }) + + it("decodes reserved numeric slots opaquely without making them valid", () => { + const desc: EnumDescriptor = { + name: "AttestationType", + fullName: "AttestationType", + values: [ + { name: "UNSPECIFIED", number: 0 }, + { name: "ACTIVE", number: 3003 } + ], + reservedRanges: [ + { start: 3001, end: 3001 }, + { start: 4000, end: 4002 } + ], + underlyingType: "uint16" + } + + const result = genEnumDefinition(desc) + expect(result).toContain("return _raw == 0 || _raw == 3003;") + expect(result).toContain( + "if (_raw == 3001) return AttestationType.wrap(uint16(_raw));" + ) + expect(result).toContain( + "if (_raw >= 4000 && _raw <= 4002) return AttestationType.wrap(uint16(_raw));" + ) + }) }) From db54a6e94acdc503a58b49ad17eacbfff6fd8698 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 31 Jul 2026 20:57:15 +0000 Subject: [PATCH 2/9] Rebuild retired staking contract artifact Change-Id: I5089a8c7ef276f6977909595d7616e448e7e751d --- contracts/sysio.msgch/src/sysio.msgch.cpp | 1 + contracts/sysio.msgch/sysio.msgch.wasm | Bin 154374 -> 154578 bytes .../tools/protoc-gen-solidity/src/plugin.ts | 1 + 3 files changed, 2 insertions(+) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index 143195e22a..f28de6e804 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -86,6 +86,7 @@ constexpr size_t ENVELOPE_BASELINE_BYTES = 512; constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; constexpr int32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; +/// Return true when an attestation carries a retired pre-launch staking slot. bool is_retired_staking_attestation(AttestationType type) { const auto value = magic_enum::enum_integer(type); return value == RETIRED_STAKE_ATTESTATION_VALUE || diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 72453c49cab2216b371c9a17409e61d8b8d99067..51749ed2709f793674d6719b0ffe9cdf7a9d381c 100755 GIT binary patch delta 3879 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 fRXre*f>#{lAV3C!&Xk!tb*i0$@)A3U zL_vXFr4h%12ZCxamXrrWzEore5Ch%shZLLnnFmG535`@ zkUt;86I;ayo{;1XH=Py3EQGD1X;Y@SX;wC&<6XgU@jVRpg;O+L>o+9k*kZcYZOoel zAkLuSriA8DgPM(mTqoit*vu7w$O!T=B@K%?VvdfFE$Z}syr_d;_o;mv?C*;yPl^qyP!(q$ zI4&}=f~G7S4f%|Xh2}FGOhd0xufr=45rHS8p>gCRp=Q9;Ta!_TK#`NfpJC8BOC2fR#bJLkoZ;3M^$-xQdtenhDUz z+Y-UHgi_OECZcg0w5llZBhC74Xj2&ryQ2V?5@VqRNaRr;7I9Ydnm%3|VQli74^uU; zLltMMX-$vw;*MBS%|V4C)R%LrTdxz4`Pq#)ro^D(5C|;ORNQRf_s_E*{maud$MXBGu^#$+eGAUG0@1A5bSTXm@f{08JkvRZ^A1(Y;^|}f5^Z#^rc?SLdvrR!;ZVjxV(8K)z>NgCN$pI z;#x)GxWI<0Ce8*#9mdwU))BdJCCzdA`Mr<2KZ;dO7s!9(GreWNaM9L$cl|ywVz`Mg zRwFGxT}drphR2uXpeo%B&U`@;T?e2jOjSUP})tU40D3o+j3$17)ECy2Wy zL{>N*;p@auE^el|=*9B+W=e`JhCJL%HucDp&9r`!?_dW^My($CU<)N~E}%c9#SSD_ zkON_KxnPFc-P=NUX1QOceApm|hn#XOt?_#+OEE0hkw~_wT7OPgxtLe&8mEr+Cix8- z+jw(Ht`g+81Q9EligFKBPi6mA!QHK1aVJzZEe?X}97-N3$s^-(ju3jR-XE3QAEg=D zEir6`t|A9ng{V%?>DJ@Ew1D#TLq9GBx=bnSII(zzj~gJZV~p0PlBeq6RLhm>lrn>I z!lsnyHm!=;RepgL%w&W)i1;;WaFHs3>JB4F|DYcnh)s|eVshjG6QievT?lI{?Pzma0-09O@kLl42*1eD;MCFl=FA zo!*_*{lfy|0){!RIyjf%vmwX52y?|hbK#tMQ?uUY3gn&7Sc@QqUWb@eKiz4k69a2L}-%Q!KLt9wg*ozJ+5x5-ma(#-1j$kuTAu>lDKtp5X& zM2ml&C@U{LNw0^}##sY<@`1idPTNUm?C zxpdz9&kp*D?uqAVP(Uf|rf22qcG^ip9>(v$J9%8vSLr=D@)T8gb_a<#{Y}=sOfC(} zoiEdE^!K)3uDhLv+XmO!_@<|(hcD^Q2hY;@ubj#B54m+eJxXW1kN4wuj{GD~OJ(cN zXd9i~-APZ=dAYumW{XjM_f4Wgp6CS1MbA+;JtR+mQ6%MoPMRjq2vjs|a~BW{br-edKM(h=%@K9)b8C0e~- IH;M}De| Date: Fri, 31 Jul 2026 21:07:29 +0000 Subject: [PATCH 3/9] Address final compliance findings Change-Id: I854a2f145483551ecde57249a92b19265cc2e9ce --- contracts/tests/sysio.dispatch_tests.cpp | 34 ++++++++++++++++--- .../src/generator/index.ts | 8 ++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 2dae3bb57e..fe85e453cd 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -103,6 +103,34 @@ std::vector encode_envelope_with_one_attestation( return out; } +/// Encode a single historical attestation whose numeric enum slot is no +/// longer declared by the current protobuf schema. +std::vector encode_envelope_with_one_raw_attestation( + uint32_t epoch_index, + int32_t raw_att_type, + const std::string& att_data) +{ + sysio::opp::Envelope env; + env.set_epoch_index(epoch_index); + env.set_epoch_envelope_index(1); + env.set_epoch_timestamp(1'775'612'516'983ULL); + + auto* msg = env.add_messages(); + auto* payload = msg->mutable_payload(); + auto* att = payload->add_attestations(); + const auto* field = att->GetDescriptor()->FindFieldByName("type"); + BOOST_REQUIRE(field != nullptr); + att->GetReflection()->SetEnumValue(att, field, raw_att_type); + att->set_data(att_data); + att->set_data_size(static_cast(att_data.size())); + + oracle::finalize_header(*env.mutable_messages(0), {}, 1'775'612'516'983ULL); + + std::vector out(env.ByteSizeLong()); + env.SerializeToArray(out.data(), static_cast(out.size())); + return out; +} + /// Encode an Envelope wrapping N attestations of the same type. Used to fit /// multiple OPERATOR_ACTIONs into a single delivery, since the depot /// deduplicates per-(batch_op, outpost, epoch) — a second `deliver` from @@ -890,11 +918,9 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat bootstrap_for_dispatch(); const auto eth_code = fc::slug_name{"ETH"}.value; - constexpr auto retired_stake_attestation = - static_cast(3001); - auto envelope = encode_envelope_with_one_attestation( + auto envelope = encode_envelope_with_one_raw_attestation( current_epoch(), - retired_stake_attestation, + /*raw_att_type=*/3001, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts index 977a331818..07fc27c5d2 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts @@ -3,5 +3,11 @@ export { generateRuntime } from "./runtime.js" export type { MessageDescriptor, TypeRegistry } from "./message.js" export type { FieldInfo } from "./field.js" export { PROTO_TYPE_MAP, WireType, resolveSolType, fieldTag } from "./type-map.js" -export type { EnumDescriptor, EnumValueInfo, EnumReservedRangeInfo, EnumRegistry, EnumFieldInfo } from "./enum.js" +export type { + EnumDescriptor, + EnumValueInfo, + EnumReservedRangeInfo, + EnumRegistry, + EnumFieldInfo +} from "./enum.js" export { genEnumDefinition, enumLibName, computeUnderlyingType } from "./enum.js" From e394e854294e45d2a1178f7769c4cb43210509ee Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Tue, 4 Aug 2026 16:14:07 +0000 Subject: [PATCH 4/9] Address PR review feedback Change-Id: I54ece9a5e0960db22552d75ab8a85dc6e7a9390f --- contracts/sysio.msgch/src/sysio.msgch.cpp | 12 +- contracts/sysio.msgch/sysio.msgch.wasm | Bin 154578 -> 154788 bytes contracts/tests/sysio.msgch_tests.cpp | 105 ++++++++++++++++++ .../opp/proto/sysio/opp/types/types.proto | 2 +- .../protoc-gen-solidity/src/generator/enum.ts | 13 ++- .../tools/protoc-gen-solidity/src/plugin.ts | 4 +- .../protoc-gen-solidity/tests/enum.test.ts | 39 ++++++- 7 files changed, 160 insertions(+), 15 deletions(-) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index f28de6e804..5674aa509c 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -86,6 +86,10 @@ constexpr size_t ENVELOPE_BASELINE_BYTES = 512; constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; constexpr int32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; +/// Bound legacy-row cleanup work performed by one buildenv action. Remaining +/// tombstones stay READY for a later call but are never envelope candidates. +constexpr size_t MAX_RETIRED_STAKING_PRUNE_PER_BUILD = 32; + /// Return true when an attestation carries a retired pre-launch staking slot. bool is_retired_staking_attestation(AttestationType type) { const auto value = magic_enum::enum_integer(type); @@ -1701,6 +1705,7 @@ void msgch::buildenv(uint64_t chain_code) { std::vector candidate_ids; auto status_idx = atts.get_index<"bystatus"_n>(); + size_t retired_staking_pruned = 0; for (auto it = status_idx.lower_bound( static_cast(AttestationStatus::ATTESTATION_STATUS_READY)); it != status_idx.end() && @@ -1710,7 +1715,12 @@ void msgch::buildenv(uint64_t chain_code) { // terminal-account gate nor an outpost decoder can be blocked by a // protocol value that no longer has a generated enum/message type. if (is_retired_staking_attestation(it->type)) { - it = status_idx.erase(std::move(it)); + if (retired_staking_pruned < MAX_RETIRED_STAKING_PRUNE_PER_BUILD) { + it = status_idx.erase(std::move(it)); + ++retired_staking_pruned; + } else { + ++it; + } continue; } if (it->chain_code != chain_code) { diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 51749ed2709f793674d6719b0ffe9cdf7a9d381c..d2f072c35d4a8bae2f8ea9793b0f1baabb6cbeca 100755 GIT binary patch delta 2604 zcmcImU1%It6rOWuGdp)?Ck{4IY5ch}McE>;f@M>J*xW*`B|;GVc`#HoR^x2a5}QKt zp}WB%D+F0+%B0eVR`Vh#1PmrhAN(m)Awm?K`(a&s~^D?+&?F)y*x-`rVuV^>?x7-_G$R&_Gzt@G!)+RmTvYCFG?Yd;SFltlSL zKlw`NH75zbrR|s+@S*6V_7bVrCZP4BB8`sC#>wAsCiE$edQ?j+ZVKjuqe*MQ!KgQ#BzjvsI8K4 z71nmbmI1*lg(Q}+jpymthpCs`^zOqnP%#J3Ik8vPJ(IBy$H9|7p8fo)H4+fK_lKK6 zW40?bO~)SY#WUQeS6NC)eRcKh69@a0ue!pQBgsG-CY=9X>`VIUDmt4n~2X;^8!jC8_w7|DiRa&}Y;A=n;3#=Q3b8vR1ICjf% zP>#J4cEJ7sC^Rvb*;PU4Y|n0uz>6XvXD+HcSyb2E$n#Z!Gs5WNOP~gq)g2`&3VP=- zDT;X~f~R=4!P$c~7TRE8$=IM7%wR`h0g7c==M#Nm3T|q+a8j0v{cRb+cdhuGQn;1@ zT!dKIUl1&F4q3CyN|)ZGZ7&$DTn2Ev@0;w=jeJrRR%eFlQ*Dbv*YZhb)>fYX0;V?e zJnb8&m+plN^?WV?JsHhXF7tv+-yf&G=Z&91ZXbGeH3$2pwhu%BYLmoM5JRX?a57E< z3zUHrv4d$@-V`A3IRgv{!uce1&qNY_M;r+NGF$c5mOY(>mnibnnYXBVNNN!Ufad_yPTPt|IXrah zyCN6M(4DC6CoM~L?{jO4_U(D^RMd^ptwGe)6;8tnu_5xTje4kOs=0%KWplgmiUQ?ZdFhQZ z^;Y;Ta+zeVo;DnlhPwI$3K7#oqHCC_Pk`DvB(#qc*yWcj$e@&+s4dOVxYc*+w&@#S zJTR>xiA!SE&fdUICp|qu+xh^|kO#B?5GVlCjUKrEn+e+S04jxE^b>u560z26o|&4C z369YuJw-Adnxtn{9;GFTx)49X=nLROixgbS3hAellpZoR%B)j%u{yJ0Qo9ajvdMtE z>bI@0HP;R6QD_WVjGw_4jI%cP3&|ZLy>Nt9QSpx)rSd(lvBvZ?J-->j^v|R8+0706 zQhUSx9-O^x!@edu%K=(nbH!Ab8vZx!SfI@^5Tg1uzbbq`?VF;)f1%u~Q}p4@M%>v~ z$2_2ArLy(avb&8udp1qz{NOMa?B|AA6zMx}gT@_~+)-hKN5DA~z&6cI+Gaaf+6n!8 tM(Av3y}9#Vw delta 2395 zcmcIlU1$_n6rOWucjnH_ZX9a*n-=bjv}`D;6w4~1w7G({q9DaU9~2*IwQ)AFY9jS1 zyQQJzArD(rZY)07B9uN90u{C3Lu& z&bjxT@0|0Ub1!@o{Bk*%JLQQ>GasGtE}!ve4Rw9&N6|(^}18vQM#6jmx zyE|;l8Zg#<{#>!+{Q06i&u*6r`DubAERj9m?=E|0>Nq`Z?!HUw>g;hYQi8Wn`>D_C zl%L94Ra}gerzDPEB93Dvmh*7WyDoUC+|9tWQx({zM87*1Z5AvkWvSfi*mvG~CE^{D zU$5eocf=WgeQW+aNE_?c-Z?J~YPxqU4K-!J{nOd67oFW@QbPYBGn~$~Qqx3=6e&DA z`}92TQc^F@pWU>hPlc)&?@g?ymQCG*papB7^}u!qHuJ?H8Z63qpOR+r5T!Z1S?>)F zmZNNWn_UMf^R>*X;@Fj@HUwXb6KRM@@BsQh_@OfT zFRVVW2dWT%P6>)p5e$CCRk-3S4b3O-(fYa#uZc3$(o!uAs$xNcM0zU5`{HL|d-M9f&&WdK*P&3rII$uNpt5e;7x z>_wKYy5(mT;dC=zPjv`|p7ygmj{AM{8#i3uH|E|5z5OKc>e)gDX0nTmg*fyDfpUP0|Mx(H^j3!YLr-9={ zKtCR)fqh_WhQ#~27nD}vb`DF)GPOg&k$bV&m{BpgyopDT-7HrY?vMrQ`lC0 z6kIN#X+xZVmOt_$gHmp$?t6wu8-`_d)5Yjy-ZQ3K7?xEr~%S`uRW+NoS0w7=rxk&u_Lrg^VH+H%W!OcqNPn`f{E>x`SA(IsCN zX8e7cC*KSmrP>p|jfQiIJgrvHe0P+-T6+Or@4NtiK&L(Q0$di|;{Z2STVfMGjsNX9 zF3{>2h(n}uz`5gb3z~b!sPtbbx9K>2x;BVMyTxY#t@{>#rGq?MRzqlC;kbHMBiP25 zVnvu;Fu7GeK{){snv$jzjdb().get_index(); + char primary_key[chain::kv_pri_key_size]; + chain::kv_encode_be64(primary_key, id); + const auto kv_itr = kv_idx.find(boost::make_tuple( + MSGCH_ACCOUNT, table_id, + std::string_view(primary_key, chain::kv_pri_key_size))); + BOOST_REQUIRE(kv_itr != kv_idx.end()); + + auto row = msgch_abi.binary_to_variant( + "attestation_entry", + std::vector(kv_itr->value.data(), kv_itr->value.data() + kv_itr->value.size()), + abi_serializer::create_yield_function(abi_serializer_max_time)); + fc::mutable_variant_object updated(row.get_object()); + updated.set("chain_code", chain_code); + const auto encoded = msgch_abi.variant_to_binary( + "attestation_entry", updated, + abi_serializer::create_yield_function(abi_serializer_max_time)); + + auto& db = const_cast(control->db()); + db.modify(*kv_itr, [&](auto& object) { + object.value.assign(encoded.data(), encoded.size()); + }); + } + /// Count READY-status attestations for `chain_code` by probing the /// table by-id. Avoids needing an ABI binding for the secondary index. uint32_t count_ready_attestations(uint64_t chain_code, uint64_t scan_until) { @@ -363,6 +392,7 @@ constexpr auto SWAP_REMIT_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_SWA constexpr auto UNCOVERED_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_STAKING_REWARD; /// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. constexpr uint32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; +constexpr uint32_t RETIRED_STAKING_PRUNE_LIMIT = 32; /// Decode the emitted OPP envelope and count attestations in its single message. uint32_t emitted_attestation_count(const fc::variant& emitted_row) { @@ -431,6 +461,81 @@ BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_retired_staking_rows, BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); } FC_LOG_AND_RETHROW() } +/// A retired row at the end of the READY index is safe to erase. In +/// particular, `erase` returning the index end iterator must not be +/// incremented or dereferenced by the collection loop. +BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_last_retired_staking_row, + sysio_msgch_envlog_tester) { try { + bootstrap_epoch_config(/*retention=*/200); + register_outpost(opp::types::CHAIN_KIND_EVM, 31337); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); + BOOST_REQUIRE_EQUAL(1u, count_ready_attestations(ETH_OUTPOST_ID, 4)); + + BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 4)); + BOOST_REQUIRE(find_outbound_envelope().is_null()); +} FC_LOG_AND_RETHROW() } + +/// Tombstones are removed before destination-specific collection. Current +/// queueout correctly refuses to create an invalid legacy SVM row, so queue +/// through the EVM-compatible path and retarget the stored row to recreate +/// the pre-upgrade SVM state. A Solana build must remove it before the dynamic +/// account estimator sees the unknown type. +BOOST_FIXTURE_TEST_CASE(buildenv_tombstone_cleanup_precedes_svm_estimation, + sysio_msgch_envlog_tester) { try { + bootstrap_epoch_config(/*retention=*/200); + register_outpost(opp::types::CHAIN_KIND_EVM, 31337); + register_outpost(opp::types::CHAIN_KIND_SVM, 31338); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); + retarget_attestation_for_upgrade_test(/*id=*/1, /*chain_code=*/SOL_OUTPOST_ID); + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/SOL_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); + + BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/SOL_OUTPOST_ID)); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(SOL_OUTPOST_ID, 8)); + const auto emitted = find_outbound_envelope(); + BOOST_REQUIRE(!emitted.is_null()); + BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); +} FC_LOG_AND_RETHROW() } + +/// Cleanup is capped per action so an upgrade cannot turn an epoch advance +/// into an unbounded erase sweep. Rows beyond the cap remain READY but are +/// skipped as candidates, while an active row behind them still emits. +BOOST_FIXTURE_TEST_CASE(buildenv_bounds_retired_staking_row_cleanup, + sysio_msgch_envlog_tester) { try { + bootstrap_epoch_config(/*retention=*/200); + register_outpost(opp::types::CHAIN_KIND_EVM, 31337); + produce_blocks(); + + for (uint32_t i = 0; i < RETIRED_STAKING_PRUNE_LIMIT + 1; ++i) { + BOOST_REQUIRE_EQUAL(success(), + queueout_with_data( + /*chain_code=*/ETH_OUTPOST_ID, + RETIRED_STAKE_ATTESTATION_VALUE, + std::vector{static_cast(i)})); + } + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); + + BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); + produce_blocks(); + + BOOST_REQUIRE_EQUAL(1u, count_ready_attestations(ETH_OUTPOST_ID, 64)); + const auto emitted = find_outbound_envelope(); + BOOST_REQUIRE(!emitted.is_null()); + BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); +} FC_LOG_AND_RETHROW() } + /// Eviction at the boundary. Set `retention=2` and one outpost → /// `cap = 1*2*2 = 4`. After 5 buildenv rounds (5 rows inserted), the /// oldest full epoch (`per_epoch = 1*2 = 2` rows) gets evicted; final diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index a3b8e93fd5..1483626c6b 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -229,7 +229,7 @@ message ReserveAmount { // --------------------------------------------------------------------------- enum AttestationType { - reserved 3001, 3002; + reserved 3001, 3002, 60929, 60933, 60935 to 60938, 60946, 60948, 60954, 60957; reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; ATTESTATION_TYPE_UNSPECIFIED = 0; diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts index 52c12290fa..a1e745dae5 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts @@ -42,12 +42,8 @@ export interface EnumFieldInfo { /** * Compute the smallest unsigned integer type that can hold all enum values. */ -export function computeUnderlyingType( - values: EnumValueInfo[], - reservedRanges: EnumReservedRangeInfo[] = [] -): string { - const maxReserved = reservedRanges.map(range => range.end) - const maxVal = Math.max(0, ...values.map(v => v.number), ...maxReserved) +export function computeUnderlyingType(values: EnumValueInfo[]): string { + const maxVal = Math.max(0, ...values.map(v => v.number)) if (maxVal <= 0xff) return "uint8" if (maxVal <= 0xffff) return "uint16" if (maxVal <= 0xffffff) return "uint24" @@ -111,6 +107,11 @@ export function genEnumDefinition(desc: EnumDescriptor): string { for (const val of uniqueValues) { lines.push(` if (_raw == ${val.number}) return ${val.name};`) } + if (desc.reservedRanges.length > 0) { + lines.push( + ` if (_raw > type(${underlying}).max) revert InvalidEnumValue(_raw);` + ) + } for (const range of desc.reservedRanges) { const condition = range.end === range.start diff --git a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts index a4836f0a3d..5f6047fc97 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts @@ -248,7 +248,7 @@ function buildEnumRegistry(protoFiles: any[]): EnumRegistry { fullName, values, reservedRanges, - underlyingType: computeUnderlyingType(values, reservedRanges) + underlyingType: computeUnderlyingType(values) }) } } @@ -346,7 +346,7 @@ function extractEnums(protoFile: any, packageName: string): EnumDescriptor[] { fullName, values, reservedRanges, - underlyingType: computeUnderlyingType(values, reservedRanges) + underlyingType: computeUnderlyingType(values) }) } } diff --git a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts index 569a41e732..827a2bb17b 100644 --- a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts +++ b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts @@ -58,11 +58,12 @@ describe("computeUnderlyingType", () => { expect(computeUnderlyingType(values)).toBe("uint64") }) - it("includes reserved slots when selecting the underlying type", () => { - const values: EnumValueInfo[] = [{ name: "UNSPECIFIED", number: 0 }] - expect(computeUnderlyingType(values, [{ start: 3001, end: 3002 }])).toBe( - "uint16" - ) + it("does not let an open-ended reservation widen the underlying type", () => { + const values: EnumValueInfo[] = [ + { name: "UNSPECIFIED", number: 0 }, + { name: "ACTIVE", number: 255 } + ] + expect(computeUnderlyingType(values)).toBe("uint8") }) }) @@ -237,5 +238,33 @@ describe("genEnumDefinition", () => { expect(result).toContain( "if (_raw >= 4000 && _raw <= 4002) return AttestationType.wrap(uint16(_raw));" ) + expect(result).toContain( + "if (_raw > type(uint16).max) revert InvalidEnumValue(_raw);" + ) + expect(result.indexOf("type(uint16).max")).toBeLessThan( + result.indexOf("AttestationType.wrap(uint16(_raw))") + ) + }) + + it("rejects an unrepresentable reserved value before narrowing it", () => { + const desc: EnumDescriptor = { + name: "Small", + fullName: "Small", + values: [ + { name: "UNSPECIFIED", number: 0 }, + { name: "ACTIVE", number: 255 } + ], + reservedRanges: [{ start: 5, end: 0x7fffffff }], + underlyingType: "uint8" + } + + const result = genEnumDefinition(desc) + expect(result).toContain("type Small is uint8;") + expect(result).toContain( + "if (_raw > type(uint8).max) revert InvalidEnumValue(_raw);" + ) + expect(result.indexOf("type(uint8).max")).toBeLessThan( + result.indexOf("Small.wrap(uint8(_raw))") + ) }) }) From 5f501d609a50209cf1c96ad44eb2acde5e98208a Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Tue, 4 Aug 2026 16:26:29 +0000 Subject: [PATCH 5/9] Address final test style feedback Change-Id: I07bc8da01d67db07ef97f2349fe10d6d9eaffd53 --- contracts/tests/sysio.dispatch_tests.cpp | 5 ++++- contracts/tests/sysio.msgch_tests.cpp | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index ae6bd1a5dc..4d031d186c 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -167,6 +167,9 @@ std::vector encode_envelope_with_attestations( /// mirror, same as the outbound packing tests in sysio.msgch_tests.cpp. constexpr size_t MAX_ENVELOPE_BYTES = 65'536; +/// Historical STAKE protobuf wire value used to verify dispatch drops retired types. +constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; + /// Encode a decodable envelope whose serialised size is EXACTLY `target_bytes`, padded with a /// single challenge-response attestation (dispatch drops it with no value-bearing effect). Probe /// once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with @@ -1001,7 +1004,7 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_raw_attestation( current_epoch(), - /*raw_att_type=*/3001, + RETIRED_STAKE_ATTESTATION_VALUE, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); diff --git a/contracts/tests/sysio.msgch_tests.cpp b/contracts/tests/sysio.msgch_tests.cpp index 0c4baffca8..0c4d0f35c2 100644 --- a/contracts/tests/sysio.msgch_tests.cpp +++ b/contracts/tests/sysio.msgch_tests.cpp @@ -202,6 +202,9 @@ class sysio_msgch_envlog_tester : public tester { static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; + /// Attestation queue table used by historical-row upgrade fixtures. + static constexpr auto ATTESTATIONS_TABLE = "attestations"_n; + sysio_msgch_envlog_tester() { produce_blocks(2); create_accounts({ MSGCH_ACCOUNT, EPOCH_ACCOUNT, CHALG_ACCOUNT, CHAINS_ACCOUNT }); @@ -298,7 +301,7 @@ class sysio_msgch_envlog_tester : public tester { /// destination chain. The indexed fields (id, status, type, epoch) stay /// unchanged, so the existing secondary-index entries remain valid. void retarget_attestation_for_upgrade_test(uint64_t id, uint64_t chain_code) { - const auto table_id = chain::compute_table_id("attestations"_n.value); + const auto table_id = chain::compute_table_id(ATTESTATIONS_TABLE.value); const auto& kv_idx = control->db().get_index(); char primary_key[chain::kv_pri_key_size]; chain::kv_encode_be64(primary_key, id); @@ -328,7 +331,7 @@ class sysio_msgch_envlog_tester : public tester { uint32_t count_ready_attestations(uint64_t chain_code, uint64_t scan_until) { uint32_t n = 0; for (uint64_t id = 0; id < scan_until; ++id) { - auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, "attestations"_n, id); + auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, ATTESTATIONS_TABLE, id); if (data.empty()) continue; auto row = msgch_abi.binary_to_variant( "attestation_entry", data, @@ -392,6 +395,7 @@ constexpr auto SWAP_REMIT_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_SWA constexpr auto UNCOVERED_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_STAKING_REWARD; /// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. constexpr uint32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; +/// Maximum retired READY rows pruned during one deterministic build call. constexpr uint32_t RETIRED_STAKING_PRUNE_LIMIT = 32; /// Decode the emitted OPP envelope and count attestations in its single message. From 90e92f54d26d46a4252e73fc18e14c63c4dd63f5 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 6 Aug 2026 18:45:23 +0000 Subject: [PATCH 6/9] Address PR review feedback Change-Id: I07c0b476f48297807dc3a1c45fcff926e3a397bf --- libraries/opp/proto/sysio/opp/types/types.proto | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index c274c91520..9a656e4ae5 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -229,7 +229,7 @@ message ReserveAmount { // --------------------------------------------------------------------------- enum AttestationType { - reserved 3001, 3002, 60929, 60933, 60935 to 60938, 60946, 60948, 60954, 60957; + reserved 3001, 3002, 60929, 60931, 60933, 60935 to 60942, 60946, 60948, 60954, 60957; reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; ATTESTATION_TYPE_UNSPECIFIED = 0; @@ -247,6 +247,7 @@ enum AttestationType { // 60929 (0xEE01) was ATTESTATION_TYPE_NATIVE_YIELD_REWARD — removed; do not reuse. // DEPRECATED — pre-launch only, do not use in new code. ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE = 60930; + // 60931 was ATTESTATION_TYPE_OPERATOR_REG_DEREG — replaced by OPERATOR_ACTION; do not reuse. ATTESTATION_TYPE_CHALLENGE_RESPONSE = 60932; // 60933 was standalone SLASH_OPERATOR — removed; SLASH is now an OperatorAction sub-type. ATTESTATION_TYPE_SWAP_REQUEST = 60934; @@ -254,6 +255,10 @@ enum AttestationType { // 60936 was ATTESTATION_TYPE_UNDERWRITE_CONFIRM — removed; do not reuse. // 60937 was ATTESTATION_TYPE_UNDERWRITE_REJECT — removed; do not reuse. // 60938 was ATTESTATION_TYPE_UNDERWRITE_UNLOCK — removed; do not reuse. + // 60939 was ATTESTATION_TYPE_CHALLENGE_REQUEST — renumbered to 60945; do not reuse. + // 60940 was ATTESTATION_TYPE_EPOCH_SYNC — renumbered to 60946, then removed; do not reuse. + // 60941 was ATTESTATION_TYPE_ROSTER_UPDATE — removed; do not reuse. + // 60942 was ATTESTATION_TYPE_REMIT_CONFIRM — renumbered to 60948, then removed; do not reuse. ATTESTATION_TYPE_SWAP_REMIT = 60944; ATTESTATION_TYPE_CHALLENGE_REQUEST = 60945; // 60946 was ATTESTATION_TYPE_EPOCH_SYNC — removed; do not reuse. From f74fd17f120e1af5e9a97a69fca3022ee5b65cb0 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 7 Aug 2026 14:54:29 +0000 Subject: [PATCH 7/9] Address PR review feedback Change-Id: I819c3799420430cdb24cc17dcb988f35ed0e70f6 --- contracts/sysio.msgch/src/sysio.msgch.cpp | 34 +++++++------ contracts/sysio.msgch/sysio.msgch.wasm | Bin 155309 -> 155412 bytes contracts/tests/sysio.msgch_tests.cpp | 46 +++++++++++------- .../sysio/opp/attestations/attestations.proto | 4 ++ .../opp/proto/sysio/opp/types/types.proto | 10 +++- 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index a4af9a3ffe..111be49ea4 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -82,21 +83,24 @@ constexpr size_t ATTESTATION_OVERHEAD_BYTES = 24; /// + payload preamble, and a safety margin for `zpp::bits` length prefixes. constexpr size_t ENVELOPE_BASELINE_BYTES = 512; -/// Retired pre-launch attestation wire slots. They remain recognizable here -/// only so an upgraded contract can tombstone READY rows queued by the prior -/// implementation instead of forwarding them or blocking envelope creation. -constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; -constexpr int32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; +/// Retired attestation wire slots from the numeric `reserved` declarations in +/// `libraries/opp/proto/sysio/opp/types/types.proto`. They remain recognizable +/// here only so an upgraded contract can tombstone READY rows queued by prior +/// implementations instead of forwarding them or blocking envelope creation. +constexpr std::array RETIRED_ATTESTATION_VALUES{ + 3001, 3002, 60929, 60931, 60933, 60935, 60936, 60937, 60938, + 60939, 60940, 60941, 60942, 60946, 60948, 60954, 60957 +}; /// Bound legacy-row cleanup work performed by one buildenv action. Remaining /// tombstones stay READY for a later call but are never envelope candidates. -constexpr size_t MAX_RETIRED_STAKING_PRUNE_PER_BUILD = 32; +constexpr size_t MAX_RETIRED_ATTESTATION_PRUNE_PER_BUILD = 32; -/// Return true when an attestation carries a retired pre-launch staking slot. -bool is_retired_staking_attestation(AttestationType type) { +/// Return true when an attestation carries any retired protobuf wire slot. +bool is_retired_attestation(AttestationType type) { const auto value = magic_enum::enum_integer(type); - return value == RETIRED_STAKE_ATTESTATION_VALUE || - value == RETIRED_UNSTAKE_ATTESTATION_VALUE; + return std::find(RETIRED_ATTESTATION_VALUES.begin(), RETIRED_ATTESTATION_VALUES.end(), value) != + RETIRED_ATTESTATION_VALUES.end(); } using namespace sysio::msgch_svm_terminal_budget; @@ -1721,19 +1725,19 @@ void msgch::buildenv(uint64_t chain_code) { std::vector candidate_ids; auto status_idx = atts.get_index<"bystatus"_n>(); - size_t retired_staking_pruned = 0; + size_t retired_pruned = 0; for (auto it = status_idx.lower_bound( static_cast(AttestationStatus::ATTESTATION_STATUS_READY)); it != status_idx.end() && it->status == AttestationStatus::ATTESTATION_STATUS_READY; ) { - // Upgrade tombstone: legacy builds could persist STAKE / UNSTAKE rows. + // Upgrade tombstone: legacy builds could persist retired protocol rows. // Erase them before destination-specific estimation so neither the SVM // terminal-account gate nor an outpost decoder can be blocked by a // protocol value that no longer has a generated enum/message type. - if (is_retired_staking_attestation(it->type)) { - if (retired_staking_pruned < MAX_RETIRED_STAKING_PRUNE_PER_BUILD) { + if (is_retired_attestation(it->type)) { + if (retired_pruned < MAX_RETIRED_ATTESTATION_PRUNE_PER_BUILD) { it = status_idx.erase(std::move(it)); - ++retired_staking_pruned; + ++retired_pruned; } else { ++it; } diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index dac36e9c4777e5dfd72071975e5e192b3e8e9522..304c28c858ccb762a06e841038813aa5f7d6c3ca 100755 GIT binary patch delta 519 zcmYL@K}Zx~6vy9tGrKe6?6jN2mXh%89Bd61qJz+>`N~yD@F00HWChW~oZJvQbeKFu z3lc0$Fo%vp1sN>tVJcF(=^;o3L1Eq5gNG1N5v+)!-tGz=e((FgkKg0HcPqw=Rik3X zjh_*i9>^yRKOOfbPRqu&qC)Dr`4AP4903+cNScysmi7w*WNO|<*QS2%+P}4etq5>s z(v`DC8~UkM;6ak9IRV4Xan9(n@1MqpF7EJ@Xh{-o^Skfg(|Yk+wWyVncvAh9ewm}C z$PWev@k_Nn@IHf$V0IBN({jC9nR&V;$`Pc{_9J_@@jmlB5 z?%>s)wk|LSpQ+_?DsgJNDDjVtYr)?s{Bcu$@-n)hEd7pW406a(9HGm@J#e!G!WguW zTh+C73le0SLeGb>X*}-4@8Q`aOdEI@CKhn2laMv?9Zn!^LjIBQQ`~=Rc%q$E7THHm9%=JtT3Jkmqjtuu`#4t~ZJn}3PT)@8bXZ}M~(H^z?5 z_gr=;O3k^)Ai&5CG?G!1`2(X8vr~N#$W+G{cQM|T)0;>hGI)E&oi9ofCj*Nem7?(`{pUn83 zALI=Urax?s$1}1V8NIh_q%yu2V%)S{qJmM3lX1m%{YJ)QE9G8EUVo+%9;PE~+nb}AqL`*D#xcET*`UwB F000u^erEsx diff --git a/contracts/tests/sysio.msgch_tests.cpp b/contracts/tests/sysio.msgch_tests.cpp index 3ca3d9c62e..30e89d1b77 100644 --- a/contracts/tests/sysio.msgch_tests.cpp +++ b/contracts/tests/sysio.msgch_tests.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "contracts.hpp" #include @@ -397,8 +399,17 @@ constexpr auto UNCOVERED_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_ST constexpr uint32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; /// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. constexpr uint32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; +/// Former OPERATOR_REG_DEREG slot used to cover a non-staking retirement. +constexpr uint32_t RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE = 60931; +/// Exhaustive retired AttestationType slots mirrored from the protobuf schema. +constexpr std::array RETIRED_ATTESTATION_VALUES{ + RETIRED_STAKE_ATTESTATION_VALUE, RETIRED_UNSTAKE_ATTESTATION_VALUE, + 60929, RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE, 60933, + 60935, 60936, 60937, 60938, 60939, 60940, 60941, 60942, + 60946, 60948, 60954, 60957 +}; /// Maximum retired READY rows pruned during one deterministic build call. -constexpr uint32_t RETIRED_STAKING_PRUNE_LIMIT = 32; +constexpr uint32_t RETIRED_ATTESTATION_PRUNE_LIMIT = 32; /// Decode the emitted OPP envelope and count attestations in its single message. uint32_t emitted_attestation_count(const fc::variant& emitted_row) { @@ -442,28 +453,29 @@ BOOST_FIXTURE_TEST_CASE(buildenv_writes_envlog_row, sysio_msgch_envlog_tester) { BOOST_REQUIRE_EQUAL(31337u, row["endpoints"]["end"]["id"]["value"].as_uint64()); } FC_LOG_AND_RETHROW() } -/// READY rows written by a pre-upgrade contract with a retired staking wire -/// value are tombstoned before destination-specific envelope construction. +/// READY rows written by pre-upgrade contracts with any retired wire value +/// are tombstoned before destination-specific envelope construction. /// Active rows behind the tombstone still emit normally, so one legacy row /// cannot strand the queue or reach an outpost decoder. -BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_retired_staking_rows, +BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_all_retired_attestation_rows, sysio_msgch_envlog_tester) { try { bootstrap_epoch_config(/*retention=*/200); register_outpost(opp::types::CHAIN_KIND_EVM, 31337); produce_blocks(); - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_UNSTAKE_ATTESTATION_VALUE)); + for (const auto retired_value : RETIRED_ATTESTATION_VALUES) { + BOOST_REQUIRE_EQUAL(success(), + queueout(/*chain_code=*/ETH_OUTPOST_ID, retired_value)); + } BOOST_REQUIRE_EQUAL(success(), queueout(/*chain_code=*/ETH_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); - BOOST_REQUIRE_EQUAL(3u, count_ready_attestations(ETH_OUTPOST_ID, 8)); + BOOST_REQUIRE_EQUAL(RETIRED_ATTESTATION_VALUES.size() + 1, + count_ready_attestations(ETH_OUTPOST_ID, 32)); BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); produce_blocks(); - BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 8)); + BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 32)); const auto emitted = find_outbound_envelope(); BOOST_REQUIRE(!emitted.is_null()); BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); @@ -472,7 +484,7 @@ BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_retired_staking_rows, /// A retired row at the end of the READY index is safe to erase. In /// particular, `erase` returning the index end iterator must not be /// incremented or dereferenced by the collection loop. -BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_last_retired_staking_row, +BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_last_retired_attestation_row, sysio_msgch_envlog_tester) { try { bootstrap_epoch_config(/*retention=*/200); register_outpost(opp::types::CHAIN_KIND_EVM, 31337); @@ -491,9 +503,9 @@ BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_last_retired_staking_row, /// Tombstones are removed before destination-specific collection. Current /// queueout correctly refuses to create an invalid legacy SVM row, so queue -/// through the EVM-compatible path and retarget the stored row to recreate -/// the pre-upgrade SVM state. A Solana build must remove it before the dynamic -/// account estimator sees the unknown type. +/// through the EVM-compatible path and retarget a non-staking retired row to +/// recreate the pre-upgrade SVM state. A Solana build must remove it before +/// the dynamic account estimator sees the unknown type. BOOST_FIXTURE_TEST_CASE(buildenv_tombstone_cleanup_precedes_svm_estimation, sysio_msgch_envlog_tester) { try { bootstrap_epoch_config(/*retention=*/200); @@ -502,7 +514,7 @@ BOOST_FIXTURE_TEST_CASE(buildenv_tombstone_cleanup_precedes_svm_estimation, produce_blocks(); BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); + queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE)); retarget_attestation_for_upgrade_test(/*id=*/1, /*chain_code=*/SOL_OUTPOST_ID); BOOST_REQUIRE_EQUAL(success(), queueout(/*chain_code=*/SOL_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); @@ -519,13 +531,13 @@ BOOST_FIXTURE_TEST_CASE(buildenv_tombstone_cleanup_precedes_svm_estimation, /// Cleanup is capped per action so an upgrade cannot turn an epoch advance /// into an unbounded erase sweep. Rows beyond the cap remain READY but are /// skipped as candidates, while an active row behind them still emits. -BOOST_FIXTURE_TEST_CASE(buildenv_bounds_retired_staking_row_cleanup, +BOOST_FIXTURE_TEST_CASE(buildenv_bounds_retired_attestation_row_cleanup, sysio_msgch_envlog_tester) { try { bootstrap_epoch_config(/*retention=*/200); register_outpost(opp::types::CHAIN_KIND_EVM, 31337); produce_blocks(); - for (uint32_t i = 0; i < RETIRED_STAKING_PRUNE_LIMIT + 1; ++i) { + for (uint32_t i = 0; i < RETIRED_ATTESTATION_PRUNE_LIMIT + 1; ++i) { BOOST_REQUIRE_EQUAL(success(), queueout_with_data( /*chain_code=*/ETH_OUTPOST_ID, diff --git a/libraries/opp/proto/sysio/opp/attestations/attestations.proto b/libraries/opp/proto/sysio/opp/attestations/attestations.proto index 97ccaac9d4..181d005a29 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -74,11 +74,15 @@ message WireTokenPurchase { message OperatorAction { enum ActionType { + reserved 5; + reserved "ACTION_TYPE_WITHDRAW_CONFIRMED"; + ACTION_TYPE_UNKNOWN = 0; ACTION_TYPE_DEPOSIT_REQUEST = 1; ACTION_TYPE_WITHDRAW_REQUEST = 2; ACTION_TYPE_WITHDRAW_REMIT = 3; ACTION_TYPE_SLASH = 4; + // Slot 5 was ACTION_TYPE_WITHDRAW_CONFIRMED; the confirmation stage was removed. }; ActionType action_type = 1; // Operator's outpost-chain identity (full chain public key). diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index 9a656e4ae5..3fceed1fd3 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -13,12 +13,15 @@ option cc_enable_arenas = true; // --------------------------------------------------------------------------- enum ChainKind { + reserved 4; + reserved "CHAIN_KIND_SUI"; + CHAIN_KIND_UNKNOWN = 0; CHAIN_KIND_WIRE = 1; // The WIRE depot itself (singleton; Chain.code = "WIRE") CHAIN_KIND_EVM = 2; // All EVM-compatible chains (Ethereum, Polygon, BSC, Arbitrum, Optimism, Base, …) CHAIN_KIND_SVM = 3; // Solana Virtual Machine chains (Solana mainnet, Eclipse, …) - // Slots 4+ reserved. Slot 4 was previously CHAIN_KIND_SUI; do not reuse. + // Slot 4 was previously CHAIN_KIND_SUI; do not reuse. } // Chain instance identifier — used by `Envelope.Endpoints` to identify @@ -102,6 +105,8 @@ enum UnderwriteRequestStatus { // --------------------------------------------------------------------------- enum TokenKind { + reserved 256 to 259, 496, 512, 752; + TOKEN_KIND_UNKNOWN = 0; TOKEN_KIND_NATIVE = 1; // chain-native asset (ETH on EVM, SOL on SVM, WIRE on WIRE) TOKEN_KIND_ERC20 = 2; @@ -371,10 +376,13 @@ enum AttestationStatus { } enum UnderwriteStatus { + reserved 4; + UNDERWRITE_STATUS_INTENT_CREATED = 0; UNDERWRITE_STATUS_INTENT_SUBMITTED = 1; UNDERWRITE_STATUS_INTENT_CONFIRMED = 2; UNDERWRITE_STATUS_READY = 3; + // Slot 4 was UNDERWRITE_STATUS_SLASHED before that state moved to 10; do not reuse. UNDERWRITE_STATUS_RELEASED = 5; UNDERWRITE_STATUS_SLASHED = 10; // Candidate-specific, pre-settlement invalidity in the underwriter race (bad From c2c10fa70afd57519ebe6cbe68fce7aa6e646600 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Sun, 9 Aug 2026 00:40:51 +0000 Subject: [PATCH 8/9] Remove pre-release attestation compatibility paths Change-Id: If79c1c0ca74d9ae52145860e224ef8d5882c3059 --- contracts/sysio.msgch/src/sysio.msgch.cpp | 51 +----- contracts/sysio.msgch/sysio.msgch.wasm | Bin 155412 -> 154895 bytes .../sysio.opp.common/opp_table_types.hpp | 3 - contracts/tests/sysio.dispatch_tests.cpp | 38 +---- contracts/tests/sysio.msgch_tests.cpp | 154 +----------------- .../sysio/opp/attestations/attestations.proto | 10 +- .../opp/proto/sysio/opp/types/types.proto | 22 +-- .../protoc-gen-solidity/src/generator/enum.ts | 23 +-- .../src/generator/index.ts | 8 +- .../tools/protoc-gen-solidity/src/plugin.ts | 18 -- .../protoc-gen-solidity/tests/enum.test.ts | 67 -------- 11 files changed, 15 insertions(+), 379 deletions(-) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index 111be49ea4..ecd7ec7738 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -14,8 +14,6 @@ #include #include #include -#include -#include #include namespace sysio { @@ -83,26 +81,6 @@ constexpr size_t ATTESTATION_OVERHEAD_BYTES = 24; /// + payload preamble, and a safety margin for `zpp::bits` length prefixes. constexpr size_t ENVELOPE_BASELINE_BYTES = 512; -/// Retired attestation wire slots from the numeric `reserved` declarations in -/// `libraries/opp/proto/sysio/opp/types/types.proto`. They remain recognizable -/// here only so an upgraded contract can tombstone READY rows queued by prior -/// implementations instead of forwarding them or blocking envelope creation. -constexpr std::array RETIRED_ATTESTATION_VALUES{ - 3001, 3002, 60929, 60931, 60933, 60935, 60936, 60937, 60938, - 60939, 60940, 60941, 60942, 60946, 60948, 60954, 60957 -}; - -/// Bound legacy-row cleanup work performed by one buildenv action. Remaining -/// tombstones stay READY for a later call but are never envelope candidates. -constexpr size_t MAX_RETIRED_ATTESTATION_PRUNE_PER_BUILD = 32; - -/// Return true when an attestation carries any retired protobuf wire slot. -bool is_retired_attestation(AttestationType type) { - const auto value = magic_enum::enum_integer(type); - return std::find(RETIRED_ATTESTATION_VALUES.begin(), RETIRED_ATTESTATION_VALUES.end(), value) != - RETIRED_ATTESTATION_VALUES.end(); -} - using namespace sysio::msgch_svm_terminal_budget; static_assert(svm_hard_dynamic_account_budget() == 16, @@ -821,8 +799,7 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) /// in `evalcons` after a consensus envelope has been unpacked. Dispatch is /// best-effort — silently no-ops on unknown / out-of-scope types so the /// inbound stream can keep flowing even when the depot hasn't yet wired up -/// every active handler (for example, STAKE_UPDATE from the separate staking -/// track). Retired STAKE / UNSTAKE wire values also land on this no-op path. +/// every handler (for example, STAKE_UPDATE from the separate staking track). void dispatch_attestation(name self, uint64_t attestation_id, AttestationType type, const std::vector& data, @@ -949,8 +926,8 @@ void dispatch_attestation(name self, uint64_t attestation_id, case AttestationType::ATTESTATION_TYPE_STAKE_UPDATE: case AttestationType::ATTESTATION_TYPE_STAKE_RESULT: - // Post-launch validator-staking lifecycle; depot-side handlers land - // alongside liqEth / liqsol-token wiring. + // Validator-staking lifecycle; depot-side handlers land in a later + // task alongside liqEth / liqsol-token wiring. break; // Outbound-only types (depot emits these, never receives them inbound) @@ -1725,28 +1702,11 @@ void msgch::buildenv(uint64_t chain_code) { std::vector candidate_ids; auto status_idx = atts.get_index<"bystatus"_n>(); - size_t retired_pruned = 0; for (auto it = status_idx.lower_bound( static_cast(AttestationStatus::ATTESTATION_STATUS_READY)); it != status_idx.end() && - it->status == AttestationStatus::ATTESTATION_STATUS_READY; ) { - // Upgrade tombstone: legacy builds could persist retired protocol rows. - // Erase them before destination-specific estimation so neither the SVM - // terminal-account gate nor an outpost decoder can be blocked by a - // protocol value that no longer has a generated enum/message type. - if (is_retired_attestation(it->type)) { - if (retired_pruned < MAX_RETIRED_ATTESTATION_PRUNE_PER_BUILD) { - it = status_idx.erase(std::move(it)); - ++retired_pruned; - } else { - ++it; - } - continue; - } - if (it->chain_code != chain_code) { - ++it; - continue; - } + it->status == AttestationStatus::ATTESTATION_STATUS_READY; ++it) { + if (it->chain_code != chain_code) continue; opp::AttestationEntry entry; entry.type = it->type; @@ -1754,7 +1714,6 @@ void msgch::buildenv(uint64_t chain_code) { entry.data = it->data; candidate_entries.push_back(std::move(entry)); candidate_ids.push_back(it->id); - ++it; } if (candidate_entries.empty()) return; diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 304c28c858ccb762a06e841038813aa5f7d6c3ca..c563e90bd75836b56249b9ee0db966fb38d07f8a 100755 GIT binary patch delta 5125 zcma)A4RBP|6@KU5{dxP7MsEke9ScBqp+tAsRwoVk#6eD7PY-ka%Mh%wgmeMx0PVIN@-3=SX zR)&4=zH`qz_nhzi=Jh-k+p#y6|5+#<3x}nY)pfH)Lu0I3R^xw+M4cc>)l$S78yW>j zQsiC@z3*Um_UssuXsoWTZmg@z6~i-Q`Qui?w^aUk?!t(F^N+X3ekVhgGbeE;NnfkrJ7-bNQF89VNui8Oikw#lv)@|u480mWH;*2p zKNY^&LOG(>0y&>Pi`%^UbjO@=s&_-mT3~fa+=nTHYd(Uj^EzRfl}rHHc_VmnKHct) zmK6y#TbsgHS6R>r%aM>)XY^R%hRbM8RE!=AM%t)xreSxf*%}s_7ppF;pgMS=X78D+b{!l!h{TLn{+8P8_3vF2BLO!lW4LfDN+Ki!mm)%Hp3`Ud8djm_(K6^x}Xf?)o;scpv`=)Jfc<03(Kc?59+M zjbk)Ec$No0u`#^AANMrQ_B3`yDB0m9JG@v6cA$6?S}0lqjT!r-W7iv@DL9f}^9|<3 zP)f_$A5Fy)EF--Mg|2E*?79(WuPJC(SbM@c*mDOt*tMo%H<9rY%2`$=5;%`C(3~>} z+Qd=s0KHvB=#MEoSkpo0e@5#GAFJ9`8Rr?|$bhceNRCH#zmjV+&OS=TgM0^F;U8ue z1JdCb7}Y>Hrd$@onN$Sa02bzpR1(HVU@_QIch?O`XxTDvu`F0u9-mv_Qf~&}vIhLuk7_(nT0_n5;pZ3%`wz zLoLy#dP%MWvYpFy&1OWLY4K^}djzt=lEa1@f1?=(n2KdJLK5XoPb*mobHSu13`|_k zb7W=;vmz~;q;I70DApCnAvkoHQx^%yd6|_?r=jLis7yjnQDH|r zve!^i|CqyA2%Vi@u~~Ty0Y4rjb^2n>u0^NyF=^rN(FJfQ>ReFIps^TFM~H0-dzihW z3A&CJQ6D*8z_>eJLC>SvGF_KPv_YNrVCt4!PdI2(HmaKTKJ@(K=k}Js9)!SjpZgf~L-BNk~%omr+N?QoIAzJIdx}`0Ll+a9 z!4z&S;#@t!pWYNWiJIgX-ZmpN&g7`jQ#F|u0ezeosGcXWvWkc=to!h{j*d9z5IHZN zjXs#+9Hvrqb;qWU4L*>zE2_x=+6v4up}1eqsnZ9BqL?lqEw+>>dbsJrwG^NIR5@;x zr%u!y7^JeLGVYr>sWiiUsE(fn*l09;gwY|{%>_J*8rLpL6ABRG6Lbk@<3Hjr8-W~= zKJcAFScCqh@*KgVStZI^U;-O>sHDZ`D&C(zg)e%}*=epkLqaJ%Lkbk~64d{5TGaT9ig9sDx9LzD)y~y;%+6S4 zOFC*=lGz!r+?kl#Fz8GhlgMFR! zI=vL^T1Qt0o!jW5AhChwHM~r&#f2U~U;+N$BT=lxSBU)JlN)GONOn47g?l#83bK!Y z*Oktp!q`TLqCXU#x|gj(NNhpPqdyj|yO-{xr2aI4 z*OleGQh04E?IL=su)T|JCHiyW?fa;S=vc1tb}v|PKP{PaT!#lf7EF*HtUmhtM=pCQS-0GCjVaQ-BwV(Glewg~4?(DEI6 zSWHmRyq#`Ho@6%u8iL|qIJFL6kRYdcmzFbQJ@uT=e}0tYjIbO)7c_r|>VqTeX)lY} zeHVR!P8OPW(D!9wUJsoRG!eYJNCd%?0c|zw9ff_pbS0n(#6$FXdO2|KrV9$!KSUxy ze+h1Ulrr>o@bsf}5xtXpYr`eXd|5rezc1gihsx;RVQkL z(Bsq)+?}H#`daYIU9=>q%~4%p*5mZ;2ZHO`#4s$ZQQ`?nrwTv6N?ZvW3$QUs#=eHY zTOz&-Q+8h_Rs^+6#Y{RK+Q7g6ppp xGlQQl6tn4H!I6ccv+(b0g+pTBtU}dtu{J68M+%$2DsB=%>K5@%C_mS-{tE@bFtPvu delta 5670 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 diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp index 41562122d8..fd50af9012 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp @@ -298,9 +298,6 @@ DataStream& operator>>(DataStream& ds, ReserveBalanceSheet& t) { return ds >> t.chain_code >> t.reserves; } -// PretokenStakeChange DataStream operators were removed with the retired -// pre-launch STAKE / UNSTAKE lifecycle (enum slots 3001 and 3002). - // PretokenPurchase (deprecated; pre-launch only) template DataStream& operator<<(DataStream& ds, const PretokenPurchase& t) { diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 2b03c25430..a6e057cf22 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -105,34 +105,6 @@ std::vector encode_envelope_with_one_attestation( return out; } -/// Encode a single historical attestation whose numeric enum slot is no -/// longer declared by the current protobuf schema. -std::vector encode_envelope_with_one_raw_attestation( - uint32_t epoch_index, - int32_t raw_att_type, - const std::string& att_data) -{ - sysio::opp::Envelope env; - env.set_epoch_index(epoch_index); - env.set_epoch_envelope_index(1); - env.set_epoch_timestamp(1'775'612'516'983ULL); - - auto* msg = env.add_messages(); - auto* payload = msg->mutable_payload(); - auto* att = payload->add_attestations(); - const auto* field = att->GetDescriptor()->FindFieldByName("type"); - BOOST_REQUIRE(field != nullptr); - att->GetReflection()->SetEnumValue(att, field, raw_att_type); - att->set_data(att_data); - att->set_data_size(static_cast(att_data.size())); - - oracle::finalize_header(*env.mutable_messages(0), {}, 1'775'612'516'983ULL); - - std::vector out(env.ByteSizeLong()); - env.SerializeToArray(out.data(), static_cast(out.size())); - return out; -} - /// Encode an Envelope wrapping N attestations of the same type. Used to fit /// multiple OPERATOR_ACTIONs into a single delivery, since the depot /// deduplicates per-(batch_op, outpost, epoch) — a second `deliver` from @@ -169,11 +141,9 @@ std::vector encode_envelope_with_attestations( /// mirror, same as the outbound packing tests in sysio.msgch_tests.cpp. constexpr size_t MAX_ENVELOPE_BYTES = 65'536; -/// Historical STAKE protobuf wire value used to verify dispatch drops retired types. -constexpr int32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; - /// Encode a decodable envelope whose serialised size is EXACTLY `target_bytes`, padded with a -/// single challenge-response attestation (dispatch drops it with no value-bearing effect). Probe +/// single out-of-scope challenge-response attestation (dispatch drops it with no value-bearing +/// effect). Probe /// once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with /// the pad shrunk by that overhead: at sizes near the 64 KiB envelope cap every nested length /// prefix and the `data_size` varint sit in the same 3-byte width band (16 KiB .. 2 MiB), so the @@ -1095,9 +1065,9 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat bootstrap_for_dispatch(); const auto eth_code = fc::slug_name{"ETH"}.value; - auto envelope = encode_envelope_with_one_raw_attestation( + auto envelope = encode_envelope_with_one_attestation( current_epoch(), - RETIRED_STAKE_ATTESTATION_VALUE, + sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); diff --git a/contracts/tests/sysio.msgch_tests.cpp b/contracts/tests/sysio.msgch_tests.cpp index 30e89d1b77..f4630afa05 100644 --- a/contracts/tests/sysio.msgch_tests.cpp +++ b/contracts/tests/sysio.msgch_tests.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include "contracts.hpp" #include @@ -204,9 +202,6 @@ class sysio_msgch_envlog_tester : public tester { static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; - /// Attestation queue table used by historical-row upgrade fixtures. - static constexpr auto ATTESTATIONS_TABLE = "attestations"_n; - sysio_msgch_envlog_tester() { produce_blocks(2); create_accounts({ MSGCH_ACCOUNT, EPOCH_ACCOUNT, CHALG_ACCOUNT, CHAINS_ACCOUNT }); @@ -299,41 +294,12 @@ class sysio_msgch_envlog_tester : public tester { ); } - /// Recreate a historical pre-validation row by changing only its - /// destination chain. The indexed fields (id, status, type, epoch) stay - /// unchanged, so the existing secondary-index entries remain valid. - void retarget_attestation_for_upgrade_test(uint64_t id, uint64_t chain_code) { - const auto table_id = chain::compute_table_id(ATTESTATIONS_TABLE.value); - const auto& kv_idx = control->db().get_index(); - char primary_key[chain::kv_pri_key_size]; - chain::kv_encode_be64(primary_key, id); - const auto kv_itr = kv_idx.find(boost::make_tuple( - MSGCH_ACCOUNT, table_id, - std::string_view(primary_key, chain::kv_pri_key_size))); - BOOST_REQUIRE(kv_itr != kv_idx.end()); - - auto row = msgch_abi.binary_to_variant( - "attestation_entry", - std::vector(kv_itr->value.data(), kv_itr->value.data() + kv_itr->value.size()), - abi_serializer::create_yield_function(abi_serializer_max_time)); - fc::mutable_variant_object updated(row.get_object()); - updated.set("chain_code", chain_code); - const auto encoded = msgch_abi.variant_to_binary( - "attestation_entry", updated, - abi_serializer::create_yield_function(abi_serializer_max_time)); - - auto& db = const_cast(control->db()); - db.modify(*kv_itr, [&](auto& object) { - object.value.assign(encoded.data(), encoded.size()); - }); - } - /// Count READY-status attestations for `chain_code` by probing the /// table by-id. Avoids needing an ABI binding for the secondary index. uint32_t count_ready_attestations(uint64_t chain_code, uint64_t scan_until) { uint32_t n = 0; for (uint64_t id = 0; id < scan_until; ++id) { - auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, ATTESTATIONS_TABLE, id); + auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, "attestations"_n, id); if (data.empty()) continue; auto row = msgch_abi.binary_to_variant( "attestation_entry", data, @@ -395,21 +361,6 @@ constexpr uint64_t SOL_OUTPOST_ID = "SOL"_s.value; constexpr auto EVM_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_OPERATORS; constexpr auto SWAP_REMIT_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_SWAP_REMIT; constexpr auto UNCOVERED_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_STAKING_REWARD; -/// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. -constexpr uint32_t RETIRED_STAKE_ATTESTATION_VALUE = 3001; -/// Raw protobuf wire slot used only to seed the pre-upgrade READY-row shape. -constexpr uint32_t RETIRED_UNSTAKE_ATTESTATION_VALUE = 3002; -/// Former OPERATOR_REG_DEREG slot used to cover a non-staking retirement. -constexpr uint32_t RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE = 60931; -/// Exhaustive retired AttestationType slots mirrored from the protobuf schema. -constexpr std::array RETIRED_ATTESTATION_VALUES{ - RETIRED_STAKE_ATTESTATION_VALUE, RETIRED_UNSTAKE_ATTESTATION_VALUE, - 60929, RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE, 60933, - 60935, 60936, 60937, 60938, 60939, 60940, 60941, 60942, - 60946, 60948, 60954, 60957 -}; -/// Maximum retired READY rows pruned during one deterministic build call. -constexpr uint32_t RETIRED_ATTESTATION_PRUNE_LIMIT = 32; /// Decode the emitted OPP envelope and count attestations in its single message. uint32_t emitted_attestation_count(const fc::variant& emitted_row) { @@ -453,109 +404,6 @@ BOOST_FIXTURE_TEST_CASE(buildenv_writes_envlog_row, sysio_msgch_envlog_tester) { BOOST_REQUIRE_EQUAL(31337u, row["endpoints"]["end"]["id"]["value"].as_uint64()); } FC_LOG_AND_RETHROW() } -/// READY rows written by pre-upgrade contracts with any retired wire value -/// are tombstoned before destination-specific envelope construction. -/// Active rows behind the tombstone still emit normally, so one legacy row -/// cannot strand the queue or reach an outpost decoder. -BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_all_retired_attestation_rows, - sysio_msgch_envlog_tester) { try { - bootstrap_epoch_config(/*retention=*/200); - register_outpost(opp::types::CHAIN_KIND_EVM, 31337); - produce_blocks(); - - for (const auto retired_value : RETIRED_ATTESTATION_VALUES) { - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, retired_value)); - } - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); - BOOST_REQUIRE_EQUAL(RETIRED_ATTESTATION_VALUES.size() + 1, - count_ready_attestations(ETH_OUTPOST_ID, 32)); - - BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 32)); - const auto emitted = find_outbound_envelope(); - BOOST_REQUIRE(!emitted.is_null()); - BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); -} FC_LOG_AND_RETHROW() } - -/// A retired row at the end of the READY index is safe to erase. In -/// particular, `erase` returning the index end iterator must not be -/// incremented or dereferenced by the collection loop. -BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_last_retired_attestation_row, - sysio_msgch_envlog_tester) { try { - bootstrap_epoch_config(/*retention=*/200); - register_outpost(opp::types::CHAIN_KIND_EVM, 31337); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_STAKE_ATTESTATION_VALUE)); - BOOST_REQUIRE_EQUAL(1u, count_ready_attestations(ETH_OUTPOST_ID, 4)); - - BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(ETH_OUTPOST_ID, 4)); - BOOST_REQUIRE(find_outbound_envelope().is_null()); -} FC_LOG_AND_RETHROW() } - -/// Tombstones are removed before destination-specific collection. Current -/// queueout correctly refuses to create an invalid legacy SVM row, so queue -/// through the EVM-compatible path and retarget a non-staking retired row to -/// recreate the pre-upgrade SVM state. A Solana build must remove it before -/// the dynamic account estimator sees the unknown type. -BOOST_FIXTURE_TEST_CASE(buildenv_tombstone_cleanup_precedes_svm_estimation, - sysio_msgch_envlog_tester) { try { - bootstrap_epoch_config(/*retention=*/200); - register_outpost(opp::types::CHAIN_KIND_EVM, 31337); - register_outpost(opp::types::CHAIN_KIND_SVM, 31338); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, RETIRED_OPERATOR_REG_DEREG_ATTESTATION_VALUE)); - retarget_attestation_for_upgrade_test(/*id=*/1, /*chain_code=*/SOL_OUTPOST_ID); - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/SOL_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); - - BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/SOL_OUTPOST_ID)); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(0u, count_ready_attestations(SOL_OUTPOST_ID, 8)); - const auto emitted = find_outbound_envelope(); - BOOST_REQUIRE(!emitted.is_null()); - BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); -} FC_LOG_AND_RETHROW() } - -/// Cleanup is capped per action so an upgrade cannot turn an epoch advance -/// into an unbounded erase sweep. Rows beyond the cap remain READY but are -/// skipped as candidates, while an active row behind them still emits. -BOOST_FIXTURE_TEST_CASE(buildenv_bounds_retired_attestation_row_cleanup, - sysio_msgch_envlog_tester) { try { - bootstrap_epoch_config(/*retention=*/200); - register_outpost(opp::types::CHAIN_KIND_EVM, 31337); - produce_blocks(); - - for (uint32_t i = 0; i < RETIRED_ATTESTATION_PRUNE_LIMIT + 1; ++i) { - BOOST_REQUIRE_EQUAL(success(), - queueout_with_data( - /*chain_code=*/ETH_OUTPOST_ID, - RETIRED_STAKE_ATTESTATION_VALUE, - std::vector{static_cast(i)})); - } - BOOST_REQUIRE_EQUAL(success(), - queueout(/*chain_code=*/ETH_OUTPOST_ID, EVM_TEST_ATTESTATION_TYPE)); - - BOOST_REQUIRE_EQUAL(success(), buildenv(/*chain_code=*/ETH_OUTPOST_ID)); - produce_blocks(); - - BOOST_REQUIRE_EQUAL(1u, count_ready_attestations(ETH_OUTPOST_ID, 64)); - const auto emitted = find_outbound_envelope(); - BOOST_REQUIRE(!emitted.is_null()); - BOOST_REQUIRE_EQUAL(1u, emitted_attestation_count(emitted)); -} FC_LOG_AND_RETHROW() } - /// Eviction at the boundary. Set `retention=2` and one outpost → /// `cap = 1*2*2 = 4`. After 5 buildenv rounds (5 rows inserted), the /// oldest full epoch (`per_epoch = 1*2 = 2` rows) gets evicted; final diff --git a/libraries/opp/proto/sysio/opp/attestations/attestations.proto b/libraries/opp/proto/sysio/opp/attestations/attestations.proto index 181d005a29..f60ca46877 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -29,11 +29,9 @@ message ReserveBalanceSheet { repeated sysio.opp.types.ReserveAmount reserves = 4; } -// PretokenStakeChange was removed with the pre-launch STAKE / UNSTAKE -// lifecycle. Attestation enum slots 3001 and 3002 remain retired. - // --------------------------------------------------------------------------- -// Remaining pre-launch specific attestations (DEPRECATED) +// Pre-launch specific attestations (DEPRECATED — kept for proto compatibility +// during the deprecation pass) // --------------------------------------------------------------------------- message PretokenPurchase { @@ -74,15 +72,11 @@ message WireTokenPurchase { message OperatorAction { enum ActionType { - reserved 5; - reserved "ACTION_TYPE_WITHDRAW_CONFIRMED"; - ACTION_TYPE_UNKNOWN = 0; ACTION_TYPE_DEPOSIT_REQUEST = 1; ACTION_TYPE_WITHDRAW_REQUEST = 2; ACTION_TYPE_WITHDRAW_REMIT = 3; ACTION_TYPE_SLASH = 4; - // Slot 5 was ACTION_TYPE_WITHDRAW_CONFIRMED; the confirmation stage was removed. }; ActionType action_type = 1; // Operator's outpost-chain identity (full chain public key). diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index 3fceed1fd3..7000292bd4 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -13,15 +13,12 @@ option cc_enable_arenas = true; // --------------------------------------------------------------------------- enum ChainKind { - reserved 4; - reserved "CHAIN_KIND_SUI"; - CHAIN_KIND_UNKNOWN = 0; CHAIN_KIND_WIRE = 1; // The WIRE depot itself (singleton; Chain.code = "WIRE") CHAIN_KIND_EVM = 2; // All EVM-compatible chains (Ethereum, Polygon, BSC, Arbitrum, Optimism, Base, …) CHAIN_KIND_SVM = 3; // Solana Virtual Machine chains (Solana mainnet, Eclipse, …) - // Slot 4 was previously CHAIN_KIND_SUI; do not reuse. + // Slots 4+ reserved. Slot 4 was previously CHAIN_KIND_SUI; do not reuse. } // Chain instance identifier — used by `Envelope.Endpoints` to identify @@ -105,8 +102,6 @@ enum UnderwriteRequestStatus { // --------------------------------------------------------------------------- enum TokenKind { - reserved 256 to 259, 496, 512, 752; - TOKEN_KIND_UNKNOWN = 0; TOKEN_KIND_NATIVE = 1; // chain-native asset (ETH on EVM, SOL on SVM, WIRE on WIRE) TOKEN_KIND_ERC20 = 2; @@ -234,15 +229,8 @@ message ReserveAmount { // --------------------------------------------------------------------------- enum AttestationType { - reserved 3001, 3002, 60929, 60931, 60933, 60935 to 60942, 60946, 60948, 60954, 60957; - reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; - ATTESTATION_TYPE_UNSPECIFIED = 0; ATTESTATION_TYPE_OPERATOR_ACTION = 2001; // 0x07D1 - // 3001 was ATTESTATION_TYPE_STAKE — removed; do not reuse. The pre-launch - // stake lifecycle was retired before mainnet launch. - // 3002 was ATTESTATION_TYPE_UNSTAKE — removed; do not reuse. The pre-launch - // unstake lifecycle was retired before mainnet launch. // DEPRECATED — pre-launch only, do not use in new code. ATTESTATION_TYPE_PRETOKEN_PURCHASE = 3004; // DEPRECATED — pre-launch only, do not use in new code. @@ -252,7 +240,6 @@ enum AttestationType { // 60929 (0xEE01) was ATTESTATION_TYPE_NATIVE_YIELD_REWARD — removed; do not reuse. // DEPRECATED — pre-launch only, do not use in new code. ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE = 60930; - // 60931 was ATTESTATION_TYPE_OPERATOR_REG_DEREG — replaced by OPERATOR_ACTION; do not reuse. ATTESTATION_TYPE_CHALLENGE_RESPONSE = 60932; // 60933 was standalone SLASH_OPERATOR — removed; SLASH is now an OperatorAction sub-type. ATTESTATION_TYPE_SWAP_REQUEST = 60934; @@ -260,10 +247,6 @@ enum AttestationType { // 60936 was ATTESTATION_TYPE_UNDERWRITE_CONFIRM — removed; do not reuse. // 60937 was ATTESTATION_TYPE_UNDERWRITE_REJECT — removed; do not reuse. // 60938 was ATTESTATION_TYPE_UNDERWRITE_UNLOCK — removed; do not reuse. - // 60939 was ATTESTATION_TYPE_CHALLENGE_REQUEST — renumbered to 60945; do not reuse. - // 60940 was ATTESTATION_TYPE_EPOCH_SYNC — renumbered to 60946, then removed; do not reuse. - // 60941 was ATTESTATION_TYPE_ROSTER_UPDATE — removed; do not reuse. - // 60942 was ATTESTATION_TYPE_REMIT_CONFIRM — renumbered to 60948, then removed; do not reuse. ATTESTATION_TYPE_SWAP_REMIT = 60944; ATTESTATION_TYPE_CHALLENGE_REQUEST = 60945; // 60946 was ATTESTATION_TYPE_EPOCH_SYNC — removed; do not reuse. @@ -376,13 +359,10 @@ enum AttestationStatus { } enum UnderwriteStatus { - reserved 4; - UNDERWRITE_STATUS_INTENT_CREATED = 0; UNDERWRITE_STATUS_INTENT_SUBMITTED = 1; UNDERWRITE_STATUS_INTENT_CONFIRMED = 2; UNDERWRITE_STATUS_READY = 3; - // Slot 4 was UNDERWRITE_STATUS_SLASHED before that state moved to 10; do not reuse. UNDERWRITE_STATUS_RELEASED = 5; UNDERWRITE_STATUS_SLASHED = 10; // Candidate-specific, pre-settlement invalidity in the underwriter race (bad diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts index a1e745dae5..d0fd5a792b 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/enum.ts @@ -6,12 +6,6 @@ export interface EnumValueInfo { number: number } -/** A protobuf enum reservation. Both bounds are inclusive in descriptor.proto. */ -export interface EnumReservedRangeInfo { - start: number - end: number -} - /** Descriptor for a protobuf enum, ready for Solidity codegen. */ export interface EnumDescriptor { /** Simple name (e.g. "Role") */ @@ -20,8 +14,6 @@ export interface EnumDescriptor { fullName: string /** Enum values */ values: EnumValueInfo[] - /** Numeric slots retired with `reserved`; decoded opaquely but never valid. */ - reservedRanges: EnumReservedRangeInfo[] /** Computed smallest unsigned integer type that fits all values */ underlyingType: string } @@ -43,6 +35,7 @@ export interface EnumFieldInfo { * Compute the smallest unsigned integer type that can hold all enum values. */ export function computeUnderlyingType(values: EnumValueInfo[]): string { + if (values.length === 0) return "uint8" const maxVal = Math.max(0, ...values.map(v => v.number)) if (maxVal <= 0xff) return "uint8" if (maxVal <= 0xffff) return "uint16" @@ -107,20 +100,6 @@ export function genEnumDefinition(desc: EnumDescriptor): string { for (const val of uniqueValues) { lines.push(` if (_raw == ${val.number}) return ${val.name};`) } - if (desc.reservedRanges.length > 0) { - lines.push( - ` if (_raw > type(${underlying}).max) revert InvalidEnumValue(_raw);` - ) - } - for (const range of desc.reservedRanges) { - const condition = - range.end === range.start - ? `_raw == ${range.start}` - : `_raw >= ${range.start} && _raw <= ${range.end}` - lines.push( - ` if (${condition}) return ${name}.wrap(${underlying}(_raw));` - ) - } lines.push(` revert InvalidEnumValue(_raw);`) lines.push(` }`) diff --git a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts index 07fc27c5d2..11c70009b4 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/generator/index.ts @@ -3,11 +3,5 @@ export { generateRuntime } from "./runtime.js" export type { MessageDescriptor, TypeRegistry } from "./message.js" export type { FieldInfo } from "./field.js" export { PROTO_TYPE_MAP, WireType, resolveSolType, fieldTag } from "./type-map.js" -export type { - EnumDescriptor, - EnumValueInfo, - EnumReservedRangeInfo, - EnumRegistry, - EnumFieldInfo -} from "./enum.js" +export type { EnumDescriptor, EnumValueInfo, EnumRegistry, EnumFieldInfo } from "./enum.js" export { genEnumDefinition, enumLibName, computeUnderlyingType } from "./enum.js" diff --git a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts index 5f6047fc97..213d369e4a 100644 --- a/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts +++ b/libraries/opp/tools/protoc-gen-solidity/src/plugin.ts @@ -48,18 +48,10 @@ const EnumValueDescriptorProto = new protobuf.Type("EnumValueDescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) .add(new protobuf.Field("number", 2, "int32", "optional")) -/** Numeric interval reserved by a protobuf enum declaration. */ -const EnumReservedRange = new protobuf.Type("EnumReservedRange") - .add(new protobuf.Field("start", 1, "int32", "optional")) - .add(new protobuf.Field("end", 2, "int32", "optional")) - const EnumDescriptorProtoMsg = new protobuf.Type("EnumDescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) .add(new protobuf.Field("value", 2, "EnumValueDescriptorProto", "repeated")) - .add(new protobuf.Field("reserved_range", 4, "EnumReservedRange", "repeated")) - .add(new protobuf.Field("reserved_name", 5, "string", "repeated")) .add(EnumValueDescriptorProto) - .add(EnumReservedRange) const DescriptorProto = new protobuf.Type("DescriptorProto") .add(new protobuf.Field("name", 1, "string", "optional")) @@ -239,15 +231,10 @@ function buildEnumRegistry(protoFiles: any[]): EnumRegistry { name: v.name ?? "", number: v.number ?? 0 })) - const reservedRanges = (e.reserved_range ?? []).map((range: any) => ({ - start: range.start ?? 0, - end: range.end ?? 0 - })) registry.set(fqn, { name, fullName, values, - reservedRanges, underlyingType: computeUnderlyingType(values) }) } @@ -337,15 +324,10 @@ function extractEnums(protoFile: any, packageName: string): EnumDescriptor[] { name: v.name ?? "", number: v.number ?? 0 })) - const reservedRanges = (e.reserved_range ?? []).map((range: any) => ({ - start: range.start ?? 0, - end: range.end ?? 0 - })) result.push({ name, fullName, values, - reservedRanges, underlyingType: computeUnderlyingType(values) }) } diff --git a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts index 827a2bb17b..3ce892b441 100644 --- a/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts +++ b/libraries/opp/tools/protoc-gen-solidity/tests/enum.test.ts @@ -57,14 +57,6 @@ describe("computeUnderlyingType", () => { const values: EnumValueInfo[] = [{ name: "A", number: 0x100000000 }] expect(computeUnderlyingType(values)).toBe("uint64") }) - - it("does not let an open-ended reservation widen the underlying type", () => { - const values: EnumValueInfo[] = [ - { name: "UNSPECIFIED", number: 0 }, - { name: "ACTIVE", number: 255 } - ] - expect(computeUnderlyingType(values)).toBe("uint8") - }) }) describe("enumLibName", () => { @@ -87,7 +79,6 @@ describe("genEnumDefinition", () => { { name: "ADMIN", number: 1 }, { name: "USER", number: 2 } ], - reservedRanges: [], underlyingType: "uint8" } @@ -130,7 +121,6 @@ describe("genEnumDefinition", () => { name: "Status", fullName: "deep.nested.package.Status", values: [{ name: "OK", number: 0 }], - reservedRanges: [], underlyingType: "uint8" } @@ -144,7 +134,6 @@ describe("genEnumDefinition", () => { name: "Empty", fullName: "Empty", values: [], - reservedRanges: [], underlyingType: "uint8" } @@ -170,7 +159,6 @@ describe("genEnumDefinition", () => { { name: "HIGH", number: 100 }, { name: "MEDIUM", number: 50 } ], - reservedRanges: [], underlyingType: "uint8" } @@ -188,7 +176,6 @@ describe("genEnumDefinition", () => { { name: "RUNNING", number: 1 }, { name: "STARTED", number: 1 } ], - reservedRanges: [], underlyingType: "uint8" } @@ -207,64 +194,10 @@ describe("genEnumDefinition", () => { name: "Big", fullName: "Big", values: [{ name: "VAL", number: 0x10000 }], - reservedRanges: [], underlyingType: "uint24" } const result = genEnumDefinition(desc) expect(result).toContain("type Big is uint24;") }) - - it("decodes reserved numeric slots opaquely without making them valid", () => { - const desc: EnumDescriptor = { - name: "AttestationType", - fullName: "AttestationType", - values: [ - { name: "UNSPECIFIED", number: 0 }, - { name: "ACTIVE", number: 3003 } - ], - reservedRanges: [ - { start: 3001, end: 3001 }, - { start: 4000, end: 4002 } - ], - underlyingType: "uint16" - } - - const result = genEnumDefinition(desc) - expect(result).toContain("return _raw == 0 || _raw == 3003;") - expect(result).toContain( - "if (_raw == 3001) return AttestationType.wrap(uint16(_raw));" - ) - expect(result).toContain( - "if (_raw >= 4000 && _raw <= 4002) return AttestationType.wrap(uint16(_raw));" - ) - expect(result).toContain( - "if (_raw > type(uint16).max) revert InvalidEnumValue(_raw);" - ) - expect(result.indexOf("type(uint16).max")).toBeLessThan( - result.indexOf("AttestationType.wrap(uint16(_raw))") - ) - }) - - it("rejects an unrepresentable reserved value before narrowing it", () => { - const desc: EnumDescriptor = { - name: "Small", - fullName: "Small", - values: [ - { name: "UNSPECIFIED", number: 0 }, - { name: "ACTIVE", number: 255 } - ], - reservedRanges: [{ start: 5, end: 0x7fffffff }], - underlyingType: "uint8" - } - - const result = genEnumDefinition(desc) - expect(result).toContain("type Small is uint8;") - expect(result).toContain( - "if (_raw > type(uint8).max) revert InvalidEnumValue(_raw);" - ) - expect(result.indexOf("type(uint8).max")).toBeLessThan( - result.indexOf("Small.wrap(uint8(_raw))") - ) - }) }) From f47f103c54a1e33fa8cae6716d39bb86f2cf615d Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Mon, 17 Aug 2026 16:51:00 +0000 Subject: [PATCH 9/9] Address PR review feedback Change-Id: If570d4fac4f6ae24f1f1f1794bee3ad20ecb0e71 --- contracts/tests/sysio.dispatch_tests.cpp | 13 ++++++------- contracts/tests/sysio.msgch_chain_tests.cpp | 12 ++++++------ libraries/opp/proto/sysio/opp/types/types.proto | 5 +++++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 0efa2b1525..4b390c9636 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -187,20 +187,19 @@ std::vector encode_envelope_with_attestations( constexpr size_t MAX_ENVELOPE_BYTES = 32'768; /// Encode a decodable envelope whose serialised size is EXACTLY `target_bytes`, padded with a -/// single out-of-scope challenge-response attestation (dispatch drops it with no value-bearing -/// effect). Probe -/// once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with +/// permanently inert processing-error attestation (dispatch drops it with no value-bearing effect). +/// Probe once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with /// the pad shrunk by that overhead: at sizes near the 32 KiB envelope cap every nested length /// prefix and the `data_size` varint sit in the same 3-byte width band (16 KiB .. 2 MiB), so the /// second pass lands exactly on target — the final REQUIRE pins it. std::vector encode_envelope_padded_to(uint32_t epoch_index, size_t target_bytes) { auto probe = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, + epoch_index, sysio::opp::types::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR, std::string(target_bytes, 'x')); BOOST_REQUIRE_GT(probe.size(), target_bytes); const size_t overhead = probe.size() - target_bytes; auto padded = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, + epoch_index, sysio::opp::types::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR, std::string(target_bytes - overhead, 'x')); BOOST_REQUIRE_EQUAL(target_bytes, padded.size()); return padded; @@ -1393,7 +1392,7 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, + sysio::opp::types::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); @@ -1788,7 +1787,7 @@ BOOST_FIXTURE_TEST_CASE(deliver_duplicate_from_same_operator_reverts, sysio_disp const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE, + sysio::opp::types::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); diff --git a/contracts/tests/sysio.msgch_chain_tests.cpp b/contracts/tests/sysio.msgch_chain_tests.cpp index fd9969bec4..4d871f87d3 100644 --- a/contracts/tests/sysio.msgch_chain_tests.cpp +++ b/contracts/tests/sysio.msgch_chain_tests.cpp @@ -344,12 +344,12 @@ class sysio_msgch_chain_tester : public tester { // -- Inbound envelope builder -- - /// Encode a deliverable envelope carrying one out-of-scope CHALLENGE_RESPONSE attestation - /// (dispatch drops the attestation silently; acceptance is still fully observable via - /// `outpcons` and the stored attestation row). The semantic header is derived per the spec — `apply_consensus` + /// Encode a deliverable envelope carrying one permanently inert processing-error attestation. + /// Dispatch drops the attestation silently; acceptance is still fully observable via `outpcons` + /// and the stored attestation row. The semantic header is derived per the spec — `apply_consensus` /// drops envelopes whose header fields do not recompute or whose message does not continue the - /// per-outpost message chain. `prev` (previous_envelope_hash), `prev_message_id`, and - /// `env_hash` are raw 32-byte strings (or empty for stream genesis). + /// per-outpost message chain. `prev` (previous_envelope_hash), `prev_message_id`, and `env_hash` + /// are raw 32-byte strings (or empty for stream genesis). std::vector encode_delivery(uint32_t epoch_index, const std::string& att_data, const std::string& prev = {}, const std::string& prev_message_id = {}, @@ -361,7 +361,7 @@ class sysio_msgch_chain_tester : public tester { if (!prev.empty()) env.set_previous_envelope_hash(prev); if (!env_hash.empty()) env.set_envelope_hash(env_hash); auto* att = env.add_messages()->mutable_payload()->add_attestations(); - att->set_type(sysio::opp::types::ATTESTATION_TYPE_CHALLENGE_RESPONSE); + att->set_type(sysio::opp::types::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR); att->set_data(att_data); att->set_data_size(static_cast(att_data.size())); oracle::finalize_header(*env.mutable_messages(0), prev_message_id, 1'775'612'516'983ULL); diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index 7000292bd4..9a073faec9 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -229,8 +229,13 @@ message ReserveAmount { // --------------------------------------------------------------------------- enum AttestationType { + reserved 3001, 3002; + reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; + ATTESTATION_TYPE_UNSPECIFIED = 0; ATTESTATION_TYPE_OPERATOR_ACTION = 2001; // 0x07D1 + // 3001 was ATTESTATION_TYPE_STAKE — removed; do not reuse. + // 3002 was ATTESTATION_TYPE_UNSTAKE — removed; do not reuse. // DEPRECATED — pre-launch only, do not use in new code. ATTESTATION_TYPE_PRETOKEN_PURCHASE = 3004; // DEPRECATED — pre-launch only, do not use in new code.