diff --git a/Cargo.toml b/Cargo.toml index 99e9d030..d2775243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = ["proc"] [dependencies] ahash = "0.8" base64 = { version = "0.21.0", optional = true } +bitflags = "2.3" crc32c = "0.6" hex = "0.4" once_cell = "1.16" @@ -41,8 +42,8 @@ anyhow = "1.0" base64 = "0.21" criterion = "0.5" libc = "0.2" -rand_xorshift = "0.3" rand = "0.8" +rand_xorshift = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/models/message/mod.rs b/src/models/message/mod.rs index 73758d16..15c74cb4 100644 --- a/src/models/message/mod.rs +++ b/src/models/message/mod.rs @@ -14,6 +14,70 @@ mod address; #[cfg(test)] mod tests; +/// Lazy-loaded message model. +#[derive(Debug, Clone, Eq)] +#[repr(transparent)] +pub struct LazyMessage(Cell); + +impl LazyMessage { + /// Serializes the provided data and returns the typed wrapper around it. + pub fn new(data: &Message) -> Result { + let mut builder = CellBuilder::new(); + let finalizer = &mut Cell::default_finalizer(); + ok!(data.store_into(&mut builder, finalizer)); + Ok(Self::from_raw(ok!(builder.build_ext(finalizer)))) + } + + /// Wraps the cell in a typed wrapper. + #[inline] + pub fn from_raw(cell: Cell) -> Self { + Self(cell) + } + + /// Converts into the underlying cell. + #[inline] + pub fn into_inner(self) -> Cell { + self.0 + } + + /// Returns the underlying cell. + #[inline] + pub fn inner(&self) -> &Cell { + &self.0 + } + + /// Loads inner data from cell. + pub fn load(&self) -> Result, Error> { + self.0.as_ref().parse::() + } +} + +impl PartialEq for LazyMessage { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl PartialEq for LazyMessage { + #[inline] + fn eq(&self, other: &Cell) -> bool { + self.0.as_ref() == other.as_ref() + } +} + +impl Store for LazyMessage { + fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn Finalizer) -> Result<(), Error> { + builder.store_reference(self.0.clone()) + } +} + +impl<'a> Load<'a> for LazyMessage { + fn load_from(slice: &mut CellSlice<'a>) -> Result { + slice.load_reference_cloned().map(Self) + } +} + /// Blockchain message. #[derive(Debug, Clone)] pub struct Message<'a> { diff --git a/src/models/mod.rs b/src/models/mod.rs index 124d139f..5eac9c4a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -14,6 +14,7 @@ pub use global_version::*; pub use message::*; pub use shard::*; pub use transaction::*; +pub use vm::*; pub mod account; pub mod block; @@ -23,6 +24,7 @@ pub mod global_version; pub mod message; pub mod shard; pub mod transaction; +pub mod vm; #[cfg(feature = "sync")] #[doc(hidden)] diff --git a/src/models/transaction/mod.rs b/src/models/transaction/mod.rs index 4be958c4..136d6b0a 100644 --- a/src/models/transaction/mod.rs +++ b/src/models/transaction/mod.rs @@ -1,4 +1,4 @@ -//! Message models. +//! Transaction models. use crate::cell::*; use crate::dict::{self, Dict}; diff --git a/src/models/vm/mod.rs b/src/models/vm/mod.rs new file mode 100644 index 00000000..c1b9b0ec --- /dev/null +++ b/src/models/vm/mod.rs @@ -0,0 +1,5 @@ +//! VM related models. + +pub use self::out_actions::*; + +mod out_actions; diff --git a/src/models/vm/out_actions.rs b/src/models/vm/out_actions.rs new file mode 100644 index 00000000..83f83d68 --- /dev/null +++ b/src/models/vm/out_actions.rs @@ -0,0 +1,266 @@ +use bitflags::bitflags; + +use crate::cell::*; +use crate::error::Error; +use crate::models::currency::CurrencyCollection; +use crate::models::message::LazyMessage; + +/// Out actions list reverse iterator. +pub struct OutActionsRevIter<'a> { + slice: CellSlice<'a>, +} + +impl<'a> OutActionsRevIter<'a> { + /// Creates a new output actions list iterator from the list rev head. + pub fn new(slice: CellSlice<'a>) -> Self { + Self { slice } + } +} + +impl<'a> Iterator for OutActionsRevIter<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + let prev_cell = match self.slice.load_reference() { + Ok(cell) => cell, + Err(_) => { + return if self.slice.is_data_empty() && self.slice.is_refs_empty() { + None + } else { + Some(Err(Error::InvalidData)) + } + } + }; + + let action = match OutAction::load_from(&mut self.slice) { + Ok(action) => action, + Err(e) => return Some(Err(e)), + }; + self.slice = match prev_cell.as_slice() { + Ok(slice) => slice, + Err(e) => return Some(Err(e)), + }; + Some(Ok(action)) + } +} + +bitflags! { + /// Mode flags for `SendMsg` output action. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct SendMsgFlags: u8 { + /// The sender will pay transfer fees separately. + const PAY_FEE_SEPARATELY = 1; + /// Any errors arising while processing this message during + /// the action phase should be ignored. + const IGNORE_ERROR = 2; + /// The current account must be destroyed if its resulting balance is zero. + const DELETE_IF_EMPTY = 32; + /// Message will carry all the remaining value of the inbound message + /// in addition to the value initially indicated in the new message + /// (if bit 0 is not set, the gas fees are deducted from this amount). + const WITH_REMAINING_BALANCE = 64; + /// Message will carry all the remaining balance of the current smart contract + /// (instead of the value originally indicated in the message). + const ALL_BALANCE = 128; + } +} + +impl Store for SendMsgFlags { + fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn Finalizer) -> Result<(), Error> { + builder.store_u8(self.bits()) + } +} + +impl<'a> Load<'a> for SendMsgFlags { + fn load_from(slice: &mut CellSlice<'a>) -> Result { + Ok(Self::from_bits_retain(ok!(slice.load_u8()))) + } +} + +bitflags! { + /// Mode flags for `ReserveCurrency` output action. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ReserveCurrencyFlags: u8 { + /// Output action will reserve all but x nanograms. + const ALL_BUT = 1; + /// The external action does not fail if the specified amount cannot bereserved. + /// Instead, all remaining balance is reserve. + const IGNORE_ERROR = 2; + /// x is increased by the original balance of the current account (before the + /// compute phase), including all extra currencies, before performing any + /// other checks and actions. + const WITH_ORIGINAL_BALANCE = 4; + /// `x = −x` before performing any further action. + const REVERSE = 8; + } +} + +impl Store for ReserveCurrencyFlags { + fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn Finalizer) -> Result<(), Error> { + builder.store_u8(self.bits()) + } +} + +impl<'a> Load<'a> for ReserveCurrencyFlags { + #[inline] + fn load_from(slice: &mut CellSlice<'a>) -> Result { + Ok(Self::from_bits_retain(ok!(slice.load_u8()))) + } +} + +bitflags! { + /// Mode flags for `ChangeLibrary` output action. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ChangeLibraryFlags: u8 { + /// Output action will reserve all but x nanograms. + const REMOVE = 1; + /// Adds library as private. + const PRIVATE = 2 + 1; + /// Adds library as public. + const PUBLIC = 4 + 1; + } +} + +impl Store for ChangeLibraryFlags { + fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn Finalizer) -> Result<(), Error> { + builder.store_u8(self.bits()) + } +} + +impl<'a> Load<'a> for ChangeLibraryFlags { + #[inline] + fn load_from(slice: &mut CellSlice<'a>) -> Result { + Ok(Self::from_bits_retain(ok!(slice.load_u8()))) + } +} + +/// Output action. +pub enum OutAction { + /// Sends a raw message contained in cell. + SendMsg { + /// Behavior flags. + mode: SendMsgFlags, + /// A cell with a message. + out_msg: LazyMessage, + }, + /// Creates an output action that would change this smart contract code + /// to that given by cell. + SetCode { + /// A cell with new code. + new_code: Cell, + }, + /// Creates an output action which would reserve exactly some balance. + ReserveCurrency { + /// Behavior flags. + mode: ReserveCurrencyFlags, + /// Reserved value. + value: CurrencyCollection, + }, + /// Creates an output action that would modify the collection of this + /// smart contract libraries by adding or removing library with code given in cell. + ChangeLibrary { + /// Behavior flags. + mode: ChangeLibraryFlags, + /// Cell library hash. + hash: Option, + /// Cell library code. + code: Option, + }, + /// Copyleft action. + CopyLeft { + /// License number. + license: u8, + /// Owner address. + address: HashBytes, + }, +} + +impl OutAction { + const TAG_SEND_MSG: u32 = 0x0ec3c86d; + const TAG_SET_CODE: u32 = 0xad4de08e; + const TAG_RESERVE: u32 = 0x36e6b809; + const TAG_CHANGE_LIB: u32 = 0x26fa1dd4; + const TAG_COPYLEFT: u32 = 0x24486f7a; +} + +impl Store for OutAction { + fn store_into( + &self, + builder: &mut CellBuilder, + finalizer: &mut dyn Finalizer, + ) -> Result<(), Error> { + match self { + Self::SendMsg { mode, out_msg } => { + ok!(builder.store_u32(Self::TAG_SEND_MSG)); + ok!(builder.store_u8(mode.bits())); + builder.store_reference(out_msg.inner().clone()) + } + Self::SetCode { new_code } => { + ok!(builder.store_u32(Self::TAG_SET_CODE)); + builder.store_reference(new_code.clone()) + } + Self::ReserveCurrency { mode, value } => { + ok!(builder.store_u32(Self::TAG_RESERVE)); + ok!(builder.store_u8(mode.bits())); + value.store_into(builder, finalizer) + } + Self::ChangeLibrary { mode, code, hash } => { + ok!(builder.store_u32(Self::TAG_CHANGE_LIB)); + ok!(builder.store_u8(mode.bits())); + if let Some(hash) = hash { + ok!(builder.store_u256(hash)); + } + if let Some(code) = code { + ok!(builder.store_reference(code.clone())) + } + Ok(()) + } + Self::CopyLeft { license, address } => { + ok!(builder.store_u32(Self::TAG_COPYLEFT)); + ok!(builder.store_u8(*license)); + builder.store_u256(address) + } + } + } +} + +impl<'a> Load<'a> for OutAction { + fn load_from(slice: &mut CellSlice<'a>) -> Result { + let tag = ok!(slice.load_u32()); + Ok(match tag { + Self::TAG_SEND_MSG => Self::SendMsg { + mode: ok!(SendMsgFlags::load_from(slice)), + out_msg: ok!(LazyMessage::load_from(slice)), + }, + Self::TAG_SET_CODE => Self::SetCode { + new_code: ok!(slice.load_reference_cloned()), + }, + Self::TAG_RESERVE => Self::ReserveCurrency { + mode: ok!(ReserveCurrencyFlags::load_from(slice)), + value: ok!(CurrencyCollection::load_from(slice)), + }, + Self::TAG_CHANGE_LIB => { + let mode = ok!(ChangeLibraryFlags::load_from(slice)); + let load_hash = mode.is_empty(); + Self::ChangeLibrary { + mode, + hash: if load_hash { + Some(ok!(slice.load_u256())) + } else { + None + }, + code: if !load_hash { + Some(ok!(slice.load_reference_cloned())) + } else { + None + }, + } + } + Self::TAG_COPYLEFT => Self::CopyLeft { + license: ok!(slice.load_u8()), + address: ok!(slice.load_u256()), + }, + _ => return Err(Error::InvalidTag), + }) + } +}