From 880cb803399ae24de7dadc0860fe7664faa445b6 Mon Sep 17 00:00:00 2001 From: Bai Chuan Date: Mon, 24 Feb 2025 08:59:10 +0800 Subject: [PATCH 1/4] Validate the l1 tx before execution (#3342) * validate the l1 tx before execution via contract * fixup fmt * refactor l1 tx validate and identifying l1 tx validate errors --- Cargo.lock | 1 + crates/rooch-executor/src/actor/executor.rs | 14 ++++- crates/rooch-relayer/src/actor/relayer.rs | 36 +++++++---- crates/rooch-types/Cargo.toml | 1 + crates/rooch-types/src/bitcoin/mod.rs | 22 +++++++ .../src/bitcoin/transaction_validator.rs | 63 +++++++++++++++++++ crates/rooch-types/src/error.rs | 14 +++++ .../move_cli/commands/run_function.rs | 29 +++++++-- frameworks/bitcoin-move/doc/README.md | 1 + frameworks/bitcoin-move/doc/bitcoin.md | 13 ++++ .../bitcoin-move/doc/transaction_validator.md | 39 ++++++++++++ frameworks/bitcoin-move/sources/bitcoin.move | 7 +++ .../sources/transaction_validator.move | 29 +++++++++ 13 files changed, 253 insertions(+), 16 deletions(-) create mode 100644 crates/rooch-types/src/bitcoin/transaction_validator.rs create mode 100644 frameworks/bitcoin-move/doc/transaction_validator.md create mode 100644 frameworks/bitcoin-move/sources/transaction_validator.move diff --git a/Cargo.lock b/Cargo.lock index 5fc6b1b32b..f503d489be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11140,6 +11140,7 @@ dependencies = [ "bs58 0.5.1", "chacha20poly1305", "clap 4.5.17", + "coerce", "derive_more 1.0.0", "enum_dispatch", "ethers", diff --git a/crates/rooch-executor/src/actor/executor.rs b/crates/rooch-executor/src/actor/executor.rs index 4977769ab3..8eefe5c1f8 100644 --- a/crates/rooch-executor/src/actor/executor.rs +++ b/crates/rooch-executor/src/actor/executor.rs @@ -33,6 +33,7 @@ use rooch_genesis::FrameworksGasParameters; use rooch_store::state_store::StateStore; use rooch_store::RoochStore; use rooch_types::address::{BitcoinAddress, MultiChainAddress}; +// use rooch_types::bitcoin::transaction_validator::TransactionValidator as L1TransactionValidator; use rooch_types::bitcoin::BitcoinModule; use rooch_types::framework::auth_validator::{ AuthValidatorCaller, BuiltinAuthValidator, TxValidateResult, @@ -236,9 +237,19 @@ impl ExecutorActor { let tx_hash = l1_tx.tx_hash(); let tx_size = l1_tx.tx_size(); let ctx = TxContext::new_system_call_ctx(tx_hash, tx_size); - //TODO we should call the contract to validate the l1 tx has been executed let result = match RoochMultiChainID::try_from(l1_tx.chain_id.id())? { RoochMultiChainID::Bitcoin => { + //L1 tx validate first launches the contract, then opens the Rust code + // // Validate the l1 tx before execution via contract, + // let l1_tx_validator = self.as_module_binding::(); + // let tx_validator_result = l1_tx_validator + // .validate_l1_tx(ctx, tx_hash, vec![]) + // .map_err(Into::into)?; + // // If the l1 tx already execute, skip the tx. + // if !tx_validator_result { + // return Err(RoochError::L1TxAlreadyExecuted); + // } + let action = VerifiedMoveAction::Function { call: BitcoinModule::create_execute_l1_tx_call(l1_tx.block_hash, l1_tx.txid)?, bypass_visibility: true, @@ -249,6 +260,7 @@ impl ExecutorActor { action, )) } + // _id => Err(RoochError::InvalidChainID), id => Err(anyhow::anyhow!("Chain {} not supported yet", id)), }; diff --git a/crates/rooch-relayer/src/actor/relayer.rs b/crates/rooch-relayer/src/actor/relayer.rs index 4ab2337e22..093687b938 100644 --- a/crates/rooch-relayer/src/actor/relayer.rs +++ b/crates/rooch-relayer/src/actor/relayer.rs @@ -19,6 +19,7 @@ use rooch_event::event::ServiceStatusEvent; use rooch_executor::proxy::ExecutorProxy; use rooch_pipeline_processor::proxy::PipelineProcessorProxy; use rooch_types::bitcoin::pending_block::PendingBlockModule; +use rooch_types::error::RoochError; use rooch_types::multichain_id::RoochMultiChainID; use rooch_types::service_status::ServiceStatus; use rooch_types::transaction::{L1BlockWithBody, L1Transaction}; @@ -126,19 +127,32 @@ impl RelayerActor { async fn handle_l1_tx(&mut self, l1_tx: L1Transaction) -> Result<()> { let txid = hex::encode(&l1_tx.txid); - let result = self.processor.execute_l1_tx(l1_tx).await?; - - match result.execution_info.status { - KeptVMStatus::Executed => { - info!("Relayer execute relay tx(txid: {}) success", txid); - } - _ => { - error!( - "Relayer execute relay tx(txid: {}) failed, status: {:?}", - txid, result.execution_info.status - ); + match self.processor.execute_l1_tx(l1_tx).await { + Ok(result) => match result.execution_info.status { + KeptVMStatus::Executed => { + info!("Relayer execute relay tx(txid: {}) success", txid); + } + _ => { + error!( + "Relayer execute relay tx(txid: {}) failed, status: {:?}", + txid, result.execution_info.status + ); + } + }, + Err(error) => { + // Handle specific RoochError::L1TxAlreadyExecuted case + if error.downcast_ref::() == Some(&RoochError::L1TxAlreadyExecuted) { + info!("Relayer has skip execute relay tx(txid: {}) due to it has been already executed", txid); + } else { + error!( + "Relayer execute relay tx(txid: {}) failed, error: {}", + txid, + error.to_string() + ); + } } } + Ok(()) } diff --git a/crates/rooch-types/Cargo.toml b/crates/rooch-types/Cargo.toml index f222e123cd..b0784b66bd 100644 --- a/crates/rooch-types/Cargo.toml +++ b/crates/rooch-types/Cargo.toml @@ -43,6 +43,7 @@ argon2 = { workspace = true } tracing = { workspace = true } xxhash-rust = { workspace = true, features = ["xxh3"] } lz4 = { workspace = true } +coerce = { workspace = true } move-core-types = { workspace = true } move-vm-types = { workspace = true } diff --git a/crates/rooch-types/src/bitcoin/mod.rs b/crates/rooch-types/src/bitcoin/mod.rs index e1aab01774..0f3b109ad0 100644 --- a/crates/rooch-types/src/bitcoin/mod.rs +++ b/crates/rooch-types/src/bitcoin/mod.rs @@ -8,6 +8,7 @@ use bitcoin::BlockHash; use move_core_types::{ account_address::AccountAddress, ident_str, identifier::IdentStr, value::MoveValue, }; +use moveos_types::h256::H256; use moveos_types::{ module_binding::{ModuleBinding, MoveFunctionCaller}, move_std::option::MoveOption, @@ -40,6 +41,7 @@ pub mod utxo; #[cfg(test)] mod tests; +pub mod transaction_validator; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BitcoinBlockStore { @@ -99,6 +101,7 @@ impl<'a> BitcoinModule<'a> { pub const EXECUTE_L1_BLOCK_FUNCTION_NAME: &'static IdentStr = ident_str!("execute_l1_block"); pub const GET_GENESIS_BLOCK_FUNCTION_NAME: &'static IdentStr = ident_str!("get_genesis_block"); pub const EXECUTE_L1_TX_FUNCTION_NAME: &'static IdentStr = ident_str!("execute_l1_tx"); + pub const EXIST_L1_TX_FUNCTION_NAME: &'static IdentStr = ident_str!("exist_l1_tx"); pub fn get_block(&self, block_hash: BlockHash) -> Result> { let call = Self::create_function_call( @@ -229,6 +232,25 @@ impl<'a> BitcoinModule<'a> { vec![MoveValue::Address(block_hash), MoveValue::Address(txid)], )) } + + pub fn exist_l1_tx(&self, tx_hash: H256) -> Result { + let call = Self::create_function_call( + Self::EXIST_L1_TX_FUNCTION_NAME, + vec![], + vec![MoveValue::Address(tx_hash.into_address())], + ); + let ctx = TxContext::new_readonly_ctx(AccountAddress::ZERO); + let exist = self + .caller + .call_function(&ctx, call)? + .into_result() + .map(|mut values| { + let value = values.pop().expect("should have one return value"); + bcs::from_bytes::(&value.value).expect("should be a valid bool") + }) + .map_err(|e| anyhow::anyhow!("Failed to check l1 tx exist: {:?}", e))?; + Ok(exist) + } } impl<'a> ModuleBinding<'a> for BitcoinModule<'a> { diff --git a/crates/rooch-types/src/bitcoin/transaction_validator.rs b/crates/rooch-types/src/bitcoin/transaction_validator.rs new file mode 100644 index 0000000000..27a85dbbe8 --- /dev/null +++ b/crates/rooch-types/src/bitcoin/transaction_validator.rs @@ -0,0 +1,63 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use framework_types::addresses::BITCOIN_MOVE_ADDRESS; +use move_core_types::{ + account_address::AccountAddress, ident_str, identifier::IdentStr, value::MoveValue, +}; + +use crate::into_address::IntoAddress; +use moveos_types::h256::H256; +use moveos_types::{ + module_binding::{ModuleBinding, MoveFunctionCaller}, + moveos_std::tx_context::TxContext, +}; + +/// Rust bindings for BitcoinMove transaction_validator module +pub struct TransactionValidator<'a> { + caller: &'a dyn MoveFunctionCaller, +} + +impl<'a> TransactionValidator<'a> { + pub const VALIDATE_L1_TX_FUNCTION_NAME: &'static IdentStr = ident_str!("validate_l1_tx"); + + pub fn validate_l1_tx( + &self, + ctx: &TxContext, + tx_hash: H256, + _payload: Vec, + ) -> Result { + let tx_validator_call = Self::create_function_call( + Self::VALIDATE_L1_TX_FUNCTION_NAME, + vec![], + vec![ + MoveValue::Address(tx_hash.into_address()), + MoveValue::vector_u8(vec![]), + ], + ); + + let result = self + .caller + .call_function(ctx, tx_validator_call)? + .into_result() + .map(|mut values| { + let value = values.pop().expect("should have one return value"); + bcs::from_bytes::(&value.value).expect("should be a valid bool") + }) + .map_err(|e| anyhow::anyhow!("Failed to validate l1 tx: {:?}", e))?; + Ok(result) + } +} + +impl<'a> ModuleBinding<'a> for TransactionValidator<'a> { + const MODULE_NAME: &'static IdentStr = ident_str!("transaction_validator"); + const MODULE_ADDRESS: AccountAddress = BITCOIN_MOVE_ADDRESS; + + fn new(caller: &'a impl MoveFunctionCaller) -> Self + where + Self: Sized, + { + Self { caller } + } +} diff --git a/crates/rooch-types/src/error.rs b/crates/rooch-types/src/error.rs index 20c4762193..61b6ad95e2 100644 --- a/crates/rooch-types/src/error.rs +++ b/crates/rooch-types/src/error.rs @@ -1,6 +1,7 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 +use coerce::actor::ActorRefErr; use move_binary_format::errors::VMError; use moveos_types::genesis_info::GenesisInfo; use std::io; @@ -134,8 +135,15 @@ pub enum RoochError { #[error("The onchain gas schedule is empty.")] OnchainGasScheduleIsEmpty, + #[error("The l1 tx has been executed.")] + L1TxAlreadyExecuted, + #[error("VM error: {0}")] VMError(VMError), + + // Add new variant for ActorRefErr + #[error("Actor reference error: {0}")] + ActorRefError(String), } impl From for RoochError { @@ -191,6 +199,12 @@ impl From for RoochError { } } +impl From for RoochError { + fn from(e: ActorRefErr) -> Self { + RoochError::ActorRefError(e.to_string()) + } +} + #[derive(Debug, Error, Eq, PartialEq)] pub enum GenesisError { #[error("Genesis version mismatch: from store({from_store}), from binary({from_binary}).")] diff --git a/crates/rooch/src/commands/move_cli/commands/run_function.rs b/crates/rooch/src/commands/move_cli/commands/run_function.rs index b24e2d9d79..7cad6f72e0 100644 --- a/crates/rooch/src/commands/move_cli/commands/run_function.rs +++ b/crates/rooch/src/commands/move_cli/commands/run_function.rs @@ -75,6 +75,7 @@ impl CommandAction for RunFunction { let address_mapping = context.address_mapping(); let sender: RoochAddress = context.resolve_address(self.tx_options.sender)?.into(); let max_gas_amount: Option = self.tx_options.max_gas_amount; + let sequence_number: Option = self.tx_options.sequence_number; let function_id = self.function.into_function_id(&address_mapping)?; let args = self .args @@ -94,7 +95,12 @@ impl CommandAction for RunFunction { if self.dry_run { let rooch_tx_data = context - .build_tx_data(sender, action.clone(), max_gas_amount) + .build_tx_data_with_sequence_number( + sender, + action.clone(), + max_gas_amount, + sequence_number, + ) .await?; let dry_run_result_opt = dry_run_tx_locally(context.get_client().await?, rooch_tx_data).await?; @@ -109,7 +115,12 @@ impl CommandAction for RunFunction { let result = match (self.tx_options.authenticator, self.tx_options.session_key) { (Some(authenticator), _) => { let tx_data = context - .build_tx_data(sender, action, max_gas_amount) + .build_tx_data_with_sequence_number( + sender, + action, + max_gas_amount, + sequence_number, + ) .await?; //TODO the authenticator usually is associated with the RoochTransactinData //So we need to find a way to let user generate the authenticator based on the tx_data. @@ -118,7 +129,12 @@ impl CommandAction for RunFunction { } (_, Some(session_key)) => { let tx_data = context - .build_tx_data(sender, action, max_gas_amount) + .build_tx_data_with_sequence_number( + sender, + action, + max_gas_amount, + sequence_number, + ) .await?; let tx = context .sign_transaction_via_session_key(&sender, tx_data, &session_key) @@ -127,7 +143,12 @@ impl CommandAction for RunFunction { } (None, None) => { let tx_data = context - .build_tx_data(sender, action.clone(), max_gas_amount) + .build_tx_data_with_sequence_number( + sender, + action.clone(), + max_gas_amount, + sequence_number, + ) .await?; let tx_execution_result = context.sign_and_execute(sender, tx_data.clone()).await?; diff --git a/frameworks/bitcoin-move/doc/README.md b/frameworks/bitcoin-move/doc/README.md index 27144daf0e..0558f164d3 100644 --- a/frameworks/bitcoin-move/doc/README.md +++ b/frameworks/bitcoin-move/doc/README.md @@ -27,6 +27,7 @@ This is the reference documentation of the Bitcoin Move Framework. - [`0x4::script_buf`](script_buf.md#0x4_script_buf) - [`0x4::taproot_builder`](taproot_builder.md#0x4_taproot_builder) - [`0x4::temp_state`](temp_state.md#0x4_temp_state) +- [`0x4::transaction_validator`](transaction_validator.md#0x4_transaction_validator) - [`0x4::types`](types.md#0x4_types) - [`0x4::utxo`](utxo.md#0x4_utxo) diff --git a/frameworks/bitcoin-move/doc/bitcoin.md b/frameworks/bitcoin-move/doc/bitcoin.md index d240e2663f..c9d4628990 100644 --- a/frameworks/bitcoin-move/doc/bitcoin.md +++ b/frameworks/bitcoin-move/doc/bitcoin.md @@ -20,6 +20,7 @@ - [Function `get_latest_block`](#0x4_bitcoin_get_latest_block) - [Function `get_bitcoin_time`](#0x4_bitcoin_get_bitcoin_time) - [Function `contains_header`](#0x4_bitcoin_contains_header) +- [Function `exist_l1_tx`](#0x4_bitcoin_exist_l1_tx)
use 0x1::option;
@@ -273,3 +274,15 @@ Get the bitcoin time in seconds
 
 
public fun contains_header(block_header: &types::Header): bool
 
+ + + + + +## Function `exist_l1_tx` + +Check is l1 tx exist + + +
public fun exist_l1_tx(tx_hash: address): bool
+
diff --git a/frameworks/bitcoin-move/doc/transaction_validator.md b/frameworks/bitcoin-move/doc/transaction_validator.md new file mode 100644 index 0000000000..785ca48739 --- /dev/null +++ b/frameworks/bitcoin-move/doc/transaction_validator.md @@ -0,0 +1,39 @@ + + + +# Module `0x4::transaction_validator` + + + +- [Struct `TransactionValidatorPlaceholder`](#0x4_transaction_validator_TransactionValidatorPlaceholder) +- [Function `validate_l1_tx`](#0x4_transaction_validator_validate_l1_tx) + + +
use 0x4::bitcoin;
+
+ + + + + +## Struct `TransactionValidatorPlaceholder` + +The l1 tx already execute +Just using to get module signer + + +
struct TransactionValidatorPlaceholder
+
+ + + + + +## Function `validate_l1_tx` + +This function is for Rooch to validate the l1 transaction. +If validate fails, abort this function. + + +
public fun validate_l1_tx(tx_hash: address, _payload: vector<u8>): bool
+
diff --git a/frameworks/bitcoin-move/sources/bitcoin.move b/frameworks/bitcoin-move/sources/bitcoin.move index db03825d40..ce3dc7dc47 100644 --- a/frameworks/bitcoin-move/sources/bitcoin.move +++ b/frameworks/bitcoin-move/sources/bitcoin.move @@ -401,6 +401,13 @@ module bitcoin_move::bitcoin{ option::is_some(&block) } + /// Check is l1 tx exist + public fun exist_l1_tx(tx_hash: address): bool{ + let btc_block_store_obj = borrow_block_store(); + let btc_block_store = object::borrow(btc_block_store_obj); + table::contains(&btc_block_store.txs, tx_hash) + } + #[test_only] public fun execute_l1_block_for_test(block_height: u64, block: Block){ let block_hash = types::header_to_hash(types::header(&block)); diff --git a/frameworks/bitcoin-move/sources/transaction_validator.move b/frameworks/bitcoin-move/sources/transaction_validator.move new file mode 100644 index 0000000000..2a6e004b04 --- /dev/null +++ b/frameworks/bitcoin-move/sources/transaction_validator.move @@ -0,0 +1,29 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module bitcoin_move::transaction_validator { + use bitcoin_move::bitcoin; + + /// The l1 tx already execute + // const ErrorValidateL1TxAlreadyExist: u64 = 1001; + + /// Just using to get module signer + struct TransactionValidatorPlaceholder {} + + /// This function is for Rooch to validate the l1 transaction. + /// If validate fails, abort this function. + public fun validate_l1_tx( + tx_hash: address, + _payload: vector + ): bool { + // If the l1 tx has been executed, then skip the tx. + // And the validate result will be false instead of abort the function + if(bitcoin::exist_l1_tx(tx_hash)){ + return false + }; + + // If validate fails, abort this function. + + true + } +} From 24bcd3132298959f18b402a94297696b9bf76405 Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Mon, 24 Feb 2025 13:20:14 +0800 Subject: [PATCH 2/4] Update website (#3357) * fix-typo * fix * fix * fix * fix * fix * fix * fix * fix * add rooch-book to .gitignore --- .gitignore | 2 ++ .../accounts/account-abstraction.zh-CN.mdx | 6 ++-- .../accounts/session-key.en-US.mdx | 2 +- .../accounts/session-key.zh-CN.mdx | 4 +-- .../move-contracts/move-on-rooch.zh-CN.mdx | 2 +- .../core-concepts/objects/object.zh-CN.mdx | 2 +- .../objects/storage-abstraction.zh-CN.mdx | 2 +- .../transaction/transaction-flow.zh-CN.mdx | 6 ++-- .../pages/learn/core-concepts/vapp.zh-CN.mdx | 2 +- .../client-side-validation.zh-CN.mdx | 4 +-- .../pages/learn/in-depth-tech/dstp.zh-CN.mdx | 29 +++++++++---------- .../in-depth-tech/sequence-proof.zh-CN.mdx | 2 +- 12 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 2cb3089b13..9bc5e30095 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ sdk/typescript/src/generated/ # pnpm store .pnpm-store/ + +rooch-book \ No newline at end of file diff --git a/docs/website/pages/learn/core-concepts/accounts/account-abstraction.zh-CN.mdx b/docs/website/pages/learn/core-concepts/accounts/account-abstraction.zh-CN.mdx index 2f539af771..e4448fe6af 100644 --- a/docs/website/pages/learn/core-concepts/accounts/account-abstraction.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/accounts/account-abstraction.zh-CN.mdx @@ -1,4 +1,4 @@ -# 账户抽象(Account Abstraction) +# 账户抽象(Account Abstraction) 账户抽象是 Ethereum 社区提出的,尝试统一外部账户(EOA)和合约账户的一系列解决方案。账户抽象的关键是让开发者可以通过自定义的代码逻辑来校验交易。 @@ -10,9 +10,9 @@ 这是拓展比特币生态的重要基础,Rooch 支持将 Bitcoin 的地址自动映射成 Rooch 的地址,或者在已有 Rooch 地址上绑定 Bitcoin 地址。地址映射将比特币资产扩展到应用生态提供更多可能,比如引入智能合约管理,方便更多 DApp 交互等。 -## 身份验证器(Authentication Validator) +## 身份验证器(Authentication Validator) -开发者可以自定义身份验证器,可以用来实现 Web2 社交登陆,多重签名,多因素认证等功能。身份认证器是独立的模块,用户只需要添加身份认证器,就可以使用身份认证器提供的功能,而不需要部署合约。 +开发者可以自定义身份验证器,可以用来实现 Web2 社交登录,多重签名,多因素认证等功能。身份认证器是独立的模块,用户只需要添加身份认证器,就可以使用身份认证器提供的功能,而不需要部署合约。 同一个账户支持多个身份验证器,用户可以通过交易来添加或者禁用身份验证器,而无需担心验证器切换带来的兼容性问题。 ## 会话私钥(SessionKey) diff --git a/docs/website/pages/learn/core-concepts/accounts/session-key.en-US.mdx b/docs/website/pages/learn/core-concepts/accounts/session-key.en-US.mdx index fb635e4b90..ea087ed2e2 100644 --- a/docs/website/pages/learn/core-concepts/accounts/session-key.en-US.mdx +++ b/docs/website/pages/learn/core-concepts/accounts/session-key.en-US.mdx @@ -56,7 +56,7 @@ When we use session keys, module names and function names can use `*` to match a ### Get session key -Under the premise of knowing the account address and authentication key, we can use the `get_seesion_key` function to obtain the session key of the current account. +Under the premise of knowing the account address and authentication key, we can use the `get_session_key` function to obtain the session key of the current account. ### Expiration diff --git a/docs/website/pages/learn/core-concepts/accounts/session-key.zh-CN.mdx b/docs/website/pages/learn/core-concepts/accounts/session-key.zh-CN.mdx index 68833d4678..b2a38bbba7 100644 --- a/docs/website/pages/learn/core-concepts/accounts/session-key.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/accounts/session-key.zh-CN.mdx @@ -14,7 +14,7 @@ Rooch 的会话密钥(Session Key)是一个方便用户跟链交互的一种 Rooch 的会话密钥提供以下几个优势: -1. 缩短链游交互的签名繁杂步骤,以 2048 小游戏为例,每执行一步都需要发送一次交易上链,用户端会频繁调用钱包来签名,这个过程会相当耗时。有些钱包可能会使用委托代理,即预付一部分 Gas,当执行到预算 Gas 耗尽时,再一次性签名交易上链,这种托管的形式存在资金风险和游戏作弊的情况。使用会话密钥时,只需要开始游戏时签名一次交易,后续的游戏执行中,只要会话密钥没有过期,每进行一步,Rooch 系统都会自动签名交易上链。 +1. 缩短链游交互的签名繁杂步骤,以 2048 小游戏为例,每执行一步都需要发送一次交易上链,用户端会频繁调用钱包来签名,这个过程会相当耗时。有些钱包可能会使用委托代理,即预付一部分 Gas,当执行到预付 Gas 耗尽时,再一次性签名交易上链,这种托管的形式存在资金风险和游戏作弊的情况。使用会话密钥时,只需要开始游戏时签名一次交易,后续的游戏执行中,只要会话密钥没有过期,每进行一步,Rooch 系统都会自动签名交易上链。 2. 提供更好的权限管理,对于不同的账户地址,能够使用哪些函数,在第一次签名时就能够确定。 3. 用户更友好,为最终用户使用 Web3 提供 Web2 一样的流畅体验。 @@ -62,7 +62,7 @@ struct SessionKey has store,copy,drop { ### 获取会话密钥 -知道账户地址和认证密钥的前提下,我们可以使用 `get_seesion_key` 这个函数来获取当前账户的会话密钥。 +知道账户地址和认证密钥的前提下,我们可以使用 `get_session_key` 这个函数来获取当前账户的会话密钥。 ### 过期时间 diff --git a/docs/website/pages/learn/core-concepts/move-contracts/move-on-rooch.zh-CN.mdx b/docs/website/pages/learn/core-concepts/move-contracts/move-on-rooch.zh-CN.mdx index 0ebfbece75..5ed35667b7 100644 --- a/docs/website/pages/learn/core-concepts/move-contracts/move-on-rooch.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/move-contracts/move-on-rooch.zh-CN.mdx @@ -12,7 +12,7 @@ Move 语言的特性可以查阅文末的参考资料,这里主要介绍 Rooch ### 内置标准库 -Rooch 当前内置了四个标准库,分别是 `MoveStdlib`、`MoveosStdlib` 和 `RoochFramework`,`BitcoinMove`,详细介绍参看[内置库](./built-in-library)。 +Rooch 当前内置了四个标准库,分别是 `MoveStdlib`、`MoveosStdlib`、`RoochFramework` 和 `BitcoinMove`,详细介绍参看[内置库](./built-in-library)。 ### 私有泛型 diff --git a/docs/website/pages/learn/core-concepts/objects/object.zh-CN.mdx b/docs/website/pages/learn/core-concepts/objects/object.zh-CN.mdx index e705d8e26f..659b0ad4aa 100644 --- a/docs/website/pages/learn/core-concepts/objects/object.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/objects/object.zh-CN.mdx @@ -274,7 +274,7 @@ https://dev-seed.rooch.network Rooch 为 object 提供了管理动态字段的能力。动态字段是指将 Resource 或者 Object 以 key, value 的形式储存在 Object 中。特别是,key 可以是异质的,即不受 key 类型的限制。更具体的说,Object 可以被当作 [Table](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/sources/table.move) 或 [Bag](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/sources/bag.move) 来使用。 -Rooch object 的提供了两种类型的动态字段:常规类型和Object类型。 +Rooch object 提供了两种类型的动态字段:常规类型和Object类型。 常规类型的动态字段是指任何具有 `store` ability 的类型存放在 object 下;Object 类型的动态字段是将子 Object 对象存放在 object 下。 diff --git a/docs/website/pages/learn/core-concepts/objects/storage-abstraction.zh-CN.mdx b/docs/website/pages/learn/core-concepts/objects/storage-abstraction.zh-CN.mdx index 8e394d2d5c..2021ffcf19 100644 --- a/docs/website/pages/learn/core-concepts/objects/storage-abstraction.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/objects/storage-abstraction.zh-CN.mdx @@ -4,7 +4,7 @@ 智能合约编程语言和传统编程语言最大的区别是智能合约编程语言需要在编程语言内部提供标准化的状态存储接口,屏蔽状态存储的底层实现,智能合约应用主要关心自己的业务逻辑。 -**而“存储抽象(Storage Abstraction)”的目标是让开发者可以在智能合约中更灵活地定义应用的状态存储结构,而不局限于平台提供的标准化方案**。 +**而“存储抽象(Storage Abstraction)”的目标是让开发者可以在智能合约中更灵活地定义应用的状态存储结构,而不局限于平台提供的标准化方案。** 我们先回顾一下当前的智能合约平台提供的方案,然后提出 Rooch 的 `Storage Abstraction` 方案。 diff --git a/docs/website/pages/learn/core-concepts/transaction/transaction-flow.zh-CN.mdx b/docs/website/pages/learn/core-concepts/transaction/transaction-flow.zh-CN.mdx index b057424a3e..f6c37a211c 100644 --- a/docs/website/pages/learn/core-concepts/transaction/transaction-flow.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/transaction/transaction-flow.zh-CN.mdx @@ -1,6 +1,6 @@ # 交易执行流 -本文档主要目标是解释 Rooch 中交易的处理流程,让 DApp 开发者以及 Rooch 的开发者深入理解 Rooch 的设计和实现,从而更容易的参与到 DApp 以及 Rooch 的开发中。同时,本文档也试图解答一些常见问题,比如交易的执行顺序,交易的最终确定性等。 +本文档主要目标是解释 Rooch 中交易的处理流程,让 DApp 开发者以及 Rooch 的开发者深入理解 Rooch 的设计和实现,从而更容易地参与到 DApp 以及 Rooch 的开发中。同时,本文档也试图解答一些常见问题,比如交易的执行顺序,交易的最终确定性等。 ## 用户视角 @@ -58,7 +58,7 @@ Rooch 中有两种方式实现交易包含: ### 2. 通过 L1 提交 用户可以通过 L1 发起 L2 调用,等待 Sequencer 抓取并执行。这也是 L1 向 L2 发送消息的运行机制。 -该方式使得 Sequencer 无法隐蔽的实现审查交易,因为任何人都可以很容易发现 L1 上的合约调用与相关事件,以及 L2 上是否包含该交易的事实。 +该方式使得 Sequencer 无法隐蔽地实现审查交易,因为任何人都可以很容易发现 L1 上的合约调用与相关事件,以及 L2 上是否包含该交易的事实。 因此,只要我们把这个链下验证过程重现在 L2 上,那么便可以复用 Optimistic Rollups 的欺诈证明实现对作恶 Sequencer 的惩罚。这需要: 1. L1 信息在 L2 上的审计跟踪: @@ -100,7 +100,7 @@ Sequencer 关于时效的承诺: ### 交易的执行顺序是怎么确定的? -交易的执行顺序是由 Sequencer 确定的。Sequencer 会将交易添加到 Accumulator 中,并立刻获取到交易的全局顺序,Sequencer 需要对该交易的顺序进行签名,相当于给用户一个承诺,承诺自己不会修改顺序或者丢弃交易。而 Accumulator 可以提供交易的顺序证明,如果最后 Sequencer 提交到 DA 的交易顺序和之前的承诺不一致,那么用户可以证明 Sequencer 的行为是恶意的,从对 Sequencer 进行惩罚。 +交易的执行顺序是由 Sequencer 确定的。Sequencer 会将交易添加到 Accumulator 中,并立刻获取到交易的全局顺序,Sequencer 需要对该交易的顺序进行签名,相当于给用户一个承诺,承诺自己不会修改顺序或者丢弃交易。而 Accumulator 可以提供交易的顺序证明,如果最后 Sequencer 提交到 DA 的交易顺序和之前的承诺不一致,那么用户可以证明 Sequencer 的行为是恶意的,从而对 Sequencer 进行惩罚。 ### 交易的执行结果是怎么确定的? diff --git a/docs/website/pages/learn/core-concepts/vapp.zh-CN.mdx b/docs/website/pages/learn/core-concepts/vapp.zh-CN.mdx index 5cc02ca49f..f009d1e530 100644 --- a/docs/website/pages/learn/core-concepts/vapp.zh-CN.mdx +++ b/docs/website/pages/learn/core-concepts/vapp.zh-CN.mdx @@ -4,7 +4,7 @@ ## 关键区别 -- **与 DApp 的区别**:去中心化应用(DApp)强调去中心化特性,而 VApp 则侧重于组件的可验证性。VApp 可以先采用中心化方式实施,只要满足可验证性的标准,然后逐渐过度到 DApp。 +- **与 DApp 的区别**:去中心化应用(DApp)强调去中心化特性,而 VApp 则侧重于组件的可验证性。VApp 可以先采用中心化方式实施,只要满足可验证性的标准,然后逐渐过渡到 DApp。 - **与全链上应用的区别**:全链上应用强调所有计算和状态都在区块链上管理。相比之下,VApp 强调计算和状态的可验证性,且可以托管于链下。这种方式为应用架构提供了更大的灵活性,应用的启动成本更低。 ## VApp 容器 diff --git a/docs/website/pages/learn/in-depth-tech/client-side-validation.zh-CN.mdx b/docs/website/pages/learn/in-depth-tech/client-side-validation.zh-CN.mdx index 4347b05891..2cd415c9fb 100644 --- a/docs/website/pages/learn/in-depth-tech/client-side-validation.zh-CN.mdx +++ b/docs/website/pages/learn/in-depth-tech/client-side-validation.zh-CN.mdx @@ -64,9 +64,9 @@ Ordinals 协议定义了 ID,以及追踪资产的所有权,留了 Metadata 所以数据和资产在格式上并没有本质的区别。数据上链的途径应该是用户先自己将数据提交到链上,保存到资产容器中,取得所有权,然后再通过资产验证器验证数据符合资产协议的规则,实现数据的资产化。 -## 资产跃迁协议(Asset Leap Protocol) +## 资产跃迁协议 (Asset Leap Protocol) -**跃迁(Leap)**:资产迁移的过程,我们称之为“跃迁”,用户将资产从一个资产仓库跃迁到另一个资产仓库。跃迁的过程是一个销毁和重新铸造的过程。注:Leap 这个词最初来源于 RGB++ 协议,我们认为这个词很贴切,所以沿用了这个词。 +**跃迁 (Leap)**:资产迁移的过程,我们称之为“跃迁”,用户将资产从一个资产仓库跃迁到另一个资产仓库。跃迁的过程是一个销毁和重新铸造的过程。注:Leap 这个词最初来源于 RGB++ 协议,我们认为这个词很贴切,所以沿用了这个词。 当前区块链的资产跨链协议,都是基于一边锁定,一边映射的模式,而这种模式并不能降低链的状态存储的压力,也无法支持资产先在链下发行,从而很难支持大规模数据的资产化。而 CSV 资产可以真正实现资产在不同资产仓库之间的迁移,只要保证资产验证器可以验证两边的资产,就可以实现资产的迁移。 diff --git a/docs/website/pages/learn/in-depth-tech/dstp.zh-CN.mdx b/docs/website/pages/learn/in-depth-tech/dstp.zh-CN.mdx index 5af700b00e..9ea7b0dc12 100644 --- a/docs/website/pages/learn/in-depth-tech/dstp.zh-CN.mdx +++ b/docs/website/pages/learn/in-depth-tech/dstp.zh-CN.mdx @@ -2,28 +2,27 @@ import { Callout, FileTree } from 'nextra/components' -分布式状态树协议,简称**DSTP**,旨在通过将状态树分层并将全局状态树的子树分散到P2P网络的不同节点上,从而提高系统的可扩展性。尽管这些节点在物理上是分散的,但它们仍然可以通过全局状态树实现互操作性。 +分布式状态树协议,简称**DSTP**,旨在通过将状态树分层并将全局状态树的子树分散到P2P网络的不同节点上,从而提高系统的可扩展性。尽管这些节点在物理上是分散的,但它们仍然可以通过全局状态树实现互操作性。 ![Distributed State Tree Protocol](/blog/bitcoin-application-layer/dstp.jpg) ## 动机与目标 -区块链系统的扩展性一直是业界面临的主要挑战之一。为解决这一问题,业界已经提出了多种探索路径,而DSTP是其中一种创新尝试。区块链系统通常采用状态树来保存状态,以便快速校验状态的一致性。DSTP的核心思想是将状态树分层,并将全局状态树的子树分散到不同的节点上,从而实现状态的分布式存储,进而提高系统的可扩展性。这一目标决定了DSTP的关键特性,也使其与现有解决方案有所不同。 +区块链系统的扩展性一直是业界面临的主要挑战之一。为解决这一问题,业界已经提出了多种探索路径,而DSTP是其中一种创新尝试。区块链系统通常采用状态树来保存状态,以便快速校验状态的一致性。DSTP的核心思想是将状态树分层,并将全局状态树的子树分散到不同的节点上,从而实现状态的分布式存储,进而提高系统的可扩展性。这一目标决定了DSTP的关键特性,也使其与现有解决方案有所不同。 DSTP的主要特点包括: -1. 数据性质: DSTP主要用于存储智能合约的状态,而非文件。这是它与IPFS等分布式文件系统的根本区别。DSTP中的所有数据都经过智能合约的生成或验证。 +1. 数据性质: DSTP主要用于存储智能合约的状态,而非文件。这是它与IPFS等分布式文件系统的根本区别。DSTP中的所有数据都经过智能合约的生成或验证。 -2. 全局状态保证: DSTP会生成一个全局状态树,其根节点会定期提交到比特币网络,为所有状态提供时间戳证明。 +2. 全局状态保证: DSTP会生成一个全局状态树,其根节点会定期提交到比特币网络,为所有状态提供时间戳证明。 -3. 安全机制: DSTP子树的更新权限和安全机制由智能合约定义,而非由协议本身提供统一的共识机制。这是DSTP与区块链分片等扩容方案的主要区别。 +3. 安全机制: DSTP子树的更新权限和安全机制由智能合约定义,而非由协议本身提供统一的共识机制。这是DSTP与区块链分片等扩容方案的主要区别。 -4. 一致性模型: DSTP采用最终一致性设计,子树的根节点不会实时提交到全局状态树,因此不保证强一致性。 +4. 一致性模型: DSTP采用最终一致性设计,子树的根节点不会实时提交到全局状态树,因此不保证强一致性。 ## DSTP的设计 - -为实现上述目标,DSTP的设计需要解决以下几个关键挑战: +为实现上述目标,DSTP的设计需要解决以下几个关键挑战: 1. 状态树的分层设计 2. 在智能合约中表达状态树 @@ -32,24 +31,24 @@ DSTP的主要特点包括: ### 状态树的分层设计及其在智能合约中的表达 -Rooch平台采用Object模型来表达应用状态,每个Object可以包含子Object,从而形成树状结构。这种设计为DSTP的实现提供了基础。具体来说: +Rooch平台采用Object模型来表达应用状态,每个Object可以包含子Object,从而形成树状结构。这种设计为DSTP的实现提供了基础。具体来说: * ObjectID: 作为Object的唯一标识符。子Object的ObjectID由其父ObjectID和子Object的Key共同生成。 -* StateRoot: 每个Object都包含一个StateRoot字段,用于存储该Object子树的根节点信息。 +* StateRoot: 每个Object都包含一个StateRoot字段,用于存储该Object子树的根节点信息。 -关于Object模型的更多细节,请参考[存储抽象](/learn/core-concepts/objects/storage-abstraction)和[Object模型](/learn/core-concepts/objects/object)文档。 +关于Object模型的更多细节,请参考[存储抽象](/learn/core-concepts/objects/storage-abstraction)和[Object模型](/learn/core-concepts/objects/object)文档。 ![statedb](/docs/rooch-design-statedb.svg) ### 状态树安全机制在智能合约中的实现 -不同应用在不同发展阶段对安全性的要求各不相同。DSTP支持多种安全模式,主要包括: +不同应用在不同发展阶段对安全性的要求各不相同。DSTP支持多种安全模式,主要包括: -1. PoA (Proof of Authority) 模式: 状态树根的更新由特定账户执行,通常是开发者账户。VApp节点也由开发者运营。这种模式适合应用的开发和概念验证阶段,或作为Web2应用向Web3过渡的桥梁。 +1. PoA (Proof of Authority) 模式: 状态树根的更新由特定账户执行,通常是开发者账户。VApp节点也由开发者运营。这种模式适合应用的开发和概念验证阶段,或作为Web2应用向Web3过渡的桥梁。 -2. PoS (Proof of Stake) 模式: 状态树根的更新由通过PoS机制选出的账户执行。该模式包含轮替机制和针对欺诈行为的惩罚机制,以确保安全性。 +2. PoS (Proof of Stake) 模式: 状态树根的更新由通过PoS机制选出的账户执行。该模式包含轮替机制和针对欺诈行为的惩罚机制,以确保安全性。 -3. 多方协作模式: 状态树根的更新需要多个协作者共同签名。智能合约中需要包含处理不合作情况的机制。这种模式适用于需要多方共同管理的应用,也可用于实现状态通道。 +3. 多方协作模式: 状态树根的更新需要多个协作者共同签名。智能合约中需要包含处理不合作情况的机制。这种模式适用于需要多方共同管理的应用,也可用于实现状态通道。 ### P2P网络中的状态读取和同步协议 diff --git a/docs/website/pages/learn/in-depth-tech/sequence-proof.zh-CN.mdx b/docs/website/pages/learn/in-depth-tech/sequence-proof.zh-CN.mdx index de25803982..ebcc1324aa 100644 --- a/docs/website/pages/learn/in-depth-tech/sequence-proof.zh-CN.mdx +++ b/docs/website/pages/learn/in-depth-tech/sequence-proof.zh-CN.mdx @@ -6,7 +6,7 @@ ![Without Sequence Proof Diagram](/docs/without-sequence-proof.png) -在 Layer2 网络中,受欺诈证明(Fraud Proof)或零知识证明(ZK Proof)的限制,排序器(Sequencer)不能更改交易内容,否则将面临惩罚。但是,排序器可以拒绝某个用户的交易(通过不将交易发送给 DA 和 Layer1),或者改变交易的位置,通过串通第三方获得间接利。(例如拒绝保证金交易以获取清仓利润,或更改交易订单以获取 MEV 利润) +在 Layer2 网络中,受欺诈证明(Fraud Proof)或零知识证明(ZK Proof)的限制,排序器(Sequencer)不能更改交易内容,否则将面临惩罚。但是,排序器可以拒绝某个用户的交易(通过不将交易发送给 DA 和 Layer1),或者改变交易的位置,通过串通第三方获得间接利益。(例如拒绝保证金交易以获取清仓利润,或更改交易订单以获取 MEV 利润) ## 有排序证明的情况 From 2afead363cfa648b9ad1b7ba2adbfba15d09c1fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:29:48 +0800 Subject: [PATCH 3/4] chore(deps): bump rand_core from 0.9.1 to 0.9.2 (#3355) Bumps [rand_core](https://github.com/rust-random/rand) from 0.9.1 to 0.9.2. - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/rand/compare/rand_core-0.9.1...rand_core-0.9.2) --- updated-dependencies: - dependency-name: rand_core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f503d489be..03f35c23c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.8.5", - "rand_core 0.9.1", + "rand_core 0.9.2", "rocksdb", "serde 1.0.218", "tracing", @@ -9754,7 +9754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.1", + "rand_core 0.9.2", "zerocopy 0.8.16", ] @@ -9785,7 +9785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.1", + "rand_core 0.9.2", ] [[package]] @@ -9808,9 +9808,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" dependencies = [ "getrandom 0.3.1", "zerocopy 0.8.16", diff --git a/Cargo.toml b/Cargo.toml index fa3a690b06..76025e5cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,7 @@ proptest = "1.6.0" proptest-derive = "0.3.0" rayon = "1.5.2" rand = "0.8.5" -rand_core = { version = "0.9.1", default-features = false } +rand_core = { version = "0.9.2", default-features = false } reqwest = { version = "0.12", features = ["json", "stream"] } schemars = { version = "0.8.21", features = ["either"] } serde = { version = "1.0.218", features = ["derive", "rc"] } From 6a95b052f912b828a8d1d8b1de06b1c72830dd10 Mon Sep 17 00:00:00 2001 From: mx <53249469+mx819812523@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:00:27 +0800 Subject: [PATCH 4/4] feat: add MesonFi tunnel (#3359) * feat: add MesonFi tunnel * fix: remove free_tunnel_rooch fork --- apps/minter_manager/Move.toml | 14 + .../minter_manager/sources/MinterManager.move | 242 ++++++++++++++++++ apps/minter_manager/sources/coins/usdt_m.move | 23 ++ apps/minter_manager/test/deploy.sh | 6 + 4 files changed, 285 insertions(+) create mode 100644 apps/minter_manager/Move.toml create mode 100644 apps/minter_manager/sources/MinterManager.move create mode 100644 apps/minter_manager/sources/coins/usdt_m.move create mode 100644 apps/minter_manager/test/deploy.sh diff --git a/apps/minter_manager/Move.toml b/apps/minter_manager/Move.toml new file mode 100644 index 0000000000..612bb713ab --- /dev/null +++ b/apps/minter_manager/Move.toml @@ -0,0 +1,14 @@ +[package] +name = "minter_manager" +version = "0.0.1" + +[dependencies] +MoveStdlib = { local = "../../frameworks/move-stdlib" } +MoveosStdlib = { local = "../../frameworks/moveos-stdlib" } +RoochFramework = { local = "../../frameworks/rooch-framework" } + +[addresses] +std = "0x1" +moveos_std = "0x2" +rooch_framework = "0x3" +minter_manager = "_" \ No newline at end of file diff --git a/apps/minter_manager/sources/MinterManager.move b/apps/minter_manager/sources/MinterManager.move new file mode 100644 index 0000000000..85f406bda3 --- /dev/null +++ b/apps/minter_manager/sources/MinterManager.move @@ -0,0 +1,242 @@ +module minter_manager::minter_manager { + + // =========================== Packages =========================== + use moveos_std::event; + use moveos_std::signer; + use moveos_std::table; + use moveos_std::object::{Self, Object, ObjectID}; + use rooch_framework::coin::{Self, Coin, CoinInfo}; + use rooch_framework::account_coin_store; + + + // =========================== Constants ========================== + const ENOT_ADMIN: u64 = 120; + const ETREASURY_CAP_MANAGER_DESTROYED: u64 = 121; + const EMINTER_REVOKED: u64 = 122; + + + // ============================ Storage =========================== + struct TreasuryCapManager has key { + admin: address, + coinInfoObj: Object>, + revokedMinters: table::Table, + } + + struct MinterCap has key, store { + managerId: ObjectID, + } + + #[event] + struct AdminTransferred has drop, copy { + prevAdmin: address, + newAdmin: address, + } + + #[event] + struct TreasuryCapManagerSetup has drop, copy { + admin: address, + treasuryCapManagerId: ObjectID, + } + + #[event] + struct TreasuryCapManagerDestroyed has drop, copy { + treasuryCapManagerId: ObjectID, + } + + #[event] + struct MinterCapIssued has drop, copy { + recipient: address, + minterCapId: ObjectID, + } + + #[event] + struct MinterCapRevoked has drop, copy { + minterCapId: ObjectID, + } + + #[event] + struct MinterCapDestroyed has drop, copy { + minterCapId: ObjectID, + } + + + // =========================== Coin Admin Functions =========================== + public entry fun transferAdmin( + coinAdmin: &signer, + treasuryCapManagerObj: &mut Object>, + newAdmin: address, + ) { + let treasuryCapManager = object::borrow_mut(treasuryCapManagerObj); + assert!(signer::address_of(coinAdmin) == treasuryCapManager.admin, ENOT_ADMIN); + treasuryCapManager.admin = newAdmin; + event::emit(AdminTransferred { prevAdmin: signer::address_of(coinAdmin), newAdmin }); + } + + public entry fun setupTreasuryCapManager( + coinAdmin: &signer, + coinInfoObj: Object>, + ) { + let treasuryCapManager = TreasuryCapManager { + admin: signer::address_of(coinAdmin), + coinInfoObj, + revokedMinters: table::new(), + }; + let treasuryCapManagerObject = object::new(treasuryCapManager); + let treasuryCapManagerId = object::id(&treasuryCapManagerObject); + object::to_shared(treasuryCapManagerObject); + event::emit(TreasuryCapManagerSetup { admin: signer::address_of(coinAdmin), treasuryCapManagerId }); + } + + public entry fun destroyTreasuryCapManager( + coinAdmin: &signer, + treasuryCapManagerId: ObjectID, + ) { + let treasuryCapManager = object::remove( + object::take_object_extend>(treasuryCapManagerId) + ); + assert!(signer::address_of(coinAdmin) == treasuryCapManager.admin, ENOT_ADMIN); + let TreasuryCapManager { + admin: _, coinInfoObj, revokedMinters, + } = treasuryCapManager; + + table::drop(revokedMinters); + object::transfer(coinInfoObj, signer::address_of(coinAdmin)); + event::emit(TreasuryCapManagerDestroyed { treasuryCapManagerId }); + } + + public entry fun issueMinterCap( + coinAdmin: &signer, + treasuryCapManagerObj: &mut Object>, + recipient: address, + ) { + let treasuryCapManager = object::borrow_mut(treasuryCapManagerObj); + assert!(signer::address_of(coinAdmin) == treasuryCapManager.admin, ENOT_ADMIN); + let minterCapObj = object::new(MinterCap { + managerId: object::id(treasuryCapManagerObj), + }); + let minterCapId = object::id(&minterCapObj); + object::transfer(minterCapObj, recipient); + event::emit(MinterCapIssued { recipient, minterCapId }); + } + + public entry fun revokeMinterCap( + coinAdmin: &signer, + treasuryCapManagerObj: &mut Object>, + minterCapId: ObjectID, + ) { + let treasuryCapManager = object::borrow_mut(treasuryCapManagerObj); + assert!(signer::address_of(coinAdmin) == treasuryCapManager.admin, ENOT_ADMIN); + table::add(&mut treasuryCapManager.revokedMinters, minterCapId, true); + event::emit(MinterCapRevoked { minterCapId }); + } + + public entry fun destroyMinterCap( + _minter: &signer, + minterCapObj: Object>, + ) { + let minterCapId = object::id(&minterCapObj); + let MinterCap { managerId: _ } = object::remove(minterCapObj); + event::emit(MinterCapDestroyed { minterCapId }); + } + + + // =========================== Minter Functions =========================== + public entry fun mint( + _minter: &signer, + treasuryCapManagerObj: &mut Object>, + minterCapObj: &mut Object>, + amount: u256, + recipient: address, + ) { + let treasuryCapManagerId = object::id(treasuryCapManagerObj); + let treasuryCapManager = object::borrow_mut(treasuryCapManagerObj); + let minterCapId = object::id(minterCapObj); + let minterCap = object::borrow(minterCapObj); + assert!( + !table::contains(&treasuryCapManager.revokedMinters, minterCapId), + EMINTER_REVOKED, + ); + assert!( + minterCap.managerId == treasuryCapManagerId, + ETREASURY_CAP_MANAGER_DESTROYED, + ); + let coinToDeposit = coin::mint(&mut treasuryCapManager.coinInfoObj, amount); + account_coin_store::deposit(recipient, coinToDeposit); + } + + public entry fun burnFromSigner( + burner: &signer, + treasuryCapManagerObj: &mut Object>, + minterCapObj: &mut Object>, + amount: u256, + ) { + let coinToBurn = account_coin_store::withdraw(burner, amount); + burn(burner, treasuryCapManagerObj, minterCapObj, coinToBurn); + } + + public fun burn( + _sender: &signer, + treasuryCapManagerObj: &mut Object>, + minterCapObj: &mut Object>, + coinToBurn: Coin, + ) { + let treasuryCapManagerId = object::id(treasuryCapManagerObj); + let treasuryCapManager = object::borrow_mut(treasuryCapManagerObj); + let minterCapId = object::id(minterCapObj); + let minterCap = object::borrow(minterCapObj); + assert!( + !table::contains(&treasuryCapManager.revokedMinters, minterCapId), + EMINTER_REVOKED, + ); + assert!( + minterCap.managerId == treasuryCapManagerId, + ETREASURY_CAP_MANAGER_DESTROYED, + ); + coin::burn(&mut treasuryCapManager.coinInfoObj, coinToBurn); + } + + + // =========================== Test =========================== + + #[test_only] + use std::option; + + #[test_only] + use std::string::utf8; + + #[test_only] + struct FakeMoney has key, store {} + + #[test(coinAdmin = @minter_manager)] + fun testIssueCoin(coinAdmin: &signer): ObjectID { + rooch_framework::genesis::init_for_test(); + let coinInfoObj = coin::register_extend( + utf8(b"FakeMoney"), + utf8(b"FM"), + option::none(), + 18, // decimal + ); + let coinInfoObjId = object::id(&coinInfoObj); + object::transfer(coinInfoObj, signer::address_of(coinAdmin)); + coinInfoObjId + } + + #[test(coinAdmin = @minter_manager, to = @0x44cc)] + fun testNormalMint(coinAdmin: &signer, to: &signer) { + let coinInfoObjId = testIssueCoin(coinAdmin); + let coinInfoObjMut = object::borrow_mut_object>( + coinAdmin, coinInfoObjId + ); + let coinToDeposit = coin::mint(coinInfoObjMut, 1000000); + account_coin_store::deposit(signer::address_of(to), coinToDeposit); + + } + + #[test(coinAdmin = @minter_manager)] + fun testSetupTreasury(coinAdmin: &signer) { + let coinInfoObjId = testIssueCoin(coinAdmin); + let coinInfoObj = object::take_object(coinAdmin, coinInfoObjId); + setupTreasuryCapManager(coinAdmin, coinInfoObj); + } + +} diff --git a/apps/minter_manager/sources/coins/usdt_m.move b/apps/minter_manager/sources/coins/usdt_m.move new file mode 100644 index 0000000000..61a6014419 --- /dev/null +++ b/apps/minter_manager/sources/coins/usdt_m.move @@ -0,0 +1,23 @@ + +module minter_manager::usdt_m { + use std::option::some; + use std::string; + use std::string::utf8; + use moveos_std::signer::module_signer; + use minter_manager::minter_manager::setupTreasuryCapManager; + use rooch_framework::coin; + + struct USDT has key, store{} + + const COIN_URL: vector = b"tether-usdt-logo"; + + fun init() { + let coin_info_obj = coin::register_extend( + string::utf8(b"USDT From Mesion Fi"), + string::utf8(b"USDT.M"), + some(utf8(COIN_URL)), + 6, + ); + setupTreasuryCapManager(&module_signer(), coin_info_obj) + } +} diff --git a/apps/minter_manager/test/deploy.sh b/apps/minter_manager/test/deploy.sh new file mode 100644 index 0000000000..8ac27e9680 --- /dev/null +++ b/apps/minter_manager/test/deploy.sh @@ -0,0 +1,6 @@ +# Export your address here +export MINTER_MANAGER="0x0af854fcad035f4134636ff2d7fa22591f8ff2f264f354ac04e53da06e318529" # address #1 + +# Deploy minter_manager package +cd minter_manager +rooch move publish --named-addresses minter_manager="$MINTER_MANAGER" \ No newline at end of file