diff --git a/contracts/proxy-deployer/Cargo.toml b/contracts/proxy-deployer/Cargo.toml index 43d2b10f..06413223 100644 --- a/contracts/proxy-deployer/Cargo.toml +++ b/contracts/proxy-deployer/Cargo.toml @@ -15,5 +15,8 @@ features = ["esdt-token-payment-legacy-decode"] [dev-dependencies.multiversx-sc-scenario] version = "=0.43.4" +[dependencies.multiversx-sc-modules] +version = "0.43.4" + [dev-dependencies.adder] path = "../adder" diff --git a/contracts/proxy-deployer/src/address_to_id_mapper.rs b/contracts/proxy-deployer/src/address_to_id_mapper.rs deleted file mode 100644 index f9888f76..00000000 --- a/contracts/proxy-deployer/src/address_to_id_mapper.rs +++ /dev/null @@ -1,192 +0,0 @@ -use core::marker::PhantomData; - -use multiversx_sc::{ - api::StorageMapperApi, - storage::{storage_get_from_address, storage_get_len_from_address, StorageKey}, - storage_clear, storage_get, storage_get_len, storage_set, -}; - -multiversx_sc::imports!(); - -static ID_SUFFIX: &[u8] = b"userId"; -static ADDRESS_SUFFIX: &[u8] = b"addr"; -static LAST_ID_SUFFIX: &[u8] = b"lastId"; - -static UNKNOW_ADDR_ERR_MSG: &[u8] = b"Unknown address"; - -pub type AddressId = u64; -pub const NULL_ID: AddressId = 0; - -pub struct AddressToIdMapper -where - SA: StorageMapperApi, -{ - _phantom_api: PhantomData, - base_key: StorageKey, -} - -impl StorageMapper for AddressToIdMapper -where - SA: StorageMapperApi, -{ - fn new(base_key: StorageKey) -> Self { - AddressToIdMapper { - _phantom_api: PhantomData, - base_key, - } - } -} - -impl AddressToIdMapper -where - SA: StorageMapperApi, -{ - pub fn contains_id(&self, id: AddressId) -> bool { - let key = self.id_to_address_key(id); - storage_get_len(key.as_ref()) != 0 - } - - pub fn get_id(&self, address: &ManagedAddress) -> AddressId { - let key = self.address_to_id_key(address); - storage_get(key.as_ref()) - } - - pub fn get_id_at_address( - &self, - sc_address: &ManagedAddress, - address_to_find: &ManagedAddress, - ) -> AddressId { - let key = self.address_to_id_key(address_to_find); - storage_get_from_address(sc_address.as_ref(), key.as_ref()) - } - - pub fn get_id_non_zero(&self, address: &ManagedAddress) -> AddressId { - let id = self.get_id(address); - if id == NULL_ID { - SA::error_api_impl().signal_error(UNKNOW_ADDR_ERR_MSG); - } - - id - } - - pub fn get_id_at_address_non_zero( - &self, - sc_address: &ManagedAddress, - address_to_find: &ManagedAddress, - ) -> AddressId { - let id = self.get_id_at_address(sc_address, address_to_find); - if id == NULL_ID { - SA::error_api_impl().signal_error(UNKNOW_ADDR_ERR_MSG); - } - - id - } - - pub fn insert_new(&self, address: &ManagedAddress) -> AddressId { - let existing_id = self.get_id(address); - if existing_id != NULL_ID { - SA::error_api_impl().signal_error(b"Address already registered"); - } - - self.insert_address(address) - } - - pub fn get_address(&self, id: AddressId) -> Option> { - let key = self.id_to_address_key(id); - if storage_get_len(key.as_ref()) == 0 { - return None; - } - - let addr = storage_get(key.as_ref()); - Some(addr) - } - - pub fn get_address_at_address( - &self, - sc_address: &ManagedAddress, - id: AddressId, - ) -> Option> { - let key = self.id_to_address_key(id); - if storage_get_len_from_address(sc_address.as_ref(), key.as_ref()) == 0 { - return None; - } - - let addr = storage_get_from_address(sc_address.as_ref(), key.as_ref()); - Some(addr) - } - - pub fn get_id_or_insert(&self, address: &ManagedAddress) -> AddressId { - let current_id = storage_get(self.address_to_id_key(address).as_ref()); - if current_id != 0 { - return current_id; - } - - self.insert_address(address) - } - - pub fn remove_by_id(&self, id: AddressId) -> Option> { - let address = self.get_address(id)?; - self.remove_entry(id, &address); - - Some(address) - } - - pub fn remove_by_address(&self, address: &ManagedAddress) -> AddressId { - let current_id = self.get_id(address); - if current_id != NULL_ID { - self.remove_entry(current_id, address); - } - - current_id - } - - fn insert_address(&self, address: &ManagedAddress) -> AddressId { - let new_id = self.get_last_id() + 1; - storage_set(self.address_to_id_key(address).as_ref(), &new_id); - storage_set(self.id_to_address_key(new_id).as_ref(), address); - - self.set_last_id(new_id); - - new_id - } - - fn remove_entry(&self, id: AddressId, address: &ManagedAddress) { - storage_clear(self.address_to_id_key(address).as_ref()); - storage_clear(self.id_to_address_key(id).as_ref()); - } - - fn id_to_address_key(&self, id: AddressId) -> StorageKey { - let mut item_key = self.base_key.clone(); - item_key.append_bytes(ID_SUFFIX); - item_key.append_item(&id); - - item_key - } - - fn address_to_id_key(&self, address: &ManagedAddress) -> StorageKey { - let mut item_key = self.base_key.clone(); - item_key.append_bytes(ADDRESS_SUFFIX); - item_key.append_item(address); - - item_key - } - - fn last_id_key(&self) -> StorageKey { - let mut item_key = self.base_key.clone(); - item_key.append_bytes(LAST_ID_SUFFIX); - - item_key - } - - pub fn get_last_id(&self) -> AddressId { - storage_get(self.last_id_key().as_ref()) - } - - fn set_last_id(&self, last_id: AddressId) { - if last_id == 0 { - SA::error_api_impl().signal_error(b"ID Overflow"); - } - - storage_set(self.last_id_key().as_ref(), &last_id); - } -} diff --git a/contracts/proxy-deployer/src/config.rs b/contracts/proxy-deployer/src/config.rs index 4e8492da..7e798f4d 100644 --- a/contracts/proxy-deployer/src/config.rs +++ b/contracts/proxy-deployer/src/config.rs @@ -1,33 +1,68 @@ multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); -use crate::address_to_id_mapper::{AddressId, AddressToIdMapper}; +#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] +pub struct OngoingUpgradeOperation { + pub template_address: ManagedAddress, + pub arguments: ManagedArgBuffer, + pub contracts_remaining: ManagedVec>, +} + +impl OngoingUpgradeOperation { + #[inline] + pub fn new( + template_address: ManagedAddress, + arguments: ManagedArgBuffer, + contracts_remaining: ManagedVec>, + ) -> Self { + OngoingUpgradeOperation { + template_address, + arguments, + contracts_remaining, + } + } +} #[multiversx_sc::module] pub trait ConfigModule { #[only_owner] - #[endpoint(addContractTemplate)] - fn add_contract_template(&self, template_address: ManagedAddress) -> AddressId { + #[endpoint(addDeployerToBlacklist)] + fn add_deployer_to_blacklist(&self, blacklisted_address: ManagedAddress) { require!( - self.blockchain().is_smart_contract(&template_address), - "Invalid template address" + self.deployers_list().contains(&blacklisted_address), + "The address is not a deployer" ); - - self.address_ids().insert_new(&template_address) + require!( + !self + .blacklisted_deployers_list() + .contains(&blacklisted_address), + "Address already blacklisted" + ); + self.blacklisted_deployers_list() + .insert(blacklisted_address); } #[only_owner] - #[endpoint(removeContractTemplate)] - fn remove_contract_template(&self, address_id: AddressId) { + #[endpoint(removeDeployerFromBlacklist)] + fn remove_deployer_from_blacklist(&self, address: ManagedAddress) { require!( - self.address_ids().contains_id(address_id), - "Invalid address id" + self.blacklisted_deployers_list().contains(&address), + "Address is not blacklisted" ); - self.address_ids().remove_by_id(address_id); + self.blacklisted_deployers_list().swap_remove(&address); } - #[storage_mapper("addressIds")] - fn address_ids(&self) -> AddressToIdMapper; + #[view(getAllDeployedContractsByTemplate)] + #[storage_mapper("deployedContractsByTemplate")] + fn deployed_contracts_list_by_template( + &self, + template_address: &ManagedAddress, + ) -> SingleValueMapper>; + + #[view(getOngoingUpgradeOperations)] + #[storage_mapper("ongoingUpgradeOperation")] + fn ongoing_upgrade_operation(&self) -> SingleValueMapper>; #[view(getAllDeployers)] #[storage_mapper("deployersList")] @@ -39,4 +74,8 @@ pub trait ConfigModule { &self, deployer_address: &ManagedAddress, ) -> UnorderedSetMapper; + + #[view(getAllBlacklistedDeployers)] + #[storage_mapper("blacklistedDeployersList")] + fn blacklisted_deployers_list(&self) -> UnorderedSetMapper; } diff --git a/contracts/proxy-deployer/src/contract_interactions.rs b/contracts/proxy-deployer/src/contract_interactions.rs index dd5a54f3..78c1cccf 100644 --- a/contracts/proxy-deployer/src/contract_interactions.rs +++ b/contracts/proxy-deployer/src/contract_interactions.rs @@ -1,40 +1,41 @@ multiversx_sc::imports!(); -use crate::address_to_id_mapper::AddressId; -use crate::config; +use multiversx_sc_modules::pause; + +use crate::config::{self, OngoingUpgradeOperation}; + +const DEFAULT_GAS_FOR_SAVE: u64 = 1_000_000; #[multiversx_sc::module] -pub trait ContractInteractionsModule: config::ConfigModule { +pub trait ContractInteractionsModule: config::ConfigModule + pause::PauseModule { #[endpoint(contractDeploy)] fn contract_deploy( &self, - template_address_id: AddressId, + template_address: ManagedAddress, args: MultiValueEncoded, ) -> ManagedAddress { - let caller = self.blockchain().get_caller(); - - let mut arguments = ManagedArgBuffer::new(); - for arg in args { - arguments.push_arg(arg); - } - - let opt_template_address = self.address_ids().get_address(template_address_id); - let template_address = match opt_template_address { - Some(template_address) => template_address, - None => sc_panic!("Template not found"), - }; + self.require_not_paused(); + require!( + self.blockchain().is_smart_contract(&template_address), + "Template address is not a SC" + ); let (new_contract_address, _) = self.send_raw().deploy_from_source_contract( self.blockchain().get_gas_left(), &BigUint::zero(), &template_address, CodeMetadata::DEFAULT, - &arguments, + &args.to_arg_buffer(), ); + let caller = self.blockchain().get_caller(); self.deployer_contract_addresses(&caller) .insert(new_contract_address.clone()); self.deployers_list().insert(caller); + self.deployed_contracts_list_by_template(&template_address) + .update(|deployed_contracts| { + deployed_contracts.push(new_contract_address.clone()); + }); new_contract_address } @@ -43,26 +44,17 @@ pub trait ContractInteractionsModule: config::ConfigModule { fn contract_upgrade( &self, contract_address: ManagedAddress, - template_address_id: AddressId, + template_address: ManagedAddress, args: MultiValueEncoded, ) { - let caller = self.blockchain().get_caller(); + self.require_not_paused(); + self.check_caller_can_call_endpoint(&contract_address); require!( - self.deployer_contract_addresses(&caller) - .contains(&contract_address), - "Caller is not the deployer of the contract" + self.blockchain().is_smart_contract(&template_address), + "Template address is not a SC" ); - let mut arguments = ManagedArgBuffer::new(); - for arg in args { - arguments.push_arg(arg); - } - - let opt_template_address = self.address_ids().get_address(template_address_id); - let template_address = match opt_template_address { - Some(template_address) => template_address, - None => sc_panic!("Template not found"), - }; + let arguments = args.to_arg_buffer(); self.send_raw().upgrade_from_source_contract( &contract_address, @@ -74,29 +66,115 @@ pub trait ContractInteractionsModule: config::ConfigModule { ); } - #[endpoint(callContractEndpoint)] - fn call_contract_endpoint( + #[endpoint(contractCallByAddress)] + fn contract_call_by_address( &self, contract_address: ManagedAddress, function_name: ManagedBuffer, args: MultiValueEncoded, ) { - let caller = self.blockchain().get_caller(); + self.require_not_paused(); + self.check_caller_can_call_endpoint(&contract_address); + + self.send() + .contract_call::<()>(contract_address, function_name.clone()) + .with_gas_limit(self.blockchain().get_gas_left()) + .with_raw_arguments(args.to_arg_buffer()) + .execute_on_dest_context() + } + + #[only_owner] + #[endpoint(upgradeContractsByTemplate)] + fn upgrade_contracts_by_template( + &self, + gas_per_action: u64, + opt_template_address: OptionalValue, + opt_args: OptionalValue>, + ) -> bool { + let mut ongoing_upgrade_operation = + self.get_ongoing_operation(opt_template_address, opt_args); + while self.blockchain().get_gas_left() >= gas_per_action + DEFAULT_GAS_FOR_SAVE + && ongoing_upgrade_operation.contracts_remaining.len() > 0 + { + let contract_address = ongoing_upgrade_operation + .contracts_remaining + .get(0) + .clone_value(); + self.send_raw().upgrade_from_source_contract( + &contract_address, + gas_per_action, + &BigUint::zero(), + &ongoing_upgrade_operation.template_address, + CodeMetadata::DEFAULT, + &ongoing_upgrade_operation.arguments, + ); + ongoing_upgrade_operation.contracts_remaining.remove(0); + } + if ongoing_upgrade_operation.contracts_remaining.len() > 0 { + self.ongoing_upgrade_operation() + .set(ongoing_upgrade_operation); + return false; + } + + self.ongoing_upgrade_operation().clear(); + true + } + + fn get_ongoing_operation( + &self, + opt_template_address: OptionalValue, + opt_args: OptionalValue>, + ) -> OngoingUpgradeOperation { + if opt_template_address.is_none() { + require!( + !self.ongoing_upgrade_operation().is_empty(), + "There is no operation ongoing" + ); + return self.ongoing_upgrade_operation().get(); + } + require!( - self.deployer_contract_addresses(&caller) - .contains(&contract_address), - "Caller is not the deployer of the contract" + self.ongoing_upgrade_operation().is_empty(), + "Another operation is currently ongoing" + ); + let template_address = opt_template_address + .into_option() + .unwrap_or_else(|| sc_panic!("Error decoding the template address")); + require!( + self.blockchain().is_smart_contract(&template_address), + "Template address is not a SC" + ); + let contracts_by_template = self + .deployed_contracts_list_by_template(&template_address) + .get(); + require!( + contracts_by_template.len() > 0, + "No contracts deployed with this template" ); + let args = opt_args + .into_option() + .unwrap_or_else(|| sc_panic!("Unable to decode the arguments")); - let gas_left = self.blockchain().get_gas_left(); - let mut contract_call = self - .send() - .contract_call::<()>(contract_address, function_name) - .with_gas_limit(gas_left); + OngoingUpgradeOperation::new( + template_address, + args.to_arg_buffer(), + contracts_by_template, + ) + } - for arg in args { - contract_call.push_raw_argument(arg); - } - let _: IgnoreValue = contract_call.execute_on_dest_context(); + fn check_caller_can_call_endpoint(&self, contract_address: &ManagedAddress) { + let caller = self.blockchain().get_caller(); + let owner = self.blockchain().get_owner_address(); + + require!( + !self.blacklisted_deployers_list().contains(&caller), + "User is blacklisted" + ); + require!( + self.deployer_contract_addresses(&caller) + .contains(contract_address) + || caller == owner, + "Only owner and deployer can call this function" + ); } } diff --git a/contracts/proxy-deployer/src/lib.rs b/contracts/proxy-deployer/src/lib.rs index e3bd0875..2811a156 100644 --- a/contracts/proxy-deployer/src/lib.rs +++ b/contracts/proxy-deployer/src/lib.rs @@ -2,13 +2,14 @@ multiversx_sc::imports!(); -pub mod address_to_id_mapper; +use multiversx_sc_modules::pause; + pub mod config; pub mod contract_interactions; #[multiversx_sc::contract] pub trait ProxyDeployer: - contract_interactions::ContractInteractionsModule + config::ConfigModule + contract_interactions::ContractInteractionsModule + config::ConfigModule + pause::PauseModule { #[init] fn init(&self) {} diff --git a/contracts/proxy-deployer/tests/proxy_deployer_blackbox_test.rs b/contracts/proxy-deployer/tests/proxy_deployer_blackbox_test.rs index 38b16895..411ad762 100644 --- a/contracts/proxy-deployer/tests/proxy_deployer_blackbox_test.rs +++ b/contracts/proxy-deployer/tests/proxy_deployer_blackbox_test.rs @@ -6,9 +6,7 @@ use multiversx_sc::{ use multiversx_sc_scenario::{api::StaticApi, num_bigint::BigUint, scenario_model::*, *}; use adder::ProxyTrait as _; -use proxy_deployer::{ - config::ProxyTrait as _, contract_interactions::ProxyTrait as _, ProxyTrait as _, -}; +use proxy_deployer::{contract_interactions::ProxyTrait as _, ProxyTrait as _}; const PROXY_DEPLOYER_ADDRESS_EXPR: &str = "sc:proxy_deployer"; const TEMPLATE_CONTRACT_ADDRESS_EXPR: &str = "sc:template_contract"; @@ -35,7 +33,7 @@ struct ProxyDeployerTestState { world: ScenarioWorld, proxy_deployer_contract: ProxyDeployerContract, template_contract: TemplateContract, - template_contracts_ids: Vec, + template_contract_address: Address, deployed_contracts: Vec
, } @@ -47,12 +45,13 @@ impl ProxyDeployerTestState { ); let proxy_deployer_contract = ProxyDeployerContract::new(PROXY_DEPLOYER_ADDRESS_EXPR); let template_contract = TemplateContract::new(TEMPLATE_CONTRACT_ADDRESS_EXPR); + let template_contract_address = template_contract.to_address(); Self { world, proxy_deployer_contract, template_contract, - template_contracts_ids: vec![], + template_contract_address, deployed_contracts: vec![], } } @@ -83,16 +82,6 @@ impl ProxyDeployerTestState { .from(OWNER_ADDRESS_EXPR) .code(template_contract_code) .call(self.template_contract.init(BigUint::from(0u64))), - ) - .sc_call_use_result( - ScCallStep::new().from(OWNER_ADDRESS_EXPR).call( - self.proxy_deployer_contract.add_contract_template( - ManagedAddress::from_address(&self.template_contract.to_address()), - ), - ), - |r: TypedResponse| { - self.template_contracts_ids.push(r.result.unwrap()); - }, ); self @@ -100,7 +89,7 @@ impl ProxyDeployerTestState { fn deploy_contract( &mut self, - template_address_id: u64, + template_address: &Address, args: MultiValueEncoded>, ) -> &mut Self { self.world @@ -115,7 +104,7 @@ impl ProxyDeployerTestState { .to(PROXY_DEPLOYER_ADDRESS_EXPR) .call( self.proxy_deployer_contract - .contract_deploy(template_address_id, args), + .contract_deploy(ManagedAddress::from_address(template_address), args), ), |r: TypedResponse>| { self.deployed_contracts.push(r.result.unwrap().to_address()); @@ -128,7 +117,7 @@ impl ProxyDeployerTestState { fn upgrade_contract( &mut self, contract_address: &Address, - template_address_id: u64, + template_address: &Address, args: MultiValueEncoded>, ) -> &mut Self { self.world.sc_call( @@ -137,7 +126,7 @@ impl ProxyDeployerTestState { .to(PROXY_DEPLOYER_ADDRESS_EXPR) .call(self.proxy_deployer_contract.contract_upgrade( ManagedAddress::from_address(contract_address), - template_address_id, + ManagedAddress::from_address(template_address), args, )), ); @@ -156,7 +145,7 @@ impl ProxyDeployerTestState { ScCallStep::new() .from(OWNER_ADDRESS_EXPR) .to(PROXY_DEPLOYER_ADDRESS_EXPR) - .call(self.proxy_deployer_contract.call_contract_endpoint( + .call(self.proxy_deployer_contract.contract_call_by_address( ManagedAddress::from_address(contract_address), function, args, @@ -182,10 +171,12 @@ fn proxy_deployer_blackbox_test() { let mut state = ProxyDeployerTestState::new(); state.deploy_proxy_deployer_contract(); + let template_address = state.template_contract_address.clone(); + // Test contract deploy let mut deploy_args = MultiValueEncoded::new(); deploy_args.push(ManagedBuffer::from(top_encode_to_vec_u8_or_panic(&1u64))); - state.deploy_contract(state.template_contracts_ids[0], deploy_args); + state.deploy_contract(&template_address, deploy_args); state.check_contract_storage(1u64); let contract_address = state.deployed_contracts[0].to_owned(); @@ -196,13 +187,8 @@ fn proxy_deployer_blackbox_test() { state.check_contract_storage(10u64); // Test contract upgrade - state.world.dump_state_step(); let mut upgrade_args = MultiValueEncoded::new(); upgrade_args.push(ManagedBuffer::from(top_encode_to_vec_u8_or_panic(&5u64))); - state.upgrade_contract( - &contract_address, - state.template_contracts_ids[0], - upgrade_args, - ); + state.upgrade_contract(&contract_address, &template_address, upgrade_args); state.check_contract_storage(5u64); } diff --git a/contracts/proxy-deployer/wasm/Cargo.lock b/contracts/proxy-deployer/wasm/Cargo.lock index 8066d1b0..d376210c 100644 --- a/contracts/proxy-deployer/wasm/Cargo.lock +++ b/contracts/proxy-deployer/wasm/Cargo.lock @@ -113,6 +113,15 @@ dependencies = [ "syn", ] +[[package]] +name = "multiversx-sc-modules" +version = "0.43.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75dc2548fe5072cad37b5c816d2344d7cd12e8cafb1a0ff8bbf2bc1829054710" +dependencies = [ + "multiversx-sc", +] + [[package]] name = "multiversx-sc-wasm-adapter" version = "0.43.4" @@ -160,6 +169,7 @@ name = "proxy-deployer" version = "0.0.0" dependencies = [ "multiversx-sc", + "multiversx-sc-modules", ] [[package]] diff --git a/contracts/proxy-deployer/wasm/src/lib.rs b/contracts/proxy-deployer/wasm/src/lib.rs index a9ff5a45..0ea592e3 100644 --- a/contracts/proxy-deployer/wasm/src/lib.rs +++ b/contracts/proxy-deployer/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 8 +// Endpoints: 15 // Async Callback (empty): 1 -// Total number of exported functions: 10 +// Total number of exported functions: 17 #![no_std] @@ -25,11 +25,18 @@ multiversx_sc_wasm_adapter::endpoints! { upgrade => upgrade contractDeploy => contract_deploy contractUpgrade => contract_upgrade - callContractEndpoint => call_contract_endpoint - addContractTemplate => add_contract_template - removeContractTemplate => remove_contract_template + contractCallByAddress => contract_call_by_address + upgradeContractsByTemplate => upgrade_contracts_by_template + addDeployerToBlacklist => add_deployer_to_blacklist + removeDeployerFromBlacklist => remove_deployer_from_blacklist + getAllDeployedContractsByTemplate => deployed_contracts_list_by_template + getOngoingUpgradeOperations => ongoing_upgrade_operation getAllDeployers => deployers_list getDeployerContractAddresses => deployer_contract_addresses + getAllBlacklistedDeployers => blacklisted_deployers_list + pause => pause_endpoint + unpause => unpause_endpoint + isPaused => paused_status ) }