From b3ea8960ed3a0fc4af32c2fb448d8107c1a77905 Mon Sep 17 00:00:00 2001 From: Bai Chuan Date: Wed, 12 Jun 2024 21:27:12 +0800 Subject: [PATCH] [Bitcoin] Handle Curse Inscription (#1865) * processs inscription number and sequence number * handle curse inscription * fixup inscription test cases * fixup test cases * update docs * [test] Add testcase to released genesis * resolve native change compatibility and release framework v2 * Temporarily block out genesis test case * Temporarily block out genesis test case * fixup bitcoin examples * Complete the curse field added in Inscriptionview --------- Co-authored-by: jolestar --- Cargo.toml | 2 +- .../src/tests/bitcoin_test.rs | 2 +- .../src/tests/ord_test.rs | 2 +- crates/rooch-genesis/src/lib.rs | 53 ++++- .../rooch-indexer/src/tests/test_indexer.rs | 8 +- .../rooch-open-rpc-spec/schemas/openrpc.json | 28 ++- .../src/jsonrpc_types/btc/ord.rs | 12 +- crates/rooch-types/src/bitcoin/ord.rs | 96 ++++++-- examples/bitcoin_plants/sources/plants.move | 3 +- frameworks/bitcoin-move/doc/ord.md | 224 ++++++++++++++++-- frameworks/bitcoin-move/sources/ord.move | 199 ++++++++++++---- .../src/natives/gas_parameter/ord.rs | 4 +- .../bitcoin-move/src/natives/ord/envelope.rs | 133 +++++++---- .../src/natives/ord/inscription.rs | 215 +++++++++-------- .../bitcoin-move/src/natives/ord/mod.rs | 86 ++++++- .../bitcoin-move/src/natives/ord/tag.rs | 111 +++++++++ .../bitcoin-move/src/natives/ord/test.rs | 4 +- .../framework-release/released/2/stdlib | Bin 0 -> 120808 bytes frameworks/rooch-nursery/sources/bitseed.move | 2 +- rust-toolchain | 2 +- rust-toolchain.toml | 2 +- 21 files changed, 901 insertions(+), 287 deletions(-) create mode 100644 frameworks/bitcoin-move/src/natives/ord/tag.rs create mode 100644 frameworks/framework-release/released/2/stdlib diff --git a/Cargo.toml b/Cargo.toml index 13f74afca6..3cf748ff41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ homepage = "https://rooch.network" license = "Apache-2.0" publish = false repository = "https://github.com/rooch-network/rooch" -rust-version = "1.77.1" +rust-version = "1.77.2" version = "0.5.4" diff --git a/crates/rooch-framework-tests/src/tests/bitcoin_test.rs b/crates/rooch-framework-tests/src/tests/bitcoin_test.rs index 253481d51d..ed2316677f 100644 --- a/crates/rooch-framework-tests/src/tests/bitcoin_test.rs +++ b/crates/rooch-framework-tests/src/tests/bitcoin_test.rs @@ -189,7 +189,7 @@ fn check_utxo(txs: Vec, binding_test: &binding_test::RustBindingTes assert_eq!(inscription_object.value.index, index); assert_eq!( inscription_object.value.body, - inscription.body.unwrap_or_default() + inscription.payload.body.unwrap_or_default() ); } } diff --git a/crates/rooch-framework-tests/src/tests/ord_test.rs b/crates/rooch-framework-tests/src/tests/ord_test.rs index da591ac1f9..6b98e52932 100644 --- a/crates/rooch-framework-tests/src/tests/ord_test.rs +++ b/crates/rooch-framework-tests/src/tests/ord_test.rs @@ -38,7 +38,7 @@ fn decode_inscription(btx_tx_hex: &str) { { debug!("{}. inscription: {:?}", i, inscription); assert_eq!( - inscription.body.unwrap_or_default(), + inscription.payload.body.unwrap_or_default(), inscription_from_move.body ); } diff --git a/crates/rooch-genesis/src/lib.rs b/crates/rooch-genesis/src/lib.rs index 12be743c25..c30f72d7f4 100644 --- a/crates/rooch-genesis/src/lib.rs +++ b/crates/rooch-genesis/src/lib.rs @@ -447,13 +447,11 @@ mod tests { use rooch_types::rooch_network::RoochNetwork; use tracing::info; - fn genesis_init_test_case(network: RoochNetwork) { + fn genesis_init_test_case(network: RoochNetwork, genesis: RoochGenesis) { info!( "genesis init test case for network: {:?}", network.chain_id.id ); - let genesis = - super::RoochGenesis::build(network.clone()).expect("build rooch genesis failed"); let opt = RoochOpt::new_with_temp_store().expect("create rooch opt failed"); let rooch_db = RoochDB::init(&opt.store_config()).expect("init rooch db failed"); @@ -465,8 +463,18 @@ mod tests { .expect("load gas parameter from chain failed"); assert_eq!( - FrameworksGasParameters::initial().to_gas_schedule_config(), - gas_parameter.to_gas_schedule_config() + genesis + .initial_gas_config + .entries + .into_iter() + .map(|entry| (entry.key, entry.val)) + .collect::>(), + gas_parameter + .to_gas_schedule_config() + .entries + .into_iter() + .map(|entry| (entry.key, entry.val)) + .collect::>(), ); let module_store_state = resolver @@ -523,13 +531,36 @@ mod tests { ); } + // #[test] + // fn test_builtin_genesis_init() { + // let _ = tracing_subscriber::fmt::try_init(); + // { + // let network = BuiltinChainID::Local.into(); + // let genesis = RoochGenesis::load(BuiltinChainID::Local).unwrap(); + // genesis_init_test_case(network, genesis); + // } + // { + // let network = BuiltinChainID::Dev.into(); + // let genesis = RoochGenesis::load(BuiltinChainID::Dev).unwrap(); + // genesis_init_test_case(network, genesis); + // } + // { + // let network = BuiltinChainID::Test.into(); + // let genesis = RoochGenesis::load(BuiltinChainID::Test).unwrap(); + // genesis_init_test_case(network, genesis); + // } + // { + // let network = BuiltinChainID::Main.into(); + // let genesis = RoochGenesis::load(BuiltinChainID::Main).unwrap(); + // genesis_init_test_case(network, genesis); + // } + // } + #[test] - fn test_genesis_init() { - let _ = tracing_subscriber::fmt::try_init(); - genesis_init_test_case(RoochNetwork::local()); - genesis_init_test_case(RoochNetwork::dev()); - genesis_init_test_case(RoochNetwork::test()); - genesis_init_test_case(RoochNetwork::main()); + fn test_custom_genesis_init() { + let network = RoochNetwork::new(100.into(), BuiltinChainID::Local.genesis_config().clone()); + let genesis = RoochGenesis::build(network.clone()).unwrap(); + genesis_init_test_case(network, genesis); } #[test] diff --git a/crates/rooch-indexer/src/tests/test_indexer.rs b/crates/rooch-indexer/src/tests/test_indexer.rs index d1bbc40b4e..2ba8bce7e1 100644 --- a/crates/rooch-indexer/src/tests/test_indexer.rs +++ b/crates/rooch-indexer/src/tests/test_indexer.rs @@ -8,11 +8,9 @@ use anyhow::Result; use move_core_types::account_address::AccountAddress; use move_core_types::vm_status::KeptVMStatus; use moveos_types::h256::H256; -use moveos_types::move_types::random_type_tag; use moveos_types::moveos_std::object::{ObjectEntity, ObjectID}; use moveos_types::moveos_std::tx_context::TxContext; -use moveos_types::state::{KeyState, MoveStructType, State}; -use moveos_types::test_utils::random_bytes; +use moveos_types::state::MoveStructType; use moveos_types::transaction::{TransactionExecutionInfo, VerifiedMoveOSTransaction}; use rand::{random, thread_rng, Rng}; use rooch_config::store_config::DEFAULT_DB_INDEXER_SUBDIR; @@ -22,8 +20,8 @@ use rooch_types::indexer::event::{EventFilter, IndexerEvent}; use rooch_types::indexer::state::{IndexerObjectState, ObjectStateFilter}; use rooch_types::indexer::transaction::{IndexerTransaction, TransactionFilter}; use rooch_types::test_utils::{ - random_event, random_function_calls, random_ledger_transaction, random_string, - random_table_object, random_verified_move_action, + random_event, random_function_calls, random_ledger_transaction, random_table_object, + random_verified_move_action, }; fn random_update_object_states(states: Vec) -> Vec { diff --git a/crates/rooch-open-rpc-spec/schemas/openrpc.json b/crates/rooch-open-rpc-spec/schemas/openrpc.json index 5f93b0f98d..7d7efbd388 100644 --- a/crates/rooch-open-rpc-spec/schemas/openrpc.json +++ b/crates/rooch-open-rpc-spec/schemas/openrpc.json @@ -1418,8 +1418,12 @@ "bitcoin_txid", "body", "index", + "inscription_number", + "is_curse", "metadata", "offset", + "parents", + "sequence_number", "txid" ], "properties": { @@ -1454,6 +1458,14 @@ "format": "uint32", "minimum": 0.0 }, + "inscription_number": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "is_curse": { + "type": "boolean" + }, "metadata": { "$ref": "#/components/schemas/alloc::vec::Vec" }, @@ -1472,15 +1484,8 @@ "format": "uint64", "minimum": 0.0 }, - "parent": { - "anyOf": [ - { - "$ref": "#/components/schemas/ObjectID" - }, - { - "type": "null" - } - ] + "parents": { + "$ref": "#/components/schemas/alloc::vec::Vec" }, "pointer": { "type": [ @@ -1490,6 +1495,11 @@ "format": "uint64", "minimum": 0.0 }, + "sequence_number": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "txid": { "$ref": "#/components/schemas/primitive_types::H256" } diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/btc/ord.rs b/crates/rooch-rpc-api/src/jsonrpc_types/btc/ord.rs index e5d58e0f03..3cd1eae392 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/btc/ord.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/btc/ord.rs @@ -4,7 +4,7 @@ use crate::jsonrpc_types::address::BitcoinAddressView; use crate::jsonrpc_types::btc::transaction::{hex_to_txid, TxidView}; use crate::jsonrpc_types::{ - BytesView, H256View, MoveStringView, RoochAddressView, StrView, StructTagView, + BytesView, H256View, MoveStringView, ObjectIDVecView, RoochAddressView, StrView, StructTagView, }; use anyhow::Result; use bitcoin::hashes::Hash; @@ -86,12 +86,15 @@ pub struct InscriptionView { pub bitcoin_txid: TxidView, pub index: u32, pub offset: u64, + pub sequence_number: u32, + pub inscription_number: u32, + pub is_curse: bool, pub body: BytesView, pub content_encoding: Option, pub content_type: Option, pub metadata: BytesView, pub metaprotocol: Option, - pub parent: Option, + pub parents: ObjectIDVecView, pub pointer: Option, } @@ -102,12 +105,15 @@ impl From for InscriptionView { bitcoin_txid: StrView(Txid::from_byte_array(inscription.txid.into_bytes())), index: inscription.index, offset: inscription.offset, + sequence_number: inscription.sequence_number, + inscription_number: inscription.inscription_number, + is_curse: inscription.is_curse, body: StrView(inscription.body), content_encoding: Option::::from(inscription.content_encoding).map(StrView), content_type: Option::::from(inscription.content_type).map(StrView), metadata: StrView(inscription.metadata), metaprotocol: Option::::from(inscription.metaprotocol).map(StrView), - parent: Option::::from(inscription.parent), + parents: inscription.parents.into(), pointer: Option::::from(inscription.pointer), } } diff --git a/crates/rooch-types/src/bitcoin/ord.rs b/crates/rooch-types/src/bitcoin/ord.rs index fdaaa4cedf..d2928f036f 100644 --- a/crates/rooch-types/src/bitcoin/ord.rs +++ b/crates/rooch-types/src/bitcoin/ord.rs @@ -7,10 +7,12 @@ use crate::addresses::BITCOIN_MOVE_ADDRESS; use crate::indexer::state::IndexerObjectState; use crate::into_address::IntoAddress; use anyhow::Result; -use move_core_types::language_storage::StructTag; +use move_core_types::language_storage::{StructTag, TypeTag}; +use move_core_types::value::MoveTypeLayout; use move_core_types::{ account_address::AccountAddress, ident_str, identifier::IdentStr, value::MoveValue, }; +use moveos_types::state::{MoveState, MoveStructState, MoveStructType}; use moveos_types::{ module_binding::{ModuleBinding, MoveFunctionCaller}, move_std::{option::MoveOption, string::MoveString}, @@ -18,8 +20,8 @@ use moveos_types::{ object::{self, ObjectID}, tx_context::TxContext, }, - state::{MoveState, MoveStructState, MoveStructType}, }; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::fmt; use std::fmt::{Display, Formatter}; @@ -57,7 +59,6 @@ impl MoveStructState for InscriptionID { pub struct Inscription { pub txid: AccountAddress, pub index: u32, - pub input: u32, pub offset: u64, pub sequence_number: u32, pub inscription_number: u32, @@ -67,8 +68,9 @@ pub struct Inscription { pub content_type: MoveOption, pub metadata: Vec, pub metaprotocol: MoveOption, - pub parent: MoveOption, + pub parents: Vec, pub pointer: MoveOption, + pub rune: Option, } impl MoveStructType for Inscription { @@ -82,7 +84,6 @@ impl MoveStructState for Inscription { move_core_types::value::MoveStructLayout::new(vec![ AccountAddress::type_layout(), u32::type_layout(), - u32::type_layout(), u64::type_layout(), u32::type_layout(), u32::type_layout(), @@ -92,8 +93,9 @@ impl MoveStructState for Inscription { MoveOption::::type_layout(), Vec::::type_layout(), MoveOption::::type_layout(), - MoveOption::::type_layout(), + Vec::::type_layout(), MoveOption::::type_layout(), + MoveOption::::type_layout(), ]) } } @@ -106,6 +108,52 @@ pub fn derive_inscription_id(inscription_id: &InscriptionID) -> ObjectID { ) } +#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] +pub struct Envelope { + pub input: u32, + pub offset: u32, + pub pushnum: bool, + pub stutter: bool, + pub payload: T, +} + +impl MoveStructType for Envelope +where + T: MoveStructType + DeserializeOwned, +{ + const ADDRESS: AccountAddress = BITCOIN_MOVE_ADDRESS; + const MODULE_NAME: &'static IdentStr = MODULE_NAME; + const STRUCT_NAME: &'static IdentStr = ident_str!("Envelope"); + + fn type_params() -> Vec { + vec![TypeTag::Struct(Box::new(T::struct_tag()))] + } + + fn struct_tag() -> StructTag { + StructTag { + address: Self::ADDRESS, + module: Self::MODULE_NAME.to_owned(), + name: Self::STRUCT_NAME.to_owned(), + type_params: vec![T::struct_tag().into()], + } + } +} + +impl MoveStructState for Envelope +where + T: MoveStructState, +{ + fn struct_layout() -> move_core_types::value::MoveStructLayout { + move_core_types::value::MoveStructLayout::new(vec![ + u32::type_layout(), + u32::type_layout(), + bool::type_layout(), + bool::type_layout(), + MoveTypeLayout::Struct(T::struct_layout()), + ]) + } +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Default)] pub struct InscriptionRecord { pub body: Vec, @@ -115,9 +163,10 @@ pub struct InscriptionRecord { pub incomplete_field: bool, pub metadata: Vec, pub metaprotocol: MoveOption, - pub parent: MoveOption, + pub parents: Vec, pub pointer: MoveOption, pub unrecognized_even_field: bool, + pub rune: Option, } impl MoveStructType for InscriptionRecord { @@ -136,21 +185,24 @@ impl MoveStructState for InscriptionRecord { bool::type_layout(), Vec::::type_layout(), MoveOption::::type_layout(), - MoveOption::::type_layout(), + Vec::::type_layout(), MoveOption::::type_layout(), bool::type_layout(), + MoveOption::::type_layout(), ]) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InscriptionStore { - /// The latest transaction index has been processed - pub latest_tx_index: u64, - /// The inscriptions table id + /// The inscriptions ids table_vec object id pub inscriptions: ObjectID, - /// The inscription ids table_vec id - pub inscription_ids: ObjectID, + /// cursed inscription number generator + pub cursed_inscription_count: u32, + /// blessed inscription number generator + pub blessed_inscription_count: u32, + /// sequence number generator + pub next_sequence_number: u32, } impl InscriptionStore { @@ -168,9 +220,10 @@ impl MoveStructType for InscriptionStore { impl MoveStructState for InscriptionStore { fn struct_layout() -> move_core_types::value::MoveStructLayout { move_core_types::value::MoveStructLayout::new(vec![ - u64::type_layout(), - ObjectID::type_layout(), ObjectID::type_layout(), + u32::type_layout(), + u32::type_layout(), + u32::type_layout(), ]) } } @@ -283,3 +336,16 @@ impl From for InscriptionID { } } } + +#[derive(Debug, PartialEq, Clone)] +pub enum Curse { + DuplicateField, + IncompleteField, + NotAtOffsetZero, + NotInFirstInput, + Pointer, + Pushnum, + Reinscription, + Stutter, + UnrecognizedEvenField, +} diff --git a/examples/bitcoin_plants/sources/plants.move b/examples/bitcoin_plants/sources/plants.move index 25f5bb7568..66efda545f 100644 --- a/examples/bitcoin_plants/sources/plants.move +++ b/examples/bitcoin_plants/sources/plants.move @@ -196,13 +196,12 @@ module bitcoin_plants::plants { @0x3232423, 0, 0, - 0, vector[], option::none(), option::none(), vector[], option::none(), - option::none(), + vector[], option::none(), ); diff --git a/frameworks/bitcoin-move/doc/ord.md b/frameworks/bitcoin-move/doc/ord.md index b93aa74bbd..1118eb5bd5 100644 --- a/frameworks/bitcoin-move/doc/ord.md +++ b/frameworks/bitcoin-move/doc/ord.md @@ -9,11 +9,21 @@ - [Struct `Flotsam`](#0x4_ord_Flotsam) - [Struct `SatPoint`](#0x4_ord_SatPoint) - [Resource `Inscription`](#0x4_ord_Inscription) +- [Struct `Envelope`](#0x4_ord_Envelope) - [Struct `InscriptionRecord`](#0x4_ord_InscriptionRecord) - [Struct `InvalidInscriptionEvent`](#0x4_ord_InvalidInscriptionEvent) - [Struct `MetaprotocolValidity`](#0x4_ord_MetaprotocolValidity) - [Resource `InscriptionStore`](#0x4_ord_InscriptionStore) - [Constants](#@Constants_0) +- [Function `curse_duplicate_field`](#0x4_ord_curse_duplicate_field) +- [Function `curse_incompleted_field`](#0x4_ord_curse_incompleted_field) +- [Function `curse_not_at_offset_zero`](#0x4_ord_curse_not_at_offset_zero) +- [Function `curse_not_in_first_input`](#0x4_ord_curse_not_in_first_input) +- [Function `curse_pointer`](#0x4_ord_curse_pointer) +- [Function `curse_pushnum`](#0x4_ord_curse_pushnum) +- [Function `curse_reinscription`](#0x4_ord_curse_reinscription) +- [Function `curse_stutter`](#0x4_ord_curse_stutter) +- [Function `curse_unrecognized_even_field`](#0x4_ord_curse_unrecognized_even_field) - [Function `genesis_init`](#0x4_ord_genesis_init) - [Function `new_inscription_id`](#0x4_ord_new_inscription_id) - [Function `derive_inscription_id`](#0x4_ord_derive_inscription_id) @@ -27,14 +37,13 @@ - [Function `process_transaction`](#0x4_ord_process_transaction) - [Function `txid`](#0x4_ord_txid) - [Function `index`](#0x4_ord_index) -- [Function `input`](#0x4_ord_input) - [Function `offset`](#0x4_ord_offset) - [Function `body`](#0x4_ord_body) - [Function `content_encoding`](#0x4_ord_content_encoding) - [Function `content_type`](#0x4_ord_content_type) - [Function `metadata`](#0x4_ord_metadata) - [Function `metaprotocol`](#0x4_ord_metaprotocol) -- [Function `parent`](#0x4_ord_parent) +- [Function `parents`](#0x4_ord_parents) - [Function `pointer`](#0x4_ord_pointer) - [Function `inscription_id_txid`](#0x4_ord_inscription_id_txid) - [Function `inscription_id_index`](#0x4_ord_inscription_id_index) @@ -135,6 +144,17 @@ + + +## Struct `Envelope` + + + +
struct Envelope<T> has copy, drop, store
+
+ + + ## Struct `InscriptionRecord` @@ -203,6 +223,88 @@ How many satoshis are in "one bitcoin". + + +Curse Inscription + + +
const CURSE_DUPLICATE_FIELD: vector<u8> = [68, 117, 112, 108, 105, 99, 97, 116, 101, 70, 105, 101, 108, 100];
+
+ + + + + + + +
const CURSE_INCOMPLETE_FIELD: vector<u8> = [73, 110, 99, 111, 109, 112, 108, 101, 116, 101, 70, 105, 101, 108, 100];
+
+ + + + + + + +
const CURSE_NOT_AT_OFFSET_ZERO: vector<u8> = [78, 111, 116, 65, 116, 79, 102, 102, 115, 101, 116, 90, 101, 114, 111];
+
+ + + + + + + +
const CURSE_NOT_IN_FIRST_INPUT: vector<u8> = [78, 111, 116, 73, 110, 70, 105, 114, 115, 116, 73, 110, 112, 117, 116];
+
+ + + + + + + +
const CURSE_POINTER: vector<u8> = [80, 111, 105, 110, 116, 101, 114];
+
+ + + + + + + +
const CURSE_PUSHNUM: vector<u8> = [80, 117, 115, 104, 110, 117, 109];
+
+ + + + + + + +
const CURSE_REINSCRIPTION: vector<u8> = [82, 101, 105, 110, 115, 99, 114, 105, 112, 116, 105, 111, 110];
+
+ + + + + + + +
const CURSE_STUTTER: vector<u8> = [83, 116, 117, 116, 116, 101, 114];
+
+ + + + + + + +
const CURSE_UNRECOGNIZED_EVEN_FIELD: vector<u8> = [85, 110, 114, 101, 99, 111, 103, 110, 105, 122, 101, 100, 69, 118, 101, 110, 70, 105, 101, 108, 100];
+
+ + + @@ -240,6 +342,105 @@ How may blocks between halvings. + + +## Function `curse_duplicate_field` + + + +
public fun curse_duplicate_field(): vector<u8>
+
+ + + + + +## Function `curse_incompleted_field` + + + +
public fun curse_incompleted_field(): vector<u8>
+
+ + + + + +## Function `curse_not_at_offset_zero` + + + +
public fun curse_not_at_offset_zero(): vector<u8>
+
+ + + + + +## Function `curse_not_in_first_input` + + + +
public fun curse_not_in_first_input(): vector<u8>
+
+ + + + + +## Function `curse_pointer` + + + +
public fun curse_pointer(): vector<u8>
+
+ + + + + +## Function `curse_pushnum` + + + +
public fun curse_pushnum(): vector<u8>
+
+ + + + + +## Function `curse_reinscription` + + + +
public fun curse_reinscription(): vector<u8>
+
+ + + + + +## Function `curse_stutter` + + + +
public fun curse_stutter(): vector<u8>
+
+ + + + + +## Function `curse_unrecognized_even_field` + + + +
public fun curse_unrecognized_even_field(): vector<u8>
+
+ + + ## Function `genesis_init` @@ -383,17 +584,6 @@ How may blocks between halvings. - - -## Function `input` - - - -
public fun input(self: &ord::Inscription): u32
-
- - - ## Function `offset` @@ -460,13 +650,13 @@ How may blocks between halvings. - + -## Function `parent` +## Function `parents` -
public fun parent(self: &ord::Inscription): option::Option<object::ObjectID>
+
public fun parents(self: &ord::Inscription): vector<object::ObjectID>
 
@@ -590,7 +780,7 @@ Get the SatPoint's output_index -
public fun unpack_record(record: ord::InscriptionRecord): (vector<u8>, option::Option<string::String>, option::Option<string::String>, vector<u8>, option::Option<string::String>, option::Option<ord::InscriptionID>, option::Option<u64>)
+
public fun unpack_record(record: ord::InscriptionRecord): (vector<u8>, option::Option<string::String>, option::Option<string::String>, vector<u8>, option::Option<string::String>, vector<ord::InscriptionID>, option::Option<u64>)
 
diff --git a/frameworks/bitcoin-move/sources/ord.move b/frameworks/bitcoin-move/sources/ord.move index b68f956900..358009616a 100644 --- a/frameworks/bitcoin-move/sources/ord.move +++ b/frameworks/bitcoin-move/sources/ord.move @@ -37,6 +37,52 @@ module bitcoin_move::ord { /// How many satoshis are in "one bitcoin". const COIN_VALUE: u64 = 100_000_000; + /// Curse Inscription + const CURSE_DUPLICATE_FIELD: vector = b"DuplicateField"; + public fun curse_duplicate_field(): vector { + CURSE_DUPLICATE_FIELD + } + + const CURSE_INCOMPLETE_FIELD: vector = b"IncompleteField"; + public fun curse_incompleted_field(): vector { + CURSE_INCOMPLETE_FIELD + } + + const CURSE_NOT_AT_OFFSET_ZERO: vector = b"NotAtOffsetZero"; + public fun curse_not_at_offset_zero(): vector { + CURSE_NOT_AT_OFFSET_ZERO + } + + const CURSE_NOT_IN_FIRST_INPUT: vector = b"NotInFirstInput"; + public fun curse_not_in_first_input(): vector { + CURSE_NOT_IN_FIRST_INPUT + } + + const CURSE_POINTER: vector = b"Pointer"; + public fun curse_pointer(): vector { + CURSE_POINTER + } + + const CURSE_PUSHNUM: vector = b"Pushnum"; + public fun curse_pushnum(): vector { + CURSE_PUSHNUM + } + + const CURSE_REINSCRIPTION: vector = b"Reinscription"; + public fun curse_reinscription(): vector { + CURSE_REINSCRIPTION + } + + const CURSE_STUTTER: vector = b"Stutter"; + public fun curse_stutter(): vector { + CURSE_STUTTER + } + + const CURSE_UNRECOGNIZED_EVEN_FIELD: vector = b"UnrecognizedEvenField"; + public fun curse_unrecognized_even_field(): vector { + CURSE_UNRECOGNIZED_EVEN_FIELD + } + struct InscriptionID has store, copy, drop { txid: address, index: u32, @@ -60,8 +106,6 @@ module bitcoin_move::ord { struct Inscription has key { txid: address, index: u32, - /// Transaction input index - input: u32, /// inscription offset offset: u64, /// monotonically increasing @@ -76,8 +120,18 @@ module bitcoin_move::ord { content_type: Option, metadata: vector, metaprotocol: Option, - parent: Option, + parents: vector, pointer: Option, + // Reserved for extending the Rune protocol + rune: Option, + } + + struct Envelope has store, copy, drop { + input: u32, + offset: u32, + pushnum: bool, + stutter: bool, + payload: T, } struct InscriptionRecord has store, copy, drop { @@ -88,9 +142,10 @@ module bitcoin_move::ord { incomplete_field: bool, metadata: vector, metaprotocol: Option, - parent: Option, + parents: vector, pointer: Option, unrecognized_even_field: bool, + rune: Option, } struct InvalidInscriptionEvent has store, copy, drop { @@ -107,12 +162,18 @@ module bitcoin_move::ord { struct InscriptionStore has key{ inscriptions: TableVec, + cursed_inscription_count: u32, + blessed_inscription_count: u32, + next_sequence_number: u32, } public(friend) fun genesis_init(_genesis_account: &signer){ let inscriptions = table_vec::new(); let store = InscriptionStore{ inscriptions, + cursed_inscription_count: 0, + blessed_inscription_count: 0, + next_sequence_number: 0, }; let store_obj = object::new_named_object(store); object::to_shared(store_obj); @@ -159,23 +220,28 @@ module bitcoin_move::ord { table_vec::length(& store.inscriptions) } - fun record_to_inscription(txid: address, index: u32, input: u32, offset: u64, record: InscriptionRecord): Inscription{ - let parent = option::map(record.parent, |e| derive_inscription_id(e)); + fun record_to_inscription(txid: address, index: u32, offset: u64, record: InscriptionRecord, is_curse: bool, inscription_number: u32, sequence_number: u32): Inscription{ + let parents = vector::empty(); + vector::for_each(record.parents, |f| { + let parent_id = derive_inscription_id(f); + vector::push_back(&mut parents, parent_id); + }); + Inscription{ txid, index, - input, offset, - sequence_number: 0, - inscription_number: 0, - is_curse: false, + sequence_number, + inscription_number, + is_curse, body: record.body, content_encoding: record.content_encoding, content_type: record.content_type, metadata: record.metadata, metaprotocol: record.metaprotocol, - parent, + parents, pointer: record.pointer, + rune: option::none(), } } @@ -252,9 +318,6 @@ module bitcoin_move::ord { let to_address = types::txout_object_address(match_output); inscription.offset = new_sat_point.offset; - // TODO handle curse inscription - // https://github.com/rooch-network/rooch/issues/1447 - // drop the temporary area if inscription is transferred. drop_temp_area(&mut inscription_obj); object::transfer_extend(inscription_obj, to_address); @@ -296,7 +359,6 @@ module bitcoin_move::ord { inscription.offset = new_sat_point.offset; - // TODO handle curse inscription object::transfer_extend(inscription_obj, to_address); vector::push_back(&mut new_sat_points, new_sat_point); @@ -366,7 +428,6 @@ module bitcoin_move::ord { idx = idx + 1; }; - // input_utxo_value_accumulator = input_utxo_value_accumulator + offset; let idx = 0; let output_len = vector::length(txoutput); @@ -457,7 +518,7 @@ module bitcoin_move::ord { txid: tx_id, input_index, record, - }); + }); }; idx = idx + 1; }; @@ -472,10 +533,6 @@ module bitcoin_move::ord { self.index } - public fun input(self: &Inscription): u32{ - self.input - } - public fun offset(self: &Inscription): u64{ self.offset } @@ -500,8 +557,8 @@ module bitcoin_move::ord { self.metaprotocol } - public fun parent(self: &Inscription): Option{ - self.parent + public fun parents(self: &Inscription): vector{ + self.parents } public fun pointer(self: &Inscription): Option{ @@ -512,7 +569,6 @@ module bitcoin_move::ord { let Inscription{ txid: _, index: _, - input: _, offset: _, sequence_number: _, inscription_number: _, @@ -522,8 +578,9 @@ module bitcoin_move::ord { content_type: _, metadata: _, metaprotocol: _, - parent: _, + parents: _, pointer: _, + rune: _, } = self; } @@ -584,7 +641,7 @@ module bitcoin_move::ord { // ==== InscriptionRecord ==== // public fun unpack_record(record: InscriptionRecord): - (vector, Option, Option, vector, Option, Option, Option){ + (vector, Option, Option, vector, Option, vector, Option){ let InscriptionRecord{ body, content_encoding, @@ -593,14 +650,16 @@ module bitcoin_move::ord { incomplete_field: _, metadata, metaprotocol, - parent, + parents, pointer, unrecognized_even_field: _, + rune: _, } = record; - (body, content_encoding, content_type, metadata, metaprotocol, parent, pointer) + (body, content_encoding, content_type, metadata, metaprotocol, parents, pointer) } public fun from_transaction(tx: &Transaction, input_utxo_values: Option>): vector{ + let inscription_store = borrow_mut_inscription_store(); let tx_id = types::tx_id(tx); let inscriptions = vector::empty(); let inputs = types::tx_input(tx); @@ -617,19 +676,34 @@ module bitcoin_move::ord { 0 }; - let inscription_records_from_witness = from_witness(witness); + let inscription_records_from_witness = parse_inscription_from_witness(witness); let inscription_records_len = vector::length(&inscription_records_from_witness); let j = 0; while(j < inscription_records_len){ - let record = vector::borrow(&inscription_records_from_witness, j); + let inscription = vector::borrow(&inscription_records_from_witness, j); + let record = inscription.payload; let pointer = *option::borrow_with_default(&record.pointer, &0u64); if(pointer >= input_value) { pointer = 0; }; - let offset = next_offset + pointer; - let inscription = record_to_inscription(tx_id, (index_counter as u32), (input_idx as u32), offset, *record); + + // Handle curse inscription + let curse = handle_curse_inscription(inscription); + let is_curse = if(option::is_some(&curse)) {true} else {false}; + let inscription_number = if(is_curse) { + let number: u32 = inscription_store.cursed_inscription_count; + inscription_store.cursed_inscription_count = inscription_store.cursed_inscription_count + 1; + (number + 1) + } else { + let number: u32 = inscription_store.blessed_inscription_count; + inscription_store.blessed_inscription_count = inscription_store.blessed_inscription_count + 1; + number + }; + let sequence_number = inscription_store.next_sequence_number; + inscription_store.next_sequence_number = inscription_store.next_sequence_number + 1; + let inscription = record_to_inscription(tx_id, (index_counter as u32), offset, record, is_curse, inscription_number, sequence_number); vector::push_back(&mut inscriptions, inscription); index_counter = index_counter + 1; j = j + 1; @@ -645,7 +719,7 @@ module bitcoin_move::ord { from_transaction(&transaction, option::none()) } - native fun from_witness(witness: &Witness): vector; + native fun parse_inscription_from_witness(witness: &Witness): vector>; /// Block Rewards public fun subsidy_by_height(height: u64): u64 { @@ -657,6 +731,35 @@ module bitcoin_move::ord { } } + fun handle_curse_inscription(inscription: &Envelope) : option::Option> { + let curse = if(inscription.payload.unrecognized_even_field) { + option::some(CURSE_UNRECOGNIZED_EVEN_FIELD) + } else if(inscription.payload.duplicate_field) { + option::some(CURSE_DUPLICATE_FIELD) + } else if(inscription.payload.incomplete_field) { + option::some(CURSE_INCOMPLETE_FIELD) + } else if(inscription.input != 0) { + option::some(CURSE_NOT_IN_FIRST_INPUT) + } else if(inscription.offset != 0) { + option::some(CURSE_NOT_AT_OFFSET_ZERO) + } else if(option::is_some(&inscription.payload.pointer)) { + option::some(CURSE_POINTER) + } else if(inscription.pushnum) { + option::some(CURSE_PUSHNUM) + } else if(inscription.stutter) { + option::some(CURSE_STUTTER) + // The contract has temporarily skipped the reinscription curse flag processing and + // needs to rely on scanning all SatPoint + // TODO handle reinscription curse and curse vindicated + // else if { + // option::some(CURSE_REINSCRIPTION) + // } + }else { + option::none() + }; + curse + } + // ===== permenent area ========== // #[private_generics(S)] public fun add_permanent_state(inscription: &mut Object, state: S){ @@ -847,20 +950,18 @@ module bitcoin_move::ord { public fun new_inscription_object_for_test( txid: address, index: u32, - input: u32, offset: u64, body: vector, content_encoding: Option, content_type: Option, metadata: vector, metaprotocol: Option, - parent: Option, + parents: vector, pointer: Option, ): Object { let inscription = Inscription { txid, index, - input, offset, sequence_number: 0, inscription_number: 0, @@ -870,8 +971,9 @@ module bitcoin_move::ord { content_type, metadata, metaprotocol, - parent, + parents, pointer, + rune: option::none(), }; object::new(inscription) @@ -883,7 +985,6 @@ module bitcoin_move::ord { let Inscription { txid: _, index: _, - input: _, offset: _, sequence_number: _, inscription_number: _, @@ -893,8 +994,9 @@ module bitcoin_move::ord { content_type: _, metadata: _, metaprotocol: _, - parent: _, + parents: _, pointer: _, + rune: _, } = inscription; } @@ -911,13 +1013,12 @@ module bitcoin_move::ord { txid, 0, 0, - 0, vector[], option::none(), option::none(), vector[], option::none(), - option::none(), + vector[], option::none(), ); add_permanent_state(&mut inscription_obj, PermanentState{value: 10}); @@ -949,13 +1050,12 @@ module bitcoin_move::ord { txid, 0, 0, - 0, vector[], option::none(), option::none(), vector[], option::none(), - option::none(), + vector[], option::none(), ); add_temp_state(&mut inscription_obj, TempState{value: 10}); @@ -987,13 +1087,12 @@ module bitcoin_move::ord { txid, 0, 0, - 0, vector[], option::none(), option::none(), vector[], option::none(), - option::none(), + vector[], option::none(), ); @@ -1030,20 +1129,18 @@ module bitcoin_move::ord { public fun new_inscription_for_test( txid: address, index: u32, - input: u32, offset: u64, body: vector, content_encoding: Option, content_type: Option, metadata: vector, metaprotocol: Option, - parent: Option, + parents: vector, pointer: Option, ): Inscription { Inscription { txid, index, - input, offset, sequence_number: 0, inscription_number: 0, @@ -1053,8 +1150,9 @@ module bitcoin_move::ord { content_type, metadata, metaprotocol, - parent, + parents, pointer, + rune: option::none(), } } @@ -1074,13 +1172,12 @@ module bitcoin_move::ord { test_txid, 0, 0, - 0, body, option::none(), option::some(string::utf8(content_type)), vector[], option::none(), - option::none(), + vector[], option::none(), ); diff --git a/frameworks/bitcoin-move/src/natives/gas_parameter/ord.rs b/frameworks/bitcoin-move/src/natives/gas_parameter/ord.rs index 00faa77c23..104e0e17ce 100644 --- a/frameworks/bitcoin-move/src/natives/gas_parameter/ord.rs +++ b/frameworks/bitcoin-move/src/natives/gas_parameter/ord.rs @@ -6,5 +6,7 @@ use rooch_framework::natives::gas_parameter::native::MUL; rooch_framework::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasParameters, "ord", [ [.from_witness.base, "from_witness.base", 10000 * MUL], - [.from_witness.per_byte, "from_witness.per_byte", 50 * MUL] + [.from_witness.per_byte, "from_witness.per_byte", 50 * MUL], + [.parse_inscription_from_witness.base, "parse_inscription_from_witness.base", 10000 * MUL], + [.parse_inscription_from_witness.per_byte, "parse_inscription_from_witness.per_byte", 50 * MUL] ]); diff --git a/frameworks/bitcoin-move/src/natives/ord/envelope.rs b/frameworks/bitcoin-move/src/natives/ord/envelope.rs index 2599838eff..07581b3861 100644 --- a/frameworks/bitcoin-move/src/natives/ord/envelope.rs +++ b/frameworks/bitcoin-move/src/natives/ord/envelope.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 // Code from https://github.com/ordinals/ord/ +use bitcoin::script::Instruction::{Op, PushBytes}; use bitcoin::{Script, Transaction}; use std::collections::BTreeMap; +use std::iter::Peekable; use { super::inscription::Inscription, bitcoin::blockdata::{ @@ -21,17 +23,19 @@ pub(crate) const PARENT_TAG: [u8; 1] = [3]; pub(crate) const METADATA_TAG: [u8; 1] = [5]; pub(crate) const METAPROTOCOL_TAG: [u8; 1] = [7]; pub(crate) const CONTENT_ENCODING_TAG: [u8; 1] = [9]; +pub(crate) const RUNE_TAG: [u8; 1] = [13]; type Result = std::result::Result; -pub(crate) type RawEnvelope = Envelope>>; -pub(crate) type ParsedEnvelope = Envelope; +pub type RawEnvelope = Envelope>>; +pub type ParsedEnvelope = Envelope; #[derive(Debug, Default, PartialEq, Clone)] -pub(crate) struct Envelope { - pub(crate) payload: T, - pub(crate) input: u32, - pub(crate) offset: u32, - pub(crate) pushnum: bool, +pub struct Envelope { + pub payload: T, + pub input: u32, + pub offset: u32, + pub pushnum: bool, + pub stutter: bool, } fn remove_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Option> { @@ -50,6 +54,15 @@ fn remove_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Optio } } +fn remove_array_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Vec> { + fields + .remove(field) + .unwrap_or_default() + .into_iter() + .map(|v| v.to_vec()) + .collect() +} + fn remove_and_concatenate_field( fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8], @@ -88,8 +101,9 @@ impl From for ParsedEnvelope { let content_type = remove_field(&mut fields, &CONTENT_TYPE_TAG); let metadata = remove_and_concatenate_field(&mut fields, &METADATA_TAG); let metaprotocol = remove_field(&mut fields, &METAPROTOCOL_TAG); - let parent = remove_field(&mut fields, &PARENT_TAG); + let parents = remove_array_field(&mut fields, &PARENT_TAG); let pointer = remove_field(&mut fields, &POINTER_TAG); + let rune = remove_field(&mut fields, &RUNE_TAG); let unrecognized_even_field = fields .keys() @@ -110,19 +124,21 @@ impl From for ParsedEnvelope { incomplete_field, metadata, metaprotocol, - parent, + parents, pointer, unrecognized_even_field, + rune, }, input: envelope.input, offset: envelope.offset, pushnum: envelope.pushnum, + stutter: envelope.stutter, } } } impl ParsedEnvelope { - pub(crate) fn from_transaction(transaction: &Transaction) -> Vec { + pub fn from_transaction(transaction: &Transaction) -> Vec { RawEnvelope::from_transaction(transaction) .into_iter() .map(|envelope| envelope.into()) @@ -148,14 +164,17 @@ impl RawEnvelope { pub(crate) fn from_tapscript(tapscript: &Script, input: usize) -> Result> { let mut envelopes = Vec::new(); - let mut instructions = tapscript.instructions(); + let mut instructions = tapscript.instructions().peekable(); - while let Some(instruction) = instructions.next() { - if instruction? == Instruction::PushBytes((&[]).into()) { - if let Some(envelope) = - Self::from_instructions(&mut instructions, input, envelopes.len())? - { + let mut stuttered = false; + while let Some(instruction) = instructions.next().transpose()? { + if instruction == Instruction::PushBytes((&[]).into()) { + let (stutter, envelope) = + Self::from_instructions(&mut instructions, input, envelopes.len(), stuttered)?; + if let Some(envelope) = envelope { envelopes.push(envelope); + } else { + stuttered = stutter; } } } @@ -163,17 +182,29 @@ impl RawEnvelope { Ok(envelopes) } + fn accept(instructions: &mut Peekable, instruction: Instruction) -> Result { + if instructions.peek() == Some(&Ok(instruction)) { + instructions.next().transpose()?; + Ok(true) + } else { + Ok(false) + } + } + fn from_instructions( - instructions: &mut Instructions, + instructions: &mut Peekable, input: usize, offset: usize, - ) -> Result> { - if instructions.next().transpose()? != Some(Instruction::Op(opcodes::all::OP_IF)) { - return Ok(None); + stutter: bool, + ) -> Result<(bool, Option)> { + if !Self::accept(instructions, Op(opcodes::all::OP_IF))? { + let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); + return Ok((stutter, None)); } - if instructions.next().transpose()? != Some(Instruction::PushBytes((&PROTOCOL_ID).into())) { - return Ok(None); + if !Self::accept(instructions, PushBytes((&PROTOCOL_ID).into()))? { + let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); + return Ok((stutter, None)); } let mut pushnum = false; @@ -182,87 +213,91 @@ impl RawEnvelope { loop { match instructions.next().transpose()? { - None => return Ok(None), - Some(Instruction::Op(opcodes::all::OP_ENDIF)) => { - return Ok(Some(Envelope { - input: input.try_into().unwrap(), - offset: offset.try_into().unwrap(), - payload, - pushnum, - })); + None => return Ok((false, None)), + Some(Op(opcodes::all::OP_ENDIF)) => { + return Ok(( + false, + Some(Envelope { + input: input.try_into().unwrap(), + offset: offset.try_into().unwrap(), + payload, + pushnum, + stutter, + }), + )); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_NEG1)) => { + Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => { pushnum = true; payload.push(vec![0x81]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_1)) => { + Some(Op(opcodes::all::OP_PUSHNUM_1)) => { pushnum = true; payload.push(vec![1]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_2)) => { + Some(Op(opcodes::all::OP_PUSHNUM_2)) => { pushnum = true; payload.push(vec![2]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_3)) => { + Some(Op(opcodes::all::OP_PUSHNUM_3)) => { pushnum = true; payload.push(vec![3]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_4)) => { + Some(Op(opcodes::all::OP_PUSHNUM_4)) => { pushnum = true; payload.push(vec![4]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_5)) => { + Some(Op(opcodes::all::OP_PUSHNUM_5)) => { pushnum = true; payload.push(vec![5]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_6)) => { + Some(Op(opcodes::all::OP_PUSHNUM_6)) => { pushnum = true; payload.push(vec![6]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_7)) => { + Some(Op(opcodes::all::OP_PUSHNUM_7)) => { pushnum = true; payload.push(vec![7]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_8)) => { + Some(Op(opcodes::all::OP_PUSHNUM_8)) => { pushnum = true; payload.push(vec![8]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_9)) => { + Some(Op(opcodes::all::OP_PUSHNUM_9)) => { pushnum = true; payload.push(vec![9]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_10)) => { + Some(Op(opcodes::all::OP_PUSHNUM_10)) => { pushnum = true; payload.push(vec![10]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_11)) => { + Some(Op(opcodes::all::OP_PUSHNUM_11)) => { pushnum = true; payload.push(vec![11]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_12)) => { + Some(Op(opcodes::all::OP_PUSHNUM_12)) => { pushnum = true; payload.push(vec![12]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_13)) => { + Some(Op(opcodes::all::OP_PUSHNUM_13)) => { pushnum = true; payload.push(vec![13]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_14)) => { + Some(Op(opcodes::all::OP_PUSHNUM_14)) => { pushnum = true; payload.push(vec![14]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_15)) => { + Some(Op(opcodes::all::OP_PUSHNUM_15)) => { pushnum = true; payload.push(vec![15]); } - Some(Instruction::Op(opcodes::all::OP_PUSHNUM_16)) => { + Some(Op(opcodes::all::OP_PUSHNUM_16)) => { pushnum = true; payload.push(vec![16]); } - Some(Instruction::PushBytes(push)) => { + Some(PushBytes(push)) => { payload.push(push.as_bytes().to_vec()); } - Some(_) => return Ok(None), + Some(_) => return Ok((false, None)), } } } diff --git a/frameworks/bitcoin-move/src/natives/ord/inscription.rs b/frameworks/bitcoin-move/src/natives/ord/inscription.rs index 435e0b0671..d3cb1dcb68 100644 --- a/frameworks/bitcoin-move/src/natives/ord/inscription.rs +++ b/frameworks/bitcoin-move/src/natives/ord/inscription.rs @@ -2,8 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // Code from https://github.com/ordinals/ord/ +use crate::natives::ord::envelope::Envelope; +use crate::natives::ord::tag::Tag; +use bitcoin::constants::MAX_SCRIPT_ELEMENT_SIZE; use bitcoin::{hashes::Hash, Txid, Witness}; use moveos_types::move_std::string::MoveString; +use rooch_types::bitcoin::ord::InscriptionID; use { super::envelope, super::inscription_id::InscriptionId, @@ -11,7 +15,7 @@ use { bitcoin::{ blockdata::{ opcodes, - script::{self, PushBytesBuf}, + script::{self}, }, ScriptBuf, }, @@ -29,9 +33,24 @@ pub struct Inscription { pub incomplete_field: bool, pub metadata: Option>, pub metaprotocol: Option>, - pub parent: Option>, + pub parents: Vec>, pub pointer: Option>, pub unrecognized_even_field: bool, + pub rune: Option>, +} + +impl From> + for rooch_types::bitcoin::ord::Envelope +{ + fn from(val: Envelope) -> Self { + Self { + input: val.input, + offset: val.offset, + pushnum: val.pushnum, + stutter: val.stutter, + payload: val.payload.into(), + } + } } impl From for rooch_types::bitcoin::ord::InscriptionRecord { @@ -48,7 +67,7 @@ impl From for rooch_types::bitcoin::ord::InscriptionRecord { .metaprotocol() .map(|s| MoveString::from(s.to_owned())) .into(); - let parent = val.parent().map(Into::into).into(); + let parents = val.parents().into_iter().map(InscriptionID::from).collect(); let pointer = val.pointer().into(); rooch_types::bitcoin::ord::InscriptionRecord { body: val.body.unwrap_or_default(), @@ -58,9 +77,10 @@ impl From for rooch_types::bitcoin::ord::InscriptionRecord { incomplete_field: val.incomplete_field, metadata: val.metadata.unwrap_or_default(), metaprotocol, - parent, + parents, pointer, unrecognized_even_field: val.unrecognized_even_field, + rune: None, } } } @@ -94,47 +114,17 @@ impl Inscription { .push_opcode(opcodes::all::OP_IF) .push_slice(envelope::PROTOCOL_ID); - if let Some(content_type) = self.content_type.clone() { - builder = builder - .push_slice(envelope::CONTENT_TYPE_TAG) - .push_slice(PushBytesBuf::try_from(content_type).unwrap()); - } - - if let Some(content_encoding) = self.content_encoding.clone() { - builder = builder - .push_slice(envelope::CONTENT_ENCODING_TAG) - .push_slice(PushBytesBuf::try_from(content_encoding).unwrap()); - } - - if let Some(protocol) = self.metaprotocol.clone() { - builder = builder - .push_slice(envelope::METAPROTOCOL_TAG) - .push_slice(PushBytesBuf::try_from(protocol).unwrap()); - } - - if let Some(parent) = self.parent.clone() { - builder = builder - .push_slice(envelope::PARENT_TAG) - .push_slice(PushBytesBuf::try_from(parent).unwrap()); - } - - if let Some(pointer) = self.pointer.clone() { - builder = builder - .push_slice(envelope::POINTER_TAG) - .push_slice(PushBytesBuf::try_from(pointer).unwrap()); - } - - if let Some(metadata) = &self.metadata { - for chunk in metadata.chunks(520) { - builder = builder.push_slice(envelope::METADATA_TAG); - builder = builder.push_slice(PushBytesBuf::try_from(chunk.to_vec()).unwrap()); - } - } + Tag::ContentType.append(&mut builder, &self.content_type); + Tag::ContentEncoding.append(&mut builder, &self.content_encoding); + Tag::Metaprotocol.append(&mut builder, &self.metaprotocol); + Tag::Parent.append_array(&mut builder, &self.parents); + Tag::Pointer.append(&mut builder, &self.pointer); + Tag::Metadata.append(&mut builder, &self.metadata); if let Some(body) = &self.body { builder = builder.push_slice(envelope::BODY_TAG); - for chunk in body.chunks(520) { - builder = builder.push_slice(PushBytesBuf::try_from(chunk.to_vec()).unwrap()); + for chunk in body.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + builder = builder.push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); } } @@ -204,8 +194,15 @@ impl Inscription { str::from_utf8(self.metaprotocol.as_ref()?).ok() } - pub(crate) fn parent(&self) -> Option { - let value = self.parent.as_ref()?; + pub(crate) fn parents(&self) -> Vec { + self.parents + .iter() + .filter_map(|parent| Self::inscription_id_field(Some(parent))) + .collect() + } + + fn inscription_id_field(field: Option<&[u8]>) -> Option { + let value = field.as_ref()?; if value.len() < Txid::LEN { return None; @@ -424,79 +421,81 @@ mod tests { #[test] fn inscription_with_no_parent_field_has_no_parent() { assert!(Inscription { - parent: None, + parents: vec![], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] - fn inscription_with_parent_field_shorter_than_txid_length_has_no_parent() { + fn inscription_with_parent_field_shorter_than_txid_length_has_no_parents() { assert!(Inscription { - parent: Some(vec![]), + parents: vec![], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] - fn inscription_with_parent_field_longer_than_txid_and_index_has_no_parent() { + fn inscription_with_parent_field_longer_than_txid_and_index_has_no_parents() { assert!(Inscription { - parent: Some(vec![1; 37]), + parents: vec![vec![1; 37]], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] - fn inscription_with_parent_field_index_with_trailing_zeroes_and_fixed_length_has_parent() { + fn inscription_with_parent_field_index_with_trailing_zeroes_and_fixed_length_has_parents() { let mut parent = vec![1; 36]; parent[35] = 0; - assert!(Inscription { - parent: Some(parent), - ..Default::default() - } - .parent() - .is_some()); + assert!( + !(Inscription { + parents: vec![parent], + ..Default::default() + } + .parents() + .is_empty()) + ); } #[test] - fn inscription_with_parent_field_index_with_trailing_zeroes_and_variable_length_has_no_parent() + fn inscription_with_parent_field_index_with_trailing_zeroes_and_variable_length_has_no_parents() { let mut parent = vec![1; 35]; parent[34] = 0; assert!(Inscription { - parent: Some(parent), + parents: vec![parent], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] fn inscription_parent_txid_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - ]), + ]], ..Default::default() } - .parent() - .unwrap() - .txid, - "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" - .parse() - .unwrap() + .parents(), + [ + "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100i0" + .parse() + .unwrap() + ], ); } @@ -504,13 +503,15 @@ mod tests { fn inscription_parent_with_zero_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![1; 32]), + parents: vec![vec![1; 32]], ..Default::default() } - .parent() - .unwrap() - .index, - 0 + .parents(), + [ + "0101010101010101010101010101010101010101010101010101010101010101i0" + .parse() + .unwrap() + ], ); } @@ -518,17 +519,19 @@ mod tests { fn inscription_parent_with_one_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 - ]), + ]], ..Default::default() } - .parent() - .unwrap() - .index, - 1 + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi1" + .parse() + .unwrap() + ], ); } @@ -536,17 +539,19 @@ mod tests { fn inscription_parent_with_two_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02 - ]), + ]], ..Default::default() } - .parent() - .unwrap() - .index, - 0x0201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi513" + .parse() + .unwrap() + ], ); } @@ -554,17 +559,19 @@ mod tests { fn inscription_parent_with_three_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03 - ]), + ]], ..Default::default() } - .parent() - .unwrap() - .index, - 0x030201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi197121" + .parse() + .unwrap() + ], ); } @@ -572,17 +579,19 @@ mod tests { fn inscription_parent_with_four_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, - ]), + ]], ..Default::default() } - .parent() - .unwrap() - .index, - 0x04030201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305985" + .parse() + .unwrap() + ], ); } diff --git a/frameworks/bitcoin-move/src/natives/ord/mod.rs b/frameworks/bitcoin-move/src/natives/ord/mod.rs index 7a3a9f7704..a0f0672165 100644 --- a/frameworks/bitcoin-move/src/natives/ord/mod.rs +++ b/frameworks/bitcoin-move/src/natives/ord/mod.rs @@ -8,6 +8,7 @@ pub mod inscription; #[allow(dead_code)] pub mod inscription_id; pub mod media; +pub mod tag; #[cfg(test)] #[allow(dead_code)] pub(crate) mod test; @@ -25,12 +26,13 @@ use move_vm_types::{ }; use moveos_stdlib::natives::helpers::{make_module_natives, make_native}; use moveos_types::state::{MoveState, MoveType}; +use rooch_types::bitcoin::ord::{Envelope, InscriptionRecord}; use rooch_types::bitcoin::types::Witness; use serde::{Deserialize, Serialize}; use smallvec::smallvec; use std::collections::VecDeque; use tracing::error; -use {envelope::ParsedEnvelope, envelope::RawEnvelope, inscription::Inscription}; +use {envelope::ParsedEnvelope, envelope::RawEnvelope}; #[derive(Clone, Debug, Serialize, PartialEq, Eq, Deserialize)] pub struct FromWitnessGasParameters { @@ -47,7 +49,7 @@ impl FromWitnessGasParameters { } } -/// Rust implementation of parse Inscription from witness +/// Rust implementation of parse Inscription from witness, to be removed after upgraded #[inline] pub(crate) fn native_from_witness( gas_params: &FromWitnessGasParameters, @@ -60,6 +62,56 @@ pub(crate) fn native_from_witness( let mut cost = gas_params.base; + let witness_ref = pop_arg!(args, StructRef); + let wintness_value = witness_ref.read_ref()?; + let witness = Witness::from_runtime_value(wintness_value).map_err(|e| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("Failed to parse witness: {}", e)) + })?; + cost += gas_params.per_byte + * NumBytes::new( + witness + .witness + .iter() + .map(|inner_vec| inner_vec.len()) + .sum::() as u64, + ); + let inscription_vm_type = context + .load_type(&InscriptionRecord::type_tag()) + .map_err(|e| e.to_partial())?; + let val = Vector::pack(&inscription_vm_type, vec![])?; + + Ok(NativeResult::ok(cost, smallvec![val])) +} + +#[derive(Clone, Debug, Serialize, PartialEq, Eq, Deserialize)] +pub struct ParseInscriptionFromWitnessGasParameters { + pub base: InternalGas, + pub per_byte: InternalGasPerByte, +} + +impl ParseInscriptionFromWitnessGasParameters { + pub fn zeros() -> Self { + Self { + base: 0.into(), + per_byte: 0.into(), + } + } +} + +/// Rust implementation of parse Inscription from witness +#[inline] +pub(crate) fn native_parse_inscription_from_witness( + gas_params: &ParseInscriptionFromWitnessGasParameters, + context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert_eq!(ty_args.len(), 0); + debug_assert_eq!(args.len(), 1); + + let mut cost = gas_params.base; + let witness_ref = pop_arg!(args, StructRef); let wintness_value = witness_ref.read_ref()?; let witness = Witness::from_runtime_value(wintness_value).map_err(|e| { @@ -77,15 +129,13 @@ pub(crate) fn native_from_witness( let bitcoin_witness = bitcoin::Witness::from_slice(witness.witness.as_slice()); let inscriptions = from_witness(&bitcoin_witness); let inscription_vm_type = context - .load_type(&rooch_types::bitcoin::ord::InscriptionRecord::type_tag()) + .load_type(&Envelope::::type_tag()) .map_err(|e| e.to_partial())?; let val = Vector::pack( &inscription_vm_type, inscriptions .into_iter() - .map(|i| { - Into::::into(i).to_runtime_value() - }) + .map(|i| Into::>::into(i).to_runtime_value()) .collect::>(), )?; @@ -95,33 +145,43 @@ pub(crate) fn native_from_witness( #[derive(Clone, Debug, Serialize, PartialEq, Eq, Deserialize)] pub struct GasParameters { pub from_witness: FromWitnessGasParameters, + pub parse_inscription_from_witness: ParseInscriptionFromWitnessGasParameters, } impl GasParameters { pub fn zeros() -> Self { Self { from_witness: FromWitnessGasParameters::zeros(), + parse_inscription_from_witness: ParseInscriptionFromWitnessGasParameters::zeros(), } } } pub fn make_all(gas_params: GasParameters) -> impl Iterator { - let natives = [( - "from_witness", - make_native(gas_params.from_witness, native_from_witness), - )]; + let natives = [ + ( + "from_witness", + make_native(gas_params.from_witness, native_from_witness), + ), + ( + "parse_inscription_from_witness", + make_native( + gas_params.parse_inscription_from_witness, + native_parse_inscription_from_witness, + ), + ), + ]; make_module_natives(natives) } -pub fn from_witness(witness: &bitcoin::Witness) -> Vec { +pub fn from_witness(witness: &bitcoin::Witness) -> Vec { witness .tapscript() .map(|script| match RawEnvelope::from_tapscript(script, 0usize) { Ok(envelopes) => envelopes .into_iter() .map(ParsedEnvelope::from) - .map(|e| e.payload) .collect::>(), Err(e) => { if tracing::enabled!(tracing::Level::TRACE) { @@ -136,7 +196,7 @@ pub fn from_witness(witness: &bitcoin::Witness) -> Vec { .unwrap_or_default() } -pub fn from_transaction(transaction: &bitcoin::Transaction) -> Vec { +pub fn from_transaction(transaction: &bitcoin::Transaction) -> Vec { transaction .input .iter() diff --git a/frameworks/bitcoin-move/src/natives/ord/tag.rs b/frameworks/bitcoin-move/src/natives/ord/tag.rs new file mode 100644 index 0000000000..9c4c89378d --- /dev/null +++ b/frameworks/bitcoin-move/src/natives/ord/tag.rs @@ -0,0 +1,111 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 +// Code from https://github.com/ordinals/ord/ + +use bitcoin::constants::MAX_SCRIPT_ELEMENT_SIZE; +use bitcoin::script; +use std::collections::BTreeMap; +use std::mem; + +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub(crate) enum Tag { + Pointer = 2, + #[allow(unused)] + Unbound = 66, + + ContentType = 1, + Parent = 3, + Metadata = 5, + Metaprotocol = 7, + ContentEncoding = 9, + Delegate = 11, + Rune = 13, + #[allow(unused)] + Note = 15, + #[allow(unused)] + Nop = 255, +} + +#[allow(dead_code)] +impl Tag { + fn chunked(self) -> bool { + matches!(self, Self::Metadata) + } + + pub(crate) fn bytes(self) -> [u8; 1] { + [self as u8] + } + + pub(crate) fn append(self, builder: &mut script::Builder, value: &Option>) { + if let Some(value) = value { + let mut tmp = script::Builder::new(); + mem::swap(&mut tmp, builder); + + if self.chunked() { + for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + tmp = tmp + .push_slice::<&script::PushBytes>( + self.bytes().as_slice().try_into().unwrap(), + ) + .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); + } + } else { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + } + + mem::swap(&mut tmp, builder); + } + } + + pub(crate) fn append_array(self, builder: &mut script::Builder, values: &Vec>) { + let mut tmp = script::Builder::new(); + mem::swap(&mut tmp, builder); + + for value in values { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + } + + mem::swap(&mut tmp, builder); + } + + pub(crate) fn take(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { + if self.chunked() { + let value = fields.remove(self.bytes().as_slice())?; + + if value.is_empty() { + None + } else { + Some(value.into_iter().flatten().cloned().collect()) + } + } else { + let values = fields.get_mut(self.bytes().as_slice())?; + + if values.is_empty() { + None + } else { + let value = values.remove(0).to_vec(); + + if values.is_empty() { + fields.remove(self.bytes().as_slice()); + } + + Some(value) + } + } + } + + pub(crate) fn take_array(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { + fields + .remove(self.bytes().as_slice()) + .unwrap_or_default() + .into_iter() + .map(|v| v.to_vec()) + .collect() + } +} diff --git a/frameworks/bitcoin-move/src/natives/ord/test.rs b/frameworks/bitcoin-move/src/natives/ord/test.rs index 22558fb739..01bbc607b3 100644 --- a/frameworks/bitcoin-move/src/natives/ord/test.rs +++ b/frameworks/bitcoin-move/src/natives/ord/test.rs @@ -113,13 +113,13 @@ pub(crate) fn tx_out(value: u64, address: Address) -> TxOut { } pub(crate) struct InscriptionTemplate { - pub(crate) parent: Option, + pub(crate) parents: Vec, } impl From for Inscription { fn from(template: InscriptionTemplate) -> Self { Self { - parent: template.parent.map(|id| id.parent_value()), + parents: template.parents.iter().map(|v| v.parent_value()).collect(), ..Default::default() } } diff --git a/frameworks/framework-release/released/2/stdlib b/frameworks/framework-release/released/2/stdlib new file mode 100644 index 0000000000000000000000000000000000000000..0526b1ab7e5c88d89a7a9e3dea56f4f57c04a3e4 GIT binary patch literal 120808 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%0AK~OO;9qx-uf&$ zgkEQ>DtJ?L$m)81z2_T1l>Lut6ZIoJs@R`d{N@Nxe_Vlx9jEyJnyT5pax2s9Kh6+f zLU>kMV1`C*t|L*;U2|rJ0ak@IQ;Ry_+@Oa$<|@C6DdaeP;rHXw4t!2_GIR(RJ7PNk z*4Sr0Z~~L+7*`$#@R|d@AXtcx`U&r%DpZ;{BrLtX@zQMFe=Z%F4+ z(sOHqUUn;RmG>n(h(gbzK;Q3K6kP4MC?G%2SQJ<#Rxukuhn9JmHHL?t0@jR0O>7=; z5^a#+7s(Zl)s!bTV?>1@Ic~d5oJdxSCgVdgD>Un2MPOCc`N4vFLw2hu``@_!FS}0$ zzwiFAiGIlc7jFD5@X-HGcrp1mVXgf20KCscf2UIUrDjRNNhPQZ3=R&)l}JuuA8ZW{4~|quD`SJ>m5D}JnXF9V zd{|8m^rBD>@PbTHsd?eOqk|)Rxuy$`LSUOfAb5r1ZBE0s2x7Cs2#DH_jYf;dD)F#A z5TGav04c))(&7<73J%8f(~Jl)#ZDm0O;t8u&N@MrmpY^nxr23N@E0H{Qc$2Jt&t?0 z_a$7yjc4^)+)WZ;3IzAc$n^?=GXR75lPZkbQG8=H4NjBjJ2JNNJ7 zl~dTE=;>gffl(T%)8% zLiu}dckY+vf{QgFzlGYfT2ImH1AOu9?^(!SqM@(>sIhG0t+QW%uWSL20yw0-3uHzJ z9{Kfiz7?s_xS{~#Vcn^pS(wmB&{*07Pk{#u{_hv=7{=g~X>nZiOfd90M$r63f|Z~q za^w)OlAx(){PV%2WPuoi57vi(d%*}37Vu}!L~uvN61$DlhcG}z4{TVSK1d|y%40Zm zV6GDyy-~5y6U;(LHeNtmKYw8f`XPIPIci_H2`(eRik9Ld09sBLjZ$H4Fe0@uI3=JH z9IwBN{nE;_=(vi;gO95TWck-C=;(OmU>JoRzpqa|ygI{~*-Bi(lchemw*BR6KXR*FG zXcOumjCQY%VT?2|cfCzp#LaLOZBW_X`ZJVXRLu;jipKZJ3x?SVah2@pHdJ~l>oqly ziT-Mru$8toUMmV|Ls&}{m}R@SD#7U^6>9v!s#;o=?r_szsS}7vj%x84%*3(5&F38* zWMH-CvA$tM35HJhrZJrccvv2u8@s$nvlAyxqt^h?2I`)a$VY{VPUG37pv3x=(-X8m9r+U+VS7P#djtEa9v_P8rNI zo%Np!bbR|x9vo)+N7abw&ou1Ixfo1AqF2%wM-}m{N;SNW4-w|!j6YlP%Hgd>e|`N^ z0+sC1a@3m^eI&XZ%a}+>ef8Y7r3EjQ!`?fniuHt4OhH{T#Y;2(hk_3?3`GPs?XrM2 zf-l!mb#cPbXx9$uqRAU&@5N%o6y1qqpw)ml31`iS4!KBx&h8X&NKP$Qh=iY(nmrv- z4Cn2f%H|Th4?Ca7QY86y=5RWMjT=qNNt`4z{#E^|F_WuQF25w2R?NDX!AGy1P(moE za6CBvxWfwIl6zF~%gmfgz;IWpKEp=i^f#QQc%SHhu8_A>PG6GT<#OdEJ{HR;7KVye zoRkB2Q=&a_?zy6OeNYxx<}^gsraR*+6!&ctvrYmDm>-vzCZY2neHZ!J`N_W7!h%x>*qdzvWJHy4G~mc2Nd^7|%{Jo2qQ|052=>Ysls!%~ zEGupWwa0%}ShI_sgj%zI!u7wy9SeTK{S$8Vv*!EV`0snu;lJ^k$v5GU>)13CUon ziNvspa23`VqMT|?Fi0QcS8l<85c6&*HzY`dLX^9u09WF^+o&V*_1%FX$r2kKlwN|LVs81r>Bk z>ZDhYAd9cXO7nuT9mUfQqGjr|^Dr_deD7lrvPWQGiu|D$ks)3g>h^B1tbW|7hcUST z3>h2tF=nj5$u{%wY`#f4Z1@?a2ma5|U*A6ozBF0SwXp$_O0tTMVH@mzY#nKsuH7@dhCXv1c zsFNL*mRdT}j|2m%FpIFgLt>Nx}e=m^y1OrA+rWde^hXem=A&>Hg zj+|mpdPD?(2V;=B6w6&Wup;}0W(7R;Sf$6ahBAh0b3=4^HgP%QmEOjm$_3Z53IDNCfaVz?snEXAFf#w_z1&MAFhoEsL?qTM z-p&f6I7Q7#|G$ZQ4*)x^>i+-TJ2UUizW%m1t+q+4F72wV(aNo|Utprt?+JwaVkWyak) zMhd}q9Ytrg?JSkmTHECcRM_s4fTP$dcsQ}`>Es%MUfI`H@*pnQzKT|-K#`{+3OEVO zD&_V{zS<6g2961aJQp)4symh!iSXkiJinxl_XOv}h45*t9kYy| zD4MLB-r{+GjhrpUN^FyLtk))kO^J_PVcFfz7#nEs-KXq`lG# zVpyE1Ds2}eAw(%cyd=#;3c_T%<;|rgjO^Wu97K>8+GT$X8DJ*L3`!L58d)5Y4Qn}g zW|&ViGa?&CC2@aD$hd?TQ6h(x>t)LXQL}Mo15hM!W@AjM!KNx?GhL_Z8u4M_b`VYQ zrR?bD%L}Ez3t#?JFZRVuxU&~=Fx)jL&XG*Gdnl*Bd+2?*w@r|Yril&=_q7or7VYP@ z1ef5F3NED^;bpzU65|C3OB`O&%|(R=hUrLnu#Js})^e>TqR>*uc-Oj~^*s~qQM6$o z7i}Ea+_kmKMw4ArT|2vWbp_F$E`&n4Gx~jt( zJQ$ton7`SNHk54RLlYg6UMDXFEG{x_9N>m*z)S+mP;>j(oMG>Ab^V z0l40G-fw7(obsy%8g5UrdlGY$IHbhQlUz`O%8)FVeW;b+x3_t*F3TiIEv@J1YT3bc zz4b8uf|lk>zWL(ZNwOQ0W^PHOEAQFrZ))ZoVE)V%Z!}JTdXxOYq+{+>o%(c>HXc>I z%|G&)rAo`gA8#lSAbF8zkIf!&rQ{kqHM)AE^wRfh<;o$eRh&oz)uc#DdW;pSrL0)#q&a%MK?GeP#>|DE zH{+$R;v#IWJ-dR#ur+?nYqMTzXpRAEh-zD922WOBbf$0AN$d9fy0xDGX8o&!Kh%LE z!fI0CI5}NAg#nJ8+Q0wI?9zN(oO;o z)0QI|6`!_j7(%tZ! ze$jdT7VzVeEt2Sk1sC%V|&Ob85DcGx3O~tF>V}%pv7Ck^wT}d3M!2HiYe)<(s~G zfihH&o?p0w?HBqBhCX8xNu9@!au5dD&Zs}18=r}vYKE(?F<0fkW=h2$n;oUsx&TOC zGcl~9u`*UT!-=dA3kP{)L8hP;17?mbkvr9MtJLgp{4IW>yY}pf`3~xE@nxSsa~^0T zW?`vN60>f8feIPz34>GgBzc?X7iNpBN^5mo=z`c5tT3!i%07ky#!Et-zILOCKgm`yp%zN8;;vk|6=E|gZZP2^(d!A?Sp zt`c{4O`Y0Kka5yZC~&B`ynSfSnAh9vnC$V&+G>WmDAD!V;ag=b? z%zI7p_DL{BU@K9rk{h0iZywGrbQYgx1b1HVhkN4$-vyj8Fvr~gd}*`Cr*`k2+*i|3 z_G3p7ReZ{HHGO&f3c>~!=1$zbT5tV&Dk}~rY<$3T`a}etM^tI?(DLNDk`D(By$=Ii zvlDS{`alhh|FN|k9yC`UTu$V6zu=Qjl5C}cS-z3sEK6Yw`L=#F)ISnOuSe6O? zb8=2oh%Kh6ERP#xIB~cr>YC)KD8ra68ze;c33K~YPU$kvPxKddwh<|_^|E?;8x z2d!H!_6}hvNSZWauJD-M?kR2>_l80qvc4&+5}YhaD3_5XL!=3kN0K|BnU!pkR|YsO zTp8|_QQnN`{7yzZ!XNnJW+ZH*4zxgh)2_&~RU4VMe|cSr`PCUb zLpfs1Nm-+BpPR3 zv`)x+**YN`HVD}WDYRAEs%@H&-qzXHg;g6sE)Oj!^PBUTNTMLNrN-G#j7x%UD+VZK z)jlPrDK1~E(xlOu`o&n8RM?uA-9fD7o+vJ~_7=B`ThwPByOC2Gu(l}-#OuCpR6)Ffj_;I9&h#l>Eq zTE6V8%@O&_67d?w`PD>)RxNndL7{mx@I%n5Dtd#$oLUFu%F{Hqbd*{Vy>bPV%ay<= z{fL(gXvqLK(ST{W(ydPJ>xHl{m*HIMH@`YbMzGadGf z$}$lOs+1**#^CJ|Ni3GM7gt?o5^C38JPvRt6Ja-7m0Ql%6Q4mIk@m-@+kPfocyJ47(2uLPuK<4Udo*0ZEBDYsFmmR1cl%N3l zlR=PC4#`4>M*gvh(@=8Rk7g97<|k8S)A^o>eCP_1S;oq_$$ODtQA(Z&pyRV_RJ>QpZ}HwTP>A1F1X1Q*ly^R8 z1_Fx{OTwL4mU2((;R))23*P6FPLQ3qCS|Z2a#>c6RS=WAij|F;XgZuXzh5gKA7#lrQqp^J0GztD>1wzX$dqbT$_qC+;0!{>9tugGzgLqpLm$+w~W;? zDgY=ZRC2oT!P-OQuDkTA*e{HQkBn|A6GP0g3Phe3lk&rR6bAVX(bQ<})>oPM2fWB{ zw;QUTv6tmviV*(F;9I8joO*By)0zwsl8VC%Ey0yUq>x?`a=N_S^#UAY1{~>T8_7)cLD!eZWDtAqF5xu~Qphg&OMGN-&_q z?OgsIFaIho@=Dh~3V-`o`$=BF)wc6iOA4|4)!)%ZC6(K#e=C?NH_8Tsvjj6`>s{Kv zzkXvl1b5dVh=y451 zW+EA3I_V%js^Q(NV{uF3N5gx*F&ZpKpg=~%A7=|Wc{l69@g$u`J$y5{qHQ=90*$+J zmt8XK7smKlb}PJQL>M`V*~`C~9ST)M`#(*?tI&CQvErb8UwC~O-MBmV6!S(Czs263 z{k33S^&P?f{C9%(;*-LAZ0Siv=c|y0ff1>U7Zu2FoDJMD!fP2W zWd`lABEx#MjgdcSL(?bg45KhFdGd;*JEzu2!kl^a)o9F{pQJ7!vPouBappW@@VS}0 zPtVUBZ^mE=fI|yTkqmT@CO5ejU<5if?mPhgXBf5xP{qY46;%)PUr)ogQS{C5qMn#9 zCkspsC+6-zlYTU) zU;weHny21xGPMYFA+rpxh*T|h(h{GfOgr+~>GUIZAcu&LDKRgcn_4{8&M*1Z zSm9Y-m`4pelY)(F!Z@B}Ug-?S0?N&b8s(J?HM|ZdyEl^z@tx zZ*H!v$IAOpJ%9A{%pJ2+M-lt-9M~T+dE-|3<=I{Ek5}S?e+>gby=J^TeKU_RZp{bl zv*-$=z!o{SKWB`O--$N?U7hmOz6zv#=oi~1Y~Dr21IZ?+MG6*E3x_$P zIPaYkT7t}_n;vpI1iF}>Fk_1XnVA6K4}C?0^Kn_zh>r)`b4w!(-%X8}&60y+uVx#I z8VL$UOb21^AV&CAHL@Nsq39!hW+SWx!$&dbeth^sW1|x1}9;eqU{qf4uw zi>}VUCL0&un0=)!{k23(im8M#NN3>yV@W8W%{qW!&6NQJLX@_ct~r+nNaapfP@1S4 z2dN2Z7fW`B3>%%&xJyX4kRBnua`An{6a@V;>S;DX*pJW}^l}BE%=u=3=Vu3bEy$Tq zM-v&|0x_&JxJX)TGLoz?ddQ5e<3sB(vP`j9P0I#|dE)UsQdRSi2;S!^0#da12;PAuK6$0{|9 zt8>S@nwFB2bTqG3xwd89*?C6LmUY?z85#=tyfyDg<@ueyE30`OU3*t|_d6&GcD~T&pvfgacfvVv)_%U|>{u^4~4Gv@pb?Ni6 zJ#q-+6zJqmzGna+07t$Q@{L)baH*5((ehkAb67?i$;g02!Vw=A@;$OkjTm3q$#0}g zd*jA#6-JVcQS-(g^RcyJn9*w3E zxQWmSQW{pI`l*Bz!2}vk1cPhUt%)=jQQGUMCP{*-?})^X*zwA=Iva<@R687Oc}-I0 z@~;J9=egPVqiwfm-;zI&|872C{AvCxw(>>tO0m{TMO<;9IjCk_vjles>cB+@LKKlS z>|li93E!^Q2dH%-zB3*?fDwKa>Xh-QOW>Y?K4t9Tsxy6<(KG!LHa<{>4Ca9T90C&v z41~3aJ%Xe&Oygiw%H;)PQl2&#@9H6{dtD!4)9WF@1eIjsV8al@Q?!u-m`#Jj7>+mR zMl#`+Q4YAZHZDgp+YIZ=)k-@rLuW^4t+&0mqqp1-#&d5^XHRcmad2!+aj?B_WDTZ3 zMmV&4fv6NTN`^T_m_%WB5GK0B8A)>_W?LG-@(f8`l>hhd|Gj_J;ZU?#q6@G9l#f{;lNKSbSkx}U7W!AB&xo);qf&}EJ+`!GPa z*;+D$fr~sx@Z$VJ;_~Zi2=Ebve7CjYzd0!rDMOhD!fK7TaK7@^L4Ab@pm*7)dxJah zpei%B&L3)$8~w(Xp}$kg-X!B>GR6(9w6Y!;?R{~zG+Z$eY?H9thH(bhVFDiH#`F4& zk;HMgt`~RX3R9MkU-mbzyLmC`c~eY*2Lj7UfamEuXDj|UX`3$l=ScAzZP$7?0NQu! zF&s-E$^xbEnv!yOf@hcT>@K=upY`ONBxq*w?nP?BNdW8Y!kx3n@28J)Rp;cg$#I4G zbXmAVtD|&P%-uF@j~t*RQi3gA#>5)jP?Y#~BPjB9gF_ute!N zHX?GC@mLx}I2=WkCRs>kPBN8PA5-CrLTpH)Hfn?Ik~(lwz+GfRIjaoK4AvD%XhPmr zFeKU#u0dug^AfqGGSB&gBJ*<4wd?hu63~lm+^ey$wXTrT>#_`FO4-&1SSuKZ(*oQt zCn|ReVn;Miqv;B884mY?4kNLHx$C+eTDCZrQfdR%WtUWEAOak(1~dMGG}s2WmLx$MqCDmW!|JJ z0e8Qhc@uE$P;#`GvB**+_=MpdP%J2hG-#EFOYvvq>=@Zwa;G{2@p5)$WovgX3+-XX zAS+A7cua0_rXO9OD;mI<5oZRAlK;2{8k3tPbU2`VbY`S1{Ah*1l+0K_s7hvB*RRV8 zzrH4P0;qsE*d#M=q(mf^eIm08Y5QkLhs-pdfKDiRmE@ruu>B4tCPbjM=qz zI1}t{AK{&8@9!LFAM7c%uW8S=uk9S}9O)eGTr*f}9~>ITnnMu*Uuj)+ecmikIMcQ9I@* z?>1eK7U?MNw(CxF$76h9b7y8wFBX%eoyW{B%;z}e0x`%1|0{7MO;eR*do-MpB}z(5 z5xKdu(V5wWle6I&dRh>YbYJpLZ?Be-z=T6_9UA+6u(fQNa z^Jk?|q(!Vl>7l!>CGuFaGiT<{pIvH*fT`z7ZBT)aEY-c5QC%st?Uz&SwQ|8 zgLq}ImcM%bOOgju8BVy>e(U?VW2~s;J&N;ntBNOY3>K;SYo1(7rg3}2dz5bPZ`i-( zEoE?v|op!83O>MW4;t8U&NSrHkj?*2}1-a23%u)hu zS}^;Wf~T={=Yzc@$J(AkuqV@1!1^qKkS=UCI~}S|+)St`tT&pD1n{&rZ$31B(`47NK_rcR+{q z0TGDRlH^hvo(_jYK@cMnflw}P_gF4oCzX@H6l0Pf%e6@@)?UdqQpu@cFM7pPKQC*} zjGh$VgcO8T9v_4hGnEeP#{i$K#;egMd~oqY^I|G4j+GGqc4~>V2ki=CY%(A2XpljW zai)UHKW4&v?XKv9_SrV~k?@Hq>3Nj&!c_H3g=g9Pw@L>JZz|j3edRw7OFwGQmVeeB zR_^Ig--EdD#P@*j1Sv$qr3t9OaL7MN&XzQ6uoIjfA5;LG%Z;oj@)ZdHlpQE| zI?xVON#qY^R^*QeuTZ8Niu|FpkNLVtM!2*YP&z1)*_kz>22)F2f>PI(gdfhy`jN8m zqZI(IGh|)JHtN8};r598AMEG>5I2~VQY67tAM5STj)<_Z zT^+K1cPCbWXq{RC_J}AH?Ima|4yJpF+3l>_j;?BV6*S3?zG{DUpgP!}>sV9GcMMg7 zjfnN&OAqgnd+Ck&`2Ys30QN09j&~PnzM)!nqpJLbrf~!FIVN!s!uW zLZDqY;=J@;4yN7Vo?vy5lfwkI>EN);;mG>AbLmV8t= z${m_HJwxhl+hNB2TQEV)77kMbZpp>f6|bBW-TVIfJRPFR{G{N!nR2Tnw$eF%eD%sF-!^j>g^2&I2s7F#nj@vwZ{# zNoo1zH=^dZ)X$bg`_pc|j#bv7I-_kt&#a z(bK0XuQvDk_5UA9b#)mnIYC{PTfx`&BW9pzZ7LqQSAYNhrZR{ z{`A4Nv~*$TQVKXxLHH^fxL z4IHzn!hGx4^%!9NW|GR1&607!ox?;h4LX>_SxdA+#1v1gs2EWqDR5y`$N&;a#XZg{ z8Oq={l6XxW(EH)Px+uAd2`#_2j>xUEvlRV;S#^XXj!3+RcRb-4rEL4A| z%7=7ipsFXhxX|VfUr@k(#&SKV1K$!5G=yDes7V(j=6g_a#3=1cR|JUhpL8FgKwONl)%s-mI@Db*Jk@=0tf%dYo=ov2mC@=WPOOrl&y0L)bH2 zdSGB;>1Q~NKUHqObVBYgMjHgHGDqlCu~JUv1WP%vik74O0wb%(>_YgA#0;OLPZxN) zVh&19(H>1BV5$(xv}-5CQQ~hxkII?pf(V-91dm8}8}A4ur_FS)Kcd_X)1IB~bN+Dz zr%Z=dqj+CxPvUCG@m?S7`41D`M~$|BwXZX|d&4hg zrOyVXf3MogPt}fuJXud)a?%^_3?#RpB37haRiwIXm&k3=MinXzK_w!)37jw1B-9X* z<3!qO=RHD>6DyyD)L=kD*ehZ{lIFci`{Xm|*Zl{C9YiBRh!b{5*|kcC#o9E2a8=BV zB1d3CQaVmBN-?vJYy>_-p}{86igIO;9TDvU@}F{B2z-l;7wx*GwnDUBQN)8Cgc`-U zN$Ga3x0j=aQyAr8XSj)I%ce5ajTgz+z8z z_8;-eIF9d7y(&bWQZ%6$Rfu^8>8yyHCe9YjjZoj#xpuG>(OLX9vQ?ZzSr7Eh8Zlk@ zBRgM>-^c+u3Njj!S<)Q)6Vc>p`%03-kQkj-kja{CPd@bAVM(eG)*!M~4jwx;dv574 z5|WBj6=K5X>+2Q;?}qJ87Sa-(sf5*)%&heZfr&m5#Jm-&F6SlSC(HM0tZcA+Yv-{U z0*>gF%;ZT|Cj7*MhofO~SibXrOPU%wKDQ_}CYGJerFoQ(H+6xEXBTMPt0tvX_^98a zCM%i*5j0%Ypj#AcR-^Fgs^Wb9Uuqd4(NT|^>WUH)!JzY#fZ8K_TP{9OrjDeEe9{I8_Z)YBv` z?&euRe0v$gYA~Tv(DFBWRWS}w7YXq6md*ML=g}MFRcMizR^MYa0gwq88H+Lv)@)#W zB0vJqzL-D~iEy5L;j)`5e;@=u2lmImL#xU>I<$2S$jL#b0FJ+ z9zsgmbX$lxAQkJU^PQ68>dKP^Kr}PU(`cHCSs;c?%aJv$&K;YDl24~o38Q6ZS7#<{CP3)7UULuSi(! zDxBmvKI!$i)Ic<)DF;Bh6X0k8FG=qe*eD5%iijkZfVSFbkqLLYle%s6XBr_2Q)9O? zhKWCm*%y~!kL{qnpceW?iK!={LNMQZ#c&B=Y{?n3Xd^pK_ontNT@dgW#klWdv0q|l zSk8PQjeX_%5nzUbZNCz}9(>OCnWqpiV&5FZ--~X~ek^-={@z?vd{OTCrt(@M*T`E) z3{Mg=BGqLrDSDV6s4a`QBBi*E?Zp8$iY$V-f+V21_-giYZt4kmaMpWQcXA{03{6b9EQ0&KU(nktlQjU4C zVbt-Y8DFr@UZR#C^%}9G2nIqCA#{NhCFvWrOeseyRvK-{)zC&vKz>Cfnc-Is&xoZC znSAF-7_M_)`00KHsUS^0Qz4)w(J@Ck=EEGUW&Te6Q;p?Ab#M{?kxsAbs7l>P`QsEV zKei+#y|X7~&L5jyD!Lyxi8f7t&PR0o{FyU%yGVpowrI|3YjRlCqd1nlHs9zZYc*1| zSWsFa8kQxRH%uali|YK+iRo7FR66fAdbs3sxxk-{Y!~{|&+P&Y2x7z3S8-@X!#u8G zd0u(c&6}?|eDR5f?~(HMoW%O^no|Lj&-=?rMARxH65bHm4(I|4szzX=CS6Hl&#hAy ziD6=aKsAZ|mq-BWNuWjWwdZS9P^%c?z@7rAiiAuD^Gq5@t%E#GBE+V1#03yIz?uC? zeATv3r+HS45BS`Nf(@64C(6;%b-6z_Z?W;Wg9F(&hokwAhQ;D%!$YN4Mz-=>0{lyK zndT4+;wghT14_s+V^k{&R4bx*r25_&VPtS2HJL$?fA|#8r%i{Vh!By+ctZ6F96a5O zltt5n5)+%=GG9G-DN#1cwJ}VPYlbGKrdbJm)j@y{nQvadYq70jc}+!i{h;O3cPd0W z3~1}!u~5S zB(@-q-1JG7C0dr&M^13+`Gr8ve4|2L)+PD$D{Xa!u4=7ou&E`c&E?k8WggU4nf3zT zhHT=YUhwb!WgAOnG9H&aUJl9ZdP6O^fvJL{2RT<;o@rRW6p}h1=XFB__FRW_IN~3fX^^t|m6WIzgSZhS${RlcN zrHYVFmCpD)+_X~d;LSndW6K&egb4b`iuz}eHg+@cXv^{n$8QO0Y)pw+mZ_4I2VE9D zDOyevD=4`#5)GF|ama%Iatli&tN6K6-^ta<#lE5B-Cpeo4N-5Z8YIFfBQ;rpO0EEou94jbbh3Xxbz&jF}0_t4Ly*G!AHtAxs=tGVzEhQqZ?K zaIxjUvN2?1=B<^)lj>%c?qK*{tZ%&R?=_Fiz0^J_j$ei&^{4q~oBY#@L&et=-)~D_ zE*C4`Eo;cL1nNr2Gm{8kV%Wo1*e_q9C1?u`1WpB4<{YLKAzIClZwxtBNKJ@_7N~7m z0E05v>5yZE^kUk|^vTFIzz3fhl*BJ*W|JhZVTqF-X=B}}`02;^h%@6dy=??lC=+bL z0GczKYch>(fzMEoEb5PXqk*8GbPZ-M%_!#ElTVVbP=GVU4oEn-2(!9Dzg{(biR7xE zE5;|cNj_WoVs)*zY3ZsS3df<_iLP?v)Ep|343&{<@!(g7{l0_l=bMVak0s<-V74-$}Xe zr`%Ul?%OH%wUqlo%6-#w%sK0V_25*7O_gXZ37k@10;du_!ow<| z>uh2=6oFajb5o!{BCrvdM}w)yhHo^z(Vv;;nB2$gSA+OH+1s-(%k8OtGILyG2CsZlQEQOmx$9g`@{bkZ+Uv8z+IJ1jP7o zG1kWHPk zn1ApacLbfh-t?v$d$kJfVMmPwQc3#uE(Q!r;9ZacK){D{HDDy;SjF|ep+MtNus2ay zq!KU77>4;JD}_i_w3)tWtypz2C`;T39t&O1Tp9Hpc2OmiI)wrlzb^wmM}WO5J57l?)v%e0BEart*Gl^=iXtw*I3=fnrZEE=D(oTJ8lmQQiX5J(;ECPSntWz*J?Pz)GJ9 z87(sr$+SeIq6y{t+Ja2f&%tv8XhPXR4v-_IB@+#mG4HQsc#ekI8jtWKJ8RuzL4imC~Fr_t2W0I;(P{>J@MjFwqU zR>A>_1*Po>fE{%x9}nF2zk|%yG`z?g-SXFH8DaDh^PPx`d~DJXlb`zuWI)&`5QW7Y zMiK!cjiu6XA+9kHVI7h0AN+`Ni1nSsKYWOU+t-TWK(wY-n!vJBE)p_a0On#{G{G9i zH@>P&si(~M4}3?JqhX^LBaB#8I08UqlpBi#z#{C7y4gqi^5U)r!%460T_0TjZzlX* zJ05-5zRu=;5Pl;Uf4+Eo_Wh-;`EQoC6kl4tFI)P{&T{3GoobhN@k%0jS!&dggeM{? z13-r0JYXp@c{1OEP6mTMHEM2l(pdgVTByyWLk5H{k(|3lYVMIys#hRA`o$_Tz*vwO zlu)%bl8P`S+*;W>EGgt8g26EgY1@wc6YpT$jwahMW<*ouDaN^-oecV6ZdX?~Kl$Cg zeVP0oD)Z*|LbB70?uC8Wd+q)KqMt7@gEWdRB^NW!T{gs|5r&tq9p?F6F^<9495CyV z?+&(2u=!Bu1~!|+Ygt92-=<9ds?A$6=4x$t%{J;(1P{oJW^&hdP0_9%`XeV-sT=I~czxhKXC7amr*dpoXywPmJDJ1a(FXQ)LQ6a$c$fXGM(;)z8WS26LZokwIphDj-5n}i;rh9oSaq3Ad= zTAD1OaanE40o4uSo2h)+9J+)s3YZW{bDTaKLFl{~h-E()`Xq9#=V*)^8^%4q%w^1zfWgGrR!(!F4Wn@qUUcB3lT={r8&y~>8`VBdoH{trjMrm+kIGivXVx<%U8JzjKLB( zA_k3|Fy|Lew~JxJThF{vGOL%lSn&5k%A-_IOVL>W6l6u0*wyCF`ZX&3^Gb!B+Kc%Y zu%(nzlc03O&xnPy^Kx0lHyvMWEo!!sOrAzUHF(Vi(*w(UMrEf)t54sG8>I&W&0EqX z*|y3wej%$>gL8aEtwgkmI#yQVl2C(DtCr)VyflM9d+S~Dn%8fhT%+uLcK+;aVvGvK z4gddZ5I(bu{?$Y8kp~&%ZEh?MscE?`Z`U)pO#~Z)jbhZ;4qZi-MqO{MdIawiod0G^B|NR3sA`CS~Vlbb&h(;LMzs>DiA~K@3o>Yc!O)S>j

q z4fISO&U{yTOEent#W;!SP-CkKLwAB@%Vq4UocCMtI5+Ni8sfJtULiHV1>^jO?$@ONrdw7NOlDsHt-!#v$ zgRmZ1%?dnKg-#;vHM7&jUkH4{#x3>k2eok(?$)WXu0&)THXMt^4uG9Jc?8wyU}-Ge z5Xn;(6j6LYATN*63NubKjwlfr>?#=Xqd8EpH%J&-*?=^}DYvf?#gSuVJJWNIhXmtG zG`*}PD1M9(Gy?V=j#@Y%5}bs-?Eo-AFCW=)xtW zOHdu;Jy-N3JE5<-D6D#m^eHl@vmTX{dsH(Bvo5NR6Dgo5fV!F>8Hh&=q4@==anMDh zm4l%tn(&p#cCR%HIP|^{#{YqF@Kd=`{wqYIeIxg6O9c?sNSou)&_}xvKfC3TpbkSN zjlrv=B7Bxb7Ox08T9qm&mWsG&#M~&PxK7mZu!puekQYgPU|rbH$DJb#En^02gaev2 z?U;0PLmh0FY9F~_;YQ%1QAl=7$heSoT_pe_;#{RlNjCb({SviRv`x`O6QoqZENH}M zM7)b)J|A3kw8rD_1U_~3ArW~11&>^SjDvzVD*!$E{$D?P zw-<-}{N5@B-Cw6)qA9mZRmr@W%zO=k)5Tq9pxHHxQ2%|W<^9vSQ$Spgt+yOc`u~|; zEuXFY$=oGnz4@qCRIOPXNZ>75>0pBcoWsD?rk1tBCUk1PHOk%N{iqdDoC!l9jz-Yf z#1n&7jpB`Sg#2+fmnhVsVGOn5a5x&RwTt525w4Fm)H*Q}DzZ-JwRs(~q-U8!xz3y! z@p)0Tu89Mvx$cjs8|kk6QN*J=UFgPgXY!OsTrZhbd20Xy)98R$yp09IQSM z)VjMVf{`4WrCJYA>I^7A;R3kMAuWfDOM!q#GfJdQ*)5u4@MlWKE=IQ^Olg1*NlJa7 z^-y}TL=?FQ)G|CoVk~%JTGb5;A-=3QsP>1O!f0DLcj*UBe2*Q@z6#XK*V#)dU&hiQ zP96r(fC3*@U_*hi#c{-#%UEV{wFtU2elQ4$>zbI8R`5#v4J)`JR&zltH_j~|-I|;L zeTbfkr$a`Oy1Qff7?xO<(HLBs#d4F}GOL|D>kIs$_a={xyF@VKG!+#_v}zm321anm zc{#D~svTN_-DPu5!pgxvVzb7TbAq>|foDw4n0Vr#`^xZP<>=I8?xhlU_H5$Lz7gj0 zKO)5IXW?T@KPIa3-%jrOdbjd(QJ(#uOW*coS z=Pvn-iCU@H_U)A?<>%d-7W1% z*REl&RZJLdCasu!UY~ljc+5j~s2prL8a@}h<_pc?+$YUK__?|Inzbt0NREq5qWZnGdUEj zOq-B`5Ye|XMImJ&Ss^jG#Tf3X5obWVL5_~#5{3-XMG46jHGtFbdWm6` zC9$NspA@|e^RAS_M4~|(fF&G--zd#8@e+RWx;5B0??)u=YQt))FKdN)yE!c-4QOx4 zPic*ZFJI%zvQlAiiFhR~)myK+)#B~)0()0t19kA0{-s?x%qfw+ni@+-H5yJ1y2>x_vTc(@3E6HgB~?SCfXE5aI(K-k3*ad73S}nH z^(`_oLNgIZH43%W5KwEt_|yO^r~&g+%k!_mzamZ0-f=IPJb>!_s|<(J{8U+%ZJp$Z zgx6&bJf`~nhpo~vR#SUiI+gk76by;4e~aC+C3xtwZ1^SHAN{BOdz1TU_}YB@lk)A^ zf2_3UzgCHg->$ruTj)O3B>sm_RwICtPnFNJil3<+4Es*vaWe%;LmN;})L<@Coe)VB zyGf?BIhX0&lFJnK;H|n0$CFf7&PbkIrVsfvyM~dVFeC!(THFvBGt7O+w2vUuhNFx{ zg!}OIA{>=P*C$SSU8S1oT3@YYY8%@-GF_WG_>Q-Rgw$^%6sxkGPS$o%#Jw;{r}9&r zEW0z)YjT~t`fy?G?k5~y0=^0WE#RmUZXd)>V>&GCm&HqD!=(^x7lWDL@7!11{%}aF@s)Y7tISoLB~zA7%5EHrx~94~xn=>7;sFGn z7V*}Ib>54k^%8Im18N%%BNx}XiY(ij=x{%U*w)c@x$3jZVB2V@S*{&5mZxtWhu7X( z&pS#@I}O6fssRbhg1A!~PNN9oNACF6yKdIFTW@o@SjcXx4P^y4TeBcDK6jSM;SBB~ z%qOlcY!U=5_4k~N)Xig)M~pE^Tlxt^r>ygfv&S!xaMh3&*_eCPGEen1+{{~iHw3tH z)oG?RpV7}S7Yv79acXLw1dCj#_`--9KXa~oY2of8z7XZf)AJxk>9%G)OkXXi-WZ>tiJW=&0Eu;`y^}j{-hBsw-B@f zd#hJW-DcI+Fvg}h@CzxL|D7~|`P}*}Qoqd?cH<2okB_92L2d(Rns(rHF36wyoHmZ!dJ$wuh&p(<0>U zn2^H&P?hh*>!r<|BhMYPAVElR5%*fLR4z>y!l#<(>83V0Q7BDQsF$0s&bQi5n<@CH zwi_tFx%!RPEn)nVE0VAzgf);YpIC^VTnKVcE4;2hzHi-d_9N@=$reAd`5uWPM6N-+ z65{L%!Z}l;aLu}U;sIjA|79*$j~o=}OiAd10G}jTT@VRWOtQM*#2J(s7BV74oIx3J z24%z@FNgF+4oIV|K#Ay)~xTF5m*9w6jeA=e4HUdRK5+#uvZ zLT(gtlaOB!a74nNh9wy}BLLMRHkwSh+$Ppp83z-peRLC(Q z$A!!aIU(evkW)hDggi>f9YRhEIV0q(ka;2Jg#5CQ1tE(ERN$Hc7K1JzMl|D`B)0I9$=`)r7s?ujEeYVo)D1ENd z=PA8c>8~k$zS0*ceWB79DSffhmni*pr7uWBT zS1bJ;rLR%?yGmcH^!JqhzS7qzeZA5*D1D>SHz|Fy()*OYMd@3WzD?=dmHvU!cPM?Q z(swC+x6(gU`W~h4Rr*It|5)k!l)hi-pD6u+(mz%DL8X7D^v{+4h0+fx{Y#}ER{B>; z|61waDE(Wdf2Z^#NF1Pw zUg;NUp-FDw05rC(9{Z%V(a^lM7LuJjv9zp3 zf2j0FN`I{MCrbZa>3=BwsnVY*{ZFO;rS#`Y@6llwE7SZrsmNKJKX+sYiHL}_GpBPb zOcE)izl-OO%J0*9Is@9gPClC{stH4;!g zcS0GtZ*p?If$mfl7A3}d=JXO``msA?^GU^~U06CbbGEB_5ujZ;hTIT|&Ys+}BVh@N zmQ&1~@F$%+!3~xE`(#!31m*UzVs>Fkx_o-}#FE^X)425IWbF;SKa=cmdhyiUi6yx< z#Af{gtVWjRkL%C*)6(kf>`5RhWX0Jzy%A^KE5OgB^h|zt$XQ`_`QP3CcaQ(w>wl;H z?>;@8dsBGt=1xd&$=T7HAdfBK-MuWkHOU()XGQ6A-AG=%b93$F!i(`;6r&_w(a=M)@-QC&J-JLDn-PyW-OLup+ba!V&y1T2TySrMtyQ`(UyIPOe(%oGx-QCsF-CZr+-PO|Fmc#9C>F(~9?(S~s z?(UZE?r!Ps?$+bCba!`4cXzjRcXvy7TXx^m(%n5R-QCmD-90Vc-P6+DJuThc(|W#^ z?(S*n?w*$Jwlv?{(%ro+-QC;L-MuZ{-P_XLy}O&g?CIvkeKPyXxJ1dfnaRe5sm6s} z$pS;;k;#(ZM;CT9Ej4eOYTCB5X=zu}((b0EJ&mVJJ0hps*|?>tSGyaxG#y}XL|Zd%&cxU^62S+d_g zy=Uo?-m`Q`?^(K}_bgq~dzLQkX*xqwU-mWiWnW`orgk*;Wok!bU#506_GM~EV_&9r zH1=g`M`K^6b~N^7YDZ&Vrgk*;Wok!LUnZOSGTGFZ$)>(cHuYt)sV|dFeVJ_P%Vbku zCY$;)+0>V*roK!y^<}E5FH=o@nQH3GR8wE3n)));)R(EIzDzasWoJ`gb~g28XH#Ex zHuYs^Q(tyA^<`&MUv@V2WoJ`gb~g28S5sehHT7jzQ(ty9^<`I6Uv@S1Wmi*Qb~W{7 zS5sehHT7k8Q(tyB^<{TcUv@Y3W%n)-;uhvko?6n6eD2)rnd3;kA|EcypFS<}A)17K z<|?Ji22unkWsRf??pD!qaqclHnWCoXhVw_Mx~ommKICg$nos0!S-W)p*d20Yq)Iux z3YW5n-K#&5|`Qw&uJwcUp7Onp4(1 z-I`}ubK06$S@YZ0{Ju4>v*wsJi`M+ynkQRxk2SBf=C`c*b!&ddnpa!%5^G*$&GW2z zv^6u<+-J?Zt$CX@Z@1}_j+@T`#_X-$X3eG3LZsSn+N7g_^oLLcT{ODSry^2{0uQ!_)>thHv? z65nn{tr@ds+?sV1djhDIU_y{;t=UL6hBce5*fe-%_TT1t+~{i%dENFnk%e1V9h~m4q0>9nk%ij$`ZbAuD0eHYaU?D zwboo`&Gk4A@l9IuKx=NW=0Vo{f;A7b=Hb>n!kR}~^GntovF3JbZnowYYi_mX!PY#) zn%k^-s5QT6&5hRFWX&_J`BiHkWzBo6d9O8pWX&I2^FC|dZ_S@r^8suA)S3@k^Jmul zxix=b&4;Y{OKU!C&0ksb*Vg=vHGgZ(-&yk!Yd&ht$E^9dHGgl-C#?AgYd&esKU(uo z)_lsEPh0aDYd&kuKU?!TYd&wy7p(cBHD9vkU#$7EHUDbOSFHIrYrbmD*R1)vHQ%u2 zo7Q~Gnr~b49ZSfJ`JN@H(EPxfA6oMxYkq9aPptWOYyQKUpIY-XYyQ)k|FY(Z*8HwD zkFn;l*1Xi3*IM&?);!6Y7h3ZKYaVCK9oGDcHAk(PwPwzm6V|-Onx~1i%H_*CM&1ow zLqLvWNOX0K1dZw@u)7?S)x-;SCmq$TA*aDHq|Z18XRu>PRdozu!j36vMJskME08Zt zMR9v!svj+*HrJz-qiEBsWusvFY`;5T zKk)W?6$5tAF@tuEW7gOq#|+uEj#+Dm9W!i495Z4^9W!dj95ZIe9W!p%IcA+*@0j&= z!Z8zegJU+>jgHx9H#ugL-Rzjnc8g=S*sYG)YPUINo89i1?RJM_cGyYBOxh{OOxc}| z*=ctcYa9P=Q1qhoHgH#z1e`wNcw1$(n&Znn2L<`#RaV{Ww%cFcqA zLmcxEdz)i!vk!I5L+vj*<`?b59P=>yaK}8{KEg4Nu#a@iBkeCa=9lad#~iV@JLYyf zE;(k&o_EZ7d#7XWw2yYoqwQUexy#<| zn7i#`9P=3aSjRlpKF%?Zv%lh)U$KvO%;W779PV!2%d^ii_Sv3&wz1Fg z>~oBLu4kWX?DIVPJY( zRmT37XMfAs-}dZp8~bX{zS`K|@$Byy`x?)_#@OHW?C%=;TF<`L*x&Q)?-~31p8b78 z;T88ZUuW#=J^OlN-{9Fd82d)gzR}n>dG<}lzS*;HHugTx-e>GvJo^@7-|E@78v8cS zzRlRTd-m$D z>>qjdkBt3e&;GHo@AK^YjD5dn-*4=nc=k_>{eWjbVC9{gAPL>Dj+D_QRh2u(5yT*}pROuRZ(M#{P|G|HjzA_3Ymo`*)uG zJ7YiM*^e0eQO|zV*pGSkW5#~mvmZA^0lGW+_r`w0v!5{bA3XaH#(vVXpEUL#J^PQw z{*!0_$=FYM_EW}w+OwZF_A{RSjIp2f>}QSrXV3n#v7hto=ZyWlXFqT37d-m~W54Lx zFB|r9W546s?-=`C&wkg~?|Jrn#(v+k-#7LLp8bKbKlJPm zjs1~le`M^BJ^N#0f8yDn82j&@{dZ&k!?XWk>`y)WQ)7ST*`FEvpPv0sWB<#u|7GmY zJ^OQFE#KCd_(^DfIp}^&2$m}`+c)L@1@ScgtMK;f>!UsSFGShmSE3h1rJqR&Y=fjo z06>@{B09%=f)MD#005651X}{i^FkzAAAE4aJxYXX60!-YNYr-~j=&N^s@wC(h_(aP zcFJ-WxmXmk&6P4ikC0v=eM0($3ds9qo@(b8z4a{Hj+|SVKe;e-MxX#{ z+6XJ-QE(E}!&H^l1Vd`i6c7j<8ueGOH3X!;@d#}K{GdnB$fwvXE}rTTrlqZhMTw4$ zmnv~#q1Pu?KIfU4QrAp_vrJTPb@t)J6U|&VHSaUIr`c@UVuanb13C0Uf4nFc{F5Q- zLVv*um0(6?2ScIbUHkXfAM*bF>2s9#nQ|fOZOxJALNKs=GVwYRA8bgnV-4(YiRF&Q zG!=pZyR}@cC@GN92y4P{fHiHkl4MSRQI;XKM$*S5BSt_0RLLrm$ym)CEM@zA|3|`# ztdZnE{X10ZtW`)m6MQ0e4hMB>a>25-I>HHu93;U;-O8tz?pqt7gPTDz1BGCZZq*2D zw#&ZlBYc$LoP~ujJS>-(?QPh%W26{G*U)i*qYJ=~bJp^9IR^brjCpxHOONXP z)pKg#x9?G>^mnVCQ|^(dos}tfPC-{{zB2-Yz?z1;!IfldUF*U=LgKsE(~SXl+eTUL zA^krZcQjwEPL7mKu7%Bn zv}dZl4af-u?F*t@-i`qV)m92g6a{Ob6MTevZfQ3i0XvH5Y}KN3v0ig>c)8qzC63LZ zznI4Eqf-OrxhdHH4Fif)IeI(7U+&|iErz?%jP>(qLnk_;hW zRFVN4K@qrnNG;6tW?9!KH?m*0x~Q!J+#1RzW`-E!GiwV3@(&9~dFu=%i+TCS2x9yh=Qx~WMEzmI;puPKTKf714f|PYlost?z)hmvFpcaI$}oq z*Y$5)yJ_vlv8`iFE{1qRnPOm=WCZ+)LBU34rV{2I39ttrN(h_s8gDO4Sh^GkRErsF z6nvZDLh(t(lqQd}ltloJsm;(%Ktj21{BPu!9f21Bi$E(Cw`JORYH~oJa%B$=(gu)} zY-9=($V~~(D>s{BLjc&b*ZMoarJQIQpy_h|v`&8VIV)EY&6lb}N>{_;Ke6{2V$N|nTN#X!@V?kEWF5M+wyiN{(mic$~&bj&Xi@sS!SNds! zd9LJ^nd)ZgD92_OKbfvP@v{RbOs0O$AxraG{3BUemmoUaW`8<2@fX}WGE>jbR?eS2 zM^s9ByNZ5Uo8z~1 zGrKS^^3v>?b4z!tn4;3mvYauIm%GL^y|YFB$O93`Z51hRYcKmd;fm<00|Hlp~bJ!kG6T0exRnkv6cI%O-JLp+uVy_ zIvZEs=s$bY)3~-_44_j~c8$%53~x5Oz1$ylMqsm)yUQeS?u>Sofydh&Z7=UB?`15U zW+()udtYq#=j|osOXDn~-etk%aoZK-3t|7yI|(Zx6LGVpP`^U9zIHQ;qA|8lZEruPfhuFjO16-1lmC!5j+J(mwidy1=$#E|t%U zZN&V?sZ0!fM>d}u3vbijcIUH)Ky8p!sU+Al60T)2`AkI+9C3o+#(Z?JKRwbo5+TAd z5_z~gAEm1g^$Vm3lGt7+I9~!tk^3WafN6Psc(t4^-_|4Y^8=jxd=vEmqfVmyv(e4K zCl<14Z@?RgcRr!#~|29wRZ))i0U+m6TzR|69RxG|6 z2NsGl4n%tfi1v)YPL$!PeH7MsHBGKt(x4HCMgRpKmpXxb=)37InTZH_e3;ox2MCM#>6w`^N-QGQ zAb~)*uKkmjOkTPfn0W!xLogI*Pmv^*5d?b2B38OJ zh?|m_sY~)!DkLb#&Lo3I)MwL1beh#jwJZZ;Ckq^(XD+94IHT03k~4vt3zby)vL&H3 z2CY<}(%M8iHK3?~a>wr6DpVklP^DTGajR4)m@e(;ma0;Xb!JiK|Iy0S3Q@cqRNhd^8;}zH$ztg`AT2ukL#=45!TtNOc0V zFxMHSQO?&}Nnk93`Gw=4G~PAufmDN|Gbfu0QCAk{&YVM()`06XGw15)|4u7NfN~yu z>qBq61vAlX?Xbi%&z(IfYqUId?dI7t^LI9`31wTXyUfm%f2!*7ztNwfNn9Z4nQh7+f?!l*~NAb79ZeC~L` z;|IuM6VgDNgIb+Ay%?h{=p~8V=z(8LqKLRNa;+@gwInrT<^bTnt9*;z}j>V&1RBPwxMz0{tbtJ5cfHl5N5(}{E*$*~q=tbz);aYKs|tmB5kf-og(0LA3M+MKpmk|bEiouNUo*FKRCYv zt>TnclUP>7uniT$pESK}Y@|)9WUpL|KazGyLxa!^wHkHu=yXdcH=x5mzs6nK8;ZP< zmbyglz<;xT;{RCRkm98BwahQfkTJ}Co>@U*cpLL%R8hHIUMlRGye+JAe@>ux8yer( zv_W6Po^sdfUuW86u%XGa7d>q?+|X*pQ6X(9JtM9NMZ)Z0a8!y(YBTD?wqVi^uHXwz z#qmz+#36-IpTKkg(2hY9LlkL|r~|4rtXY%KY@=4B3Bgq37eEa}T+&mq^ieY+8YHzd zU`+%pTO@0w6=m;$v!Od1kk51{k&6-AIV_-QoX-T)IqIs49dfH{gB+Q*0Vk3TQp36y zQ=M8QKHb5gk?!ou%Rx;DxF^5Jne0f9rC2l)X$K;q3Z=eYHV~~8$yQy$AF-maqNXmG zQV+8_?TTgZ?UY`Kb&&c)B-A293)j)fkugP^14v@-PepgpKE`tFLWZouGqr1_qy2P| zjMMFMJN2qN+qr2qOs~}V>(Y9G-N4NDVEz?7Mf!y%6l(P&Y`od&ULZ%b%F%(|vb)$A z$j!`jUu}=1{Zh-Q8}QkF$>N2u zZNMtjo5ia#A`ro%`^bJ$a7n_5lt)37mjoH!MM@!pW?p-JBC~VHM(yay;F;#b+3@4J zdrkDE_$hJjE7ezb$GDN&3|jai;V*v=5VgDr!jO<-pQZUJEhoAQ`r-9D-&uYe7oN zfDe_)jEH_UN}J3WCeh5er~&KvJj2X-Xf`uJo%k@b0h-Nhj7spEpxMk;vGQ&cvR$ma zJEZ>Wq>w2gJB92LvRlX=A$x^P3)!DVfw)BUiAzOUzD&5wv)GTX5OPq=#D@aN;Z8PO zNl~cGRXw;guBIAP<{CRdR&6)r+U`NJY7GYC>(>mCRl9bWtXjFQ2T8APj3GDi8fShX z7X9Src!adt(J|6$L)4uIkFv`{EIZ$3$H}N&x1NmJi47#vZrnsNt=&vA?H0~?WGf2K z?fGq7_Y5?fIl7&`AKT83$G6jo*&U+roSd8@m9~qObCym$YHXBL+MT;dr5%DhvwKe_ z|1`6&Br@~U%_WzLI#GUxF(cTjpJ^^f+X=G-v|N$NKg%3YeJA^DU7gJm#=`37n%Y6c z=jWM&z*pt(HG<)p|20#)ilyfpbdfmDzrgIfP7a)Zp}7iJ!R!mo18V4yaxG~m;(MV#R`&BN$= zTzQ3g#9*fKO7n+$9%SkNCF@AEdHo-QQh(RF=s>F4j;C!z! z+fH(*-!&(Z%0V$Q?A$xJ}sYVKss+ssIiF>g1)qca4NXusZJ?xrWvJI!5> z$rRsZCN^gZbm#G+xxL%WvZ?J4O|U5wzQ^3dfR@R>*Bs==X8*`M@X7K_@_+1}Mz;Ds z^K>%~-rf7nHuS>lrmx@sz->2NcH{n=F1xvMOXb$XHy^&`@PiLOy*7XZDWl=Xbqf_W{A+xKY|wu_75Lk5W4#R3oiia$c4` zHN6h?z-W~l$I}M?8ecR@YK7jQFCHQNNVyGYFTE311y>SCTLP$#g1rI0ENGJ-*lHRK z4F}k{(pVBOZs~K8!sb1JHiaUDL>tNt!8{ftpwC7RBEs{$5UXCi0aBNW{p4MM>Y+m6 zvg2tG_K9Z52Md$t^bz#i*GpQ2=7kUA;l$X(X(CqoQwq`@;4E$4 zSX;|QiRfj(7WkS(ap{(YR54{k*x%knf|eed{G1oDbU#e9a-Lqhvsv4cqo&|g`neOz z+VIY!1APa-E$+gd^{vgNy8&in72|?%u3oyncrGs8y@vOXjg@98MEM(o7gK-0rEukP zbi^G-etuS?T5DU6yw{gfVQ0qLXfJX6xxcz`cj=y5trGr5AG^a|USE0C%1$?Hy@uvS zWlKTg4RLK{rwC<+jOItAQo~ZS$h548vIf?9R?athoZkC}han5Jy9r%Wd z^K@U*oft5g#53ccG$pI_8j{iV73e0+14LluXJ#Uylz|%$kn#!E=*}8816EU(!(bYU z$*^@{Xl>zFSr6w!X*dc#*O!o--n_X9l1jvar3i$uH`g|BIir%xdYVHe&8r*v9lDxm zE4I_Pjnz`0W+^te9&BN5dMr)x;2wg@^;`GQRa&`@TvBR)jZ*NO1~Q&gsT{_-i*-Nyae zCETx4Z8TR2RQOvh>o%iEew!i+X3HA47QHoWSUBuWjm+vq`yRhAC^0Ax_ndgBJETg z&uE`6F>#GQCQ;^O`Y~k`W1~%*+PRD(kx+jiyv3X8?53_k%bfzKL86N%uBJrpV&#I@ zX)_00jC0S(lymB+XTtZ+*P{{YXMD@0`;9bOW@}7#tUPkF*P~54ZwT$RwSR7)3vEB9-fcryh;xT5CG)Zu0m z1mp7P&ic|ym;xlycJ~;v{f7Bk<$@_@8Hf#l2AyT>jR*Hu8+{WY-X`)XFq;`4GZ%%W zk~h6KWQ~1m^+q}^$_rVnp}b-b?rjYdzllM`ug7$h@tmVMG82t~&ZX(o!S5D=s25`* zG%Zh|y%%kNr>y@MH(L4EkbjT&{CNKKc5ZUUV*a)H(}Sf&v&`h(#mDc;;Bp&N{FO)}wgH=Z;G^6&XKsjIdFLyuHdZ+|O^!-AAnWuc<9W;q#x%cSH-a)xhUf<6#G9NNuL{g=bwe-H>C3M~_1DVV9*KKB-ShaB?!cR+4UcS? zwZh1T1BPdc?!bsigxd&b{cZXx+ST ze#E)CVr@ikS({#1sYGke1+KTE2hn#-P!&(hNj5mF->>+UD|+!Z$IvOeYu52zK% zWLJW|^bgHPW;u_gc|I{Ums7Dn<=pLApGDt`K93NvtT|tp)|bMAsaW5q(jS=SS1GTC zgK^eCfuEdI;SSh(74C!p=__J=G!yPv2zRa^+^H189oYD8h?vDz(NLN@%5q12xgB!9 zx-B<&BsbFBA{tUgKgcwJzr!~W`G*ycARZE#(Agj|FlG+h80()%jR2D&Fj6N)rmglq zac@n!b9HXSAFz0B({xFA*bS08&IhCliADA!8q}9W%p{KpHZ2oH!8gbwr1j+pVSryr zbKv%{$dSs3G$O)}TscA{L9dErO1e`dQ=vGTBWEg^YHgED3IAIYOYccyDeZBIk$0rM zg-A%a^V)MrR$%fQuML0%vjh%ii-^fgy94)iQ3HGT3I6=xj~GcuB}4EfO+~tKQBm0r z7nY*jxUd!D#)Xs0Zv^q9!W+>+7w8aCUMjDtz>Q0VH3`V1WI&;6CT@KW>(i(G){Q@K z#Nngx-wn*{8*Qkj{Hcr~8c1_VH%P81>BdWGF2S?OlR^`Nif8-gus$3#s2=)&w}j?l z#7IN$~{7 z?3C_0&TP;P{BGFo^H|wt+6o}hRt3R}u_A)nF)=-yjK>p|JwhE`viqWx+WTrb$wV@# z76ME}$nt(SnOTxOYAayv${b>(^n?FQGnrCUW`IXDBmO81d))|Eb$$wbqvtzQ_RCJU zF8pxoaA2LC>WCraCf+ZnL)Wak$zG6=fbWEB8y^h)$ZujJxaNmJ?fBWd@h0Yi{bs!r zR2$I%CWPzB3~oC8bK~24L|p-i^Yfsxm-gMi?PK4z+347Kz=G?X?88&&O$}>y?5>2F zgt^X&hQ%FwQo<^PRh?zR%#QZS9$U0}T$^ym`GD|B9{!w!TZG%rH7&zU_5;8p&>_%r z-XjpVkXIz6N=U_dH!x1x9s4cU-?fTNpisFH&fiB57t{4Kl9_~9OVob4srk+vIi$6lsl?`P^lz0Y=A5`v(| zo=x-8m%BUhSEiaE(XN>6s1=gWuazcBe2lS>o&?IndNSV-$H_$xOxgk%3EYKZ0S$!Q zp=xMo{;uy$mI>45`S6?LdsFs1&R)M}AM`;LIVGpTvOLzIwz^K8jk@$LT`Q@VHz{7U0Q8yXCi3!H~4uv`b+cq zIiLdRC#*$Qb5Dm7{NxnxJB!&o(;DdZJjJv8Ch3Vqh>P4tcGx!SrknS2ER1Z)}rP-4hq;P#Z&g0GgfCR3Gv!(pT*HY z!*8&Rdh8NB)=~PGaSa->l;R@qsW7k?qd=BZ-)AJ#<+Mcz%c;w9TKD2H=ARZzDg%CQ z+*v^>Pcvv5zGgb{G0rD+`?;wsH4AXxK4VCl4%%2+e~u3NkvR|;m)*L0dUl!q<0^0w z{kX#3cI(B^%FCz*cC_;WDlJHdvk`5ZYGntxzu=op-t!P}mVmL87TCn8@f}U- zRb;*Xv3Vw{%+I2Pk=2K=bZ)k|uB93nr=76QT9x5dCg{L!rskq+?ilSN9{tC ziw_XcAixoI3uayWs1oWdCI#581YWC}%$H{+_6{s2z_?X$0VEW7BO4#D2wib&DG+Of zs&-BMw9zJkT_J%5#9`u3+XrP0Dhc490dz35U0_X)4Zfi|+nR09c4i0UIZWekYB^Ag zBN3jPTh`E;{iW$UKQS-3e}|Da?|-Zhs{d?1@c-6v!e2Q*x9Y!Jt2BPEhN>sEWA^~y zxkE)j?l?UN@~TR)h6_6aZ&U0FYoZ_P%NG-!j06#(eBrKItRRrMA-7HWiup2I;0dwL zhWUg6)-bQq<-p*9N27+yFxnJeWd!HsUZd8m#kE$gEjpus7?5MrCK1+)KpUyBc(;~+ zd=QtD24O#wqk44`;6!?b9G=sO{L6vn?!DEmkJmRo%y8zJL^ro$a3za;T?NAa^6HJ- zv&XLA-nj26BMv6rHc^v!SH262hcatiqASRi1d8;B%Lf0Yvg;Yz9r;Ike|PED+6`d5 z>7AgJi=;S(mE2JtnHE|^XC*6NF*n4ZxZ)>pI}2f6KM#3FMl&Pzq>qr}lRgaj6F)M7 zSprU>YGe;Q3?pP!mT$=S)-s?|d-=wOGWfEd6DD5v=!|9R9!ul1?vgD3^_PGCkMbMD zHORxU8{>!0$nSsu;%EQF3x*jGDz5H_F<|0N*>!M&VmUM1;uN>(R=KS>)7;_^cSvtd zXAdXg%A`H!?8b?m>ym`|MAms(5S8`fo)G7`9BDw~;9Hjk+GRK59Crc3YAD%ma?~kjJFCuFGQN>`A*Ia-{<%(heAKBPL!PqRQis zIQX^O=Qg%Id~I82dfPh_qY&lJ^rhqaIy30&OyTpORZ?MT)C-H*TQ5@lrt$mxOOx6D z%Kl~R$L7CV_J7(poquuu&2fJb{B`L4aOexO`orO){?CU`hW|1A5B2&l_ca^8+NTp5 zfFHC_a#b-Xj$s5N99a#k`$B1KHwTL{{7-- z2tV!`wdZXa7Slf~5Yx(e@_mDtw_T(C4u1J=ct-0s_upm;RSz$_phja<=F2hRvW?61 zKOrUtCP9H4dziP3Ddw~kb2`JZ%h)Gj2c>fkNyj{07mTR@Igd2Rd$dW|F##RV3Pki- zL5H3g;ObnDuXw-V22FFtq5sJ^%7d7wb=zYqa3^xk`Mj-U25c1f-437U5wyu{T~a?Y@p zFVN6%#e~-puP%JUXy3dvZ+N#Z0|&EPYxOHe>%*(B8m&98!fU&W!mNGo4cgLLfAfe@ zyY&{Yhwa~g8z85*h&4>xjz5auHN1^)5>aVwzWKgkZCwY^XgyfwyZxP|$Y_7Obi-&r zT%n<>$^E9(&>SxtY0r zbJKJC=c{MWoIA5{?ZV=PrRmd)mtI{1D#ii`(N@LfA+w79sKZtuh_WcMuK>R#aBI0? zD*!4gaPjI5_Rgzimn^av1_FmAg@}Vg&OruY99;$EnEs|g0(Jz4DACM=#DVxCgF^mT z%6khi&f#TU=8txT76($|yoR=vK&dNVw(-nJTSG;VRYXG+HES9a)HQ{q;|K>1TONZ2 zUXcum2Wks{R=lxX<_BdUFepDJyxG)6j6;E-Gk!i_gYH25fuZ5nyfVX&%}*N8Ab zCHPO$3fRwUnFJ67&NK2F90<~aq%hgr2+*(uI0Ou63o6f{_t_}nCY3~t0*i>&ui4;z z(O=TDI>_=P;fxx)NBC^$_*FsP`T4L?0ZNkcC5no)ioY>UfH@a>+v~``C@l>P9}TTh z7HtMMN#iIg>pL=W$6rUFei_;?CCr$1#?Y#CM@nU}P!(`kilPK*rSt%^i;$EcD`{@X zI$L0cH?1#?7?g+% z_*4!r1G=h`X6vCkj6}+xX6#csEc7uO88R!^S1dq`%`Boow?s6h1nI)RAn=bI@Uyk_mbNn_+$}DVxao@2+j(GT`I1 zi|QEsSi33s9Wg!A*{S>5J9J%DTe|5e<^^Ljf+iERlGuQWDFonWz5kOIox2oThsAna0>;+AS@Qz6%&!B7wPT^K)@3zMi!Nc=d4q*g*nao&JO7eKbG@?+*mJaP<2jk=2`L` zxHd02=j2p?k{o;#gt_okGCTl#3|OO;c$WzM;nm2|5cQ_)600@BpTWDPIQN8WSxbEW z2b?)Wr1)}5d1@jylSo{ha-vi|e8V0AM$m*IjyV^2o3I(#VKzC#B`xp*1}8}D+DmKj za|%F%LZ0asg>XPl+Hf-^gORj4iKuppFK!ewh0!OhU-$BD8mCbNBi{v^UL zVdXF#v)jHTH_1d< zK;`(2!}IgV3>=YwFF2?D#`$@q%rM^Lqu31O0rb;_HE}{jZam;Cd{N^u7}f{@AZT_JlnhUjdWMFCgoIu{wn`u~|3HFCe71 z;M!s;$BGkq2DF;pWGYoT&f3G?k&>PLQoNA(EAWovQ$mmPiv<^V8WcyRxI4QTpGGn+ zy2u2&WB%(-o0_R9ieJ zR2=gQ1=zj;Rs?PCPI3TUVGc)#Xt4@oyb~u+!8oeK4LcA45ijvebt}el5#Is>I#l7P zZ+vS=1>*xG?Jtg!tCWn;Ixm=WoA|kX?>OSZ^JTItNq_CRYb4PR&+5+~hxj_15`?5TDoa z8=x%W_{|Ui4bt@_97gh24~BVHx6d7aM8%|JT1gH;Y+^U`P6OChO25tHp)HUs)+9TA zD}EdC0u#?&bmZfA#2^?`u}tk)Lg3|eqV&~;fomZNk!4z}N5LsGeq`rtL1tbINcq;t zQTvkTynfLA?5C#pXO$1Czhn9SA6Rbqht`Su->aB{Wm2RYSs`%TIC{#o2Zzn5goZ^m zm~p1p;10kuYuLm(P3XmyIilJmosFq6WFnM;Y)DjPuUzY{|Qeb{n2aA=%|l| zRxXAp4zMCJ*bpO)szmf~D?j0u&y{+j9LP?T7t*8GOQMAIaLZo1x3;z8c_KSkZr{@D z!E0#?>BjPdt<`+{zioTH?aav2KI!ut@0WVJAZ+eAY7By29l@Y5d~VhWfOyEne3^a4 av;=@8b{;Wi8DPI*kBwUsgLb-yG5!}EFRoVr literal 0 HcmV?d00001 diff --git a/frameworks/rooch-nursery/sources/bitseed.move b/frameworks/rooch-nursery/sources/bitseed.move index 2ccd3f4eb0..8ecfc3e7dc 100644 --- a/frameworks/rooch-nursery/sources/bitseed.move +++ b/frameworks/rooch-nursery/sources/bitseed.move @@ -495,7 +495,7 @@ module rooch_nursery::bitseed { let tx = option::destroy_some(tx_option); let input = types::tx_input(&tx); - let index = ord::input(inscription); + let index = ord::index(inscription); let txin = vector::borrow(input, (index as u64)); let outpoint = types::txin_previous_output(txin); diff --git a/rust-toolchain b/rust-toolchain index 9c47a6d326..6cdeba3855 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.77.1 \ No newline at end of file +1.77.2 \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8ed2fd2b6b..8c10d70dae 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ #we keep the `rust-toolchain` and `rust-toolchain.yaml` #because some tools dose not support rust-toolchain.yaml yet [toolchain] -channel = "1.77.1" \ No newline at end of file +channel = "1.77.2" \ No newline at end of file