diff --git a/common/transaction/src/lib.rs b/common/transaction/src/lib.rs index 550155a4..2e9a2e03 100644 --- a/common/transaction/src/lib.rs +++ b/common/transaction/src/lib.rs @@ -90,6 +90,16 @@ impl From> for EsdtTokenPayment { } } +impl Default for OperationEsdtPayment { + fn default() -> Self { + OperationEsdtPayment { + token_identifier: TokenIdentifier::from(ManagedBuffer::new()), + token_nonce: 0, + token_data: StolenFromFrameworkEsdtTokenData::default(), + } + } +} + // Temporary until Clone is implemented for EsdtTokenData #[derive( TopDecode, TopEncode, NestedDecode, NestedEncode, TypeAbi, Debug, ManagedVecItem, Clone, diff --git a/common/utils/src/lib.rs b/common/utils/src/lib.rs index a23551a7..4f53c016 100644 --- a/common/utils/src/lib.rs +++ b/common/utils/src/lib.rs @@ -7,6 +7,8 @@ multiversx_sc::imports!(); pub type PaymentsVec = ManagedVec>; static ERR_EMPTY_PAYMENTS: &[u8] = b"No payments"; +const DASH: u8 = b'-'; +const MAX_TOKEN_ID_LEN: usize = 32; #[multiversx_sc::module] pub trait UtilsModule: bls_signature::BlsSignatureModule { @@ -66,13 +68,12 @@ pub trait UtilsModule: bls_signature::BlsSignatureModule { list } - fn has_sov_token_prefix(&self, token_id: &TokenIdentifier) -> bool { - let dash = b'-'; + fn has_prefix(&self, token_id: &TokenIdentifier) -> bool { let buffer = token_id.as_managed_buffer(); - let mut array_buffer = [0u8; 32]; + let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; let slice = buffer.load_to_byte_array(&mut array_buffer); - let counter = slice.iter().filter(|&&c| c == dash).count(); + let counter = slice.iter().filter(|&&c| c == DASH).count(); if counter == 2 { return true; @@ -80,4 +81,24 @@ pub trait UtilsModule: bls_signature::BlsSignatureModule { false } + + fn has_sov_prefix(&self, token_id: &TokenIdentifier, chain_prefix: ManagedBuffer) -> bool { + if !self.has_prefix(token_id) { + return false; + } + + let buffer = token_id.as_managed_buffer(); + let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; + let slice = buffer.load_to_byte_array(&mut array_buffer); + + if let Some(index) = slice.iter().position(|&b| b == DASH) { + let prefix = ManagedBuffer::from(&slice[..index]); + + if prefix == chain_prefix { + return true; + } + } + + false + } } diff --git a/enshrine-esdt-safe/src/common/storage.rs b/enshrine-esdt-safe/src/common/storage.rs index c17ff108..37097f96 100644 --- a/enshrine-esdt-safe/src/common/storage.rs +++ b/enshrine-esdt-safe/src/common/storage.rs @@ -4,4 +4,10 @@ use multiversx_sc::imports::*; pub trait CommonStorage { #[storage_mapper("isSovereignChain")] fn is_sovereign_chain(&self) -> SingleValueMapper; + + #[storage_mapper("wegldIdentifier")] + fn wegld_identifier(&self) -> SingleValueMapper; + + #[storage_mapper("sovereignTokensPrefix")] + fn sovereign_tokens_prefix(&self) -> SingleValueMapper; } diff --git a/enshrine-esdt-safe/src/enshrine_esdt_safe_proxy.rs b/enshrine-esdt-safe/src/enshrine_esdt_safe_proxy.rs index bab658db..54b2b2ff 100644 --- a/enshrine-esdt-safe/src/enshrine_esdt_safe_proxy.rs +++ b/enshrine-esdt-safe/src/enshrine_esdt_safe_proxy.rs @@ -45,14 +45,20 @@ where { pub fn init< Arg0: ProxyArg, + Arg1: ProxyArg>>, + Arg2: ProxyArg>>, >( self, is_sovereign_chain: Arg0, + opt_wegld_identifier: Arg1, + opt_sov_token_prefix: Arg2, ) -> TxTypedDeploy { self.wrapped_tx .payment(NotPayable) .raw_deploy() .argument(&is_sovereign_chain) + .argument(&opt_wegld_identifier) + .argument(&opt_sov_token_prefix) .original_result() } } @@ -181,6 +187,18 @@ where .original_result() } + pub fn register_new_token_id< + Arg0: ProxyArg>>, + >( + self, + tokens: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("registerNewTokenID") + .argument(&tokens) + .original_result() + } + pub fn set_max_tx_batch_size< Arg0: ProxyArg, >( diff --git a/enshrine-esdt-safe/src/from_sovereign/transfer_tokens.rs b/enshrine-esdt-safe/src/from_sovereign/transfer_tokens.rs index 3bdd37ec..df28fad1 100644 --- a/enshrine-esdt-safe/src/from_sovereign/transfer_tokens.rs +++ b/enshrine-esdt-safe/src/from_sovereign/transfer_tokens.rs @@ -7,6 +7,7 @@ use transaction::{GasLimit, Operation, OperationData, OperationEsdtPayment, Oper const CALLBACK_GAS: GasLimit = 10_000_000; // Increase if not enough const TRANSACTION_GAS: GasLimit = 30_000_000; +const DEFAULT_ISSUE_COST: u64 = 50000000000000000; #[multiversx_sc::module] pub trait TransferTokensModule: @@ -22,8 +23,10 @@ pub trait TransferTokensModule: { #[endpoint(executeBridgeOps)] fn execute_operations(&self, hash_of_hashes: ManagedBuffer, operation: Operation) { + let is_sovereign_chain = self.is_sovereign_chain().get(); + require!( - !self.is_sovereign_chain().get(), + !is_sovereign_chain, "Invalid method to call in current chain" ); @@ -36,13 +39,70 @@ pub trait TransferTokensModule: sc_panic!("Operation is not registered"); } + let are_tokens_registered = + self.verify_operation_tokens_issue_paid(operation.tokens.clone()); + + if !are_tokens_registered { + self.emit_transfer_failed_events( + &hash_of_hashes, + &OperationTuple { + op_hash: operation_hash.clone(), + operation: operation.clone(), + }, + ); + + return; + } + let minted_operation_tokens = self.mint_tokens(&operation.tokens); let operation_tuple = OperationTuple { - op_hash: operation_hash, - operation, + op_hash: operation_hash.clone(), + operation: operation.clone(), }; - self.distribute_payments(hash_of_hashes, operation_tuple, minted_operation_tokens); + self.distribute_payments( + hash_of_hashes.clone(), + operation_tuple, + minted_operation_tokens, + ); + } + + #[endpoint(registerNewTokenID)] + #[payable("*")] + fn register_new_token_id(&self, tokens: MultiValueEncoded) { + let call_payment = self.call_value().single_esdt().clone(); + let wegld_identifier = self.wegld_identifier().get(); + + require!( + call_payment.token_identifier == wegld_identifier, + "WEGLD is the only token accepted as register fee" + ); + + require!( + call_payment.amount == DEFAULT_ISSUE_COST * tokens.len() as u64, + "WEGLD fee amount is not met" + ); + + for token_id in tokens { + self.register_token(token_id); + } + } + + fn verify_operation_tokens_issue_paid( + &self, + tokens: ManagedVec>, + ) -> bool { + for token in tokens.iter() { + if !self.has_sov_prefix(&token.token_identifier, self.get_sovereign_prefix()) { + continue; + } + + if !self.paid_issued_tokens().contains(&token.token_identifier) { + return false; + } + } + + true } fn mint_tokens( @@ -52,7 +112,8 @@ pub trait TransferTokensModule: let mut output_payments = ManagedVec::new(); for operation_token in operation_tokens.iter() { - if !self.has_sov_token_prefix(&operation_token.token_identifier) { + let sov_prefix = self.get_sovereign_prefix(); + if !self.has_sov_prefix(&operation_token.token_identifier, sov_prefix) { output_payments.push(operation_token.clone()); continue; } @@ -113,7 +174,6 @@ pub trait TransferTokensModule: output_payments } - fn distribute_payments( &self, hash_of_hashes: ManagedBuffer, @@ -195,6 +255,7 @@ pub trait TransferTokensModule: ); } ManagedAsyncCallResult::Err(_) => { + self.burn_sovereign_tokens(&operation_tuple.operation); self.emit_transfer_failed_events(hash_of_hashes, operation_tuple); } } @@ -208,6 +269,21 @@ pub trait TransferTokensModule: .sync_call(); } + fn burn_sovereign_tokens(&self, operation: &Operation) { + for token in operation.tokens.iter() { + let sov_prefix = self.get_sovereign_prefix(); + if !self.has_sov_prefix(&token.token_identifier, sov_prefix) { + continue; + } + + self.send().esdt_local_burn( + &token.token_identifier, + token.token_nonce, + &token.token_data.amount, + ); + } + } + fn emit_transfer_failed_events( &self, hash_of_hashes: &ManagedBuffer, @@ -218,14 +294,6 @@ pub trait TransferTokensModule: operation_tuple.op_hash.clone(), ); - for operation_token in &operation_tuple.operation.tokens { - self.send().esdt_local_burn( - &operation_token.token_identifier, - operation_token.token_nonce, - &operation_token.token_data.amount, - ); - } - // deposit back mainchain tokens into user account let sc_address = self.blockchain().get_sc_address(); let tx_nonce = self.get_and_save_next_tx_id(); @@ -268,9 +336,27 @@ pub trait TransferTokensModule: } } + #[inline] + fn get_sovereign_prefix(&self) -> ManagedBuffer { + self.sovereign_tokens_prefix().get() + } + + #[inline] + fn register_token(&self, token_id: TokenIdentifier) { + self.paid_issued_tokens().insert(token_id); + } + + #[inline] + fn is_wegld(&self, token_id: &TokenIdentifier) -> bool { + token_id.eq(&self.wegld_identifier().get()) + } + #[storage_mapper("pending_hashes")] fn pending_hashes(&self, hash_of_hashes: &ManagedBuffer) -> UnorderedSetMapper; #[storage_mapper("header_verifier_address")] fn header_verifier_address(&self) -> SingleValueMapper; + + #[storage_mapper("mintedTokens")] + fn paid_issued_tokens(&self) -> UnorderedSetMapper>; } diff --git a/enshrine-esdt-safe/src/lib.rs b/enshrine-esdt-safe/src/lib.rs index 260e0159..4b0820e9 100644 --- a/enshrine-esdt-safe/src/lib.rs +++ b/enshrine-esdt-safe/src/lib.rs @@ -24,9 +24,36 @@ pub trait EnshrineEsdtSafe: + common::storage::CommonStorage { #[init] - fn init(&self, is_sovereign_chain: bool) { + fn init( + &self, + is_sovereign_chain: bool, + opt_wegld_identifier: Option, + opt_sov_token_prefix: Option, + ) { self.is_sovereign_chain().set(is_sovereign_chain); self.set_paused(true); + + if is_sovereign_chain { + return; + } + + match opt_wegld_identifier { + Some(identifier) => { + require!( + identifier.is_valid_esdt_identifier(), + "Sent Identifier is not valid" + ); + + self.wegld_identifier().set(identifier); + } + + None => sc_panic!("WEGLG identifier must be set in Mainchain"), + } + + match opt_sov_token_prefix { + Some(prefix) => self.sovereign_tokens_prefix().set(prefix), + None => sc_panic!("Sovereign Token Prefix must be set in Mainchain"), + } } #[only_owner] diff --git a/enshrine-esdt-safe/src/to_sovereign/create_tx.rs b/enshrine-esdt-safe/src/to_sovereign/create_tx.rs index aafd5cef..59d0f9ae 100644 --- a/enshrine-esdt-safe/src/to_sovereign/create_tx.rs +++ b/enshrine-esdt-safe/src/to_sovereign/create_tx.rs @@ -69,9 +69,7 @@ pub trait CreateTxModule: current_token_data.amount = payment.amount.clone(); - if self.is_sovereign_chain().get() - || self.has_sov_token_prefix(&payment.token_identifier) - { + if self.is_sovereign_chain().get() || self.has_prefix(&payment.token_identifier) { self.send().esdt_local_burn( &payment.token_identifier, payment.token_nonce, diff --git a/enshrine-esdt-safe/tests/enshrine_esdt_safe_blackbox_test.rs b/enshrine-esdt-safe/tests/enshrine_esdt_safe_blackbox_test.rs index e69e72ba..4ddf09b5 100644 --- a/enshrine-esdt-safe/tests/enshrine_esdt_safe_blackbox_test.rs +++ b/enshrine-esdt-safe/tests/enshrine_esdt_safe_blackbox_test.rs @@ -30,7 +30,7 @@ const RECEIVER_ADDRESS: TestAddress = TestAddress::new("receiver"); const NFT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("NFT-123456"); const FUNGIBLE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("CROWD-123456"); -const PREFIX_NFT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("SOV-NFT-123456"); +const PREFIX_NFT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("sov-NFT-123456"); fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); diff --git a/enshrine-esdt-safe/wasm-enshrine-esdt-safe-full/src/lib.rs b/enshrine-esdt-safe/wasm-enshrine-esdt-safe-full/src/lib.rs index 2c4ffaed..d7a87d6b 100644 --- a/enshrine-esdt-safe/wasm-enshrine-esdt-safe-full/src/lib.rs +++ b/enshrine-esdt-safe/wasm-enshrine-esdt-safe-full/src/lib.rs @@ -6,10 +6,10 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 27 +// Endpoints: 28 // Async Callback: 1 // Promise callbacks: 1 -// Total number of exported functions: 31 +// Total number of exported functions: 32 #![no_std] @@ -28,6 +28,7 @@ multiversx_sc_wasm_adapter::endpoints! { addSigners => add_signers removeSigners => remove_signers executeBridgeOps => execute_operations + registerNewTokenID => register_new_token_id setMaxTxBatchSize => set_max_tx_batch_size setMaxTxBatchBlockDuration => set_max_tx_batch_block_duration getCurrentTxBatch => get_current_tx_batch diff --git a/enshrine-esdt-safe/wasm/src/lib.rs b/enshrine-esdt-safe/wasm/src/lib.rs index 2c4ffaed..d7a87d6b 100644 --- a/enshrine-esdt-safe/wasm/src/lib.rs +++ b/enshrine-esdt-safe/wasm/src/lib.rs @@ -6,10 +6,10 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 27 +// Endpoints: 28 // Async Callback: 1 // Promise callbacks: 1 -// Total number of exported functions: 31 +// Total number of exported functions: 32 #![no_std] @@ -28,6 +28,7 @@ multiversx_sc_wasm_adapter::endpoints! { addSigners => add_signers removeSigners => remove_signers executeBridgeOps => execute_operations + registerNewTokenID => register_new_token_id setMaxTxBatchSize => set_max_tx_batch_size setMaxTxBatchBlockDuration => set_max_tx_batch_block_duration getCurrentTxBatch => get_current_tx_batch