diff --git a/Cargo.lock b/Cargo.lock index 673f10ae06..76d6081afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,6 +1211,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + [[package]] name = "bitvec" version = "0.20.4" @@ -2323,6 +2329,21 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -7242,6 +7263,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "nacl" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30aefc44d813c51b5e7952950e87c17f2e0e1a3274d63c8281a701e05323d548" + [[package]] name = "named-lock" version = "0.2.0" @@ -7414,6 +7441,7 @@ dependencies = [ "num-integer", "num-traits 0.2.19", "rand 0.8.5", + "serde 1.0.214", ] [[package]] @@ -7949,6 +7977,8 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", "hmac", + "password-hash 0.5.0", + "sha2 0.10.8", ] [[package]] @@ -10252,6 +10282,7 @@ dependencies = [ "accumulator", "anyhow", "argon2", + "base64 0.22.1", "bcs", "bech32 0.11.0", "bitcoin 0.32.3", @@ -10287,6 +10318,7 @@ dependencies = [ "strum", "strum_macros", "thiserror", + "tonlib-core", "tracing", "xxhash-rust", ] @@ -12465,6 +12497,28 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "tonlib-core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9728b9aa2d57302b2b028672af98caaa25c77b32a04f936f9b5e2bbce8f8593a" +dependencies = [ + "base64 0.22.1", + "bitstream-io", + "crc", + "hex", + "hmac", + "lazy_static 1.5.0", + "nacl", + "num-bigint 0.4.5", + "num-traits 0.2.19", + "pbkdf2 0.12.2", + "serde 1.0.214", + "serde_json", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "tower" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 0670a8c9f2..b9fb763332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -234,6 +234,7 @@ tracing = "0.1.37" tracing-appender = "0.2.2" tracing-subscriber = { version = "0.3.15" } tungstenite = "0.24.0" +tonlib-core = "0.20" codespan-reporting = "0.11.1" codespan = "0.11.1" diff --git a/crates/rooch-types/Cargo.toml b/crates/rooch-types/Cargo.toml index eca447d429..95fa4ff8f2 100644 --- a/crates/rooch-types/Cargo.toml +++ b/crates/rooch-types/Cargo.toml @@ -43,6 +43,8 @@ argon2 = { workspace = true } tracing = { workspace = true } xxhash-rust = { workspace = true, features = ["xxh3"] } lz4 = { workspace = true } +tonlib-core = { workspace = true } +base64 = { workspace = true } move-core-types = { workspace = true } move-vm-types = { workspace = true } diff --git a/crates/rooch-types/src/address.rs b/crates/rooch-types/src/address.rs index 6e46c018c9..c8d61027a5 100644 --- a/crates/rooch-types/src/address.rs +++ b/crates/rooch-types/src/address.rs @@ -93,6 +93,10 @@ impl MultiChainAddress { let pk = NostrPublicKey::from_str(str)?; Ok(pk.into()) } + RoochMultiChainID::Ton => { + let ton_address = TonAddress::from_str(str)?; + Ok(ton_address.into()) + } } } @@ -122,6 +126,10 @@ impl MultiChainAddress { let pk = NostrPublicKey::try_from(self.clone()).unwrap(); pk.to_string() } + RoochMultiChainID::Ton => { + let ton_address = TonAddress::try_from(self.clone()).unwrap(); + ton_address.to_string() + } } } @@ -987,6 +995,9 @@ impl fmt::Display for NostrPublicKey { } } +mod ton_address; +pub use ton_address::TonAddress; + // Parsed Address, either a name or a numerical address, or Bitcoin Address #[derive(Eq, PartialEq, Debug, Clone)] pub enum ParsedAddress { diff --git a/crates/rooch-types/src/address/ton_address.rs b/crates/rooch-types/src/address/ton_address.rs new file mode 100644 index 0000000000..c9d6c6809d --- /dev/null +++ b/crates/rooch-types/src/address/ton_address.rs @@ -0,0 +1,128 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use framework_types::addresses::ROOCH_FRAMEWORK_ADDRESS; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::IdentStr; +use move_core_types::language_storage::TypeTag; +use move_core_types::value::{MoveStructLayout, MoveTypeLayout}; +use moveos_types::state::{MoveStructState, MoveStructType}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use crate::multichain_id::RoochMultiChainID; +use super::MultiChainAddress; + +pub const MODULE_NAME: &IdentStr = IdentStr::new("ton_address").unwrap(); + +/// The Ton address type +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct TonAddress { + workchain: i32, + hash_part: AccountAddress, +} + +impl TonAddress { + pub fn workchain(&self) -> i32 { + self.workchain + } + + pub fn hash_part(&self) -> [u8; 32] { + self.hash_part.into() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(|e| anyhow::anyhow!("TonAddress deserialize error: {}", e)) + } + + pub fn to_bytes(&self) -> Result, anyhow::Error> { + bcs::to_bytes(self).map_err(|e| anyhow::anyhow!("TonAddress serialize error: {}", e)) + } +} + +impl MoveStructType for TonAddress { + const ADDRESS: AccountAddress = ROOCH_FRAMEWORK_ADDRESS; + const MODULE_NAME: &'static IdentStr = MODULE_NAME; + const STRUCT_NAME: &'static IdentStr = IdentStr::new("TonAddress").unwrap(); +} + +impl MoveStructState for TonAddress { + fn struct_layout() -> MoveStructLayout { + MoveStructLayout::new(vec![ + MoveTypeLayout::U32, + MoveTypeLayout::Address, + ]) + } +} + +impl fmt::Display for TonAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ton_addr: tonlib_core::TonAddress = self.clone().into(); + write!(f, "{}", ton_addr) + } +} + +impl FromStr for TonAddress { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let ton_addr = tonlib_core::TonAddress::from_str(s)?; + Ok(ton_addr.into()) + } +} + +impl From for TonAddress { + fn from(address: tonlib_core::TonAddress) -> Self { + Self { + workchain: address.workchain, + hash_part: address.hash_part.into(), + } + } +} + +impl From for tonlib_core::TonAddress { + fn from(address: TonAddress) -> Self { + tonlib_core::TonAddress { + workchain: address.workchain, + hash_part: address.hash_part.into(), + } + } +} + +impl TryFrom for TonAddress { + type Error = anyhow::Error; + + fn try_from(value: MultiChainAddress) -> Result { + if value.multichain_id != RoochMultiChainID::Ton { + return Err(anyhow::anyhow!( + "multichain_id type {} is invalid", + value.multichain_id + )); + } + TonAddress::from_bytes(&value.raw_address) + } +} + +impl From for MultiChainAddress { + fn from(address: TonAddress) -> Self { + MultiChainAddress { + multichain_id: RoochMultiChainID::Ton, + raw_address: address + .to_bytes() + .expect("TonAddress to_bytes should not fail"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ton_address() { + let addr = TonAddress::from_str("-1:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76").unwrap(); + //println!("addr: {}", addr); + let bytes = addr.to_bytes().unwrap(); + println!("bytes: {:?}", hex::encode(bytes)); + } +} \ No newline at end of file diff --git a/crates/rooch-types/src/framework/mod.rs b/crates/rooch-types/src/framework/mod.rs index ee1e122b6b..c91b888c0e 100644 --- a/crates/rooch-types/src/framework/mod.rs +++ b/crates/rooch-types/src/framework/mod.rs @@ -27,6 +27,7 @@ pub mod oracle; pub mod session_key; pub mod session_validator; pub mod timestamp; +pub mod ton_proof; pub mod transaction_validator; pub mod transfer; diff --git a/crates/rooch-types/src/framework/ton_proof.rs b/crates/rooch-types/src/framework/ton_proof.rs new file mode 100644 index 0000000000..9471f923f5 --- /dev/null +++ b/crates/rooch-types/src/framework/ton_proof.rs @@ -0,0 +1,207 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{anyhow, bail, Result}; +use base64::prelude::*; +use fastcrypto::{ + ed25519::{Ed25519PublicKey, Ed25519Signature}, + hash::{HashFunction, Sha256}, + traits::{ToFromBytes, VerifyingKey}, +}; +use once_cell::sync::Lazy; +use serde::Deserialize; +use std::{ + collections::HashMap, + time::{SystemTime, UNIX_EPOCH}, +}; +use tonlib_core::{ + cell::BagOfCells, + wallet::{WalletDataHighloadV2R2, WalletDataV1V2, WalletDataV3, WalletDataV4, WalletVersion}, + TonAddress, +}; + +const PROOF_TTL: u64 = 3600; // 1 hour + +static KNOWN_HASHES: Lazy> = Lazy::new(|| { + let mut known_hashes = HashMap::new(); + let all_versions = [ + WalletVersion::V1R1, + WalletVersion::V1R2, + WalletVersion::V1R3, + WalletVersion::V2R1, + WalletVersion::V2R2, + WalletVersion::V3R1, + WalletVersion::V3R2, + WalletVersion::V4R1, + WalletVersion::V4R2, + WalletVersion::HighloadV1R1, + WalletVersion::HighloadV1R2, + WalletVersion::HighloadV2, + WalletVersion::HighloadV2R1, + WalletVersion::HighloadV2R2, + ]; + all_versions.into_iter().for_each(|v| { + let hash: [u8; 32] = v.code().unwrap().cell_hash(); + known_hashes.insert(hash, v); + }); + known_hashes +}); + +#[derive(Deserialize)] +pub struct TonProofData { + pub name: String, + pub proof: TonProof, + pub state_init: String, +} + +const PAYLOAD_MESSAGE_IDX: u64 = 0; +const PAYLOAD_BITCOIN_ADDRESS_IDX: u64 = 1; +const PAYLOAD_TX_HASH_IDX: u64 = 2; + +#[derive(Deserialize)] +pub struct TonProof { + pub timestamp: u64, + pub domain: TonDomain, + pub signature: String, + pub payload: Vec, +} + +#[derive(Deserialize)] +pub struct TonDomain { + pub length_bytes: u64, + pub value: String, +} + +pub fn verify_proof(address: TonAddress, data: TonProofData, verify_timestamp: bool) -> Result<()> { + let proof = data.proof; + let state_init = data.state_init; + + // check ton proof expiration + if verify_timestamp { + let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + if now > proof.timestamp + PROOF_TTL { + bail!("ton proof has been expired"); + } + } + + if proof.domain.length_bytes != proof.domain.value.len() as u64 { + bail!( + "domain length {} mismatched against provided length bytes of {}", + proof.domain.value.len(), + proof.domain.length_bytes + ); + } + + let ton_proof_prefix = "ton-proof-item-v2/"; + let mut msg: Vec = Vec::new(); + msg.extend_from_slice(ton_proof_prefix.as_bytes()); + msg.extend_from_slice(&address.workchain.to_be_bytes()); + msg.extend_from_slice(&address.hash_part); + msg.extend_from_slice(&(proof.domain.length_bytes as u32).to_le_bytes()); + msg.extend_from_slice(proof.domain.value.as_bytes()); + msg.extend_from_slice(&proof.timestamp.to_le_bytes()); + msg.extend_from_slice(proof.payload.as_bytes()); + + let mut hasher = Sha256::new(); + hasher.update(msg); + let msg_hash = hasher.finalize(); + + let mut full_msg: Vec = vec![0xff, 0xff]; + let ton_connect_prefix = "ton-connect"; + full_msg.extend_from_slice(ton_connect_prefix.as_bytes()); + full_msg.extend_from_slice(msg_hash.as_ref()); + + let mut hasher = Sha256::new(); + hasher.update(full_msg); + let full_msg_hash = hasher.finalize(); + + let pubkey_bytes = { + let bytes = BASE64_STANDARD.decode(&state_init)?; + let boc = BagOfCells::parse(&bytes)?; + let hash: [u8; 32] = boc.single_root()?.cell_hash(); + + if hash != address.hash_part { + return Err(anyhow!( + "wrong address in state_init: {}", + hex::encode(hash) + )); + } + + let root = boc.single_root().expect("checked above"); + let code = root.reference(0)?; + let data = root.reference(1)?.as_ref().clone(); + + let code_hash: [u8; 32] = code.cell_hash(); + let known_hashes = &*KNOWN_HASHES; + let version = known_hashes + .get(&code_hash) + .ok_or(anyhow!("not known wallet version"))? + .clone(); + + match version { + WalletVersion::V1R1 + | WalletVersion::V1R2 + | WalletVersion::V1R3 + | WalletVersion::V2R1 + | WalletVersion::V2R2 => { + let data = WalletDataV1V2::try_from(data)?; + data.public_key + } + WalletVersion::V3R1 | WalletVersion::V3R2 => { + let data = WalletDataV3::try_from(data)?; + data.public_key + } + WalletVersion::V4R1 | WalletVersion::V4R2 => { + let data = WalletDataV4::try_from(data)?; + data.public_key + } + WalletVersion::HighloadV2R2 => { + let data = WalletDataHighloadV2R2::try_from(data)?; + data.public_key + } + _ => { + //TODO wait WalletVersion derive Debug + //bail!("can't process given wallet version {:?}", version); + bail!("can't process given wallet version"); + } + } + }; + let pubkey = Ed25519PublicKey::from_bytes(&pubkey_bytes)?; + let signature_bytes: [u8; 64] = BASE64_STANDARD + .decode(&proof.signature)? + .try_into() + .map_err(|_| anyhow!("expected 64 bit long signature"))?; + let signature = Ed25519Signature::from_bytes(&signature_bytes)?; + pubkey.verify(full_msg_hash.as_ref(), &signature)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use tonlib_core::TonAddress; + + #[test] + fn test_check_ton_proof() { + let ton_address = TonAddress::from_hex_str( + "0:b1481ee8620ebf33b7882fa749654176ef00c7e4cac95ed39f371d5775920814", + ) + .unwrap(); + let verify_proof_json = r#"{ + "name": "ton_proof", + "proof": { + "timestamp": 1730363765, + "domain": { + "length_bytes": 21, + "value": "ton-connect.github.io" + }, + "signature": "BvysFrBS8KgTa3bww9f5paEu6/jZr5jB1JmO6T8nqsLzJqB3hWHiqOG9OezPsiJX3kD9nifMbRhr1xkv37ICCw==", + "payload": ["","bc1q04uaa0mveqtt4y0sltuxtauhlyl8ctstr5x3hu",""] + }, + "state_init": "te6cckECFgEAAwQAAgE0ARUBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF1M4HQpWKrIhrdY9Ou9RtUmildvf4qB7qOpqgADYbRTiQD9nbsU=" + }"#; + let verify_proof_data: TonProofData = serde_json::from_str(verify_proof_json).unwrap(); + super::verify_proof(ton_address, verify_proof_data, false).unwrap(); + } +} diff --git a/crates/rooch-types/src/multichain_id.rs b/crates/rooch-types/src/multichain_id.rs index 5014f56a64..d1c0699aa4 100644 --- a/crates/rooch-types/src/multichain_id.rs +++ b/crates/rooch-types/src/multichain_id.rs @@ -18,7 +18,7 @@ pub const ETHER: u64 = 60; pub const SUI: u64 = 784; pub const NOSTR: u64 = 1237; pub const ROOCH: u64 = 20230101; // place holder for slip-0044 needs to replace later - +pub const TON: u64 = 607; #[derive( Clone, Copy, @@ -103,6 +103,7 @@ pub enum RoochMultiChainID { Ether = ETHER, Nostr = NOSTR, Rooch = ROOCH, + Ton = TON, } impl Display for RoochMultiChainID { @@ -112,6 +113,7 @@ impl Display for RoochMultiChainID { RoochMultiChainID::Ether => write!(f, "ether"), RoochMultiChainID::Nostr => write!(f, "nostr"), RoochMultiChainID::Rooch => write!(f, "rooch"), + RoochMultiChainID::Ton => write!(f, "ton"), } } } @@ -131,6 +133,7 @@ impl TryFrom for RoochMultiChainID { ETHER => Ok(RoochMultiChainID::Ether), NOSTR => Ok(RoochMultiChainID::Nostr), ROOCH => Ok(RoochMultiChainID::Rooch), + TON => Ok(RoochMultiChainID::Ton), _ => Err(anyhow::anyhow!("multichain id {} is invalid", value)), } } @@ -145,6 +148,7 @@ impl FromStr for RoochMultiChainID { "ether" => Ok(RoochMultiChainID::Ether), "nostr" => Ok(RoochMultiChainID::Nostr), "rooch" => Ok(RoochMultiChainID::Rooch), + "ton" => Ok(RoochMultiChainID::Ton), s => Err(format_err!("Unknown multichain: {}", s)), } } @@ -158,6 +162,7 @@ impl TryFrom for RoochMultiChainID { ETHER => Self::Ether, NOSTR => Self::Nostr, ROOCH => Self::Rooch, + TON => Self::Ton, id => bail!("{} is not a builtin multichain id", id), }) } @@ -214,6 +219,7 @@ impl RoochMultiChainID { RoochMultiChainID::Ether, RoochMultiChainID::Nostr, RoochMultiChainID::Rooch, + RoochMultiChainID::Ton, ] } } diff --git a/frameworks/bitcoin-move/sources/bitcoin_multisign_validator.move b/frameworks/bitcoin-move/sources/bitcoin_multisign_validator.move index 521c5ccb41..6b1b669085 100644 --- a/frameworks/bitcoin-move/sources/bitcoin_multisign_validator.move +++ b/frameworks/bitcoin-move/sources/bitcoin_multisign_validator.move @@ -30,7 +30,7 @@ module bitcoin_move::bitcoin_multisign_validator{ public(friend) fun genesis_init(){ let system = signer::module_signer(); - let id = auth_validator_registry::register_by_system(&system); + let id = auth_validator_registry::register_by_system_with_id(&system, BITCOIN_MULTISIGN_VALIDATOR_ID); assert!(id == BITCOIN_MULTISIGN_VALIDATOR_ID, ErrorGenesisInitError); } diff --git a/frameworks/moveos-stdlib/doc/string_utils.md b/frameworks/moveos-stdlib/doc/string_utils.md index 4767f7bf29..fdad6d9f5a 100644 --- a/frameworks/moveos-stdlib/doc/string_utils.md +++ b/frameworks/moveos-stdlib/doc/string_utils.md @@ -8,6 +8,9 @@ - [Constants](#@Constants_0) - [Function `parse_u8_option`](#0x2_string_utils_parse_u8_option) - [Function `parse_u8`](#0x2_string_utils_parse_u8) +- [Function `parse_u32_from_bytes`](#0x2_string_utils_parse_u32_from_bytes) +- [Function `parse_u32_option`](#0x2_string_utils_parse_u32_option) +- [Function `parse_u32`](#0x2_string_utils_parse_u32) - [Function `parse_u64_option`](#0x2_string_utils_parse_u64_option) - [Function `parse_u64`](#0x2_string_utils_parse_u64) - [Function `parse_u128_option`](#0x2_string_utils_parse_u128_option) @@ -67,6 +70,39 @@ + + +## Function `parse_u32_from_bytes` + + + +
public fun parse_u32_from_bytes(bytes: &vector<u8>): option::Option<u32>
+
+ + + + + +## Function `parse_u32_option` + + + +
public fun parse_u32_option(s: &string::String): option::Option<u32>
+
+ + + + + +## Function `parse_u32` + + + +
public fun parse_u32(s: &string::String): u32
+
+ + + ## Function `parse_u64_option` diff --git a/frameworks/moveos-stdlib/sources/byte_stream.move b/frameworks/moveos-stdlib/sources/byte_stream.move new file mode 100644 index 0000000000..9c78d28e5c --- /dev/null +++ b/frameworks/moveos-stdlib/sources/byte_stream.move @@ -0,0 +1,108 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module moveos_std::byte_stream { + + use std::vector; + + const ERROR_NOT_ENOUGH_BYTES: u64 = 1; + + struct ByteStream has copy, drop, store { + bytes: vector, + } + + /// Create a new byte stream with the given bytes in big endian order. + public fun new(bytes: vector): ByteStream { + vector::reverse(&mut bytes); + ByteStream { bytes} + } + + public fun read_u8(stream: &mut ByteStream): u8 { + assert!(vector::length(&stream.bytes) > 0, ERROR_NOT_ENOUGH_BYTES); + vector::pop_back(&mut stream.bytes) + } + + public fun read_u16(stream: &mut ByteStream): u16 { + let bytes = read_to_vec(stream, 2); + let result = 0u16; + let i = 0; + while (i < 2) { + result = result << 8; + result = result | (*vector::borrow(&bytes, i) as u16); + i = i + 1; + }; + result + } + + public fun read_u32(stream: &mut ByteStream): u32 { + let bytes = read_to_vec(stream, 4); + let result = 0u32; + let i = 0; + while (i < 4) { + result = result << 8; + result = result | (*vector::borrow(&bytes, i) as u32); + i = i + 1; + }; + result + } + + //TODO support read_u64, read_u128, read_u256 + + public fun read_to_vec(stream: &mut ByteStream, len: u64): vector { + assert!(vector::length(&stream.bytes) >= len, ERROR_NOT_ENOUGH_BYTES); + let bytes = vector::empty(); + let i = 0; + while (i < len) { + vector::push_back(&mut bytes, read_u8(stream)); + i = i + 1; + }; + bytes + } + + public fun read_var_size(stream: &mut ByteStream, size: u8): u64 { + let bytes = read_to_vec(stream, (size as u64)); + let result = 0u64; + let i = 0u64; + let size_u64 = (size as u64); + while (i < size_u64) { + result = result << 8; + result = result | (*vector::borrow(&bytes, i) as u64); + i = i + 1; + }; + result + } + + public fun skip(stream: &mut ByteStream, len: u64) { + assert!(vector::length(&stream.bytes) >= len, ERROR_NOT_ENOUGH_BYTES); + let i = 0; + while (i < len) { + vector::pop_back(&mut stream.bytes); + i = i + 1; + }; + } + + /// Read all remaining bytes in the stream. + public fun read_all(stream: &mut ByteStream): vector { + let len = vector::length(&stream.bytes); + read_to_vec(stream, len) + } + + #[test] + fun test_byte_stream(){ + let bytes = x"0102030405060708090a0b0c0d0e0f"; + let stream = new(bytes); + let v: u8 = read_u8(&mut stream); + assert!(v == 0x01, 1); + let result = read_u16(&mut stream); + assert!(result == 0x0203, 2); + let result = read_u32(&mut stream); + assert!(result == 0x04050607, 3); + let result = read_var_size(&mut stream, 3); + assert!(result == 0x08090a, 4); + let result = read_to_vec(&mut stream, 3); + assert!(result == x"0b0c0d", 5); + let result = read_all(&mut stream); + std::debug::print(&result); + assert!(result == x"0e0f", 6); + } +} diff --git a/frameworks/moveos-stdlib/sources/i128.move b/frameworks/moveos-stdlib/sources/i128.move new file mode 100644 index 0000000000..0fe23f04a3 --- /dev/null +++ b/frameworks/moveos-stdlib/sources/i128.move @@ -0,0 +1,552 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// original code from https://github.com/CetusProtocol/integer-mate/blob/9bac6499eada4c514e61eb2f4bee735beb469fb5/sui/sources/i128.move +module moveos_std::i128 { + use moveos_std::i64; + use moveos_std::i32; + + const ErrorOverflow: u64 = 0; + + const MIN_AS_U128: u128 = 1 << 127; + const MAX_AS_U128: u128 = 0x7fffffffffffffffffffffffffffffff; + + const LT: u8 = 0; + const EQ: u8 = 1; + const GT: u8 = 2; + + #[data_struct] + struct I128 has copy, drop, store { + bits: u128 + } + + public fun zero(): I128 { + I128 { + bits: 0 + } + } + + public fun from_u128(v: u128): I128 { + I128 { + bits: v + } + } + + public fun from_i64(v: i64::I64): I128 { + if (i64::is_neg(v)) { + neg_from((i64::abs_u64(v) as u128)) + } else { + from((i64::abs_u64(v) as u128)) + } + } + + public fun from(v: u128): I128 { + assert!(v <= MAX_AS_U128, ErrorOverflow); + I128 { + bits: v + } + } + + public fun neg_from(v: u128): I128 { + assert!(v <= MIN_AS_U128, ErrorOverflow); + if (v == 0) { + I128 { + bits: v + } + } else { + I128 { + bits: (u128_neg(v) + 1) | (1 << 127) + } + } + } + + public fun neg(v: I128): I128 { + if (is_neg(v)) { + abs(v) + } else { + neg_from(v.bits) + } + } + + public fun wrapping_add(num1: I128, num2:I128): I128 { + let sum = num1.bits ^ num2.bits; + let carry = (num1.bits & num2.bits) << 1; + while (carry != 0) { + let a = sum; + let b = carry; + sum = a ^ b; + carry = (a & b) << 1; + }; + I128 { + bits: sum + } + } + + public fun add(num1: I128, num2: I128): I128 { + let sum = wrapping_add(num1, num2); + let overflow = (sign(num1) & sign(num2) & u8_neg(sign(sum))) + (u8_neg(sign(num1)) & u8_neg(sign(num2)) & sign(sum)); + assert!(overflow == 0, ErrorOverflow); + sum + } + + public fun overflowing_add(num1: I128, num2: I128): (I128, bool) { + let sum = wrapping_add(num1, num2); + let overflow = (sign(num1) & sign(num2) & u8_neg(sign(sum))) + (u8_neg(sign(num1)) & u8_neg(sign(num2)) & sign(sum)); + (sum, overflow != 0) + } + + public fun wrapping_sub(num1: I128, num2: I128): I128 { + let sub_num = wrapping_add(I128 { + bits: u128_neg(num2.bits) + }, from(1)); + wrapping_add(num1, sub_num) + } + + public fun sub(num1: I128, num2: I128): I128 { + let sub_num = wrapping_add(I128 { + bits: u128_neg(num2.bits) + }, from(1)); + add(num1, sub_num) + } + + public fun overflowing_sub(num1: I128, num2: I128): (I128, bool) { + let sub_num = wrapping_add(I128 { + bits: u128_neg(num2.bits) + }, from(1)); + let sum = wrapping_add(num1, sub_num); + let overflow = (sign(num1) & sign(sub_num) & u8_neg(sign(sum))) + (u8_neg(sign(num1)) & u8_neg(sign(sub_num)) & sign(sum)); + (sum, overflow != 0) + } + + public fun mul(num1: I128, num2: I128): I128 { + let product = abs_u128(num1) * abs_u128(num2); + if (sign(num1) != sign(num2)) { + return neg_from(product) + }; + return from(product) + } + + public fun div(num1: I128, num2: I128): I128 { + let result = abs_u128(num1) / abs_u128(num2); + if (sign(num1) != sign(num2)) { + return neg_from(result) + }; + return from(result) + } + + public fun abs(v: I128): I128 { + if (sign(v) == 0) { + v + } else { + assert!(v.bits > MIN_AS_U128, ErrorOverflow); + I128 { + bits: u128_neg(v.bits - 1) + } + } + } + + public fun abs_u128(v: I128): u128 { + if (sign(v) == 0) { + v.bits + } else { + u128_neg(v.bits - 1) + } + } + + public fun shl(v: I128, shift: u8): I128 { + I128 { + bits: v.bits << shift + } + } + + public fun shr(v: I128, shift: u8): I128 { + if (shift == 0) { + return v + }; + let mask = 0xffffffffffffffffffffffffffffffff << (128 - shift); + if (sign(v) == 1) { + return I128 { + bits: (v.bits >> shift) | mask + } + }; + I128 { + bits: v.bits >> shift + } + } + + public fun as_u128(v: I128): u128 { + v.bits + } + + public fun as_i64(v: I128): i64::I64 { + if (is_neg(v)) { + return i64::neg_from((abs_u128(v) as u64)) + } else { + return i64::from((abs_u128(v) as u64)) + } + } + + public fun as_i32(v: I128): i32::I32 { + if (is_neg(v)) { + return i32::neg_from((abs_u128(v) as u32)) + } else { + return i32::from((abs_u128(v) as u32)) + } + } + + public fun sign(v: I128): u8 { + ((v.bits >> 127) as u8) + } + + public fun is_neg(v: I128): bool { + sign(v) == 1 + } + + public fun cmp(num1: I128, num2: I128): u8 { + if (num1.bits == num2.bits) return EQ; + if (sign(num1) > sign(num2)) return LT; + if (sign(num1) < sign(num2)) return GT; + if (num1.bits > num2.bits) { + return GT + } else { + return LT + } + } + + public fun eq(num1: I128, num2: I128): bool { + num1.bits == num2.bits + } + + public fun gt(num1: I128, num2: I128): bool { + cmp(num1, num2) == GT + } + + public fun gte(num1: I128, num2: I128): bool { + cmp(num1, num2) >= EQ + } + + public fun lt(num1: I128, num2: I128): bool { + cmp(num1, num2) == LT + } + + public fun lte(num1: I128, num2: I128): bool { + cmp(num1, num2) <= EQ + } + + public fun or(num1: I128, num2: I128): I128 { + I128 { + bits: (num1.bits | num2.bits) + } + } + + public fun and(num1: I128, num2: I128): I128 { + I128 { + bits: (num1.bits & num2.bits) + } + } + + fun u128_neg(v :u128) : u128 { + v ^ 0xffffffffffffffffffffffffffffffff + } + + fun u8_neg(v: u8): u8 { + v ^ 0xff + } + + #[test] + fun test_from_ok() { + assert!(as_u128(from(0)) == 0, 0); + assert!(as_u128(from(10)) == 10, 1); + } + + #[test] + #[expected_failure] + fun test_from_overflow() { + as_u128(from(MIN_AS_U128)); + as_u128(from(0xffffffffffffffffffffffffffffffff)); + } + + #[test] + fun test_neg_from() { + assert!(as_u128(neg_from(0)) == 0, 0); + assert!(as_u128(neg_from(1)) == 0xffffffffffffffffffffffffffffffff, 1); + assert!(as_u128(neg_from(0x7fffffffffffffffffffffffffffffff)) == 0x80000000000000000000000000000001, 2); + assert!(as_u128(neg_from(MIN_AS_U128)) == MIN_AS_U128, 2); + } + + #[test] + #[expected_failure] + fun test_neg_from_overflow() { + neg_from(0x80000000000000000000000000000001); + } + + #[test] + fun test_abs() { + assert!(as_u128(from(10)) == 10u128, 0); + assert!(as_u128(abs(neg_from(10))) == 10u128, 1); + assert!(as_u128(abs(neg_from(0))) == 0u128, 2); + assert!(as_u128(abs(neg_from(0x7fffffffffffffffffffffffffffffff))) == 0x7fffffffffffffffffffffffffffffff, 3); + assert!(as_u128(neg_from(MIN_AS_U128)) == MIN_AS_U128, 4); + } + + #[test] + #[expected_failure] + fun test_abs_overflow() { + abs(neg_from(1<<127)); + } + + #[test] + fun test_wrapping_add() { + assert!(as_u128(wrapping_add(from(0), from(1))) == 1, 0); + assert!(as_u128(wrapping_add(from(1), from(0))) == 1, 0); + assert!(as_u128(wrapping_add(from(10000), from(99999))) == 109999, 0); + assert!(as_u128(wrapping_add(from(99999), from(10000))) == 109999, 0); + assert!(as_u128(wrapping_add(from(MAX_AS_U128-1), from(1))) == MAX_AS_U128, 0); + assert!(as_u128(wrapping_add(from(0), from(0))) == 0, 0); + + assert!(as_u128(wrapping_add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u128(wrapping_add(neg_from(1), neg_from(0))) == 0xffffffffffffffffffffffffffffffff, 1); + assert!(as_u128(wrapping_add(neg_from(0), neg_from(1))) == 0xffffffffffffffffffffffffffffffff, 1); + assert!(as_u128(wrapping_add(neg_from(10000), neg_from(99999))) == 0xfffffffffffffffffffffffffffe5251, 1); + assert!(as_u128(wrapping_add(neg_from(99999), neg_from(10000))) == 0xfffffffffffffffffffffffffffe5251, 1); + assert!(as_u128(wrapping_add(neg_from(MIN_AS_U128-1), neg_from(1))) == MIN_AS_U128, 1); + + assert!(as_u128(wrapping_add(from(0), neg_from(0))) == 0, 2); + assert!(as_u128(wrapping_add(neg_from(0), from(0))) == 0, 2); + assert!(as_u128(wrapping_add(neg_from(1), from(1))) == 0, 2); + assert!(as_u128(wrapping_add(from(1), neg_from(1))) == 0, 2); + assert!(as_u128(wrapping_add(from(10000), neg_from(99999))) == 0xfffffffffffffffffffffffffffea071, 2); + assert!(as_u128(wrapping_add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u128(wrapping_add(neg_from(MIN_AS_U128), from(1))) == 0x80000000000000000000000000000001, 2); + assert!(as_u128(wrapping_add(from(MAX_AS_U128), neg_from(1))) == MAX_AS_U128 - 1, 2); + + assert!(as_u128(wrapping_add(from(MAX_AS_U128), from(1))) == MIN_AS_U128, 2); + } + + #[test] + fun test_add() { + assert!(as_u128(add(from(0), from(0))) == 0, 0); + assert!(as_u128(add(from(0), from(1))) == 1, 0); + assert!(as_u128(add(from(1), from(0))) == 1, 0); + assert!(as_u128(add(from(10000), from(99999))) == 109999, 0); + assert!(as_u128(add(from(99999), from(10000))) == 109999, 0); + assert!(as_u128(add(from(MAX_AS_U128-1), from(1))) == MAX_AS_U128, 0); + + assert!(as_u128(add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u128(add(neg_from(1), neg_from(0))) == 0xffffffffffffffffffffffffffffffff, 1); + assert!(as_u128(add(neg_from(0), neg_from(1))) == 0xffffffffffffffffffffffffffffffff, 1); + assert!(as_u128(add(neg_from(10000), neg_from(99999))) == 0xfffffffffffffffffffffffffffe5251, 1); + assert!(as_u128(add(neg_from(99999), neg_from(10000))) == 0xfffffffffffffffffffffffffffe5251, 1); + assert!(as_u128(add(neg_from(MIN_AS_U128-1), neg_from(1))) == MIN_AS_U128, 1); + + assert!(as_u128(add(from(0), neg_from(0))) == 0, 2); + assert!(as_u128(add(neg_from(0), from(0))) == 0, 2); + assert!(as_u128(add(neg_from(1), from(1))) == 0, 2); + assert!(as_u128(add(from(1), neg_from(1))) == 0, 2); + assert!(as_u128(add(from(10000), neg_from(99999))) == 0xfffffffffffffffffffffffffffea071, 2); + assert!(as_u128(add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u128(add(neg_from(MIN_AS_U128), from(1))) == 0x80000000000000000000000000000001, 2); + assert!(as_u128(add(from(MAX_AS_U128), neg_from(1))) == MAX_AS_U128 - 1, 2); + } + + #[test] + fun test_overflowing_add() { + let (result, overflow) = overflowing_add(from(MAX_AS_U128), neg_from(1)); + assert!(overflow == false && as_u128(result) == MAX_AS_U128 - 1, 1); + let (_, overflow) = overflowing_add(from(MAX_AS_U128), from(1)); + assert!(overflow == true, 1); + let (_, overflow) = overflowing_add(neg_from(MIN_AS_U128), neg_from(1)); + assert!(overflow == true, 1); + } + + #[test] + #[expected_failure] + fun test_add_overflow() { + add(from(MAX_AS_U128), from(1)); + } + + #[test] + #[expected_failure] + fun test_add_underflow() { + add(neg_from(MIN_AS_U128), neg_from(1)); + } + + #[test] + fun test_wrapping_sub() { + assert!(as_u128(wrapping_sub(from(0), from(0))) == 0, 0); + assert!(as_u128(wrapping_sub(from(1), from(0))) == 1, 0); + assert!(as_u128(wrapping_sub(from(0), from(1))) == as_u128(neg_from(1)), 0); + assert!(as_u128(wrapping_sub(from(1), from(1))) == as_u128(neg_from(0)), 0); + assert!(as_u128(wrapping_sub(from(1), neg_from(1))) == as_u128(from(2)), 0); + assert!(as_u128(wrapping_sub(neg_from(1), from(1))) == as_u128(neg_from(2)), 0); + assert!(as_u128(wrapping_sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u128(wrapping_sub(neg_from(1000000), neg_from(1))) == as_u128(neg_from(999999)), 0); + assert!(as_u128(wrapping_sub(from(1), from(1000000))) == as_u128(neg_from(999999)), 0); + assert!(as_u128(wrapping_sub(from(MAX_AS_U128), from(MAX_AS_U128))) == as_u128(from(0)), 0); + assert!(as_u128(wrapping_sub(from(MAX_AS_U128), from(1))) == as_u128(from(MAX_AS_U128 - 1)), 0); + assert!(as_u128(wrapping_sub(from(MAX_AS_U128), neg_from(1))) == as_u128(neg_from(MIN_AS_U128)), 0); + assert!(as_u128(wrapping_sub(neg_from(MIN_AS_U128), neg_from(1))) == as_u128(neg_from(MIN_AS_U128 - 1)), 0); + assert!(as_u128(wrapping_sub(neg_from(MIN_AS_U128), from(1))) == as_u128(from(MAX_AS_U128)), 0); + } + + #[test] + fun test_sub() { + assert!(as_u128(sub(from(0), from(0))) == 0, 0); + assert!(as_u128(sub(from(1), from(0))) == 1, 0); + assert!(as_u128(sub(from(0), from(1))) == as_u128(neg_from(1)), 0); + assert!(as_u128(sub(from(1), from(1))) == as_u128(neg_from(0)), 0); + assert!(as_u128(sub(from(1), neg_from(1))) == as_u128(from(2)), 0); + assert!(as_u128(sub(neg_from(1), from(1))) == as_u128(neg_from(2)), 0); + assert!(as_u128(sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u128(sub(neg_from(1000000), neg_from(1))) == as_u128(neg_from(999999)), 0); + assert!(as_u128(sub(from(1), from(1000000))) == as_u128(neg_from(999999)), 0); + assert!(as_u128(sub(from(MAX_AS_U128), from(MAX_AS_U128))) == as_u128(from(0)), 0); + assert!(as_u128(sub(from(MAX_AS_U128), from(1))) == as_u128(from(MAX_AS_U128 - 1)), 0); + assert!(as_u128(sub(neg_from(MIN_AS_U128), neg_from(1))) == as_u128(neg_from(MIN_AS_U128 - 1)), 0); + } + + #[test] + fun test_checked_sub() { + let (result, overflowing) = overflowing_sub(from(MAX_AS_U128), from(1)); + assert!(overflowing == false && as_u128(result) == MAX_AS_U128 - 1, 1); + + let (_, overflowing) = overflowing_sub(neg_from(MIN_AS_U128), from(1)); + assert!(overflowing == true, 1); + + let (_, overflowing) = overflowing_sub(from(MAX_AS_U128), neg_from(1)); + assert!(overflowing == true, 1); + } + + #[test] + #[expected_failure] + fun test_sub_overflow() { + sub(from(MAX_AS_U128), neg_from(1)); + } + + #[test] + #[expected_failure] + fun test_sub_underflow() { + sub(neg_from(MIN_AS_U128), from(1)); + } + + #[test] + fun test_mul() { + assert!(as_u128(mul(from(1), from(1))) == 1, 0); + assert!(as_u128(mul(from(10), from(10))) == 100, 0); + assert!(as_u128(mul(from(100), from(100))) == 10000, 0); + assert!(as_u128(mul(from(10000), from(10000))) == 100000000, 0); + + assert!(as_u128(mul(neg_from(1), from(1))) == as_u128(neg_from(1)), 0); + assert!(as_u128(mul(neg_from(10), from(10))) == as_u128(neg_from(100)), 0); + assert!(as_u128(mul(neg_from(100), from(100))) == as_u128(neg_from(10000)), 0); + assert!(as_u128(mul(neg_from(10000), from(10000))) == as_u128(neg_from(100000000)), 0); + + assert!(as_u128(mul(from(1), neg_from(1))) == as_u128(neg_from(1)), 0); + assert!(as_u128(mul(from(10), neg_from(10))) == as_u128(neg_from(100)), 0); + assert!(as_u128(mul(from(100), neg_from(100))) == as_u128(neg_from(10000)), 0); + assert!(as_u128(mul(from(10000), neg_from(10000))) == as_u128(neg_from(100000000)), 0); + assert!(as_u128(mul(from(MIN_AS_U128/2), neg_from(2))) == as_u128(neg_from(MIN_AS_U128)), 0); + } + + #[test] + #[expected_failure] + fun test_mul_overflow() { + mul(from(MIN_AS_U128/2), from(1)); + mul(neg_from(MIN_AS_U128/2), neg_from(2)); + } + + #[test] + fun test_div() { + assert!(as_u128(div(from(0), from(1))) == 0, 0); + assert!(as_u128(div(from(10), from(1))) == 10, 0); + assert!(as_u128(div(from(10), neg_from(1))) == as_u128(neg_from(10)), 0); + assert!(as_u128(div(neg_from(10), neg_from(1))) == as_u128(from(10)), 0); + + assert!(abs_u128(neg_from(MIN_AS_U128)) == MIN_AS_U128, 0); + assert!(as_u128(div(neg_from(MIN_AS_U128), from(1))) == MIN_AS_U128, 0); + } + + #[test] + #[expected_failure] + fun test_div_overflow() { + div(neg_from(MIN_AS_U128), neg_from(1)); + } + + #[test] + fun test_shl() { + assert!(as_u128(shl(from(10), 0)) == 10, 0); + assert!(as_u128(shl(neg_from(10), 0)) == as_u128(neg_from(10)), 0); + + assert!(as_u128(shl(from(10), 1)) == 20, 0); + assert!(as_u128(shl(neg_from(10), 1)) == as_u128(neg_from(20)), 0); + + assert!(as_u128(shl(from(10), 8)) == 2560, 0); + assert!(as_u128(shl(neg_from(10), 8)) == as_u128(neg_from(2560)), 0); + + assert!(as_u128(shl(from(10), 32)) == 42949672960, 0); + assert!(as_u128(shl(neg_from(10), 32)) == as_u128(neg_from(42949672960)), 0); + + assert!(as_u128(shl(from(10), 64)) == 184467440737095516160, 0); + assert!(as_u128(shl(neg_from(10), 64)) == as_u128(neg_from(184467440737095516160)), 0); + + assert!(as_u128(shl(from(10), 127)) == 0, 0); + assert!(as_u128(shl(neg_from(10), 127)) == 0, 0); + } + + #[test] + fun test_shr() { + assert!(as_u128(shr(from(10), 0)) == 10, 0); + assert!(as_u128(shr(neg_from(10), 0)) == as_u128(neg_from(10)), 0); + + assert!(as_u128(shr(from(10), 1)) == 5, 0); + assert!(as_u128(shr(neg_from(10), 1)) == as_u128(neg_from(5)), 0); + + assert!(as_u128(shr(from(MAX_AS_U128), 8)) == 0x7fffffffffffffffffffffffffffff, 0); + assert!(as_u128(shr(neg_from(MIN_AS_U128), 8)) == 0xff800000000000000000000000000000, 0); + + assert!(as_u128(shr(from(MAX_AS_U128), 96)) == 0x7fffffff, 0); + assert!(as_u128(shr(neg_from(MIN_AS_U128), 96)) == 0xffffffffffffffffffffffff80000000, 0); + + assert!(as_u128(shr(from(MAX_AS_U128), 127)) == 0, 0); + assert!(as_u128(shr(neg_from(MIN_AS_U128), 127)) == 0xffffffffffffffffffffffffffffffff, 0); + } + + #[test] + fun test_sign() { + assert!(sign(neg_from(10)) == 1u8, 0); + assert!(sign(from(10)) == 0u8, 0); + } + + #[test] + fun test_cmp() { + assert!(cmp(from(1), from(0)) == GT, 0); + assert!(cmp(from(0), from(1)) == LT, 0); + + assert!(cmp(from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(1), neg_from(0)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U128), from(MAX_AS_U128)) == LT, 0); + assert!(cmp(from(MAX_AS_U128), neg_from(MIN_AS_U128)) == GT, 0); + + assert!(cmp(from(MAX_AS_U128), from(MAX_AS_U128-1)) == GT, 0); + assert!(cmp(from(MAX_AS_U128-1), from(MAX_AS_U128)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U128), neg_from(MIN_AS_U128-1)) == LT, 0); + assert!(cmp(neg_from(MIN_AS_U128-1), neg_from(MIN_AS_U128)) == GT, 0); + } + + #[test] + fun test_castdown() { + assert!((1u128 as u8) == 1u8, 0); + } + + #[test] + fun test_from_i64() { + let i = from_i64(i64::neg_from(1)); + assert!(cmp(i, neg_from(1)) == EQ, 0); + + let i = from_i64(i64::from(1)); + assert!(cmp(i, from(1)) == EQ, 0); + } +} diff --git a/frameworks/moveos-stdlib/sources/i32.move b/frameworks/moveos-stdlib/sources/i32.move new file mode 100644 index 0000000000..907df6b1c3 --- /dev/null +++ b/frameworks/moveos-stdlib/sources/i32.move @@ -0,0 +1,505 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// original code from https://github.com/CetusProtocol/integer-mate/blob/9bac6499eada4c514e61eb2f4bee735beb469fb5/sui/sources/i32.move +module moveos_std::i32 { + use moveos_std::i8; + + const ErrorOverflow: u64 = 0; + + const MIN_AS_U32: u32 = 1 << 31; + const MAX_AS_U32: u32 = 0x7fffffff; + + const LT: u8 = 0; + const EQ: u8 = 1; + const GT: u8 = 2; + + #[data_struct] + struct I32 has copy, drop, store { + bits: u32 + } + + public fun zero(): I32 { + I32 { + bits: 0 + } + } + + /// Directly use the u32 value as I32 bits + public fun from_u32(v: u32): I32 { + I32 { + bits: v + } + } + + public fun from_i8(v: i8::I8): I32 { + if (i8::is_neg(v)) { + neg_from((i8::abs_u8(v) as u32)) + } else { + from((i8::abs_u8(v) as u32)) + } + } + + public fun from(v: u32): I32 { + assert!(v <= MAX_AS_U32, ErrorOverflow); + I32 { + bits: v + } + } + + public fun neg_from(v: u32): I32 { + assert!(v <= MIN_AS_U32, ErrorOverflow); + if (v == 0) { + I32 { + bits: v + } + } else { + I32 { + bits: (u32_neg(v) + 1) | (1 << 31) + } + } + } + + public fun wrapping_add(num1: I32, num2: I32): I32 { + let sum = num1.bits ^ num2.bits; + let carry = (num1.bits & num2.bits) << 1; + while (carry != 0) { + let a = sum; + let b = carry; + sum = a ^ b; + carry = (a & b) << 1; + }; + I32 { + bits: sum + } + } + + public fun add(num1: I32, num2: I32): I32 { + let sum = wrapping_add(num1, num2); + let overflow = (sign(num1) & sign(num2) & u8_neg(sign(sum))) + + (u8_neg(sign(num1)) & u8_neg(sign(num2)) & sign(sum)); + assert!(overflow == 0, ErrorOverflow); + sum + } + + public fun wrapping_sub(num1: I32, num2: I32): I32 { + let sub_num = wrapping_add(I32 { + bits: u32_neg(num2.bits) + }, from(1)); + wrapping_add(num1, sub_num) + } + + public fun sub(num1: I32, num2: I32): I32 { + let sub_num = wrapping_add(I32 { + bits: u32_neg(num2.bits) + }, from(1)); + add(num1, sub_num) + } + + public fun mul(num1: I32, num2: I32): I32 { + let product = abs_u32(num1) * abs_u32(num2); + if (sign(num1) != sign(num2)) { + return neg_from(product) + }; + return from(product) + } + + public fun div(num1: I32, num2: I32): I32 { + let result = abs_u32(num1) / abs_u32(num2); + if (sign(num1) != sign(num2)) { + return neg_from(result) + }; + return from(result) + } + + public fun abs(v: I32): I32 { + if (sign(v) == 0) { + v + } else { + assert!(v.bits > MIN_AS_U32, ErrorOverflow); + I32 { + bits: u32_neg(v.bits - 1) + } + } + } + + public fun abs_u32(v: I32): u32 { + if (sign(v) == 0) { + v.bits + } else { + u32_neg(v.bits - 1) + } + } + + public fun shl(v: I32, shift: u8): I32 { + I32 { + bits: v.bits << shift + } + } + + public fun shr(v: I32, shift: u8): I32 { + if (shift == 0) { + return v + }; + let mask = 0xffffffff << (32 - shift); + if (sign(v) == 1) { + return I32 { + bits: (v.bits >> shift) | mask + } + }; + I32 { + bits: v.bits >> shift + } + } + + public fun mod(v: I32, n: I32): I32 { + if (sign(v) == 1) { + neg_from((abs_u32(v) % abs_u32(n))) + } else { + from((as_u32(v) % abs_u32(n))) + } + } + + public fun as_u32(v: I32): u32 { + v.bits + } + + public fun sign(v: I32): u8 { + ((v.bits >> 31) as u8) + } + + public fun is_neg(v: I32): bool { + sign(v) == 1 + } + + public fun cmp(num1: I32, num2: I32): u8 { + if (num1.bits == num2.bits) return EQ; + if (sign(num1) > sign(num2)) return LT; + if (sign(num1) < sign(num2)) return GT; + if (num1.bits > num2.bits) { + return GT + } else { + return LT + } + } + + public fun eq(num1: I32, num2: I32): bool { + num1.bits == num2.bits + } + + public fun gt(num1: I32, num2: I32): bool { + cmp(num1, num2) == GT + } + + public fun gte(num1: I32, num2: I32): bool { + cmp(num1, num2) >= EQ + } + + public fun lt(num1: I32, num2: I32): bool { + cmp(num1, num2) == LT + } + + public fun lte(num1: I32, num2: I32): bool { + cmp(num1, num2) <= EQ + } + + public fun or(num1: I32, num2: I32): I32 { + I32 { + bits: (num1.bits | num2.bits) + } + } + + public fun and(num1: I32, num2: I32): I32 { + I32 { + bits: (num1.bits & num2.bits) + } + } + + fun u32_neg(v: u32): u32 { + v ^ 0xffffffff + } + + fun u8_neg(v: u8): u8 { + v ^ 0xff + } + + #[test] + fun test_from_ok() { + assert!(as_u32(from(0)) == 0, 0); + assert!(as_u32(from(10)) == 10, 1); + } + + #[test] + #[expected_failure] + fun test_from_overflow() { + as_u32(from(MIN_AS_U32)); + as_u32(from(0xffffffff)); + } + + #[test] + fun test_neg_from() { + assert!(as_u32(neg_from(0)) == 0, 0); + assert!(as_u32(neg_from(1)) == 0xffffffff, 1); + assert!(as_u32(neg_from(0x7fffffff)) == 0x80000001, 2); + assert!(as_u32(neg_from(MIN_AS_U32)) == MIN_AS_U32, 2); + } + + #[test] + #[expected_failure] + fun test_neg_from_overflow() { + neg_from(0x80000001); + } + + #[test] + fun test_abs() { + assert!(as_u32(from(10)) == 10u32, 0); + assert!(as_u32(abs(neg_from(10))) == 10u32, 1); + assert!(as_u32(abs(neg_from(0))) == 0u32, 2); + assert!(as_u32(abs(neg_from(0x7fffffff))) == 0x7fffffff, 3); + assert!(as_u32(neg_from(MIN_AS_U32)) == MIN_AS_U32, 4); + } + + #[test] + #[expected_failure] + fun test_abs_overflow() { + abs(neg_from(1 << 31)); + } + + #[test] + fun test_wrapping_add() { + assert!(as_u32(wrapping_add(from(0), from(1))) == 1, 0); + assert!(as_u32(wrapping_add(from(1), from(0))) == 1, 0); + assert!(as_u32(wrapping_add(from(10000), from(99999))) == 109999, 0); + assert!(as_u32(wrapping_add(from(99999), from(10000))) == 109999, 0); + assert!(as_u32(wrapping_add(from(MAX_AS_U32 - 1), from(1))) == MAX_AS_U32, 0); + assert!(as_u32(wrapping_add(from(0), from(0))) == 0, 0); + + assert!(as_u32(wrapping_add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u32(wrapping_add(neg_from(1), neg_from(0))) == 0xffffffff, 1); + assert!(as_u32(wrapping_add(neg_from(0), neg_from(1))) == 0xffffffff, 1); + assert!(as_u32(wrapping_add(neg_from(10000), neg_from(99999))) == 0xfffe5251, 1); + assert!(as_u32(wrapping_add(neg_from(99999), neg_from(10000))) == 0xfffe5251, 1); + assert!(as_u32(wrapping_add(neg_from(MIN_AS_U32 - 1), neg_from(1))) == MIN_AS_U32, 1); + + assert!(as_u32(wrapping_add(from(0), neg_from(0))) == 0, 2); + assert!(as_u32(wrapping_add(neg_from(0), from(0))) == 0, 2); + assert!(as_u32(wrapping_add(neg_from(1), from(1))) == 0, 2); + assert!(as_u32(wrapping_add(from(1), neg_from(1))) == 0, 2); + assert!(as_u32(wrapping_add(from(10000), neg_from(99999))) == 0xfffea071, 2); + assert!(as_u32(wrapping_add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u32(wrapping_add(neg_from(MIN_AS_U32), from(1))) == 0x80000001, 2); + assert!(as_u32(wrapping_add(from(MAX_AS_U32), neg_from(1))) == MAX_AS_U32 - 1, 2); + + assert!(as_u32(wrapping_add(from(MAX_AS_U32), from(1))) == MIN_AS_U32, 2); + } + + #[test] + fun test_add() { + assert!(as_u32(add(from(0), from(0))) == 0, 0); + assert!(as_u32(add(from(0), from(1))) == 1, 0); + assert!(as_u32(add(from(1), from(0))) == 1, 0); + assert!(as_u32(add(from(10000), from(99999))) == 109999, 0); + assert!(as_u32(add(from(99999), from(10000))) == 109999, 0); + assert!(as_u32(add(from(MAX_AS_U32 - 1), from(1))) == MAX_AS_U32, 0); + + assert!(as_u32(add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u32(add(neg_from(1), neg_from(0))) == 0xffffffff, 1); + assert!(as_u32(add(neg_from(0), neg_from(1))) == 0xffffffff, 1); + assert!(as_u32(add(neg_from(10000), neg_from(99999))) == 0xfffe5251, 1); + assert!(as_u32(add(neg_from(99999), neg_from(10000))) == 0xfffe5251, 1); + assert!(as_u32(add(neg_from(MIN_AS_U32 - 1), neg_from(1))) == MIN_AS_U32, 1); + + assert!(as_u32(add(from(0), neg_from(0))) == 0, 2); + assert!(as_u32(add(neg_from(0), from(0))) == 0, 2); + assert!(as_u32(add(neg_from(1), from(1))) == 0, 2); + assert!(as_u32(add(from(1), neg_from(1))) == 0, 2); + assert!(as_u32(add(from(10000), neg_from(99999))) == 0xfffea071, 2); + assert!(as_u32(add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u32(add(neg_from(MIN_AS_U32), from(1))) == 0x80000001, 2); + assert!(as_u32(add(from(MAX_AS_U32), neg_from(1))) == MAX_AS_U32 - 1, 2); + } + + #[test] + #[expected_failure] + fun test_add_overflow() { + add(from(MAX_AS_U32), from(1)); + } + + #[test] + #[expected_failure] + fun test_add_underflow() { + add(neg_from(MIN_AS_U32), neg_from(1)); + } + + #[test] + fun test_wrapping_sub() { + assert!(as_u32(wrapping_sub(from(0), from(0))) == 0, 0); + assert!(as_u32(wrapping_sub(from(1), from(0))) == 1, 0); + assert!(as_u32(wrapping_sub(from(0), from(1))) == as_u32(neg_from(1)), 0); + assert!(as_u32(wrapping_sub(from(1), from(1))) == as_u32(neg_from(0)), 0); + assert!(as_u32(wrapping_sub(from(1), neg_from(1))) == as_u32(from(2)), 0); + assert!(as_u32(wrapping_sub(neg_from(1), from(1))) == as_u32(neg_from(2)), 0); + assert!(as_u32(wrapping_sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u32(wrapping_sub(neg_from(1000000), neg_from(1))) == as_u32(neg_from(999999)), 0); + assert!(as_u32(wrapping_sub(from(1), from(1000000))) == as_u32(neg_from(999999)), 0); + assert!(as_u32(wrapping_sub(from(MAX_AS_U32), from(MAX_AS_U32))) == as_u32(from(0)), 0); + assert!(as_u32(wrapping_sub(from(MAX_AS_U32), from(1))) == as_u32(from(MAX_AS_U32 - 1)), 0); + assert!(as_u32(wrapping_sub(from(MAX_AS_U32), neg_from(1))) == as_u32(neg_from(MIN_AS_U32)), 0); + assert!(as_u32(wrapping_sub(neg_from(MIN_AS_U32), neg_from(1))) == as_u32(neg_from(MIN_AS_U32 - 1)), 0); + assert!(as_u32(wrapping_sub(neg_from(MIN_AS_U32), from(1))) == as_u32(from(MAX_AS_U32)), 0); + } + + #[test] + fun test_sub() { + assert!(as_u32(sub(from(0), from(0))) == 0, 0); + assert!(as_u32(sub(from(1), from(0))) == 1, 0); + assert!(as_u32(sub(from(0), from(1))) == as_u32(neg_from(1)), 0); + assert!(as_u32(sub(from(1), from(1))) == as_u32(neg_from(0)), 0); + assert!(as_u32(sub(from(1), neg_from(1))) == as_u32(from(2)), 0); + assert!(as_u32(sub(neg_from(1), from(1))) == as_u32(neg_from(2)), 0); + assert!(as_u32(sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u32(sub(neg_from(1000000), neg_from(1))) == as_u32(neg_from(999999)), 0); + assert!(as_u32(sub(from(1), from(1000000))) == as_u32(neg_from(999999)), 0); + assert!(as_u32(sub(from(MAX_AS_U32), from(MAX_AS_U32))) == as_u32(from(0)), 0); + assert!(as_u32(sub(from(MAX_AS_U32), from(1))) == as_u32(from(MAX_AS_U32 - 1)), 0); + assert!(as_u32(sub(neg_from(MIN_AS_U32), neg_from(1))) == as_u32(neg_from(MIN_AS_U32 - 1)), 0); + } + + #[test] + #[expected_failure] + fun test_sub_overflow() { + sub(from(MAX_AS_U32), neg_from(1)); + } + + #[test] + #[expected_failure] + fun test_sub_underflow() { + sub(neg_from(MIN_AS_U32), from(1)); + } + + #[test] + fun test_mul() { + assert!(as_u32(mul(from(1), from(1))) == 1, 0); + assert!(as_u32(mul(from(10), from(10))) == 100, 0); + assert!(as_u32(mul(from(100), from(100))) == 10000, 0); + assert!(as_u32(mul(from(10000), from(10000))) == 100000000, 0); + + assert!(as_u32(mul(neg_from(1), from(1))) == as_u32(neg_from(1)), 0); + assert!(as_u32(mul(neg_from(10), from(10))) == as_u32(neg_from(100)), 0); + assert!(as_u32(mul(neg_from(100), from(100))) == as_u32(neg_from(10000)), 0); + assert!(as_u32(mul(neg_from(10000), from(10000))) == as_u32(neg_from(100000000)), 0); + + assert!(as_u32(mul(from(1), neg_from(1))) == as_u32(neg_from(1)), 0); + assert!(as_u32(mul(from(10), neg_from(10))) == as_u32(neg_from(100)), 0); + assert!(as_u32(mul(from(100), neg_from(100))) == as_u32(neg_from(10000)), 0); + assert!(as_u32(mul(from(10000), neg_from(10000))) == as_u32(neg_from(100000000)), 0); + assert!(as_u32(mul(from(MIN_AS_U32 / 2), neg_from(2))) == as_u32(neg_from(MIN_AS_U32)), 0); + } + + #[test] + #[expected_failure] + fun test_mul_overflow() { + mul(from(MIN_AS_U32 / 2), from(1)); + mul(neg_from(MIN_AS_U32 / 2), neg_from(2)); + } + + #[test] + fun test_div() { + assert!(as_u32(div(from(0), from(1))) == 0, 0); + assert!(as_u32(div(from(10), from(1))) == 10, 0); + assert!(as_u32(div(from(10), neg_from(1))) == as_u32(neg_from(10)), 0); + assert!(as_u32(div(neg_from(10), neg_from(1))) == as_u32(from(10)), 0); + + assert!(abs_u32(neg_from(MIN_AS_U32)) == MIN_AS_U32, 0); + assert!(as_u32(div(neg_from(MIN_AS_U32), from(1))) == MIN_AS_U32, 0); + } + + #[test] + #[expected_failure] + fun test_div_overflow() { + div(neg_from(MIN_AS_U32), neg_from(1)); + } + + #[test] + fun test_shl() { + assert!(as_u32(shl(from(10), 0)) == 10, 0); + assert!(as_u32(shl(neg_from(10), 0)) == as_u32(neg_from(10)), 0); + + assert!(as_u32(shl(from(10), 1)) == 20, 0); + assert!(as_u32(shl(neg_from(10), 1)) == as_u32(neg_from(20)), 0); + + assert!(as_u32(shl(from(10), 8)) == 2560, 0); + assert!(as_u32(shl(neg_from(10), 8)) == as_u32(neg_from(2560)), 0); + + assert!(as_u32(shl(from(10), 31)) == 0, 0); + assert!(as_u32(shl(neg_from(10), 31)) == 0, 0); + } + + #[test] + fun test_shr() { + assert!(as_u32(shr(from(10), 0)) == 10, 0); + assert!(as_u32(shr(neg_from(10), 0)) == as_u32(neg_from(10)), 0); + + assert!(as_u32(shr(from(10), 1)) == 5, 0); + assert!(as_u32(shr(neg_from(10), 1)) == as_u32(neg_from(5)), 0); + + assert!(as_u32(shr(from(MAX_AS_U32), 8)) == MAX_AS_U32 >> 8, 0); + assert!(as_u32(shr(neg_from(MIN_AS_U32), 8)) == 0xff800000, 0); + } + + #[test] + fun test_sign() { + assert!(sign(neg_from(10)) == 1u8, 0); + assert!(sign(from(10)) == 0u8, 0); + } + + #[test] + fun test_cmp() { + assert!(cmp(from(1), from(0)) == GT, 0); + assert!(cmp(from(0), from(1)) == LT, 0); + + assert!(cmp(from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(1), neg_from(0)) == LT, 0); + assert!(!lt(from(5347), neg_from(765)), 0); + + assert!(cmp(neg_from(MIN_AS_U32), from(MAX_AS_U32)) == LT, 0); + assert!(cmp(from(MAX_AS_U32), neg_from(MIN_AS_U32)) == GT, 0); + + assert!(cmp(from(MAX_AS_U32), from(MAX_AS_U32 - 1)) == GT, 0); + assert!(cmp(from(MAX_AS_U32 - 1), from(MAX_AS_U32)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U32), neg_from(MIN_AS_U32 - 1)) == LT, 0); + assert!(cmp(neg_from(MIN_AS_U32 - 1), neg_from(MIN_AS_U32)) == GT, 0); + } + + #[test] + fun test_castdown() { + assert!((1u32 as u8) == 1u8, 0); + } + + #[test] + fun test_mod() { + //use aptos_std::debug; + let i = mod(neg_from(2), from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(neg_from(2), neg_from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(from(2), from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + + i = mod(from(2), neg_from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + } + + #[test] + fun test_from_i8() { + let i = from_i8(i8::neg_from(1)); + assert!(cmp(i, neg_from(1)) == EQ, 0); + + let i = from_i8(i8::from(1)); + assert!(cmp(i, from(1)) == EQ, 0); + } +} diff --git a/frameworks/moveos-stdlib/sources/i64.move b/frameworks/moveos-stdlib/sources/i64.move new file mode 100644 index 0000000000..75b0e8e1a7 --- /dev/null +++ b/frameworks/moveos-stdlib/sources/i64.move @@ -0,0 +1,513 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// original code from https://github.com/CetusProtocol/integer-mate/blob/9bac6499eada4c514e61eb2f4bee735beb469fb5/sui/sources/i64.move +module moveos_std::i64 { + use moveos_std::i32; + + const ErrorOverflow: u64 = 0; + + const MIN_AS_U64: u64 = 1 << 63; + const MAX_AS_U64: u64 = 0x7fffffffffffffff; + + const LT: u8 = 0; + const EQ: u8 = 1; + const GT: u8 = 2; + + #[data_struct] + struct I64 has copy, drop, store { + bits: u64 + } + + public fun zero(): I64 { + I64 { + bits: 0 + } + } + + public fun from_u64(v: u64): I64 { + I64 { + bits: v + } + } + + public fun from_i32(v: i32::I32): I64 { + if (i32::is_neg(v)) { + neg_from((i32::abs_u32(v) as u64)) + } else { + from((i32::abs_u32(v) as u64)) + } + } + + public fun from(v: u64): I64 { + assert!(v <= MAX_AS_U64, ErrorOverflow); + I64 { + bits: v + } + } + + public fun neg_from(v: u64): I64 { + assert!(v <= MIN_AS_U64, ErrorOverflow); + if (v == 0) { + I64 { + bits: v + } + } else { + I64 { + bits: (u64_neg(v) + 1) | (1 << 63) + } + } + } + + public fun wrapping_add(num1: I64, num2: I64): I64 { + let sum = num1.bits ^ num2.bits; + let carry = (num1.bits & num2.bits) << 1; + while (carry != 0) { + let a = sum; + let b = carry; + sum = a ^ b; + carry = (a & b) << 1; + }; + I64 { + bits: sum + } + } + + public fun add(num1: I64, num2: I64): I64 { + let sum = wrapping_add(num1, num2); + let overflow = (sign(num1) & sign(num2) & u8_neg(sign(sum))) + (u8_neg(sign(num1)) & u8_neg(sign(num2)) & sign( + sum + )); + assert!(overflow == 0, ErrorOverflow); + sum + } + + public fun wrapping_sub(num1: I64, num2: I64): I64 { + let sub_num = wrapping_add(I64 { + bits: u64_neg(num2.bits) + }, from(1)); + wrapping_add(num1, sub_num) + } + + public fun sub(num1: I64, num2: I64): I64 { + let sub_num = wrapping_add(I64 { + bits: u64_neg(num2.bits) + }, from(1)); + add(num1, sub_num) + } + + public fun mul(num1: I64, num2: I64): I64 { + let product = abs_u64(num1) * abs_u64(num2); + if (sign(num1) != sign(num2)) { + return neg_from(product) + }; + return from(product) + } + + public fun div(num1: I64, num2: I64): I64 { + let result = abs_u64(num1) / abs_u64(num2); + if (sign(num1) != sign(num2)) { + return neg_from(result) + }; + return from(result) + } + + public fun abs(v: I64): I64 { + if (sign(v) == 0) { + v + } else { + assert!(v.bits > MIN_AS_U64, ErrorOverflow); + I64 { + bits: u64_neg(v.bits - 1) + } + } + } + + public fun abs_u64(v: I64): u64 { + if (sign(v) == 0) { + v.bits + } else { + u64_neg(v.bits - 1) + } + } + + public fun shl(v: I64, shift: u8): I64 { + I64 { + bits: v.bits << shift + } + } + + public fun shr(v: I64, shift: u8): I64 { + if (shift == 0) { + return v + }; + let mask = 0xffffffffffffffff << (64 - shift); + if (sign(v) == 1) { + return I64 { + bits: (v.bits >> shift) | mask + } + }; + I64 { + bits: v.bits >> shift + } + } + + public fun mod(v: I64, n: I64): I64 { + if (sign(v) == 1) { + neg_from((abs_u64(v) % abs_u64(n))) + } else { + from((as_u64(v) % abs_u64(n))) + } + } + + public fun as_u64(v: I64): u64 { + v.bits + } + + public fun sign(v: I64): u8 { + ((v.bits >> 63) as u8) + } + + public fun is_neg(v: I64): bool { + sign(v) == 1 + } + + public fun cmp(num1: I64, num2: I64): u8 { + if (num1.bits == num2.bits) return EQ; + if (sign(num1) > sign(num2)) return LT; + if (sign(num1) < sign(num2)) return GT; + if (num1.bits > num2.bits) { + return GT + } else { + return LT + } + } + + public fun eq(num1: I64, num2: I64): bool { + num1.bits == num2.bits + } + + public fun gt(num1: I64, num2: I64): bool { + cmp(num1, num2) == GT + } + + public fun gte(num1: I64, num2: I64): bool { + cmp(num1, num2) >= EQ + } + + public fun lt(num1: I64, num2: I64): bool { + cmp(num1, num2) == LT + } + + public fun lte(num1: I64, num2: I64): bool { + cmp(num1, num2) <= EQ + } + + public fun or(num1: I64, num2: I64): I64 { + I64 { + bits: (num1.bits | num2.bits) + } + } + + public fun and(num1: I64, num2: I64): I64 { + I64 { + bits: (num1.bits & num2.bits) + } + } + + fun u64_neg(v: u64): u64 { + v ^ 0xffffffffffffffff + } + + fun u8_neg(v: u8): u8 { + v ^ 0xff + } + + #[test] + fun test_from_ok() { + assert!(as_u64(from(0)) == 0, 0); + assert!(as_u64(from(10)) == 10, 1); + } + + #[test] + #[expected_failure] + fun test_from_overflow() { + as_u64(from(MIN_AS_U64)); + as_u64(from(0xffffffffffffffff)); + } + + #[test] + fun test_neg_from() { + assert!(as_u64(neg_from(0)) == 0, 0); + assert!(as_u64(neg_from(1)) == 0xffffffffffffffff, 1); + assert!(as_u64(neg_from(0x7fffffffffffffff)) == 0x8000000000000001, 2); + assert!(as_u64(neg_from(MIN_AS_U64)) == MIN_AS_U64, 2); + } + + #[test] + #[expected_failure] + fun test_neg_from_overflow() { + neg_from(0x8000000000000001); + } + + #[test] + fun test_abs() { + assert!(as_u64(from(10)) == 10u64, 0); + assert!(as_u64(abs(neg_from(10))) == 10u64, 1); + assert!(as_u64(abs(neg_from(0))) == 0u64, 2); + assert!(as_u64(abs(neg_from(0x7fffffffffffffff))) == 0x7fffffffffffffff, 3); + assert!(as_u64(neg_from(MIN_AS_U64)) == MIN_AS_U64, 4); + } + + #[test] + #[expected_failure] + fun test_abs_overflow() { + abs(neg_from(1 << 63)); + } + + #[test] + fun test_wrapping_add() { + assert!(as_u64(wrapping_add(from(0), from(1))) == 1, 0); + assert!(as_u64(wrapping_add(from(1), from(0))) == 1, 0); + assert!(as_u64(wrapping_add(from(10000), from(99999))) == 109999, 0); + assert!(as_u64(wrapping_add(from(99999), from(10000))) == 109999, 0); + assert!(as_u64(wrapping_add(from(MAX_AS_U64 - 1), from(1))) == MAX_AS_U64, 0); + assert!(as_u64(wrapping_add(from(0), from(0))) == 0, 0); + + assert!(as_u64(wrapping_add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u64(wrapping_add(neg_from(1), neg_from(0))) == 0xffffffffffffffff, 1); + assert!(as_u64(wrapping_add(neg_from(0), neg_from(1))) == 0xffffffffffffffff, 1); + assert!(as_u64(wrapping_add(neg_from(10000), neg_from(99999))) == 0xfffffffffffe5251, 1); + assert!(as_u64(wrapping_add(neg_from(99999), neg_from(10000))) == 0xfffffffffffe5251, 1); + assert!(as_u64(wrapping_add(neg_from(MIN_AS_U64 - 1), neg_from(1))) == MIN_AS_U64, 1); + + assert!(as_u64(wrapping_add(from(0), neg_from(0))) == 0, 2); + assert!(as_u64(wrapping_add(neg_from(0), from(0))) == 0, 2); + assert!(as_u64(wrapping_add(neg_from(1), from(1))) == 0, 2); + assert!(as_u64(wrapping_add(from(1), neg_from(1))) == 0, 2); + assert!(as_u64(wrapping_add(from(10000), neg_from(99999))) == 0xfffffffffffea071, 2); + assert!(as_u64(wrapping_add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u64(wrapping_add(neg_from(MIN_AS_U64), from(1))) == 0x8000000000000001, 2); + assert!(as_u64(wrapping_add(from(MAX_AS_U64), neg_from(1))) == MAX_AS_U64 - 1, 2); + + assert!(as_u64(wrapping_add(from(MAX_AS_U64), from(1))) == MIN_AS_U64, 2); + } + + #[test] + fun test_add() { + assert!(as_u64(add(from(0), from(0))) == 0, 0); + assert!(as_u64(add(from(0), from(1))) == 1, 0); + assert!(as_u64(add(from(1), from(0))) == 1, 0); + assert!(as_u64(add(from(10000), from(99999))) == 109999, 0); + assert!(as_u64(add(from(99999), from(10000))) == 109999, 0); + assert!(as_u64(add(from(MAX_AS_U64 - 1), from(1))) == MAX_AS_U64, 0); + + assert!(as_u64(add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u64(add(neg_from(1), neg_from(0))) == 0xffffffffffffffff, 1); + assert!(as_u64(add(neg_from(0), neg_from(1))) == 0xffffffffffffffff, 1); + assert!(as_u64(add(neg_from(10000), neg_from(99999))) == 0xfffffffffffe5251, 1); + assert!(as_u64(add(neg_from(99999), neg_from(10000))) == 0xfffffffffffe5251, 1); + assert!(as_u64(add(neg_from(MIN_AS_U64 - 1), neg_from(1))) == MIN_AS_U64, 1); + + assert!(as_u64(add(from(0), neg_from(0))) == 0, 2); + assert!(as_u64(add(neg_from(0), from(0))) == 0, 2); + assert!(as_u64(add(neg_from(1), from(1))) == 0, 2); + assert!(as_u64(add(from(1), neg_from(1))) == 0, 2); + assert!(as_u64(add(from(10000), neg_from(99999))) == 0xfffffffffffea071, 2); + assert!(as_u64(add(from(99999), neg_from(10000))) == 89999, 2); + assert!(as_u64(add(neg_from(MIN_AS_U64), from(1))) == 0x8000000000000001, 2); + assert!(as_u64(add(from(MAX_AS_U64), neg_from(1))) == MAX_AS_U64 - 1, 2); + } + + #[test] + #[expected_failure] + fun test_add_overflow() { + add(from(MAX_AS_U64), from(1)); + } + + #[test] + #[expected_failure] + fun test_add_underflow() { + add(neg_from(MIN_AS_U64), neg_from(1)); + } + + #[test] + fun test_wrapping_sub() { + assert!(as_u64(wrapping_sub(from(0), from(0))) == 0, 0); + assert!(as_u64(wrapping_sub(from(1), from(0))) == 1, 0); + assert!(as_u64(wrapping_sub(from(0), from(1))) == as_u64(neg_from(1)), 0); + assert!(as_u64(wrapping_sub(from(1), from(1))) == as_u64(neg_from(0)), 0); + assert!(as_u64(wrapping_sub(from(1), neg_from(1))) == as_u64(from(2)), 0); + assert!(as_u64(wrapping_sub(neg_from(1), from(1))) == as_u64(neg_from(2)), 0); + assert!(as_u64(wrapping_sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u64(wrapping_sub(neg_from(1000000), neg_from(1))) == as_u64(neg_from(999999)), 0); + assert!(as_u64(wrapping_sub(from(1), from(1000000))) == as_u64(neg_from(999999)), 0); + assert!(as_u64(wrapping_sub(from(MAX_AS_U64), from(MAX_AS_U64))) == as_u64(from(0)), 0); + assert!(as_u64(wrapping_sub(from(MAX_AS_U64), from(1))) == as_u64(from(MAX_AS_U64 - 1)), 0); + assert!(as_u64(wrapping_sub(from(MAX_AS_U64), neg_from(1))) == as_u64(neg_from(MIN_AS_U64)), 0); + assert!(as_u64(wrapping_sub(neg_from(MIN_AS_U64), neg_from(1))) == as_u64(neg_from(MIN_AS_U64 - 1)), 0); + assert!(as_u64(wrapping_sub(neg_from(MIN_AS_U64), from(1))) == as_u64(from(MAX_AS_U64)), 0); + } + + #[test] + fun test_sub() { + assert!(as_u64(sub(from(0), from(0))) == 0, 0); + assert!(as_u64(sub(from(1), from(0))) == 1, 0); + assert!(as_u64(sub(from(0), from(1))) == as_u64(neg_from(1)), 0); + assert!(as_u64(sub(from(1), from(1))) == as_u64(neg_from(0)), 0); + assert!(as_u64(sub(from(1), neg_from(1))) == as_u64(from(2)), 0); + assert!(as_u64(sub(neg_from(1), from(1))) == as_u64(neg_from(2)), 0); + assert!(as_u64(sub(from(1000000), from(1))) == 999999, 0); + assert!(as_u64(sub(neg_from(1000000), neg_from(1))) == as_u64(neg_from(999999)), 0); + assert!(as_u64(sub(from(1), from(1000000))) == as_u64(neg_from(999999)), 0); + assert!(as_u64(sub(from(MAX_AS_U64), from(MAX_AS_U64))) == as_u64(from(0)), 0); + assert!(as_u64(sub(from(MAX_AS_U64), from(1))) == as_u64(from(MAX_AS_U64 - 1)), 0); + assert!(as_u64(sub(neg_from(MIN_AS_U64), neg_from(1))) == as_u64(neg_from(MIN_AS_U64 - 1)), 0); + } + + #[test] + #[expected_failure] + fun test_sub_overflow() { + sub(from(MAX_AS_U64), neg_from(1)); + } + + #[test] + #[expected_failure] + fun test_sub_underflow() { + sub(neg_from(MIN_AS_U64), from(1)); + } + + #[test] + fun test_mul() { + assert!(as_u64(mul(from(1), from(1))) == 1, 0); + assert!(as_u64(mul(from(10), from(10))) == 100, 0); + assert!(as_u64(mul(from(100), from(100))) == 10000, 0); + assert!(as_u64(mul(from(10000), from(10000))) == 100000000, 0); + + assert!(as_u64(mul(neg_from(1), from(1))) == as_u64(neg_from(1)), 0); + assert!(as_u64(mul(neg_from(10), from(10))) == as_u64(neg_from(100)), 0); + assert!(as_u64(mul(neg_from(100), from(100))) == as_u64(neg_from(10000)), 0); + assert!(as_u64(mul(neg_from(10000), from(10000))) == as_u64(neg_from(100000000)), 0); + + assert!(as_u64(mul(from(1), neg_from(1))) == as_u64(neg_from(1)), 0); + assert!(as_u64(mul(from(10), neg_from(10))) == as_u64(neg_from(100)), 0); + assert!(as_u64(mul(from(100), neg_from(100))) == as_u64(neg_from(10000)), 0); + assert!(as_u64(mul(from(10000), neg_from(10000))) == as_u64(neg_from(100000000)), 0); + assert!(as_u64(mul(from(MIN_AS_U64 / 2), neg_from(2))) == as_u64(neg_from(MIN_AS_U64)), 0); + } + + #[test] + #[expected_failure] + fun test_mul_overflow() { + mul(from(MIN_AS_U64 / 2), from(1)); + mul(neg_from(MIN_AS_U64 / 2), neg_from(2)); + } + + #[test] + fun test_div() { + assert!(as_u64(div(from(0), from(1))) == 0, 0); + assert!(as_u64(div(from(10), from(1))) == 10, 0); + assert!(as_u64(div(from(10), neg_from(1))) == as_u64(neg_from(10)), 0); + assert!(as_u64(div(neg_from(10), neg_from(1))) == as_u64(from(10)), 0); + + assert!(abs_u64(neg_from(MIN_AS_U64)) == MIN_AS_U64, 0); + assert!(as_u64(div(neg_from(MIN_AS_U64), from(1))) == MIN_AS_U64, 0); + } + + #[test] + #[expected_failure] + fun test_div_overflow() { + div(neg_from(MIN_AS_U64), neg_from(1)); + } + + #[test] + fun test_shl() { + assert!(as_u64(shl(from(10), 0)) == 10, 0); + assert!(as_u64(shl(neg_from(10), 0)) == as_u64(neg_from(10)), 0); + + assert!(as_u64(shl(from(10), 1)) == 20, 0); + assert!(as_u64(shl(neg_from(10), 1)) == as_u64(neg_from(20)), 0); + + assert!(as_u64(shl(from(10), 8)) == 2560, 0); + assert!(as_u64(shl(neg_from(10), 8)) == as_u64(neg_from(2560)), 0); + + assert!(as_u64(shl(from(10), 32)) == 42949672960, 0); + assert!(as_u64(shl(neg_from(10), 32)) == as_u64(neg_from(42949672960)), 0); + + assert!(as_u64(shl(from(10), 63)) == 0, 0); + assert!(as_u64(shl(neg_from(10), 63)) == 0, 0); + } + + #[test] + fun test_shr() { + assert!(as_u64(shr(from(10), 0)) == 10, 0); + assert!(as_u64(shr(neg_from(10), 0)) == as_u64(neg_from(10)), 0); + + assert!(as_u64(shr(from(10), 1)) == 5, 0); + assert!(as_u64(shr(neg_from(10), 1)) == as_u64(neg_from(5)), 0); + + assert!(as_u64(shr(from(MAX_AS_U64), 8)) == 36028797018963967, 0); + assert!(as_u64(shr(neg_from(MIN_AS_U64), 8)) == 0xff80000000000000, 0); + + assert!(as_u64(shr(from(MAX_AS_U64), 32)) == 2147483647, 0); + assert!(as_u64(shr(neg_from(MIN_AS_U64), 32)) == 0xffffffff80000000, 0); + + assert!(as_u64(shr(from(MAX_AS_U64), 63)) == 0, 0); + assert!(as_u64(shr(neg_from(MIN_AS_U64), 63)) == 0xffffffffffffffff, 0); + } + + #[test] + fun test_sign() { + assert!(sign(neg_from(10)) == 1u8, 0); + assert!(sign(from(10)) == 0u8, 0); + } + + #[test] + fun test_cmp() { + assert!(cmp(from(1), from(0)) == GT, 0); + assert!(cmp(from(0), from(1)) == LT, 0); + + assert!(cmp(from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(1), neg_from(0)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U64), from(MAX_AS_U64)) == LT, 0); + assert!(cmp(from(MAX_AS_U64), neg_from(MIN_AS_U64)) == GT, 0); + + assert!(cmp(from(MAX_AS_U64), from(MAX_AS_U64 - 1)) == GT, 0); + assert!(cmp(from(MAX_AS_U64 - 1), from(MAX_AS_U64)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U64), neg_from(MIN_AS_U64 - 1)) == LT, 0); + assert!(cmp(neg_from(MIN_AS_U64 - 1), neg_from(MIN_AS_U64)) == GT, 0); + } + + #[test] + fun test_castdown() { + assert!((1u64 as u8) == 1u8, 0); + } + + #[test] + fun test_mod() { + //use aptos_std::debug; + let i = mod(neg_from(2), from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(neg_from(2), neg_from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(from(2), from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + + i = mod(from(2), neg_from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + } + + #[test] + fun test_from_i32() { + let i = from_i32(i32::neg_from(1)); + assert!(cmp(i, neg_from(1)) == EQ, 0); + + let i = from_i32(i32::from(1)); + assert!(cmp(i, from(1)) == EQ, 0); + } +} diff --git a/frameworks/moveos-stdlib/sources/i8.move b/frameworks/moveos-stdlib/sources/i8.move new file mode 100644 index 0000000000..3f55d48791 --- /dev/null +++ b/frameworks/moveos-stdlib/sources/i8.move @@ -0,0 +1,473 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module moveos_std::i8 { + + const ErrorOverflow: u64 = 0; + + const MIN_AS_U8: u8 = 1 << 7; + const MAX_AS_U8: u8 = 0x7f; + + const LT: u8 = 0; + const EQ: u8 = 1; + const GT: u8 = 2; + + #[data_struct] + struct I8 has copy, drop, store { + bits: u8 + } + + public fun zero(): I8 { + I8 { + bits: 0 + } + } + + public fun from_u8(v: u8): I8 { + I8 { + bits: v + } + } + + public fun from(v: u8): I8 { + assert!(v <= MAX_AS_U8, ErrorOverflow); + I8 { + bits: v + } + } + + public fun neg_from(v: u8): I8 { + assert!(v <= MIN_AS_U8, ErrorOverflow); + if (v == 0) { + I8 { + bits: v + } + } else { + I8 { + bits: (u8_neg(v) + 1) | (1 << 7) + } + } + } + + public fun wrapping_add(num1: I8, num2: I8): I8 { + let sum = num1.bits ^ num2.bits; + let carry = (num1.bits & num2.bits) << 1; + while (carry != 0) { + let a = sum; + let b = carry; + sum = a ^ b; + carry = (a & b) << 1; + }; + I8 { + bits: sum + } + } + + public fun add(num1: I8, num2: I8): I8 { + let sum = wrapping_add(num1, num2); + let overflow = (sign(num1) & sign(num2) & u8_neg(sign(sum))) + + (u8_neg(sign(num1)) & u8_neg(sign(num2)) & sign(sum)); + assert!(overflow == 0, ErrorOverflow); + sum + } + + public fun wrapping_sub(num1: I8, num2: I8): I8 { + let sub_num = wrapping_add(I8 { + bits: u8_neg(num2.bits) + }, from(1)); + wrapping_add(num1, sub_num) + } + + public fun sub(num1: I8, num2: I8): I8 { + let sub_num = wrapping_add(I8 { + bits: u8_neg(num2.bits) + }, from(1)); + add(num1, sub_num) + } + + public fun mul(num1: I8, num2: I8): I8 { + let product = abs_u8(num1) * abs_u8(num2); + if (sign(num1) != sign(num2)) { + return neg_from(product) + }; + return from(product) + } + + public fun div(num1: I8, num2: I8): I8 { + let result = abs_u8(num1) / abs_u8(num2); + if (sign(num1) != sign(num2)) { + return neg_from(result) + }; + return from(result) + } + + public fun abs(v: I8): I8 { + if (sign(v) == 0) { + v + } else { + assert!(v.bits > MIN_AS_U8, ErrorOverflow); + I8 { + bits: u8_neg(v.bits - 1) + } + } + } + + public fun abs_u8(v: I8): u8 { + if (sign(v) == 0) { + v.bits + } else { + u8_neg(v.bits - 1) + } + } + + public fun shl(v: I8, shift: u8): I8 { + I8 { + bits: v.bits << shift + } + } + + public fun shr(v: I8, shift: u8): I8 { + if (shift == 0) { + return v + }; + let mask = 0xff << (8 - shift); + if (sign(v) == 1) { + return I8 { + bits: (v.bits >> shift) | mask + } + }; + I8 { + bits: v.bits >> shift + } + } + + public fun mod(v: I8, n: I8): I8 { + if (sign(v) == 1) { + neg_from((abs_u8(v) % abs_u8(n))) + } else { + from((as_u8(v) % abs_u8(n))) + } + } + + public fun as_u8(v: I8): u8 { + v.bits + } + + public fun sign(v: I8): u8 { + ((v.bits >> 7) as u8) + } + + public fun is_neg(v: I8): bool { + sign(v) == 1 + } + + public fun cmp(num1: I8, num2: I8): u8 { + if (num1.bits == num2.bits) return EQ; + if (sign(num1) > sign(num2)) return LT; + if (sign(num1) < sign(num2)) return GT; + if (num1.bits > num2.bits) { + return GT + } else { + return LT + } + } + + public fun eq(num1: I8, num2: I8): bool { + num1.bits == num2.bits + } + + public fun gt(num1: I8, num2: I8): bool { + cmp(num1, num2) == GT + } + + public fun gte(num1: I8, num2: I8): bool { + cmp(num1, num2) >= EQ + } + + public fun lt(num1: I8, num2: I8): bool { + cmp(num1, num2) == LT + } + + public fun lte(num1: I8, num2: I8): bool { + cmp(num1, num2) <= EQ + } + + public fun or(num1: I8, num2: I8): I8 { + I8 { + bits: (num1.bits | num2.bits) + } + } + + public fun and(num1: I8, num2: I8): I8 { + I8 { + bits: (num1.bits & num2.bits) + } + } + + fun u8_neg(v: u8): u8 { + v ^ 0xff + } + + #[test] + fun test_from_ok() { + assert!(as_u8(from(0)) == 0, 0); + assert!(as_u8(from(10)) == 10, 1); + } + + #[test] + #[expected_failure] + fun test_from_overflow() { + as_u8(from(MIN_AS_U8)); + as_u8(from(0xff)); + } + + #[test] + fun test_neg_from() { + assert!(as_u8(neg_from(0)) == 0, 0); + assert!(as_u8(neg_from(1)) == 0xff, 1); + assert!(as_u8(neg_from(0x7f)) == 129, 2); + assert!(as_u8(neg_from(MIN_AS_U8)) == MIN_AS_U8, 2); + } + + #[test] + #[expected_failure] + fun test_neg_from_overflow() { + neg_from(MIN_AS_U8+1); + } + + #[test] + fun test_abs() { + assert!(as_u8(from(10)) == 10u8, 0); + assert!(as_u8(abs(neg_from(10))) == 10u8, 1); + assert!(as_u8(abs(neg_from(0))) == 0u8, 2); + assert!(as_u8(abs(neg_from(0x7f))) == 0x7f, 3); + assert!(as_u8(neg_from(MIN_AS_U8)) == MIN_AS_U8, 4); + } + + #[test] + #[expected_failure] + fun test_abs_overflow() { + abs(neg_from(0x80)); + } + + #[test] + fun test_wrapping_add() { + assert!(as_u8(wrapping_add(from(0), from(1))) == 1, 0); + assert!(as_u8(wrapping_add(from(1), from(0))) == 1, 0); + assert!(as_u8(wrapping_add(from(100), from(27))) == 127, 0); + assert!(as_u8(wrapping_add(from(27), from(100))) == 127, 0); + assert!(as_u8(wrapping_add(from(MAX_AS_U8 - 1), from(1))) == MAX_AS_U8, 0); + assert!(as_u8(wrapping_add(from(0), from(0))) == 0, 0); + + assert!(as_u8(wrapping_add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u8(wrapping_add(neg_from(1), neg_from(0))) == 0xff, 1); + assert!(as_u8(wrapping_add(neg_from(0), neg_from(1))) == 0xff, 1); + assert!(as_u8(wrapping_add(neg_from(100), neg_from(27))) == 129, 1); + assert!(as_u8(wrapping_add(neg_from(27), neg_from(100))) == 129, 1); + assert!(as_u8(wrapping_add(neg_from(MIN_AS_U8 - 1), neg_from(1))) == MIN_AS_U8, 1); + + assert!(as_u8(wrapping_add(from(0), neg_from(0))) == 0, 2); + assert!(as_u8(wrapping_add(neg_from(0), from(0))) == 0, 2); + assert!(as_u8(wrapping_add(neg_from(1), from(1))) == 0, 2); + assert!(as_u8(wrapping_add(from(1), neg_from(1))) == 0, 2); + assert!(as_u8(wrapping_add(from(100), neg_from(27))) == 73, 2); + assert!(as_u8(wrapping_add(from(27), neg_from(100))) == 183, 2); + assert!(as_u8(wrapping_add(neg_from(MIN_AS_U8), from(1))) == 129, 2); + assert!(as_u8(wrapping_add(from(MAX_AS_U8), neg_from(1))) == 126, 2); + + assert!(as_u8(wrapping_add(from(MAX_AS_U8), from(1))) == MIN_AS_U8, 2); + } + + #[test] + fun test_add() { + assert!(as_u8(add(from(0), from(0))) == 0, 0); + assert!(as_u8(add(from(0), from(1))) == 1, 0); + assert!(as_u8(add(from(1), from(0))) == 1, 0); + assert!(as_u8(add(from(100), from(27))) == 127, 0); + assert!(as_u8(add(from(27), from(100))) == 127, 0); + assert!(as_u8(add(from(MAX_AS_U8 - 1), from(1))) == MAX_AS_U8, 0); + + assert!(as_u8(add(neg_from(0), neg_from(0))) == 0, 1); + assert!(as_u8(add(neg_from(1), neg_from(0))) == 0xff, 1); + assert!(as_u8(add(neg_from(0), neg_from(1))) == 0xff, 1); + //std::debug::print(&as_u8(add(neg_from(100), neg_from(27)))); + assert!(as_u8(add(neg_from(100), neg_from(27))) == 129, 1); + assert!(as_u8(add(neg_from(27), neg_from(100))) == 129, 1); + assert!(as_u8(add(neg_from(MIN_AS_U8 - 1), neg_from(1))) == MIN_AS_U8, 1); + + assert!(as_u8(add(from(0), neg_from(0))) == 0, 2); + assert!(as_u8(add(neg_from(0), from(0))) == 0, 2); + assert!(as_u8(add(neg_from(1), from(1))) == 0, 2); + assert!(as_u8(add(from(1), neg_from(1))) == 0, 2); + assert!(as_u8(add(from(100), neg_from(99))) == 1, 2); + // 99 - 100 = -1 + assert!(as_u8(add(from(99), neg_from(100))) == 255, 2); + // -128 - 127 = -1 + assert!(as_u8(add(neg_from(MIN_AS_U8), from(MAX_AS_U8))) == 255, 2); + assert!(as_u8(add(from(MAX_AS_U8), neg_from(1))) == MAX_AS_U8 - 1, 2); + } + + #[test] + #[expected_failure] + fun test_add_overflow() { + add(from(MAX_AS_U8), from(1)); + } + + #[test] + #[expected_failure] + fun test_add_underflow() { + add(neg_from(MIN_AS_U8), neg_from(1)); + } + + #[test] + fun test_wrapping_sub() { + assert!(as_u8(wrapping_sub(from(0), from(0))) == 0, 0); + assert!(as_u8(wrapping_sub(from(1), from(0))) == 1, 0); + assert!(as_u8(wrapping_sub(from(0), from(1))) == as_u8(neg_from(1)), 0); + assert!(as_u8(wrapping_sub(from(1), from(1))) == as_u8(neg_from(0)), 0); + assert!(as_u8(wrapping_sub(from(1), neg_from(1))) == as_u8(from(2)), 0); + assert!(as_u8(wrapping_sub(neg_from(1), from(1))) == as_u8(neg_from(2)), 0); + assert!(as_u8(wrapping_sub(from(100), from(1))) == 99, 0); + assert!(as_u8(wrapping_sub(neg_from(100), neg_from(1))) == as_u8(neg_from(99)), 0); + assert!(as_u8(wrapping_sub(from(1), from(100))) == as_u8(neg_from(99)), 0); + assert!(as_u8(wrapping_sub(from(MAX_AS_U8), from(MAX_AS_U8))) == as_u8(from(0)), 0); + assert!(as_u8(wrapping_sub(from(MAX_AS_U8), from(1))) == as_u8(from(MAX_AS_U8 - 1)), 0); + assert!(as_u8(wrapping_sub(from(MAX_AS_U8), neg_from(1))) == as_u8(neg_from(MIN_AS_U8)), 0); + assert!(as_u8(wrapping_sub(neg_from(MIN_AS_U8), neg_from(1))) == as_u8(neg_from(MIN_AS_U8 - 1)), 0); + assert!(as_u8(wrapping_sub(neg_from(MIN_AS_U8), from(1))) == as_u8(from(MAX_AS_U8)), 0); + } + + #[test] + fun test_sub() { + assert!(as_u8(sub(from(0), from(0))) == 0, 0); + assert!(as_u8(sub(from(1), from(0))) == 1, 0); + assert!(as_u8(sub(from(0), from(1))) == as_u8(neg_from(1)), 0); + assert!(as_u8(sub(from(1), from(1))) == as_u8(neg_from(0)), 0); + assert!(as_u8(sub(from(1), neg_from(1))) == as_u8(from(2)), 0); + assert!(as_u8(sub(neg_from(1), from(1))) == as_u8(neg_from(2)), 0); + assert!(as_u8(sub(from(100), from(1))) == 99, 0); + assert!(as_u8(sub(neg_from(100), neg_from(1))) == as_u8(neg_from(99)), 0); + assert!(as_u8(sub(from(1), from(100))) == as_u8(neg_from(99)), 0); + assert!(as_u8(sub(from(MAX_AS_U8), from(MAX_AS_U8))) == as_u8(from(0)), 0); + assert!(as_u8(sub(from(MAX_AS_U8), from(1))) == as_u8(from(MAX_AS_U8 - 1)), 0); + assert!(as_u8(sub(neg_from(MIN_AS_U8), neg_from(1))) == as_u8(neg_from(MIN_AS_U8 - 1)), 0); + } + + #[test] + #[expected_failure] + fun test_sub_overflow() { + sub(from(MAX_AS_U8), neg_from(1)); + } + + #[test] + #[expected_failure] + fun test_sub_underflow() { + sub(neg_from(MIN_AS_U8), from(1)); + } + + #[test] + fun test_mul() { + assert!(as_u8(mul(from(1), from(1))) == 1, 0); + assert!(as_u8(mul(from(10), from(10))) == 100, 0); + + assert!(as_u8(mul(neg_from(1), from(1))) == as_u8(neg_from(1)), 0); + assert!(as_u8(mul(neg_from(10), from(10))) == as_u8(neg_from(100)), 0); + + assert!(as_u8(mul(from(1), neg_from(1))) == as_u8(neg_from(1)), 0); + assert!(as_u8(mul(from(10), neg_from(10))) == as_u8(neg_from(100)), 0); + assert!(as_u8(mul(from(MIN_AS_U8 / 2), neg_from(2))) == as_u8(neg_from(MIN_AS_U8)), 0); + } + + #[test] + #[expected_failure] + fun test_mul_overflow() { + mul(from(MIN_AS_U8 / 2), from(1)); + mul(neg_from(MIN_AS_U8 / 2), neg_from(2)); + } + + #[test] + fun test_div() { + assert!(as_u8(div(from(0), from(1))) == 0, 0); + assert!(as_u8(div(from(10), from(1))) == 10, 0); + assert!(as_u8(div(from(10), neg_from(1))) == as_u8(neg_from(10)), 0); + assert!(as_u8(div(neg_from(10), neg_from(1))) == as_u8(from(10)), 0); + + assert!(abs_u8(neg_from(MIN_AS_U8)) == MIN_AS_U8, 0); + assert!(as_u8(div(neg_from(MIN_AS_U8), from(1))) == MIN_AS_U8, 0); + } + + #[test] + #[expected_failure] + fun test_div_overflow() { + div(neg_from(MIN_AS_U8), neg_from(1)); + } + + #[test] + fun test_shl() { + assert!(as_u8(shl(from(10), 0)) == 10, 0); + assert!(as_u8(shl(neg_from(10), 0)) == as_u8(neg_from(10)), 0); + + assert!(as_u8(shl(from(10), 1)) == 20, 0); + assert!(as_u8(shl(neg_from(10), 1)) == as_u8(neg_from(20)), 0); + + assert!(as_u8(shl(from(10), 3)) == 80, 0); + assert!(as_u8(shl(neg_from(10), 3)) == as_u8(neg_from(80)), 0); + + assert!(as_u8(shl(from(10), 7)) == 0, 0); + assert!(as_u8(shl(neg_from(10), 7)) == 0, 0); + } + + #[test] + fun test_shr() { + assert!(as_u8(shr(from(10), 0)) == 10, 0); + assert!(as_u8(shr(neg_from(10), 0)) == as_u8(neg_from(10)), 0); + + assert!(as_u8(shr(from(10), 1)) == 5, 0); + assert!(as_u8(shr(neg_from(10), 1)) == as_u8(neg_from(5)), 0); + + assert!(as_u8(shr(from(MAX_AS_U8), 7)) == 0, 0); + assert!(as_u8(shr(neg_from(MIN_AS_U8), 7)) == 255, 0); + } + + #[test] + fun test_sign() { + assert!(sign(neg_from(10)) == 1u8, 0); + assert!(sign(from(10)) == 0u8, 0); + } + + #[test] + fun test_cmp() { + assert!(cmp(from(1), from(0)) == GT, 0); + assert!(cmp(from(0), from(1)) == LT, 0); + + assert!(cmp(from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(0), neg_from(1)) == GT, 0); + assert!(cmp(neg_from(1), neg_from(0)) == LT, 0); + assert!(!lt(from(47), neg_from(47)), 0); + + assert!(cmp(neg_from(MIN_AS_U8), from(MAX_AS_U8)) == LT, 0); + assert!(cmp(from(MAX_AS_U8), neg_from(MIN_AS_U8)) == GT, 0); + + assert!(cmp(from(MAX_AS_U8), from(MAX_AS_U8 - 1)) == GT, 0); + assert!(cmp(from(MAX_AS_U8 - 1), from(MAX_AS_U8)) == LT, 0); + + assert!(cmp(neg_from(MIN_AS_U8), neg_from(MIN_AS_U8 - 1)) == LT, 0); + assert!(cmp(neg_from(MIN_AS_U8 - 1), neg_from(MIN_AS_U8)) == GT, 0); + } + + #[test] + fun test_mod() { + //use aptos_std::debug; + let i = mod(neg_from(2), from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(neg_from(2), neg_from(5)); + assert!(cmp(i, neg_from(2)) == EQ, 0); + + i = mod(from(2), from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + + i = mod(from(2), neg_from(5)); + assert!(cmp(i, from(2)) == EQ, 0); + } +} diff --git a/frameworks/moveos-stdlib/sources/string_utils.move b/frameworks/moveos-stdlib/sources/string_utils.move index de399ac712..83ef1b9d70 100644 --- a/frameworks/moveos-stdlib/sources/string_utils.move +++ b/frameworks/moveos-stdlib/sources/string_utils.move @@ -31,6 +31,32 @@ module moveos_std::string_utils { option::destroy_some(result) } + public fun parse_u32_from_bytes(bytes: &vector): Option { + let i = 0; + let result = 0u32; + while (i < vector::length(bytes)) { + let c = *vector::borrow(bytes, i); + if (c >= 48 && c <= 57) { + result = result * 10 + ((c - 48) as u32); + } else { + return option::none() + }; + i = i + 1; + }; + option::some(result) + } + + public fun parse_u32_option(s: &String):Option{ + let bytes:&vector = string::bytes(s); + parse_u32_from_bytes(bytes) + } + + public fun parse_u32(s: &String): u32 { + let result = parse_u32_option(s); + assert!(option::is_some(&result), ErrorInvalidStringNumber); + option::destroy_some(result) + } + public fun parse_u64_option(s: &String):Option{ let bytes:&vector = string::bytes(s); let i = 0; @@ -175,7 +201,7 @@ module moveos_std::string_utils { to_string_u256((n as u256)) } - public fun to_string_u32(n: u32): String { + public fun to_string_u32(n: u32): String { to_string_u256((n as u256)) } diff --git a/frameworks/rooch-framework/doc/README.md b/frameworks/rooch-framework/doc/README.md index d025e71c48..43e5be97bd 100644 --- a/frameworks/rooch-framework/doc/README.md +++ b/frameworks/rooch-framework/doc/README.md @@ -30,6 +30,7 @@ This is the reference documentation of the Rooch Framework. - [`0x3::ed25519`](ed25519.md#0x3_ed25519) - [`0x3::empty`](empty.md#0x3_empty) - [`0x3::ethereum_address`](ethereum_address.md#0x3_ethereum_address) +- [`0x3::ethereum_validator`](ethereum_validator.md#0x3_ethereum_validator) - [`0x3::gas_coin`](gas_coin.md#0x3_gas_coin) - [`0x3::genesis`](genesis.md#0x3_genesis) - [`0x3::multichain_address`](multichain_address.md#0x3_multichain_address) @@ -41,6 +42,9 @@ This is the reference documentation of the Rooch Framework. - [`0x3::session_validator`](session_validator.md#0x3_session_validator) - [`0x3::simple_rng`](simple_rng.md#0x3_simple_rng) - [`0x3::timestamp`](timestamp.md#0x3_timestamp) +- [`0x3::ton_address`](ton_address.md#0x3_ton_address) +- [`0x3::ton_proof`](ton_proof.md#0x3_ton_proof) +- [`0x3::ton_validator`](ton_validator.md#0x3_ton_validator) - [`0x3::transaction`](transaction.md#0x3_transaction) - [`0x3::transaction_fee`](transaction_fee.md#0x3_transaction_fee) - [`0x3::transaction_validator`](transaction_validator.md#0x3_transaction_validator) diff --git a/frameworks/rooch-framework/doc/address_mapping.md b/frameworks/rooch-framework/doc/address_mapping.md index 7fd3095ea8..c4ebfa790e 100644 --- a/frameworks/rooch-framework/doc/address_mapping.md +++ b/frameworks/rooch-framework/doc/address_mapping.md @@ -7,20 +7,32 @@ - [Resource `MultiChainAddressMapping`](#0x3_address_mapping_MultiChainAddressMapping) - [Resource `RoochToBitcoinAddressMapping`](#0x3_address_mapping_RoochToBitcoinAddressMapping) +- [Resource `RoochToTonAddressMapping`](#0x3_address_mapping_RoochToTonAddressMapping) - [Constants](#@Constants_0) - [Function `genesis_init`](#0x3_address_mapping_genesis_init) +- [Function `init_ton_mapping`](#0x3_address_mapping_init_ton_mapping) - [Function `resolve`](#0x3_address_mapping_resolve) - [Function `resolve_bitcoin`](#0x3_address_mapping_resolve_bitcoin) - [Function `exists_mapping`](#0x3_address_mapping_exists_mapping) -- [Function `bind_bitcoin_address`](#0x3_address_mapping_bind_bitcoin_address) +- [Function `bind_bitcoin_address_internal`](#0x3_address_mapping_bind_bitcoin_address_internal) - [Function `bind_bitcoin_address_by_system`](#0x3_address_mapping_bind_bitcoin_address_by_system) +- [Function `bind_bitcoin_address`](#0x3_address_mapping_bind_bitcoin_address) +- [Function `resolve_to_ton_address`](#0x3_address_mapping_resolve_to_ton_address) +- [Function `resolve_via_ton_address`](#0x3_address_mapping_resolve_via_ton_address) +- [Function `resolve_via_ton_address_str`](#0x3_address_mapping_resolve_via_ton_address_str) +- [Function `bind_ton_address`](#0x3_address_mapping_bind_ton_address) +- [Function `bind_ton_address_entry`](#0x3_address_mapping_bind_ton_address_entry)
use 0x1::option;
+use 0x1::string;
 use 0x2::core_addresses;
 use 0x2::object;
+use 0x2::tx_context;
 use 0x3::bitcoin_address;
 use 0x3::multichain_address;
+use 0x3::ton_address;
+use 0x3::ton_proof;
 
@@ -52,11 +64,42 @@ The mapping record is the object field, key is the rooch address, value is the B + + +## Resource `RoochToTonAddressMapping` + +Mapping from rooch address to ton address +The mapping record is the object field, key is the rooch address, value is the ton address + + +
struct RoochToTonAddressMapping has key
+
+ + + ## Constants + + + + +
const ErrorInvalidBindingAddress: u64 = 4;
+
+ + + + + + + +
const ErrorInvalidBindingProof: u64 = 3;
+
+ + + @@ -99,7 +142,18 @@ The mapping record is the object field, key is the rooch address, value is the B -
public(friend) fun genesis_init(_genesis_account: &signer)
+
public(friend) fun genesis_init()
+
+ + + + + +## Function `init_ton_mapping` + + + +
public entry fun init_ton_mapping()
 
@@ -140,13 +194,13 @@ Check if a multi-chain address is bound to a rooch address - + -## Function `bind_bitcoin_address` +## Function `bind_bitcoin_address_internal` -
public(friend) fun bind_bitcoin_address(rooch_address: address, baddress: bitcoin_address::BitcoinAddress)
+
public(friend) fun bind_bitcoin_address_internal(rooch_address: address, btc_address: bitcoin_address::BitcoinAddress)
 
@@ -157,5 +211,76 @@ Check if a multi-chain address is bound to a rooch address -
public fun bind_bitcoin_address_by_system(system: &signer, rooch_address: address, baddress: bitcoin_address::BitcoinAddress)
+
public fun bind_bitcoin_address_by_system(system: &signer, rooch_address: address, btc_address: bitcoin_address::BitcoinAddress)
+
+ + + + + +## Function `bind_bitcoin_address` + +Bind a bitcoin address to a rooch address +We can calculate the rooch address from bitcoin address +So we call this function for record rooch address to bitcoin address mapping + + +
public fun bind_bitcoin_address(btc_address: bitcoin_address::BitcoinAddress)
+
+ + + + + +## Function `resolve_to_ton_address` + + + +
public fun resolve_to_ton_address(sender: address): option::Option<ton_address::TonAddress>
+
+ + + + + +## Function `resolve_via_ton_address` + + + +
public fun resolve_via_ton_address(ton_address: ton_address::TonAddress): option::Option<address>
+
+ + + + + +## Function `resolve_via_ton_address_str` + + + +
public fun resolve_via_ton_address_str(ton_address_str: string::String): option::Option<address>
+
+ + + + + +## Function `bind_ton_address` + +Bind a ton address to a rooch address +The user needs to provide a valid ton proof and the ton address he wants to bind + + +
public fun bind_ton_address(proof_data: ton_proof::TonProofData, ton_address: ton_address::TonAddress)
+
+ + + + + +## Function `bind_ton_address_entry` + + + +
public fun bind_ton_address_entry(proof_data_bytes: vector<u8>, ton_address_str: string::String)
 
diff --git a/frameworks/rooch-framework/doc/auth_validator_registry.md b/frameworks/rooch-framework/doc/auth_validator_registry.md index e2c92fb154..fd9fdb6b63 100644 --- a/frameworks/rooch-framework/doc/auth_validator_registry.md +++ b/frameworks/rooch-framework/doc/auth_validator_registry.md @@ -8,16 +8,19 @@ - [Resource `AuthValidatorWithType`](#0x3_auth_validator_registry_AuthValidatorWithType) - [Resource `ValidatorRegistry`](#0x3_auth_validator_registry_ValidatorRegistry) - [Constants](#@Constants_0) +- [Function `system_reserved_validator_id`](#0x3_auth_validator_registry_system_reserved_validator_id) - [Function `genesis_init`](#0x3_auth_validator_registry_genesis_init) - [Function `register`](#0x3_auth_validator_registry_register) - [Function `register_by_system`](#0x3_auth_validator_registry_register_by_system) +- [Function `register_by_system_with_id`](#0x3_auth_validator_registry_register_by_system_with_id) - [Function `register_internal`](#0x3_auth_validator_registry_register_internal) - [Function `is_registered`](#0x3_auth_validator_registry_is_registered) - [Function `borrow_validator`](#0x3_auth_validator_registry_borrow_validator) - [Function `borrow_validator_by_type`](#0x3_auth_validator_registry_borrow_validator_by_type) -
use 0x1::string;
+
use 0x1::option;
+use 0x1::string;
 use 0x2::account;
 use 0x2::core_addresses;
 use 0x2::features;
@@ -56,6 +59,24 @@
 ## Constants
 
 
+
+
+
+
+
const ErrorDeprecated: u64 = 3;
+
+ + + + + + + +
const ErrorInvalidValidatorId: u64 = 4;
+
+ + + @@ -74,6 +95,27 @@ + + +From 0 to 99 are reserved for system validators. + + +
const SYSTEM_RESERVED_VALIDATOR_ID: u64 = 100;
+
+ + + + + +## Function `system_reserved_validator_id` + + + +
public fun system_reserved_validator_id(): u64
+
+ + + ## Function `genesis_init` @@ -103,10 +145,22 @@ Register a new validator. This feature not enabled in the mainnet. ## Function `register_by_system` -Register a new validator by system. This function is only called by system. +Deprecated. + + +
public fun register_by_system<ValidatorType: store>(_system: &signer): u64
+
+ + + + + +## Function `register_by_system_with_id` + +Register a new validator by system with a specific id. This function is only called by system. -
public fun register_by_system<ValidatorType: store>(system: &signer): u64
+
public fun register_by_system_with_id<ValidatorType: store>(system: &signer, id: u64): u64
 
@@ -117,7 +171,7 @@ Register a new validator by system. This function is only called by system. -
public(friend) fun register_internal<ValidatorType: store>(): u64
+
public(friend) fun register_internal<ValidatorType: store>(id: option::Option<u64>): u64
 
diff --git a/frameworks/rooch-framework/doc/builtin_validators.md b/frameworks/rooch-framework/doc/builtin_validators.md index 18bc47424f..288061c067 100644 --- a/frameworks/rooch-framework/doc/builtin_validators.md +++ b/frameworks/rooch-framework/doc/builtin_validators.md @@ -10,7 +10,8 @@ - [Function `is_builtin_auth_validator`](#0x3_builtin_validators_is_builtin_auth_validator) -
use 0x3::auth_validator_registry;
+
use 0x1::option;
+use 0x3::auth_validator_registry;
 use 0x3::bitcoin_validator;
 use 0x3::session_validator;
 
diff --git a/frameworks/rooch-framework/doc/ethereum_validator.md b/frameworks/rooch-framework/doc/ethereum_validator.md new file mode 100644 index 0000000000..cbeb2cf013 --- /dev/null +++ b/frameworks/rooch-framework/doc/ethereum_validator.md @@ -0,0 +1,106 @@ + + + +# Module `0x3::ethereum_validator` + +This module implements Ethereum validator with the ECDSA recoverable signature over Secp256k1. + + +- [Struct `EthereumValidator`](#0x3_ethereum_validator_EthereumValidator) +- [Constants](#@Constants_0) +- [Function `auth_validator_id`](#0x3_ethereum_validator_auth_validator_id) +- [Function `genesis_init`](#0x3_ethereum_validator_genesis_init) +- [Function `validate`](#0x3_ethereum_validator_validate) + + +
use 0x2::signer;
+use 0x3::auth_validator_registry;
+
+ + + + + +## Struct `EthereumValidator` + + + +
struct EthereumValidator has drop, store
+
+ + + + + +## Constants + + + + +there defines auth validator id for each blockchain + + +
const ETHEREUM_AUTH_VALIDATOR_ID: u64 = 3;
+
+ + + + + + + +
const ErrorAddressMappingRecordNotFound: u64 = 2;
+
+ + + + + + + +
const ErrorGenesisInitError: u64 = 1;
+
+ + + + + + + +
const ErrorNotImplemented: u64 = 3;
+
+ + + + + +## Function `auth_validator_id` + + + +
public fun auth_validator_id(): u64
+
+ + + + + +## Function `genesis_init` + + + +
public(friend) fun genesis_init()
+
+ + + + + +## Function `validate` + +We need to redesign the Ethereum auth validator +This module is just for placeholder the AUTH_VALIDATOR_ID + + +
public fun validate(_authenticator_payload: vector<u8>)
+
diff --git a/frameworks/rooch-framework/doc/genesis.md b/frameworks/rooch-framework/doc/genesis.md index a70fa61189..b8b38e2739 100644 --- a/frameworks/rooch-framework/doc/genesis.md +++ b/frameworks/rooch-framework/doc/genesis.md @@ -7,6 +7,7 @@ - [Struct `GenesisContext`](#0x3_genesis_GenesisContext) - [Constants](#@Constants_0) +- [Function `init_for_v15`](#0x3_genesis_init_for_v15)
use 0x1::option;
@@ -24,8 +25,10 @@
 use 0x3::builtin_validators;
 use 0x3::chain_id;
 use 0x3::coin;
+use 0x3::ethereum_validator;
 use 0x3::gas_coin;
 use 0x3::onchain_config;
+use 0x3::ton_validator;
 use 0x3::transaction_fee;
 
@@ -63,3 +66,16 @@ GenesisContext is a genesis init parameters in the TxContext.
const GENESIS_INIT_GAS_AMOUNT: u256 = 50000000000000000;
 
+ + + + + +## Function `init_for_v15` + +Because the init function only can be called once when the first deploy, +we need to add a new function to init the new validators for v15. + + +
public entry fun init_for_v15()
+
diff --git a/frameworks/rooch-framework/doc/multichain_address.md b/frameworks/rooch-framework/doc/multichain_address.md index 534d0c2dd0..c21fdffdd6 100644 --- a/frameworks/rooch-framework/doc/multichain_address.md +++ b/frameworks/rooch-framework/doc/multichain_address.md @@ -11,26 +11,30 @@ - [Function `multichain_id_ether`](#0x3_multichain_address_multichain_id_ether) - [Function `multichain_id_nostr`](#0x3_multichain_address_multichain_id_nostr) - [Function `multichain_id_rooch`](#0x3_multichain_address_multichain_id_rooch) +- [Function `multichain_id_ton`](#0x3_multichain_address_multichain_id_ton) - [Function `get_length`](#0x3_multichain_address_get_length) - [Function `new`](#0x3_multichain_address_new) - [Function `from_bytes`](#0x3_multichain_address_from_bytes) - [Function `from_eth`](#0x3_multichain_address_from_eth) - [Function `from_bitcoin`](#0x3_multichain_address_from_bitcoin) +- [Function `from_ton`](#0x3_multichain_address_from_ton) - [Function `multichain_id`](#0x3_multichain_address_multichain_id) - [Function `raw_address`](#0x3_multichain_address_raw_address) - [Function `is_rooch_address`](#0x3_multichain_address_is_rooch_address) - [Function `is_eth_address`](#0x3_multichain_address_is_eth_address) - [Function `is_bitcoin_address`](#0x3_multichain_address_is_bitcoin_address) +- [Function `is_ton_address`](#0x3_multichain_address_is_ton_address) - [Function `into_rooch_address`](#0x3_multichain_address_into_rooch_address) - [Function `into_eth_address`](#0x3_multichain_address_into_eth_address) - [Function `into_bitcoin_address`](#0x3_multichain_address_into_bitcoin_address) +- [Function `into_ton_address`](#0x3_multichain_address_into_ton_address) - [Function `mapping_to_rooch_address`](#0x3_multichain_address_mapping_to_rooch_address)
use 0x2::bcs;
-use 0x2::hash;
 use 0x3::bitcoin_address;
 use 0x3::ethereum_address;
+use 0x3::ton_address;
 
@@ -61,6 +65,15 @@ + + + + +
const ErrorDeprecated: u64 = 2;
+
+ + + @@ -106,6 +119,15 @@ + + + + +
const MULTICHAIN_ID_TON: u64 = 607;
+
+ + + ## Function `multichain_id_bitcoin` @@ -150,6 +172,17 @@ + + +## Function `multichain_id_ton` + + + +
public fun multichain_id_ton(): u64
+
+ + + ## Function `get_length` @@ -205,6 +238,17 @@ + + +## Function `from_ton` + + + +
public fun from_ton(ton_address: ton_address::TonAddress): multichain_address::MultiChainAddress
+
+ + + ## Function `multichain_id` @@ -260,6 +304,17 @@ + + +## Function `is_ton_address` + + + +
public fun is_ton_address(maddress: &multichain_address::MultiChainAddress): bool
+
+ + + ## Function `into_rooch_address` @@ -293,13 +348,25 @@ + + +## Function `into_ton_address` + + + +
public fun into_ton_address(maddress: multichain_address::MultiChainAddress): ton_address::TonAddress
+
+ + + ## Function `mapping_to_rooch_address` -Mapping from MultiChainAddress to rooch address -If the MultiChainAddress is not rooch address, it will generate a new rooch address based on the MultiChainAddress +Deprecated, will be removed in the future +Now, we only support Bitcoin address generate rooch address +Other address types need to resolve via address_mapping module: bitcoin_address::to_rooch_address -
public fun mapping_to_rooch_address(maddress: multichain_address::MultiChainAddress): address
+
public fun mapping_to_rooch_address(_maddress: multichain_address::MultiChainAddress): address
 
diff --git a/frameworks/rooch-framework/doc/ton_address.md b/frameworks/rooch-framework/doc/ton_address.md new file mode 100644 index 0000000000..186e9eb367 --- /dev/null +++ b/frameworks/rooch-framework/doc/ton_address.md @@ -0,0 +1,121 @@ + + + +# Module `0x3::ton_address` + + + +- [Struct `TonAddress`](#0x3_ton_address_TonAddress) +- [Constants](#@Constants_0) +- [Function `from_hex_str`](#0x3_ton_address_from_hex_str) +- [Function `from_string`](#0x3_ton_address_from_string) +- [Function `from_bytes`](#0x3_ton_address_from_bytes) +- [Function `into_bytes`](#0x3_ton_address_into_bytes) + + +
use 0x1::option;
+use 0x1::string;
+use 0x1::vector;
+use 0x2::bcs;
+use 0x2::hex;
+use 0x2::string_utils;
+
+ + + + + +## Struct `TonAddress` + + + +
#[data_struct]
+struct TonAddress has copy, drop, store
+
+ + + + + +## Constants + + + + + + +
const ErrorInvalidAddress: u64 = 1;
+
+ + + + + + + +
const ErrorInvalidWorkchain: u64 = 2;
+
+ + + + + +The minus char in hex address string: - + + +
const MINUS_CHAR: u8 = 45;
+
+ + + + + +The split char in hex address string: : + + +
const SPLIT_CHAR: u8 = 58;
+
+ + + + + +## Function `from_hex_str` + + + +
public fun from_hex_str(s: &string::String): ton_address::TonAddress
+
+ + + + + +## Function `from_string` + + + +
public fun from_string(addr_str: &string::String): ton_address::TonAddress
+
+ + + + + +## Function `from_bytes` + + + +
public fun from_bytes(bytes: vector<u8>): ton_address::TonAddress
+
+ + + + + +## Function `into_bytes` + + + +
public fun into_bytes(addr: ton_address::TonAddress): vector<u8>
+
diff --git a/frameworks/rooch-framework/doc/ton_proof.md b/frameworks/rooch-framework/doc/ton_proof.md new file mode 100644 index 0000000000..6508ecfa23 --- /dev/null +++ b/frameworks/rooch-framework/doc/ton_proof.md @@ -0,0 +1,283 @@ + + + +# Module `0x3::ton_proof` + + + +- [Struct `TonDomain`](#0x3_ton_proof_TonDomain) +- [Struct `TonProof`](#0x3_ton_proof_TonProof) +- [Struct `TonProofData`](#0x3_ton_proof_TonProofData) +- [Struct `RawCell`](#0x3_ton_proof_RawCell) +- [Struct `RawBagOfCells`](#0x3_ton_proof_RawBagOfCells) +- [Constants](#@Constants_0) +- [Function `decode_proof_data`](#0x3_ton_proof_decode_proof_data) +- [Function `verify_proof`](#0x3_ton_proof_verify_proof) +- [Function `name`](#0x3_ton_proof_name) +- [Function `proof`](#0x3_ton_proof_proof) +- [Function `state_init`](#0x3_ton_proof_state_init) +- [Function `domain`](#0x3_ton_proof_domain) +- [Function `payload`](#0x3_ton_proof_payload) +- [Function `payload_message`](#0x3_ton_proof_payload_message) +- [Function `payload_bitcoin_address`](#0x3_ton_proof_payload_bitcoin_address) +- [Function `payload_tx_hash`](#0x3_ton_proof_payload_tx_hash) +- [Function `signature`](#0x3_ton_proof_signature) +- [Function `timestamp`](#0x3_ton_proof_timestamp) +- [Function `domain_length_bytes`](#0x3_ton_proof_domain_length_bytes) +- [Function `domain_value`](#0x3_ton_proof_domain_value) + + +
use 0x1::string;
+use 0x2::bcs;
+use 0x3::ton_address;
+
+ + + + + +## Struct `TonDomain` + + + +
#[data_struct]
+struct TonDomain has copy, drop, store
+
+ + + + + +## Struct `TonProof` + + + +
#[data_struct]
+struct TonProof has copy, drop, store
+
+ + + + + +## Struct `TonProofData` + + + +
#[data_struct]
+struct TonProofData has copy, drop, store
+
+ + + + + +## Struct `RawCell` + + + +
#[data_struct]
+struct RawCell has copy, drop, store
+
+ + + + + +## Struct `RawBagOfCells` + + + +
#[data_struct]
+struct RawBagOfCells has copy, drop, store
+
+ + + + + +## Constants + + + + + + +
const PAYLOAD_BITCOIN_ADDRESS_IDX: u64 = 1;
+
+ + + + + + + +
const PAYLOAD_MESSAGE_IDX: u64 = 0;
+
+ + + + + + + +
const PAYLOAD_TX_HASH_IDX: u64 = 2;
+
+ + + + + +## Function `decode_proof_data` + + + +
public fun decode_proof_data(proof_data_bytes: vector<u8>): ton_proof::TonProofData
+
+ + + + + +## Function `verify_proof` + +verify the proof + + +
public fun verify_proof(_ton_addr: &ton_address::TonAddress, _ton_proof_data: &ton_proof::TonProofData): bool
+
+ + + + + +## Function `name` + + + +
public fun name(ton_proof_data: &ton_proof::TonProofData): &string::String
+
+ + + + + +## Function `proof` + + + +
public fun proof(ton_proof_data: &ton_proof::TonProofData): &ton_proof::TonProof
+
+ + + + + +## Function `state_init` + + + +
public fun state_init(ton_proof_data: &ton_proof::TonProofData): &string::String
+
+ + + + + +## Function `domain` + + + +
public fun domain(ton_proof: &ton_proof::TonProof): &ton_proof::TonDomain
+
+ + + + + +## Function `payload` + + + +
public fun payload(ton_proof: &ton_proof::TonProof): &vector<string::String>
+
+ + + + + +## Function `payload_message` + +Get the message from the payload, if the payload is not long enough, return an empty string + + +
public fun payload_message(ton_proof: &ton_proof::TonProof): string::String
+
+ + + + + +## Function `payload_bitcoin_address` + +Get the bitcoin address from the payload, if the payload is not long enough, return an empty string + + +
public fun payload_bitcoin_address(ton_proof: &ton_proof::TonProof): string::String
+
+ + + + + +## Function `payload_tx_hash` + +Get the tx hash from the payload, if the payload is not long enough, return an empty string + + +
public fun payload_tx_hash(ton_proof: &ton_proof::TonProof): string::String
+
+ + + + + +## Function `signature` + + + +
public fun signature(ton_proof: &ton_proof::TonProof): &string::String
+
+ + + + + +## Function `timestamp` + + + +
public fun timestamp(ton_proof: &ton_proof::TonProof): u64
+
+ + + + + +## Function `domain_length_bytes` + + + +
public fun domain_length_bytes(ton_domain: &ton_proof::TonDomain): u64
+
+ + + + + +## Function `domain_value` + + + +
public fun domain_value(ton_domain: &ton_proof::TonDomain): &string::String
+
diff --git a/frameworks/rooch-framework/doc/ton_validator.md b/frameworks/rooch-framework/doc/ton_validator.md new file mode 100644 index 0000000000..05b962d8e4 --- /dev/null +++ b/frameworks/rooch-framework/doc/ton_validator.md @@ -0,0 +1,103 @@ + + + +# Module `0x3::ton_validator` + +This module implements Ton blockchain auth validator. + + +- [Struct `TonValidator`](#0x3_ton_validator_TonValidator) +- [Constants](#@Constants_0) +- [Function `auth_validator_id`](#0x3_ton_validator_auth_validator_id) +- [Function `genesis_init`](#0x3_ton_validator_genesis_init) +- [Function `validate`](#0x3_ton_validator_validate) + + +
use 0x1::option;
+use 0x1::string;
+use 0x2::hex;
+use 0x2::signer;
+use 0x2::tx_context;
+use 0x3::address_mapping;
+use 0x3::auth_validator;
+use 0x3::auth_validator_registry;
+use 0x3::ton_address;
+use 0x3::ton_proof;
+
+ + + + + +## Struct `TonValidator` + + + +
struct TonValidator has drop, store
+
+ + + + + +## Constants + + + + + + +
const ErrorAddressMappingRecordNotFound: u64 = 2;
+
+ + + + + + + +
const ErrorGenesisInitError: u64 = 1;
+
+ + + + + +there defines auth validator id for each blockchain + + +
const TON_AUTH_VALIDATOR_ID: u64 = 4;
+
+ + + + + +## Function `auth_validator_id` + + + +
public fun auth_validator_id(): u64
+
+ + + + + +## Function `genesis_init` + + + +
public(friend) fun genesis_init()
+
+ + + + + +## Function `validate` + + + +
public fun validate(authenticator_payload: vector<u8>)
+
diff --git a/frameworks/rooch-framework/sources/address_mapping.move b/frameworks/rooch-framework/sources/address_mapping.move index e7e6bbdef0..6187c0c640 100644 --- a/frameworks/rooch-framework/sources/address_mapping.move +++ b/frameworks/rooch-framework/sources/address_mapping.move @@ -4,10 +4,14 @@ module rooch_framework::address_mapping{ use std::option::{Self, Option}; + use std::string::{Self, String}; use moveos_std::core_addresses; use moveos_std::object::{Self, Object}; + use moveos_std::tx_context; use rooch_framework::multichain_address::{Self, MultiChainAddress}; use rooch_framework::bitcoin_address::{Self, BitcoinAddress}; + use rooch_framework::ton_address::{Self, TonAddress}; + use rooch_framework::ton_proof::{Self, TonProofData}; friend rooch_framework::genesis; friend rooch_framework::bitcoin_validator; @@ -16,6 +20,8 @@ module rooch_framework::address_mapping{ const ErrorMultiChainAddressInvalid: u64 = 1; const ErrorUnsupportedAddress: u64 = 2; + const ErrorInvalidBindingProof: u64 = 3; + const ErrorInvalidBindingAddress: u64 = 4; const NAMED_MAPPING_INDEX: u64 = 0; const NAMED_REVERSE_MAPPING_INDEX: u64 = 1; @@ -33,7 +39,13 @@ module rooch_framework::address_mapping{ _placeholder: bool, } - public(friend) fun genesis_init(_genesis_account: &signer) { + /// Mapping from rooch address to ton address + /// The mapping record is the object field, key is the rooch address, value is the ton address + struct RoochToTonAddressMapping has key{ + _placeholder: bool, + } + + public(friend) fun genesis_init() { let multichain_mapping_id = object::named_object_id(); if(!object::exists_object(multichain_mapping_id)){ let multichain_mapping = object::new_named_object(MultiChainAddressMapping{ @@ -48,6 +60,17 @@ module rooch_framework::address_mapping{ }); object::transfer_extend(rooch_to_bitcoin_mapping, @rooch_framework); }; + Self::init_ton_mapping(); + } + + public entry fun init_ton_mapping(){ + let rooch_to_ton_mapping_id = object::named_object_id(); + if(!object::exists_object(rooch_to_ton_mapping_id)){ + let rooch_to_ton_mapping = object::new_named_object(RoochToTonAddressMapping{ + _placeholder: false + }); + object::transfer_extend(rooch_to_ton_mapping, @rooch_framework); + }; } fun borrow_multichain() : &Object { @@ -70,6 +93,16 @@ module rooch_framework::address_mapping{ object::borrow_mut_object_extend(object_id) } + fun borrow_rooch_to_ton() : &Object { + let object_id = object::named_object_id(); + object::borrow_object(object_id) + } + + fun borrow_rooch_to_ton_mut() : &mut Object { + let object_id = object::named_object_id(); + object::borrow_mut_object_extend(object_id) + } + fun resolve_address(obj: &Object, maddress: MultiChainAddress): Option
{ if (multichain_address::is_rooch_address(&maddress)) { return option::some(multichain_address::into_rooch_address(maddress)) @@ -120,17 +153,97 @@ module rooch_framework::address_mapping{ Self::exists_mapping_address(obj, maddress) } - public(friend) fun bind_bitcoin_address(rooch_address: address, baddress: BitcoinAddress) { + public(friend) fun bind_bitcoin_address_internal(rooch_address: address, btc_address: BitcoinAddress) { // bitcoin address to rooch address do not need to record, we just record rooch address to bitcoin address let obj = Self::borrow_rooch_to_bitcoin_mut(); if(!object::contains_field(obj, rooch_address)){ - object::add_field(obj, rooch_address, baddress); + object::add_field(obj, rooch_address, btc_address); } } - public fun bind_bitcoin_address_by_system(system: &signer, rooch_address: address, baddress: BitcoinAddress) { + public fun bind_bitcoin_address_by_system(system: &signer, rooch_address: address, btc_address: BitcoinAddress) { core_addresses::assert_system_reserved(system); - Self::bind_bitcoin_address(rooch_address, baddress); + Self::bind_bitcoin_address_internal(rooch_address, btc_address); + } + + /// Bind a bitcoin address to a rooch address + /// We can calculate the rooch address from bitcoin address + /// So we call this function for record rooch address to bitcoin address mapping + public fun bind_bitcoin_address(btc_address: BitcoinAddress){ + let rooch_addr = bitcoin_address::to_rooch_address(&btc_address); + Self::bind_bitcoin_address_internal(rooch_addr, btc_address); } + // ============================== Ton address mapping ============================== + + public fun resolve_to_ton_address(sender: address): Option{ + let rooch_to_ton_mapping = borrow_rooch_to_ton(); + if (object::contains_field(rooch_to_ton_mapping, sender)){ + option::some(*object::borrow_field(rooch_to_ton_mapping, sender)) + }else{ + option::none() + } + } + + public fun resolve_via_ton_address(ton_address: TonAddress): Option
{ + let maddress = multichain_address::from_ton(ton_address); + Self::resolve(maddress) + } + + public fun resolve_via_ton_address_str(ton_address_str: String): Option
{ + let ton_address = ton_address::from_string(&ton_address_str); + Self::resolve_via_ton_address(ton_address) + } + + /// Bind a ton address to a rooch address + /// The user needs to provide a valid ton proof and the ton address he wants to bind + public fun bind_ton_address(proof_data: TonProofData, ton_address: TonAddress){ + assert!(ton_proof::verify_proof(&ton_address, &proof_data), ErrorInvalidBindingProof); + let proof = ton_proof::proof(&proof_data); + let btc_addr_str = ton_proof::payload_bitcoin_address(proof); + assert!(string::length(&btc_addr_str) > 0, ErrorInvalidBindingProof); + //The ton proof payload should be a Bitcoin address, the user wants to bing. + let btc_addr = bitcoin_address::from_string(&btc_addr_str); + let rooch_addr = bitcoin_address::to_rooch_address(&btc_addr); + let sender = tx_context::sender(); + //The sender must be the owner of the Bitcoin address + assert!(rooch_addr == sender, ErrorInvalidBindingAddress); + Self::bind_ton_address_internal(sender, ton_address); + } + + fun bind_ton_address_internal(addr: address, ton_address: TonAddress){ + let rooch_to_ton_mapping = borrow_rooch_to_ton_mut(); + object::add_field(rooch_to_ton_mapping, addr, ton_address); + + let multichain_mapping = Self::borrow_multichain_mut(); + let maddress = multichain_address::from_ton(ton_address); + object::add_field(multichain_mapping, maddress, addr); + } + + public fun bind_ton_address_entry(proof_data_bytes: vector, ton_address_str: String){ + let ton_address = ton_address::from_string(&ton_address_str); + let proof_data = ton_proof::decode_proof_data(proof_data_bytes); + Self::bind_ton_address(proof_data, ton_address); + } + + #[test] + fun test_address_mapping_for_bitcoin(){ + genesis_init(); + let btc_addr = bitcoin_address::from_string(&string::utf8(b"bc1p8xpjpkc9uzj2dexcxjg9sw8lxje85xa4070zpcys589e3rf6k20qm6gjrt")); + bind_bitcoin_address(btc_addr); + let rooch_addr = bitcoin_address::to_rooch_address(&btc_addr); + let resolved_addr = resolve_bitcoin(rooch_addr); + assert!(resolved_addr == option::some(btc_addr), 1); + } + + #[test] + fun test_address_mapping_for_ton(){ + genesis_init(); + let addr_str = string::utf8(b"0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"); + let ton_addr = ton_address::from_hex_str(&addr_str); + let sender = @0x42; + bind_ton_address_internal(sender, ton_addr); + let resolved_addr = resolve_via_ton_address(ton_addr); + assert!(resolved_addr == option::some(sender), 1); + } } diff --git a/frameworks/rooch-framework/sources/address_type/multichain_address.move b/frameworks/rooch-framework/sources/address_type/multichain_address.move index dab2742d2f..2769b82cca 100644 --- a/frameworks/rooch-framework/sources/address_type/multichain_address.move +++ b/frameworks/rooch-framework/sources/address_type/multichain_address.move @@ -5,10 +5,13 @@ module rooch_framework::multichain_address { use rooch_framework::ethereum_address::{Self, ETHAddress}; use rooch_framework::bitcoin_address::{Self, BitcoinAddress}; + use rooch_framework::ton_address::{Self, TonAddress}; + use moveos_std::hash::{blake2b256}; use moveos_std::bcs; const ErrorMultiChainIDMismatch: u64 = 1; + const ErrorDeprecated: u64 = 2; const LENGTH: u64 = 31; @@ -16,8 +19,9 @@ module rooch_framework::multichain_address { //Please keep consistent with rust Symbol const MULTICHAIN_ID_BITCOIN: u64 = 0; const MULTICHAIN_ID_ETHER: u64 = 60; + const MULTICHAIN_ID_TON: u64 = 607; const MULTICHAIN_ID_NOSTR: u64 = 1237; - const MULTICHAIN_ID_ROOCH: u64 = 20230101; // placeholder for MULTICHAIN_ID_ROOCH pending for actual id from slip-0044 + const MULTICHAIN_ID_ROOCH: u64 = 20230101; public fun multichain_id_bitcoin(): u64 { MULTICHAIN_ID_BITCOIN } @@ -27,6 +31,8 @@ module rooch_framework::multichain_address { public fun multichain_id_rooch(): u64 { MULTICHAIN_ID_ROOCH } + public fun multichain_id_ton(): u64 { MULTICHAIN_ID_TON } + #[data_struct] struct MultiChainAddress has copy, store, drop { multichain_id: u64, @@ -62,6 +68,13 @@ module rooch_framework::multichain_address { } } + public fun from_ton(ton_address: TonAddress): MultiChainAddress { + MultiChainAddress { + multichain_id: MULTICHAIN_ID_TON, + raw_address: ton_address::into_bytes(ton_address), + } + } + public fun multichain_id(self: &MultiChainAddress): u64 { self.multichain_id } @@ -82,6 +95,10 @@ module rooch_framework::multichain_address { maddress.multichain_id == MULTICHAIN_ID_BITCOIN } + public fun is_ton_address(maddress: &MultiChainAddress) : bool{ + maddress.multichain_id == MULTICHAIN_ID_TON + } + public fun into_rooch_address(maddress: MultiChainAddress) : address { assert!(maddress.multichain_id == MULTICHAIN_ID_ROOCH, ErrorMultiChainIDMismatch); moveos_std::bcs::to_address(maddress.raw_address) @@ -97,15 +114,16 @@ module rooch_framework::multichain_address { bitcoin_address::new(maddress.raw_address) } - /// Mapping from MultiChainAddress to rooch address - /// If the MultiChainAddress is not rooch address, it will generate a new rooch address based on the MultiChainAddress - public fun mapping_to_rooch_address(maddress: MultiChainAddress): address { - if(is_rooch_address(&maddress)) { - into_rooch_address(maddress) - }else{ - let hash = blake2b256(&maddress.raw_address); - moveos_std::bcs::to_address(hash) - } + public fun into_ton_address(maddress: MultiChainAddress) : TonAddress { + assert!(maddress.multichain_id == MULTICHAIN_ID_TON, ErrorMultiChainIDMismatch); + ton_address::from_bytes(maddress.raw_address) + } + + /// Deprecated, will be removed in the future + /// Now, we only support Bitcoin address generate rooch address + /// Other address types need to resolve via address_mapping module: `bitcoin_address::to_rooch_address` + public fun mapping_to_rooch_address(_maddress: MultiChainAddress): address { + abort ErrorDeprecated } #[test] diff --git a/frameworks/rooch-framework/sources/address_type/ton_address.move b/frameworks/rooch-framework/sources/address_type/ton_address.move new file mode 100644 index 0000000000..f6296a76d1 --- /dev/null +++ b/frameworks/rooch-framework/sources/address_type/ton_address.move @@ -0,0 +1,219 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module rooch_framework::ton_address { + + use std::vector; + use std::option; + use std::string::{Self, String}; + + use moveos_std::string_utils; + use moveos_std::hex; + use moveos_std::bcs; + use moveos_std::i32::{Self, I32}; + use moveos_std::i8; + use moveos_std::base64; + const ErrorInvalidAddress: u64 = 1; + const ErrorInvalidWorkchain: u64 = 2; + + #[data_struct] + struct TonAddress has store, copy, drop{ + //The workchain in TonAddress is i32 + workchain: I32, + hash_part: address, + } + + /// The split char in hex address string: `:` + const SPLIT_CHAR: u8 = 58u8; + /// The minus char in hex address string: `-` + const MINUS_CHAR: u8 = 45u8; + + public fun from_hex_str(s: &String) : TonAddress { + let bytes = string::bytes(s); + let addr_len = vector::length(bytes); + let (found,idx) = vector::index_of(bytes, &58u8); + assert!(found, ErrorInvalidAddress); + assert!(idx > 0, ErrorInvalidWorkchain); + let wc_part = vector::slice(bytes, 0, idx); + let hash_part = vector::slice(bytes, idx + 1, addr_len); + let is_nagative = *vector::borrow(&wc_part,0) == 45u8; + + let wc_num_part = if(is_nagative){ + vector::slice(&wc_part, 1, idx) + }else{ + wc_part + }; + let wc_num_opt = string_utils::parse_u32_from_bytes(&wc_num_part); + assert!(option::is_some(&wc_num_opt), ErrorInvalidWorkchain); + let wc_num = option::destroy_some(wc_num_opt); + let workchain = if (is_nagative) { + i32::neg_from(wc_num) + } else { + i32::from(wc_num) + }; + let hash_part_bytes = hex::decode(&hash_part); + let hash_part = bcs::from_bytes
(hash_part_bytes); + TonAddress{ + workchain, + hash_part, + } + } + + public fun from_base64_url(s: &String): TonAddress { + let (addr, _, _) = from_base64_url_flags(s); + addr + } + + public fun from_base64_url_flags(s: &String): (TonAddress, bool, bool) { + //Because the base64::decode do not support url-safe, we need to convert it to std first. + let url_safe_str = to_base64_std(s); + let bytes = string::bytes(&url_safe_str); + let len = vector::length(bytes); + assert!(len == 48, ErrorInvalidAddress); + let decoded_bytes = base64::decode(bytes); + let (addr, non_bounceable, non_production) = from_base64_src(&decoded_bytes); + (addr, non_bounceable, non_production) + } + + public fun from_base64_std(s: &String): TonAddress { + let (addr, _, _) = from_base64_std_flags(s); + addr + } + + public fun from_base64_std_flags(s: &String): (TonAddress, bool, bool) { + let bytes = string::bytes(s); + let addr_len = vector::length(bytes); + assert!(addr_len == 48, ErrorInvalidAddress); + let decoded_bytes = base64::decode(bytes); + let (addr, non_bounceable, non_production) = from_base64_src(&decoded_bytes); + (addr, non_bounceable, non_production) + } + + fun from_base64_src(bytes: &vector) : (TonAddress, bool, bool) { + let addr_len = vector::length(bytes); + assert!(addr_len == 36, ErrorInvalidAddress); + let first_byte = *vector::borrow(bytes, 0); + let (non_production, non_bounceable) = + if(first_byte == 0x11u8){ + (false, false) + }else if(first_byte == 0x51u8){ + (false, true) + }else if(first_byte == 0x91u8){ + (true, false) + }else if(first_byte == 0xD1u8){ + (true, true) + }else{ + abort ErrorInvalidAddress + }; + let workchain = i32::from_i8(i8::from_u8(*vector::borrow(bytes, 1))); + //TODO verify the checksum + //let addr_crc = ((*vector::borrow(bytes, 34) as u16) << 8) | (*vector::borrow(bytes, 35) as u16); + + let hash_part_bytes = vector::slice(bytes, 2, 34); + let hash_part = bcs::from_bytes
(hash_part_bytes); + (TonAddress{ + workchain, + hash_part, + }, non_bounceable, non_production) + } + + const BASE64_URL_CHAR_MINUS: u8 = 45u8; // '-' + const BASE64_URL_CHAR_UNDERSCORE: u8 = 95u8; // '_' + const BASE64_STD_CHAR_PLUS: u8 = 43u8; // '+' + const BASE64_STD_CHAR_SLASH: u8 = 47u8; // '/' + const BASE64_STD_CHAR_EQUAL: u8 = 61u8; // '=' + + fun is_url_safe(s: &String): bool { + let bytes = string::bytes(s); + let len = vector::length(bytes); + let i = 0; + while(i < len){ + let c = *vector::borrow(bytes, i); + if(c == BASE64_STD_CHAR_PLUS || c == BASE64_STD_CHAR_SLASH || c == BASE64_STD_CHAR_EQUAL){ + return false + }; + i = i + 1; + }; + true + } + + fun to_base64_std(s: &String): String { + let bytes = string::bytes(s); + let len = vector::length(bytes); + let new_bytes = vector::empty(); + let i = 0; + while(i < len){ + let c = *vector::borrow(bytes, i); + if(c == BASE64_URL_CHAR_MINUS){ + vector::push_back(&mut new_bytes, BASE64_STD_CHAR_PLUS); + }else if(c == BASE64_URL_CHAR_UNDERSCORE){ + vector::push_back(&mut new_bytes, BASE64_STD_CHAR_SLASH); + }else{ + vector::push_back(&mut new_bytes, c); + }; + i = i + 1; + }; + string::utf8(new_bytes) + } + + public fun from_string(addr_str: &String): TonAddress{ + let len = string::length(addr_str); + if(len == 48){ + if(is_url_safe(addr_str)){ + from_base64_url(addr_str) + }else{ + from_base64_std(addr_str) + } + }else{ + from_hex_str(addr_str) + } + } + + public fun from_bytes(bytes: vector): TonAddress { + bcs::from_bytes(bytes) + } + + public fun into_bytes(addr: TonAddress): vector { + bcs::to_bytes(&addr) + } + + #[test] + fun test_from_string(){ + let addr_str = string::utf8(b"0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"); + let addr = from_hex_str(&addr_str); + assert!(addr.workchain == i32::from(0), 2); + assert!(addr.hash_part == @0xe4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76, 3); + assert!(addr == from_string(&addr_str), 4); + + let addr_str_base64_std = string::utf8(b"EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR"); + let addr2 = from_base64_std(&addr_str_base64_std); + assert!(addr2 == addr, 2); + assert!(addr2 == from_string(&addr_str_base64_std), 5); + + let addr_str_base64_url = string::utf8(b"EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR"); + let addr3 = from_base64_url(&addr_str_base64_url); + assert!(addr3 == addr, 2); + assert!(addr3 == from_string(&addr_str_base64_url), 5); + + } + + #[test] + fun test_from_hex_nagitave(){ + let addr_str = string::utf8(b"-1:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"); + let addr = from_hex_str(&addr_str); + assert!(addr.workchain == i32::neg_from(1), 2); + assert!(addr.hash_part == @0xe4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76, 3); + } + + #[test] + fun test_into_bytes(){ + let addr_str = string::utf8(b"-1:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"); + let addr = from_hex_str(&addr_str); + let bytes = into_bytes(addr); + std::debug::print(&bytes); + let addr2 = from_bytes(bytes); + assert!(addr2.workchain == addr.workchain, 2); + assert!(addr2.hash_part == addr.hash_part, 3); + } + +} diff --git a/frameworks/rooch-framework/sources/auth_validator/auth_validator_registry.move b/frameworks/rooch-framework/sources/auth_validator/auth_validator_registry.move index 879034dc9a..6c8da0cfec 100644 --- a/frameworks/rooch-framework/sources/auth_validator/auth_validator_registry.move +++ b/frameworks/rooch-framework/sources/auth_validator/auth_validator_registry.move @@ -3,6 +3,7 @@ module rooch_framework::auth_validator_registry { + use std::option::{Self, Option}; use moveos_std::account; use moveos_std::type_info; use moveos_std::table::{Self, Table}; @@ -14,9 +15,17 @@ module rooch_framework::auth_validator_registry { friend rooch_framework::genesis; friend rooch_framework::builtin_validators; + /// From 0 to 99 are reserved for system validators. + const SYSTEM_RESERVED_VALIDATOR_ID: u64 = 100; + public fun system_reserved_validator_id(): u64{ + SYSTEM_RESERVED_VALIDATOR_ID + } + const ErrorValidatorUnregistered: u64 = 1; const ErrorValidatorAlreadyRegistered: u64 = 2; - + const ErrorDeprecated: u64 = 3; + const ErrorInvalidValidatorId: u64 = 4; + struct AuthValidatorWithType has key,store { id: u64, } @@ -42,22 +51,40 @@ module rooch_framework::auth_validator_registry { /// Register a new validator. This feature not enabled in the mainnet. public fun register() : u64{ features::ensure_testnet_enabled(); - register_internal() + register_internal(option::none()) } - /// Register a new validator by system. This function is only called by system. - public fun register_by_system(system: &signer) : u64{ + /// Deprecated. + public fun register_by_system(_system: &signer) : u64{ + abort ErrorDeprecated + } + + /// Register a new validator by system with a specific id. This function is only called by system. + public fun register_by_system_with_id(system: &signer, id: u64) : u64{ core_addresses::assert_system_reserved(system); - register_internal() + register_internal(option::some(id)) } - public(friend) fun register_internal() : u64{ + public(friend) fun register_internal(id: Option) : u64{ let type_info = type_info::type_of(); let module_address = type_info::account_address(&type_info); let module_name = type_info::module_name(&type_info); let registry = account::borrow_mut_resource(@rooch_framework); - let id = registry.validator_num; + let id = if(option::is_some(&id)){ + let id = option::destroy_some(id); + assert!(id < SYSTEM_RESERVED_VALIDATOR_ID, ErrorInvalidValidatorId); + id + } else { + let id = registry.validator_num; + // The genesis init validator_num is 0 + // so we need to set it to SYSTEM_RESERVED_VALIDATOR_ID + if (id < SYSTEM_RESERVED_VALIDATOR_ID){ + id = SYSTEM_RESERVED_VALIDATOR_ID; + }; + registry.validator_num = id + 1; + id + }; assert!(!type_table::contains>(®istry.validators_with_type), ErrorValidatorAlreadyRegistered); @@ -72,8 +99,6 @@ module rooch_framework::auth_validator_registry { module_name, ); table::add(&mut registry.validators, id, validator); - - registry.validator_num = registry.validator_num + 1; id } diff --git a/frameworks/rooch-framework/sources/auth_validator/builtin_validators.move b/frameworks/rooch-framework/sources/auth_validator/builtin_validators.move index 39620f6e58..c3fa13f1e4 100644 --- a/frameworks/rooch-framework/sources/auth_validator/builtin_validators.move +++ b/frameworks/rooch-framework/sources/auth_validator/builtin_validators.move @@ -3,6 +3,7 @@ module rooch_framework::builtin_validators{ + use std::option; use rooch_framework::auth_validator_registry; use rooch_framework::session_validator; use rooch_framework::bitcoin_validator; @@ -18,11 +19,11 @@ module rooch_framework::builtin_validators{ public(friend) fun genesis_init(_genesis_account: &signer) { // NATIVE_AUTH_VALIDATOR_ID: u64 = 0; - let id = auth_validator_registry::register_internal(); + let id = auth_validator_registry::register_internal(option::some(SESSION_VALIDATOR_ID)); assert!(id == session_validator::auth_validator_id(), ErrorGenesisInit); // BITCOIN_AUTH_VALIDATOR_ID: u64 = 1; - let id = auth_validator_registry::register_internal(); + let id = auth_validator_registry::register_internal(option::some(BITCOIN_VALIDATOR_ID)); assert!(id == bitcoin_validator::auth_validator_id(), ErrorGenesisInit); } diff --git a/frameworks/rooch-framework/sources/auth_validator/ethereum_validator.move b/frameworks/rooch-framework/sources/auth_validator/ethereum_validator.move new file mode 100644 index 0000000000..956e3d8f08 --- /dev/null +++ b/frameworks/rooch-framework/sources/auth_validator/ethereum_validator.move @@ -0,0 +1,37 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// This module implements Ethereum validator with the ECDSA recoverable signature over Secp256k1. +module rooch_framework::ethereum_validator { + + use moveos_std::signer; + + use rooch_framework::auth_validator_registry; + + friend rooch_framework::genesis; + + /// there defines auth validator id for each blockchain + const ETHEREUM_AUTH_VALIDATOR_ID: u64 = 3; + + const ErrorGenesisInitError: u64 = 1; + const ErrorAddressMappingRecordNotFound: u64 = 2; + const ErrorNotImplemented: u64 = 3; + + struct EthereumValidator has store, drop {} + + public fun auth_validator_id(): u64 { + ETHEREUM_AUTH_VALIDATOR_ID + } + + public(friend) fun genesis_init(){ + let system = signer::module_signer(); + let id = auth_validator_registry::register_by_system_with_id(&system, ETHEREUM_AUTH_VALIDATOR_ID); + assert!(id == ETHEREUM_AUTH_VALIDATOR_ID, ErrorGenesisInitError); + } + + /// We need to redesign the Ethereum auth validator + /// This module is just for placeholder the AUTH_VALIDATOR_ID + public fun validate(_authenticator_payload: vector) { + abort ErrorNotImplemented + } +} diff --git a/frameworks/rooch-framework/sources/auth_validator/ton_validator.move b/frameworks/rooch-framework/sources/auth_validator/ton_validator.move new file mode 100644 index 0000000000..04c09c797c --- /dev/null +++ b/frameworks/rooch-framework/sources/auth_validator/ton_validator.move @@ -0,0 +1,60 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// This module implements Ton blockchain auth validator. +module rooch_framework::ton_validator { + + use std::string; + use std::option; + + use moveos_std::signer; + use moveos_std::hex; + use moveos_std::tx_context; + + use rooch_framework::auth_validator; + use rooch_framework::auth_validator_registry; + use rooch_framework::ton_proof::{Self, TonProofData}; + use rooch_framework::ton_address::{TonAddress}; + use rooch_framework::address_mapping; + + friend rooch_framework::genesis; + + /// there defines auth validator id for each blockchain + const TON_AUTH_VALIDATOR_ID: u64 = 4; + + const ErrorGenesisInitError: u64 = 1; + const ErrorAddressMappingRecordNotFound: u64 = 2; + + struct TonValidator has store, drop {} + + public fun auth_validator_id(): u64 { + TON_AUTH_VALIDATOR_ID + } + + public(friend) fun genesis_init(){ + let system = signer::module_signer(); + let id = auth_validator_registry::register_by_system_with_id(&system, TON_AUTH_VALIDATOR_ID); + assert!(id == TON_AUTH_VALIDATOR_ID, ErrorGenesisInitError); + } + + fun validate_signature(ton_address: &TonAddress, proof_data: &TonProofData, tx_hash: vector) { + assert!(ton_proof::verify_proof(ton_address, proof_data), auth_validator::error_validate_invalid_authenticator()); + let proof = ton_proof::proof(proof_data); + let tx_hash_from_payload = ton_proof::payload_tx_hash(proof); + + //make sure the tx_hash is included in the payload, maybe we need to add more info in the payload? + let tx_hex = hex::encode(tx_hash); + assert!(&tx_hex == string::bytes(&tx_hash_from_payload), auth_validator::error_validate_invalid_authenticator()); + } + + public fun validate(authenticator_payload: vector) { + let proof_data = ton_proof::decode_proof_data(authenticator_payload); + let sender = tx_context::sender(); + let sender_ton_addr_opt = address_mapping::resolve_to_ton_address(sender); + assert!(option::is_some(&sender_ton_addr_opt), ErrorAddressMappingRecordNotFound); + + let sender_ton_addr = option::destroy_some(sender_ton_addr_opt); + let tx_hash = tx_context::tx_hash(); + validate_signature(&sender_ton_addr, &proof_data, tx_hash); + } +} diff --git a/frameworks/rooch-framework/sources/genesis.move b/frameworks/rooch-framework/sources/genesis.move index da80d1035b..80f9a1b920 100644 --- a/frameworks/rooch-framework/sources/genesis.move +++ b/frameworks/rooch-framework/sources/genesis.move @@ -20,6 +20,8 @@ module rooch_framework::genesis { use rooch_framework::address_mapping; use rooch_framework::onchain_config; use rooch_framework::bitcoin_address::{Self, BitcoinAddress}; + use rooch_framework::ethereum_validator; + use rooch_framework::ton_validator; const ErrorGenesisInit: u64 = 1; @@ -52,13 +54,13 @@ module rooch_framework::genesis { account_coin_store::genesis_init(genesis_account); gas_coin::genesis_init(genesis_account); transaction_fee::genesis_init(genesis_account); - address_mapping::genesis_init(genesis_account); + address_mapping::genesis_init(); let sequencer_addr = bitcoin_address::to_rooch_address(&genesis_context.sequencer); // Some test cases use framework account as sequencer, it may already exist if(!moveos_std::account::exists_at(sequencer_addr)){ account::create_account(sequencer_addr); - address_mapping::bind_bitcoin_address(sequencer_addr, genesis_context.sequencer); + address_mapping::bind_bitcoin_address_internal(sequencer_addr, genesis_context.sequencer); }; let rooch_dao_address = bitcoin_address::to_rooch_address(&genesis_context.rooch_dao); @@ -76,7 +78,18 @@ module rooch_framework::genesis { // give initial gas to the sequencer if it's local or dev if(chain_id::is_local_or_dev()){ gas_coin::faucet(sequencer_addr, GENESIS_INIT_GAS_AMOUNT); - } + }; + + // after framework v14 + ethereum_validator::genesis_init(); + ton_validator::genesis_init(); + } + + /// Because the init function only can be called once when the first deploy, + /// we need to add a new function to init the new validators for v15. + public entry fun init_for_v15(){ + ethereum_validator::genesis_init(); + ton_validator::genesis_init(); } diff --git a/frameworks/rooch-framework/sources/tests/account_authentication_test.move b/frameworks/rooch-framework/sources/tests/account_authentication_test.move index 145436f755..36abac8403 100644 --- a/frameworks/rooch-framework/sources/tests/account_authentication_test.move +++ b/frameworks/rooch-framework/sources/tests/account_authentication_test.move @@ -19,7 +19,7 @@ module rooch_framework::account_authentication_test{ let user_signer = moveos_std::account::create_signer_for_testing(user_address); let validator_id = auth_validator_registry::register(); - + assert!(validator_id >= auth_validator_registry::system_reserved_validator_id(), 1000); install_auth_validator(&user_signer); assert!(is_auth_validator_installed(user_address, validator_id), 1000); diff --git a/frameworks/rooch-framework/sources/ton_proof.move b/frameworks/rooch-framework/sources/ton_proof.move new file mode 100644 index 0000000000..b0a1a8e682 --- /dev/null +++ b/frameworks/rooch-framework/sources/ton_proof.move @@ -0,0 +1,435 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module rooch_framework::ton_proof { + + use std::vector; + use std::string::{Self, String}; + + use moveos_std::bcs; + use moveos_std::byte_stream::{Self, ByteStream}; + use moveos_std::base64; + + use rooch_framework::ton_address::{TonAddress}; + + const GENERIC_BOC_MAGIC: u32 = 0xb5ee9c72; + + const ErrorInvalidBocMagic: u64 = 1; + + #[data_struct] + struct TonDomain has copy, drop, store{ + length_bytes: u64, + value: String, + } + + const PAYLOAD_MESSAGE_IDX: u64 = 0; + const PAYLOAD_BITCOIN_ADDRESS_IDX: u64 = 1; + const PAYLOAD_TX_HASH_IDX: u64 = 2; + + #[data_struct] + struct TonProof has copy, drop, store{ + timestamp: u64, + domain: TonDomain, + signature: String, + //We use a vector to store payload for future extension + payload: vector, + } + + #[data_struct] + struct TonProofData has copy, drop, store { + name: String, + proof: TonProof, + state_init: String, + } + + #[data_struct] + struct RawCell has copy, drop, store { + data: vector, + bit_len: u64, + references: vector, + is_exotic: bool, + level_mask: u32, + } + + #[data_struct] + struct RawBagOfCells has copy, drop, store { + cells: vector, + roots: vector, + } + + // pub(crate) fn parse(serial: &[u8]) -> Result { + // let cursor = Cursor::new(serial); + + // let mut reader: ByteReader, BigEndian> = + // ByteReader::endian(cursor, BigEndian); + // // serialized_boc#b5ee9c72 + // let magic = reader.read::().map_boc_deserialization_error()?; + + // let (has_idx, has_crc32c, _has_cache_bits, size) = match magic { + // GENERIC_BOC_MAGIC => { + // // has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } + // let header = reader.read::().map_boc_deserialization_error()?; + // let has_idx = (header >> 7) & 1 == 1; + // let has_crc32c = (header >> 6) & 1 == 1; + // let has_cache_bits = (header >> 5) & 1 == 1; + // // size:(## 3) { size <= 4 } + // let size = header & 0b0000_0111; + + // (has_idx, has_crc32c, has_cache_bits, size) + // } + // magic => { + // return Err(TonCellError::boc_deserialization_error(format!( + // "Unsupported cell magic number: {:#}", + // magic + // ))); + // } + // }; + // // off_bytes:(## 8) { off_bytes <= 8 } + // let off_bytes = reader.read::().map_boc_deserialization_error()?; + // //cells:(##(size * 8)) + // let cells = read_var_size(&mut reader, size)?; + // // roots:(##(size * 8)) { roots >= 1 } + // let roots = read_var_size(&mut reader, size)?; + // // absent:(##(size * 8)) { roots + absent <= cells } + // let _absent = read_var_size(&mut reader, size)?; + // // tot_cells_size:(##(off_bytes * 8)) + // let _tot_cells_size = read_var_size(&mut reader, off_bytes)?; + // // root_list:(roots * ##(size * 8)) + // let mut root_list = vec![]; + // for _ in 0..roots { + // root_list.push(read_var_size(&mut reader, size)?) + // } + // // index:has_idx?(cells * ##(off_bytes * 8)) + // let mut index = vec![]; + // if has_idx { + // for _ in 0..cells { + // index.push(read_var_size(&mut reader, off_bytes)?) + // } + // } + // // cell_data:(tot_cells_size * [ uint8 ]) + // let mut cell_vec = Vec::with_capacity(cells); + + // for _ in 0..cells { + // let cell = read_cell(&mut reader, size)?; + // cell_vec.push(cell); + // } + // // crc32c:has_crc32c?uint32 + // let _crc32c = if has_crc32c { + // reader.read::().map_boc_deserialization_error()? + // } else { + // 0 + // }; + // // TODO: Check crc32 + + // Ok(RawBagOfCells { + // cells: cell_vec, + // roots: root_list, + // }) + // } + + fun parse_boc(serial: &vector): RawBagOfCells { + let stream = byte_stream::new(*serial); + // serialized_boc#b5ee9c72 + let magic = byte_stream::read_u32(&mut stream); + assert!(magic == GENERIC_BOC_MAGIC, ErrorInvalidBocMagic); + + // has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } + let header = byte_stream::read_u8(&mut stream); + let has_idx = (header >> 7) & 1 == 1; + let has_crc32c = (header >> 6) & 1 == 1; + let _has_cache_bits = (header >> 5) & 1 == 1; + // size:(## 3) { size <= 4 } + let size = header & 0x07; + + // off_bytes:(## 8) { off_bytes <= 8 } + let off_bytes = byte_stream::read_u8(&mut stream); + // cells:(##(size * 8)) + let cells = byte_stream::read_var_size(&mut stream, size); + // roots:(##(size * 8)) { roots >= 1 } + let roots = byte_stream::read_var_size(&mut stream, size); + // absent:(##(size * 8)) { roots + absent <= cells } + let _absent = byte_stream::read_var_size(&mut stream, size); + // tot_cells_size:(##(off_bytes * 8)) + let _tot_cells_size = byte_stream::read_var_size(&mut stream, off_bytes); + + // root_list:(roots * ##(size * 8)) + let root_list = vector::empty(); + let i = 0; + while (i < roots) { + vector::push_back(&mut root_list, byte_stream::read_var_size(&mut stream, size)); + i = i + 1; + }; + + // index:has_idx?(cells * ##(off_bytes * 8)) + if (has_idx) { + let i = 0; + while (i < cells) { + let _index = byte_stream::read_var_size(&mut stream, off_bytes); + i = i + 1; + }; + }; + + // cell_data:(tot_cells_size * [ uint8 ]) + let cell_vec = vector::empty(); + let i = 0; + while (i < cells) { + let cell = read_cell(&mut stream, size); + vector::push_back(&mut cell_vec, cell); + i = i + 1; + }; + + // crc32c:has_crc32c?uint32 + if (has_crc32c) { + let _crc32c = byte_stream::read_u32(&mut stream); + }; + + RawBagOfCells { + cells: cell_vec, + roots: root_list, + } + } + +// fn read_cell( +// reader: &mut ByteReader, BigEndian>, +// size: u8, +// ) -> Result { +// let d1 = reader.read::().map_boc_deserialization_error()?; +// let d2 = reader.read::().map_boc_deserialization_error()?; + +// let ref_num = d1 & 0b111; +// let is_exotic = (d1 & 0b1000) != 0; +// let has_hashes = (d1 & 0b10000) != 0; +// let level_mask = (d1 >> 5) as u32; +// let data_size = ((d2 >> 1) + (d2 & 1)).into(); +// let full_bytes = (d2 & 0x01) == 0; + +// if has_hashes { +// let hash_count = LevelMask::new(level_mask).hash_count(); +// let skip_size = hash_count * (32 + 2); + +// // TODO: check depth and hashes +// reader +// .skip(skip_size as u32) +// .map_boc_deserialization_error()?; +// } + +// let mut data = reader +// .read_to_vec(data_size) +// .map_boc_deserialization_error()?; + +// let data_len = data.len(); +// let padding_len = if data_len > 0 && !full_bytes { +// // Fix last byte, +// // see https://github.com/toncenter/tonweb/blob/c2d5d0fc23d2aec55a0412940ce6e580344a288c/src/boc/BitString.js#L302 +// let num_zeros = data[data_len - 1].trailing_zeros(); +// if num_zeros >= 8 { +// return Err(TonCellError::boc_deserialization_error( +// "Last byte of binary must not be zero if full_byte flag is not set", +// )); +// } +// data[data_len - 1] &= !(1 << num_zeros); +// num_zeros + 1 +// } else { +// 0 +// }; +// let bit_len = data.len() * 8 - padding_len as usize; +// let mut references: Vec = Vec::new(); +// for _ in 0..ref_num { +// references.push(read_var_size(reader, size)?); +// } +// let cell = RawCell::new(data, bit_len, references, level_mask, is_exotic); +// Ok(cell) +// } + + fun read_cell(stream: &mut ByteStream, size: u8): RawCell { + let d1 = byte_stream::read_u8(stream); + let d2 = byte_stream::read_u8(stream); + + let ref_num = d1 & 0x07; + let is_exotic = (d1 & 0x08) != 0; + let has_hashes = (d1 & 0x10) != 0; + let level_mask = ((d1 >> 5) as u32); + let data_size = (((d2 >> 1) + (d2 & 1)) as u64); + let full_bytes = (d2 & 0x01) == 0; + + if (has_hashes) { + let hash_index = count_ones(level_mask); + let hash_count = hash_index + 1; + let skip_size = hash_count * (32 + 2); + byte_stream::skip(stream, (skip_size as u64)); + }; + + let data = byte_stream::read_to_vec(stream, data_size); + let data_len = vector::length(&data); + let padding_len = if (data_len > 0 && !full_bytes) { + // Fix last byte + let last_byte = *vector::borrow(&data, data_len - 1); + let num_zeros = trailing_zeros(last_byte); + assert!(num_zeros < 8, 1); // Last byte must not be zero if full_byte flag is not set + let mask = 1 << num_zeros; + let last_byte = last_byte & (0xff ^ mask); + vector::push_back(&mut data, last_byte); + num_zeros + 1 + } else { + 0 + }; + + let bit_len = data_len * 8 - (padding_len as u64); + let references = vector::empty(); + let i = 0; + while (i < ref_num) { + vector::push_back(&mut references, byte_stream::read_var_size(stream, size)); + i = i + 1; + }; + + RawCell { + data: data, + bit_len: bit_len, + references: references, + is_exotic: is_exotic, + level_mask: level_mask + } + } + + //TODO migrate this function to u32.move + // return the number of 1s in the binary representation of the value + fun count_ones(value: u32): u32 { + let count = 0; + let mut_value = value; + while (mut_value != 0) { + if ((mut_value & 1) == 1) { + count = count + 1; + }; + mut_value = mut_value >> 1; + }; + count + } + + fun trailing_zeros(value: u8): u8 { + let count = 0; + let mut_value = value; + while ((mut_value & 1) == 0) { + count = count + 1; + mut_value = mut_value >> 1; + }; + count + } + + public fun decode_proof_data(proof_data_bytes: vector): TonProofData { + bcs::from_bytes(proof_data_bytes) + } + + /// verify the proof + public fun verify_proof(_ton_addr: &TonAddress, ton_proof_data: &TonProofData) : bool { + let state_init_bytes = base64::decode(string::bytes(&ton_proof_data.state_init)); + let _boc = parse_boc(&state_init_bytes); + true + } + + // ======================== TonProofData functions ======================== + + public fun name(ton_proof_data: &TonProofData): &String { + &ton_proof_data.name + } + + public fun proof(ton_proof_data: &TonProofData): &TonProof { + &ton_proof_data.proof + } + + public fun state_init(ton_proof_data: &TonProofData): &String { + &ton_proof_data.state_init + } + + // ======================== TonProof functions ======================== + + public fun domain(ton_proof: &TonProof): &TonDomain { + &ton_proof.domain + } + + public fun payload(ton_proof: &TonProof): &vector { + &ton_proof.payload + } + + /// Get the message from the payload, if the payload is not long enough, return an empty string + public fun payload_message(ton_proof: &TonProof): String { + if (vector::length(&ton_proof.payload) > PAYLOAD_MESSAGE_IDX) { + *vector::borrow(&ton_proof.payload, PAYLOAD_MESSAGE_IDX) + } else { + string::utf8(b"") + } + } + + /// Get the bitcoin address from the payload, if the payload is not long enough, return an empty string + public fun payload_bitcoin_address(ton_proof: &TonProof): String { + if (vector::length(&ton_proof.payload) > PAYLOAD_BITCOIN_ADDRESS_IDX) { + *vector::borrow(&ton_proof.payload, PAYLOAD_BITCOIN_ADDRESS_IDX) + } else { + string::utf8(b"") + } + } + + /// Get the tx hash from the payload, if the payload is not long enough, return an empty string + public fun payload_tx_hash(ton_proof: &TonProof): String { + if (vector::length(&ton_proof.payload) > PAYLOAD_TX_HASH_IDX) { + *vector::borrow(&ton_proof.payload, PAYLOAD_TX_HASH_IDX) + } else { + string::utf8(b"") + } + } + + public fun signature(ton_proof: &TonProof): &String { + &ton_proof.signature + } + + public fun timestamp(ton_proof: &TonProof): u64 { + ton_proof.timestamp + } + + // ======================== TonDomain functions ======================== + + public fun domain_length_bytes(ton_domain: &TonDomain): u64 { + ton_domain.length_bytes + } + + public fun domain_value(ton_domain: &TonDomain): &String { + &ton_domain.value + } + + // let verify_proof_json = r#"{ + // "name": "ton_proof", + // "proof": { + // "timestamp": 1730363765, + // "domain": { + // "length_bytes": 21, + // "value": "ton-connect.github.io" + // }, + // "signature": "BvysFrBS8KgTa3bww9f5paEu6/jZr5jB1JmO6T8nqsLzJqB3hWHiqOG9OezPsiJX3kD9nifMbRhr1xkv37ICCw==", + // "payload": "bc1q04uaa0mveqtt4y0sltuxtauhlyl8ctstr5x3hu" + // }, + // "state_init": "te6cckECFgEAAwQAAgE0ARUBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF1M4HQpWKrIhrdY9Ou9RtUmildvf4qB7qOpqgADYbRTiQD9nbsU=" + // }"#; + + #[test_only] + use rooch_framework::ton_address; + + #[test] + fun test_verify_proof(){ + let proof = TonProofData{ + name: string::utf8(b"ton_proof"), + proof: TonProof{ + timestamp: 1730363765, + domain: TonDomain{ + length_bytes: 21, + value: string::utf8(b"ton-connect.github.io") + }, + signature: string::utf8(b"BvysFrBS8KgTa3bww9f5paEu6/jZr5jB1JmO6T8nqsLzJqB3hWHiqOG9OezPsiJX3kD9nifMbRhr1xkv37ICCw=="), + payload: vector[string::utf8(b"bc1q04uaa0mveqtt4y0sltuxtauhlyl8ctstr5x3hu")] + }, + state_init: string::utf8(b"te6cckECFgEAAwQAAgE0ARUBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF1M4HQpWKrIhrdY9Ou9RtUmildvf4qB7qOpqgADYbRTiQD9nbsU=") + }; + let ton_addr = ton_address::from_hex_str( + &string::utf8(b"0:b1481ee8620ebf33b7882fa749654176ef00c7e4cac95ed39f371d5775920814"), + ); + verify_proof(&ton_addr, &proof); + } +} \ No newline at end of file diff --git a/frameworks/rooch-framework/sources/transaction_validator.move b/frameworks/rooch-framework/sources/transaction_validator.move index 2b3a4ac4b8..3f56e1d6ad 100644 --- a/frameworks/rooch-framework/sources/transaction_validator.move +++ b/frameworks/rooch-framework/sources/transaction_validator.move @@ -128,7 +128,7 @@ module rooch_framework::transaction_validator { }; }; let bitcoin_addr = auth_validator::get_bitcoin_address_from_ctx(); - address_mapping::bind_bitcoin_address(sender, bitcoin_addr); + address_mapping::bind_bitcoin_address_internal(sender, bitcoin_addr); let tx_sequence_info = tx_context::get_attribute(); if (option::is_some(&tx_sequence_info)) { let tx_sequence_info = option::extract(&mut tx_sequence_info); diff --git a/frameworks/rooch-framework/sources/transfer.move b/frameworks/rooch-framework/sources/transfer.move index deb0f0e78e..92ccf9677b 100644 --- a/frameworks/rooch-framework/sources/transfer.move +++ b/frameworks/rooch-framework/sources/transfer.move @@ -32,7 +32,7 @@ module rooch_framework::transfer { ) { let btc_address = bitcoin_address::from_string(&to); let rooch_address = bitcoin_address::to_rooch_address(&btc_address); - address_mapping::bind_bitcoin_address(rooch_address, btc_address); + address_mapping::bind_bitcoin_address_internal(rooch_address, btc_address); account_coin_store::transfer(from, rooch_address, amount) } @@ -63,7 +63,7 @@ module rooch_framework::transfer { obj: Object) { let btc_address = bitcoin_address::from_string(&to); let rooch_address = bitcoin_address::to_rooch_address(&btc_address); - address_mapping::bind_bitcoin_address(rooch_address, btc_address); + address_mapping::bind_bitcoin_address_internal(rooch_address, btc_address); object::transfer(obj, rooch_address); } } diff --git a/frameworks/rooch-nursery/doc/README.md b/frameworks/rooch-nursery/doc/README.md index 4a16720a6d..d4ac6c5fc3 100644 --- a/frameworks/rooch-nursery/doc/README.md +++ b/frameworks/rooch-nursery/doc/README.md @@ -17,7 +17,6 @@ This is the reference documentation of the Rooch Nursery Framework. - [`0xa::cosmwasm_std`](cosmwasm_std.md#0xa_cosmwasm_std) - [`0xa::cosmwasm_vm`](cosmwasm_vm.md#0xa_cosmwasm_vm) - [`0xa::ethereum`](ethereum.md#0xa_ethereum) -- [`0xa::ethereum_validator`](ethereum_validator.md#0xa_ethereum_validator) - [`0xa::genesis`](genesis.md#0xa_genesis) - [`0xa::inscribe_factory`](inscribe_factory.md#0xa_inscribe_factory) - [`0xa::mint_get_factory`](mint_get_factory.md#0xa_mint_get_factory) diff --git a/frameworks/rooch-nursery/doc/ethereum_validator.md b/frameworks/rooch-nursery/doc/ethereum_validator.md deleted file mode 100644 index 1f1d7df740..0000000000 --- a/frameworks/rooch-nursery/doc/ethereum_validator.md +++ /dev/null @@ -1,84 +0,0 @@ - - - -# Module `0xa::ethereum_validator` - -This module implements Ethereum validator with the ECDSA recoverable signature over Secp256k1. - - -- [Struct `EthereumValidator`](#0xa_ethereum_validator_EthereumValidator) -- [Constants](#@Constants_0) -- [Function `auth_validator_id`](#0xa_ethereum_validator_auth_validator_id) -- [Function `validate_signature`](#0xa_ethereum_validator_validate_signature) -- [Function `validate`](#0xa_ethereum_validator_validate) - - -
use 0x1::string;
-use 0x2::features;
-use 0x2::tx_context;
-use 0x3::auth_payload;
-use 0x3::auth_validator;
-use 0x3::ecdsa_k1;
-use 0x3::ethereum_address;
-use 0x3::multichain_address;
-
- - - - - -## Struct `EthereumValidator` - - - -
struct EthereumValidator has drop, store
-
- - - - - -## Constants - - - - -there defines auth validator id for each blockchain - - -
const ETHEREUM_AUTH_VALIDATOR_ID: u64 = 1;
-
- - - - - -## Function `auth_validator_id` - - - -
public fun auth_validator_id(): u64
-
- - - - - -## Function `validate_signature` - -Only validate the authenticator's signature. - - -
public fun validate_signature(payload: &auth_payload::AuthPayload, tx_hash: vector<u8>): ethereum_address::ETHAddress
-
- - - - - -## Function `validate` - - - -
public fun validate(authenticator_payload: vector<u8>): multichain_address::MultiChainAddress
-
diff --git a/frameworks/rooch-nursery/sources/ethereum_validator.move b/frameworks/rooch-nursery/sources/ethereum_validator.move deleted file mode 100644 index 5307f09470..0000000000 --- a/frameworks/rooch-nursery/sources/ethereum_validator.move +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) RoochNetwork -// SPDX-License-Identifier: Apache-2.0 - -/// This module implements Ethereum validator with the ECDSA recoverable signature over Secp256k1. -module rooch_nursery::ethereum_validator { - - use std::vector; - use std::string; - use rooch_framework::multichain_address::{Self, MultiChainAddress}; - use moveos_std::tx_context; - use moveos_std::features; - use rooch_framework::auth_payload::{AuthPayload}; - use rooch_framework::ecdsa_k1; - use rooch_framework::auth_validator; - use rooch_framework::ethereum_address::{Self, ETHAddress}; - use rooch_framework::auth_payload; - - /// there defines auth validator id for each blockchain - const ETHEREUM_AUTH_VALIDATOR_ID: u64 = 1; - - struct EthereumValidator has store, drop {} - - public fun auth_validator_id(): u64 { - ETHEREUM_AUTH_VALIDATOR_ID - } - - /// Only validate the authenticator's signature. - public fun validate_signature(payload: &AuthPayload, tx_hash: vector): ETHAddress { - - let message = auth_payload::encode_full_message(payload, tx_hash); - - let pk = ecdsa_k1::ecrecover(&auth_payload::signature(payload), &message, ecdsa_k1::keccak256()); - assert!( - vector::length(&pk) == ecdsa_k1::public_key_length(), - auth_validator::error_validate_invalid_authenticator() - ); - - let address = ethereum_address::new(pk); - assert!( - ethereum_address::as_bytes(&address) == string::bytes(&auth_payload::from_address(payload)), - auth_validator::error_validate_invalid_authenticator() - ); - - address - } - - public fun validate(authenticator_payload: vector): MultiChainAddress { - features::ensure_testnet_enabled(); - - //let sender = tx_context::sender(); - let tx_hash = tx_context::tx_hash(); - let payload = auth_payload::from_bytes(authenticator_payload); - let eth_addr = validate_signature(&payload, tx_hash); - let multi_chain_addr = multichain_address::from_eth(eth_addr); - - //TODO check if the sender is related to the eth address - - // Check if the sender is related to the Rooch address - // assert!( - // sender == rooch_addr, - // auth_validator::error_validate_invalid_authenticator() - // ); - - multi_chain_addr - } -}