From 24bdb7bbfa378a807d8b2d8939bb00705b884075 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Fri, 7 Jul 2023 11:26:02 +0300 Subject: [PATCH] remove modules --- Cargo.toml | 2 - modules/Cargo.toml | 18 - modules/README.md | 7 - .../bonding_curve/curves/curve_function.rs | 12 - .../bonding_curve/curves/linear_function.rs | 25 - modules/src/bonding_curve/curves/mod.rs | 2 - modules/src/bonding_curve/mod.rs | 15 - modules/src/bonding_curve/utils/events.rs | 11 - modules/src/bonding_curve/utils/mod.rs | 5 - .../bonding_curve/utils/owner_endpoints.rs | 235 -------- modules/src/bonding_curve/utils/storage.rs | 22 - modules/src/bonding_curve/utils/structs.rs | 62 --- .../src/bonding_curve/utils/user_endpoints.rs | 303 ---------- modules/src/claim_developer_rewards.rs | 12 - modules/src/default_issue_callbacks.rs | 54 -- modules/src/dns.rs | 35 -- modules/src/esdt.rs | 117 ---- modules/src/features.rs | 58 -- modules/src/governance/README.md | 64 --- .../src/governance/governance_configurable.rs | 182 ------ modules/src/governance/governance_events.rs | 73 --- modules/src/governance/governance_proposal.rs | 112 ---- modules/src/governance/mod.rs | 524 ------------------ modules/src/lib.rs | 17 - modules/src/ongoing_operation.rs | 116 ---- modules/src/only_admin.rs | 34 -- modules/src/pause.rs | 54 -- modules/src/staking.rs | 174 ------ .../custom_merged_token_attributes.rs | 65 --- .../src/token_merge/merged_token_instances.rs | 115 ---- modules/src/token_merge/merged_token_setup.rs | 149 ----- modules/src/token_merge/mod.rs | 173 ------ modules/src/token_merge/readme.md | 30 - modules/src/transfer_role_proxy.rs | 129 ----- modules/src/users.rs | 63 --- 35 files changed, 3069 deletions(-) delete mode 100644 modules/Cargo.toml delete mode 100644 modules/README.md delete mode 100644 modules/src/bonding_curve/curves/curve_function.rs delete mode 100644 modules/src/bonding_curve/curves/linear_function.rs delete mode 100644 modules/src/bonding_curve/curves/mod.rs delete mode 100644 modules/src/bonding_curve/mod.rs delete mode 100644 modules/src/bonding_curve/utils/events.rs delete mode 100644 modules/src/bonding_curve/utils/mod.rs delete mode 100644 modules/src/bonding_curve/utils/owner_endpoints.rs delete mode 100644 modules/src/bonding_curve/utils/storage.rs delete mode 100644 modules/src/bonding_curve/utils/structs.rs delete mode 100644 modules/src/bonding_curve/utils/user_endpoints.rs delete mode 100644 modules/src/claim_developer_rewards.rs delete mode 100644 modules/src/default_issue_callbacks.rs delete mode 100644 modules/src/dns.rs delete mode 100644 modules/src/esdt.rs delete mode 100644 modules/src/features.rs delete mode 100644 modules/src/governance/README.md delete mode 100644 modules/src/governance/governance_configurable.rs delete mode 100644 modules/src/governance/governance_events.rs delete mode 100644 modules/src/governance/governance_proposal.rs delete mode 100644 modules/src/governance/mod.rs delete mode 100644 modules/src/lib.rs delete mode 100644 modules/src/ongoing_operation.rs delete mode 100644 modules/src/only_admin.rs delete mode 100644 modules/src/pause.rs delete mode 100644 modules/src/staking.rs delete mode 100644 modules/src/token_merge/custom_merged_token_attributes.rs delete mode 100644 modules/src/token_merge/merged_token_instances.rs delete mode 100644 modules/src/token_merge/merged_token_setup.rs delete mode 100644 modules/src/token_merge/mod.rs delete mode 100644 modules/src/token_merge/readme.md delete mode 100644 modules/src/transfer_role_proxy.rs delete mode 100644 modules/src/users.rs diff --git a/Cargo.toml b/Cargo.toml index 1fc21cb1..7d807b5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,6 @@ resolver = "2" members = [ - "modules", - "contracts/price-aggregator", "contracts/price-aggregator/meta", "contracts/wegld-swap", diff --git a/modules/Cargo.toml b/modules/Cargo.toml deleted file mode 100644 index 871c58f2..00000000 --- a/modules/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "multiversx-sc-modules" -version = "0.41.1" -edition = "2021" -authors = ["MultiversX "] -license = "GPL-3.0-only" -readme = "README.md" -repository = "https://github.com/multiversx/mx-sdk-rs" -homepage = "https://multiversx.com/" -documentation = "https://docs.multiversx.com/" -description = "Elrond WebAssembly standard smart contract modules" -keywords = ["multiversx", "wasm", "webassembly", "blockchain", "contract"] -categories = ["no-std", "wasm", "cryptography::cryptocurrencies"] - -[features] -alloc = ["multiversx-sc/alloc"] -[dependencies.multiversx-sc] -version = "0.41.3" diff --git a/modules/README.md b/modules/README.md deleted file mode 100644 index ff27629b..00000000 --- a/modules/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Standard modules for Elrond smart contracts - -[![Crates.io](https://img.shields.io/crates/v/multiversx-sc-modules)](https://crates.io/crates/multiversx-sc-modules) - -This crate contains several smart contract modules developed by the Elrond team, which can be used to add standard functionality to any smart contract. - -It is enough to add the module traits as supertraits of the contract traits to receive all endpoints and all functionality from the modules in contracts. diff --git a/modules/src/bonding_curve/curves/curve_function.rs b/modules/src/bonding_curve/curves/curve_function.rs deleted file mode 100644 index 860f32ec..00000000 --- a/modules/src/bonding_curve/curves/curve_function.rs +++ /dev/null @@ -1,12 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use crate::bonding_curve::utils::structs::CurveArguments; -pub trait CurveFunction { - fn calculate_price( - &self, - token_start: &BigUint, - amount: &BigUint, - arguments: &CurveArguments, - ) -> BigUint; -} diff --git a/modules/src/bonding_curve/curves/linear_function.rs b/modules/src/bonding_curve/curves/linear_function.rs deleted file mode 100644 index 698d1757..00000000 --- a/modules/src/bonding_curve/curves/linear_function.rs +++ /dev/null @@ -1,25 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use crate::bonding_curve::{curves::curve_function::CurveFunction, utils::structs::CurveArguments}; - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, PartialEq, Eq, Clone)] -pub struct LinearFunction { - pub initial_price: BigUint, - pub linear_coefficient: BigUint, -} - -impl CurveFunction for LinearFunction { - fn calculate_price( - &self, - token_start: &BigUint, - amount: &BigUint, - _arguments: &CurveArguments, - ) -> BigUint { - &self.linear_coefficient * &sum_interval(amount, token_start) + &self.initial_price * amount - } -} - -fn sum_interval(n: &BigUint, x: &BigUint) -> BigUint { - x * n + &(n - 1u32) * n / 2u32 -} diff --git a/modules/src/bonding_curve/curves/mod.rs b/modules/src/bonding_curve/curves/mod.rs deleted file mode 100644 index 31583135..00000000 --- a/modules/src/bonding_curve/curves/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod curve_function; -pub mod linear_function; diff --git a/modules/src/bonding_curve/mod.rs b/modules/src/bonding_curve/mod.rs deleted file mode 100644 index ac8804bf..00000000 --- a/modules/src/bonding_curve/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub mod curves; -pub mod utils; -use utils::{events, owner_endpoints, storage, user_endpoints}; - -#[multiversx_sc::module] -pub trait BondingCurveModule: - storage::StorageModule - + events::EventsModule - + user_endpoints::UserEndpointsModule - + owner_endpoints::OwnerEndpointsModule -{ -} diff --git a/modules/src/bonding_curve/utils/events.rs b/modules/src/bonding_curve/utils/events.rs deleted file mode 100644 index 0f874a4c..00000000 --- a/modules/src/bonding_curve/utils/events.rs +++ /dev/null @@ -1,11 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[multiversx_sc::module] -pub trait EventsModule { - #[event("buy-token")] - fn buy_token_event(&self, #[indexed] user: &ManagedAddress, amount: &BigUint); - - #[event("sell-token")] - fn sell_token_event(&self, #[indexed] user: &ManagedAddress, amount: &BigUint); -} diff --git a/modules/src/bonding_curve/utils/mod.rs b/modules/src/bonding_curve/utils/mod.rs deleted file mode 100644 index a6ce88f1..00000000 --- a/modules/src/bonding_curve/utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod events; -pub mod owner_endpoints; -pub mod storage; -pub mod structs; -pub mod user_endpoints; diff --git a/modules/src/bonding_curve/utils/owner_endpoints.rs b/modules/src/bonding_curve/utils/owner_endpoints.rs deleted file mode 100644 index 51f5eaaa..00000000 --- a/modules/src/bonding_curve/utils/owner_endpoints.rs +++ /dev/null @@ -1,235 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use multiversx_sc::contract_base::ManagedSerializer; - -use crate::bonding_curve::{ - curves::curve_function::CurveFunction, - utils::{ - events, storage, - structs::{BondingCurve, TokenOwnershipData}, - }, -}; - -use super::structs::CurveArguments; - -#[multiversx_sc::module] -pub trait OwnerEndpointsModule: storage::StorageModule + events::EventsModule { - #[endpoint(setLocalRoles)] - fn set_local_roles( - &self, - address: ManagedAddress, - token_identifier: TokenIdentifier, - roles: MultiValueEncoded, - ) { - self.send() - .esdt_system_sc_proxy() - .set_special_roles(&address, &token_identifier, roles.into_iter()) - .async_call() - .call_and_exit() - } - - #[endpoint(unsetLocalRoles)] - fn unset_local_roles( - &self, - address: ManagedAddress, - token_identifier: TokenIdentifier, - roles: MultiValueEncoded, - ) { - self.send() - .esdt_system_sc_proxy() - .unset_special_roles(&address, &token_identifier, roles.into_iter()) - .async_call() - .call_and_exit() - } - - fn set_bonding_curve( - &self, - identifier: TokenIdentifier, - function: T, - sell_availability: bool, - ) where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - require!( - !self.token_details(&identifier).is_empty(), - "Token is not issued yet!" - ); - - let caller = self.blockchain().get_caller(); - - let details = self.token_details(&identifier).get(); - require!( - details.owner == caller, - "The price function can only be set by the seller." - ); - self.bonding_curve(&identifier).update(|buffer| { - let serializer = ManagedSerializer::new(); - - let mut bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(buffer); - bonding_curve.curve = function; - bonding_curve.sell_availability = sell_availability; - *buffer = serializer.top_encode_to_managed_buffer(&bonding_curve); - }); - } - - fn deposit(&self, payment_token: OptionalValue) - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let (identifier, nonce, amount) = self.call_value().single_esdt().into_tuple(); - let caller = self.blockchain().get_caller(); - let mut set_payment = EgldOrEsdtTokenIdentifier::egld(); - - if self.bonding_curve(&identifier).is_empty() { - match payment_token { - OptionalValue::Some(token) => set_payment = EgldOrEsdtTokenIdentifier::esdt(token), - OptionalValue::None => { - sc_panic!("Expected provided accepted_payment for the token"); - }, - }; - } - if self.token_details(&identifier).is_empty() { - let nonces = ManagedVec::from_single_item(nonce); - self.token_details(&identifier).set(&TokenOwnershipData { - token_nonces: nonces, - owner: caller.clone(), - }); - } else { - let mut details = self.token_details(&identifier).get(); - require!( - details.owner == caller, - "The token was already deposited by another address" - ); - if !details.token_nonces.contains(&nonce) { - details.token_nonces.push(nonce); - self.token_details(&identifier).set(&details); - } - } - - self.set_curve_storage::(&identifier, amount.clone(), set_payment); - self.owned_tokens(&caller).insert(identifier.clone()); - self.nonce_amount(&identifier, nonce) - .update(|current_amount| *current_amount += amount); - } - - fn claim(&self) - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let caller = self.blockchain().get_caller(); - require!( - !self.owned_tokens(&caller).is_empty(), - "You have nothing to claim" - ); - - let mut tokens_to_claim = ManagedVec::>::new(); - let mut egld_to_claim = BigUint::zero(); - let serializer = ManagedSerializer::new(); - for token in self.owned_tokens(&caller).iter() { - let nonces = self.token_details(&token).get().token_nonces; - for nonce in &nonces { - tokens_to_claim.push(EsdtTokenPayment::new( - token.clone(), - nonce, - self.nonce_amount(&token, nonce).get(), - )); - - self.nonce_amount(&token, nonce).clear(); - } - - let bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(&self.bonding_curve(&token).get()); - - if let Some(esdt_token_identifier) = - bonding_curve.payment.token_identifier.into_esdt_option() - { - tokens_to_claim.push(EsdtTokenPayment::new( - esdt_token_identifier, - bonding_curve.payment.token_nonce, - bonding_curve.payment.amount, - )); - } else { - egld_to_claim += bonding_curve.payment.amount; - } - - self.token_details(&token).clear(); - self.bonding_curve(&token).clear(); - } - self.owned_tokens(&caller).clear(); - self.send().direct_multi(&caller, &tokens_to_claim); - if egld_to_claim > BigUint::zero() { - self.send().direct_egld(&caller, &egld_to_claim); - } - } - - fn set_curve_storage( - &self, - identifier: &TokenIdentifier, - amount: BigUint, - payment_token_identifier: EgldOrEsdtTokenIdentifier, - ) where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let mut curve: T = T::default(); - let mut arguments; - let payment; - let sell_availability: bool; - let serializer = ManagedSerializer::new(); - - if self.bonding_curve(identifier).is_empty() { - arguments = CurveArguments { - available_supply: amount.clone(), - balance: amount, - }; - payment = EgldOrEsdtTokenPayment::new(payment_token_identifier, 0, BigUint::zero()); - sell_availability = false; - } else { - let bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(&self.bonding_curve(identifier).get()); - - payment = bonding_curve.payment; - curve = bonding_curve.curve; - arguments = bonding_curve.arguments; - arguments.balance += &amount; - arguments.available_supply += amount; - sell_availability = bonding_curve.sell_availability; - } - let encoded_curve = serializer.top_encode_to_managed_buffer(&BondingCurve { - curve, - arguments, - sell_availability, - payment, - }); - self.bonding_curve(identifier).set(encoded_curve); - } -} diff --git a/modules/src/bonding_curve/utils/storage.rs b/modules/src/bonding_curve/utils/storage.rs deleted file mode 100644 index d5716334..00000000 --- a/modules/src/bonding_curve/utils/storage.rs +++ /dev/null @@ -1,22 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use super::structs::TokenOwnershipData; - -#[multiversx_sc::module] -pub trait StorageModule { - #[storage_mapper("token_details")] - fn token_details( - &self, - token: &TokenIdentifier, - ) -> SingleValueMapper>; - - #[storage_mapper("bonding_curve")] - fn bonding_curve(&self, token: &TokenIdentifier) -> SingleValueMapper; - - #[storage_mapper("owned_tokens")] - fn owned_tokens(&self, owner: &ManagedAddress) -> SetMapper; - - #[storage_mapper("nonce_amount")] - fn nonce_amount(&self, identifier: &TokenIdentifier, nonce: u64) -> SingleValueMapper; -} diff --git a/modules/src/bonding_curve/utils/structs.rs b/modules/src/bonding_curve/utils/structs.rs deleted file mode 100644 index 59836bef..00000000 --- a/modules/src/bonding_curve/utils/structs.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::bonding_curve::curves::curve_function::CurveFunction; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, PartialEq, Eq, Clone)] -pub struct CurveArguments { - pub available_supply: BigUint, - pub balance: BigUint, -} - -impl CurveArguments { - pub fn first_token_available(&self) -> BigUint { - &self.available_supply - &self.balance - } -} - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, PartialEq, Eq, Clone)] -pub struct BondingCurve< - M: ManagedTypeApi, - T: CurveFunction + TopEncode + TopDecode + NestedEncode + NestedDecode + TypeAbi, -> { - pub curve: T, - pub arguments: CurveArguments, - pub sell_availability: bool, - pub payment: EgldOrEsdtTokenPayment, -} - -impl< - M: ManagedTypeApi, - T: CurveFunction + TopEncode + TopDecode + NestedEncode + NestedDecode + TypeAbi, - > BondingCurve -{ - pub fn payment_token(&self) -> EgldOrEsdtTokenIdentifier { - self.payment.token_identifier.clone() - } - pub fn payment_is_egld(&self) -> bool { - self.payment.token_identifier.is_egld() - } -} - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, PartialEq, Eq, Clone)] -pub struct TokenOwnershipData { - pub token_nonces: ManagedVec, - pub owner: ManagedAddress, -} - -impl TokenOwnershipData { - pub fn add_nonce(&mut self, nonce: u64) { - if !self.token_nonces.contains(&nonce) { - self.token_nonces.push(nonce); - } - } - pub fn remove_nonce(&mut self, nonce: u64) { - let index = self.token_nonces.iter().position(|n| n == nonce); - - match index { - Some(value) => self.token_nonces.remove(value), - None => M::error_api_impl().signal_error(b"Nonce requested is not available"), - }; - } -} diff --git a/modules/src/bonding_curve/utils/user_endpoints.rs b/modules/src/bonding_curve/utils/user_endpoints.rs deleted file mode 100644 index 49502276..00000000 --- a/modules/src/bonding_curve/utils/user_endpoints.rs +++ /dev/null @@ -1,303 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use multiversx_sc::contract_base::ManagedSerializer; - -use crate::bonding_curve::{ - curves::curve_function::CurveFunction, - utils::{events, storage, structs::BondingCurve}, -}; - -#[multiversx_sc::module] -pub trait UserEndpointsModule: storage::StorageModule + events::EventsModule { - fn sell_token(&self) - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let (offered_token, nonce, sell_amount) = self.call_value().single_esdt().into_tuple(); - let _ = self.check_owned_return_payment_token::(&offered_token, &sell_amount); - - let (calculated_price, payment_token) = - self.bonding_curve(&offered_token).update(|buffer| { - let serializer = ManagedSerializer::new(); - - let mut bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(buffer); - - require!( - bonding_curve.sell_availability, - "Selling is not available on this token" - ); - let price = self.compute_sell_price::(&offered_token, &sell_amount); - bonding_curve.payment.amount -= &price; - bonding_curve.arguments.balance += &sell_amount; - let payment_token = bonding_curve.payment_token(); - *buffer = serializer.top_encode_to_managed_buffer(&bonding_curve); - (price, payment_token) - }); - - let caller = self.blockchain().get_caller(); - - self.nonce_amount(&offered_token, nonce) - .update(|val| *val += sell_amount); - - self.send() - .direct(&caller, &payment_token, 0u64, &calculated_price); - self.token_details(&offered_token) - .update(|details| details.add_nonce(nonce)); - - self.sell_token_event(&caller, &calculated_price); - } - - fn buy_token( - &self, - requested_amount: BigUint, - requested_token: TokenIdentifier, - requested_nonce: OptionalValue, - ) where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let (offered_token, payment) = self.call_value().egld_or_single_fungible_esdt(); - let payment_token = - self.check_owned_return_payment_token::(&requested_token, &requested_amount); - self.check_given_token(&payment_token, &offered_token); - - let calculated_price = self.bonding_curve(&requested_token).update(|buffer| { - let serializer = ManagedSerializer::new(); - - let mut bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(buffer); - - let price = self.compute_buy_price::(&requested_token, &requested_amount); - require!( - price <= payment, - "The payment provided is not enough for the transaction" - ); - bonding_curve.payment.amount += &price; - bonding_curve.arguments.balance -= &requested_amount; - *buffer = serializer.top_encode_to_managed_buffer(&bonding_curve); - - price - }); - - let caller = self.blockchain().get_caller(); - - match requested_nonce { - OptionalValue::Some(nonce) => { - self.send() - .direct_esdt(&caller, &requested_token, nonce, &requested_amount); - if self.nonce_amount(&requested_token, nonce).get() - requested_amount.clone() > 0 { - self.nonce_amount(&requested_token, nonce) - .update(|val| *val -= requested_amount.clone()); - } else { - self.nonce_amount(&requested_token, nonce).clear(); - self.token_details(&requested_token) - .update(|details| details.remove_nonce(nonce)); - } - } - OptionalValue::None => { - self.send_next_available_tokens(&caller, requested_token, requested_amount); - } - }; - - self.send().direct( - &caller, - &offered_token, - 0u64, - &(&payment - &calculated_price), - ); - - self.buy_token_event(&caller, &calculated_price); - } - - fn send_next_available_tokens( - &self, - caller: &ManagedAddress, - token: TokenIdentifier, - amount: BigUint, - ) { - let mut nonces = self.token_details(&token).get().token_nonces; - let mut total_amount = amount; - let mut tokens_to_send = ManagedVec::>::new(); - loop { - require!(!nonces.is_empty(), "Insufficient balance"); - let nonce = nonces.get(0); - let available_amount = self.nonce_amount(&token, nonce).get(); - - let amount_to_send: BigUint; - if available_amount <= total_amount { - amount_to_send = available_amount.clone(); - total_amount -= amount_to_send.clone(); - self.nonce_amount(&token, nonce).clear(); - nonces.remove(0); - } else { - self.nonce_amount(&token, nonce) - .update(|val| *val -= total_amount.clone()); - amount_to_send = total_amount.clone(); - total_amount = BigUint::zero(); - } - tokens_to_send.push(EsdtTokenPayment::new(token.clone(), nonce, amount_to_send)); - if total_amount == BigUint::zero() { - break; - } - } - - self.send().direct_multi(caller, &tokens_to_send); - - self.token_details(&token) - .update(|token_ownership| token_ownership.token_nonces = nonces); - } - - fn get_buy_price(&self, amount: BigUint, identifier: TokenIdentifier) -> BigUint - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - self.check_token_exists(&identifier); - self.compute_buy_price::(&identifier, &amount) - } - - fn get_sell_price(&self, amount: BigUint, identifier: TokenIdentifier) -> BigUint - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - self.check_token_exists(&identifier); - self.compute_sell_price::(&identifier, &amount) - } - - fn check_token_exists(&self, issued_token: &TokenIdentifier) { - require!( - !self.bonding_curve(issued_token).is_empty(), - "Token is not issued yet!" - ); - } - - #[view(getTokenAvailability)] - fn get_token_availability( - &self, - identifier: TokenIdentifier, - ) -> MultiValueEncoded> { - let token_nonces = self.token_details(&identifier).get().token_nonces; - let mut availability = MultiValueEncoded::new(); - - for current_check_nonce in &token_nonces { - availability.push(MultiValue2(( - current_check_nonce, - self.nonce_amount(&identifier, current_check_nonce).get(), - ))); - } - availability - } - - fn check_owned_return_payment_token( - &self, - issued_token: &TokenIdentifier, - amount: &BigUint, - ) -> EgldOrEsdtTokenIdentifier - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - self.check_token_exists(issued_token); - - let serializer = ManagedSerializer::new(); - let bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(&self.bonding_curve(issued_token).get()); - - require!( - bonding_curve.curve != T::default(), - "The token price was not set yet!" - ); - require!(amount > &BigUint::zero(), "Must pay more than 0 tokens!"); - bonding_curve.payment_token() - } - - fn check_given_token( - &self, - accepted_token: &EgldOrEsdtTokenIdentifier, - given_token: &EgldOrEsdtTokenIdentifier, - ) { - require!( - given_token == accepted_token, - "Only {} tokens accepted", - accepted_token - ); - } - - fn compute_buy_price(&self, identifier: &TokenIdentifier, amount: &BigUint) -> BigUint - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let serializer = ManagedSerializer::new(); - let bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(&self.bonding_curve(identifier).get()); - - let arguments = &bonding_curve.arguments; - let function_selector = &bonding_curve.curve; - - let token_start = &arguments.first_token_available(); - function_selector.calculate_price(token_start, amount, arguments) - } - - fn compute_sell_price(&self, identifier: &TokenIdentifier, amount: &BigUint) -> BigUint - where - T: CurveFunction - + TopEncode - + TopDecode - + NestedEncode - + NestedDecode - + TypeAbi - + PartialEq - + Default, - { - let serializer = ManagedSerializer::new(); - let bonding_curve: BondingCurve = - serializer.top_decode_from_managed_buffer(&self.bonding_curve(identifier).get()); - - let arguments = &bonding_curve.arguments; - let function_selector = &bonding_curve.curve; - - let token_start = arguments.first_token_available() - amount; - function_selector.calculate_price(&token_start, amount, arguments) - } -} diff --git a/modules/src/claim_developer_rewards.rs b/modules/src/claim_developer_rewards.rs deleted file mode 100644 index 05be2a00..00000000 --- a/modules/src/claim_developer_rewards.rs +++ /dev/null @@ -1,12 +0,0 @@ -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait ClaimDeveloperRewardsModule { - #[endpoint(claimDeveloperRewards)] - fn claim_developer_rewards(&self, child_sc_address: ManagedAddress) { - let () = self - .send() - .claim_developer_rewards(child_sc_address) - .execute_on_dest_context(); - } -} diff --git a/modules/src/default_issue_callbacks.rs b/modules/src/default_issue_callbacks.rs deleted file mode 100644 index 7c0d096b..00000000 --- a/modules/src/default_issue_callbacks.rs +++ /dev/null @@ -1,54 +0,0 @@ -use multiversx_sc::{storage::StorageKey, storage_clear, storage_set}; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -// Always keep in sync with the token-related storage mappers. Only modify if really necessary. -#[multiversx_sc::module] -pub trait DefaultIssueCallbacksModule { - #[callback] - fn default_issue_cb( - &self, - initial_caller: ManagedAddress, - storage_key: ManagedBuffer, - #[call_result] result: ManagedAsyncCallResult, - ) { - let key = StorageKey::from(storage_key); - match result { - ManagedAsyncCallResult::Ok(token_id) => { - storage_set(key.as_ref(), &TokenMapperState::Token(token_id)); - }, - ManagedAsyncCallResult::Err(_) => { - self.return_failed_issue_funds(initial_caller); - storage_clear(key.as_ref()); - }, - } - } - - #[callback] - fn default_issue_init_supply_cb( - &self, - initial_caller: ManagedAddress, - storage_key: ManagedBuffer, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let key = StorageKey::from(storage_key); - match result { - ManagedAsyncCallResult::Ok(()) => { - let token_id = self.call_value().single_esdt().token_identifier; - storage_set(key.as_ref(), &TokenMapperState::Token(token_id)); - }, - ManagedAsyncCallResult::Err(_) => { - self.return_failed_issue_funds(initial_caller); - storage_clear(key.as_ref()); - }, - } - } - - fn return_failed_issue_funds(&self, initial_caller: ManagedAddress) { - let egld_returned = self.call_value().egld_value(); - if *egld_returned > 0u32 { - self.send().direct_egld(&initial_caller, &egld_returned); - } - } -} diff --git a/modules/src/dns.rs b/modules/src/dns.rs deleted file mode 100644 index 4034d7e1..00000000 --- a/modules/src/dns.rs +++ /dev/null @@ -1,35 +0,0 @@ -mod dns_proxy { - multiversx_sc::imports!(); - - #[multiversx_sc::proxy] - pub trait Dns { - #[payable("EGLD")] - #[endpoint] - fn register(&self, name: &ManagedBuffer); - } -} - -multiversx_sc::imports!(); - -/// Standard smart contract module that deals with registering usernames in a DNS contract. -/// -/// Elrond usernames/herotags need to be requested by the beneficiary. -/// For a contract, this means that they need an endpoint via which to request a username from the DNS. -/// -#[multiversx_sc::module] -pub trait DnsModule { - #[proxy] - fn dns_proxy(&self, to: ManagedAddress) -> dns_proxy::Proxy; - - #[payable("EGLD")] - #[only_owner] - #[endpoint(dnsRegister)] - fn dns_register(&self, dns_address: ManagedAddress, name: ManagedBuffer) { - let payment = self.call_value().egld_value().clone_value(); - self.dns_proxy(dns_address) - .register(&name) - .with_egld_transfer(payment) - .async_call() - .call_and_exit() - } -} diff --git a/modules/src/esdt.rs b/modules/src/esdt.rs deleted file mode 100644 index 18f43b89..00000000 --- a/modules/src/esdt.rs +++ /dev/null @@ -1,117 +0,0 @@ -multiversx_sc::imports!(); - -/// Standard smart contract module for managing a single ESDT. -/// -/// When added to a smart contract offers basic ESDT usage. -/// A lot of contracts use an owned ESDT for various purposes. -/// This module is used to offer a standard way of performing the basic operations. -/// -/// It provides endpoints for: -/// * issuing of an ESDT -/// * setting local roles -/// * minting/burning -/// -#[multiversx_sc::module] -pub trait EsdtModule { - /* - EsdtTokenType is an enum (u8): - 0 - Fungible, - 1 - NonFungible, - 2 - SemiFungible, - 3 - Meta, - - Note: Only Fungible and Meta tokens have decimals - */ - #[payable("EGLD")] - #[only_owner] - #[endpoint(issueToken)] - fn issue_token( - &self, - token_display_name: ManagedBuffer, - token_ticker: ManagedBuffer, - token_type: EsdtTokenType, - opt_num_decimals: OptionalValue, - ) { - require!(self.token_id().is_empty(), "Token already issued"); - - let issue_cost = self.call_value().egld_value().clone_value(); - let num_decimals = match opt_num_decimals { - OptionalValue::Some(d) => d, - OptionalValue::None => 0, - }; - - self.send() - .esdt_system_sc_proxy() - .issue_and_set_all_roles( - issue_cost, - token_display_name, - token_ticker, - token_type, - num_decimals, - ) - .async_call() - .with_callback(self.callbacks().issue_callback()) - .call_and_exit() - } - - #[callback] - fn issue_callback(&self, #[call_result] result: ManagedAsyncCallResult) { - match result { - ManagedAsyncCallResult::Ok(token_id) => { - self.token_id().set(&token_id); - }, - ManagedAsyncCallResult::Err(_) => { - // return payment to initial caller - let initial_caller = self.blockchain().get_owner_address(); - let egld_returned = self.call_value().egld_value(); - if *egld_returned > 0u32 { - self.send().direct_egld(&initial_caller, &egld_returned); - } - }, - } - } - - fn mint(&self, token_nonce: u64, amount: &BigUint) { - let token_id = self.token_id().get(); - self.send().esdt_local_mint(&token_id, token_nonce, amount); - } - - fn burn(&self, token_nonce: u64, amount: &BigUint) { - let token_id = self.token_id().get(); - self.send().esdt_local_burn(&token_id, token_nonce, amount); - } - - fn nft_create(&self, amount: &BigUint, attributes: &T) -> u64 { - let token_id = self.token_id().get(); - let empty_buffer = ManagedBuffer::new(); - let empty_vec = ManagedVec::from_handle(empty_buffer.get_handle()); - - self.send().esdt_nft_create( - &token_id, - amount, - &empty_buffer, - &BigUint::zero(), - &empty_buffer, - &attributes, - &empty_vec, - ) - } - - fn get_token_attributes(&self, token_nonce: u64) -> T { - let own_sc_address = self.blockchain().get_sc_address(); - let token_id = self.token_id().get(); - let token_data = - self.blockchain() - .get_esdt_token_data(&own_sc_address, &token_id, token_nonce); - - token_data.decode_attributes() - } - - fn require_token_issued(&self) { - require!(!self.token_id().is_empty(), "Token must be issued first"); - } - - // Note: to issue another token, you have to clear this storage - #[storage_mapper("token_id")] - fn token_id(&self) -> SingleValueMapper; -} diff --git a/modules/src/features.rs b/modules/src/features.rs deleted file mode 100644 index 35af2d2d..00000000 --- a/modules/src/features.rs +++ /dev/null @@ -1,58 +0,0 @@ -multiversx_sc::imports!(); - -pub const FEATURE_NOT_SET: u8 = 0; -pub const FEATURE_ON: u8 = 1; -pub const FEATURE_OFF: u8 = 2; - -/// This is a standard smart contract module, that when added to a smart contract offers feature flag capabilities. -/// -/// It offers: -/// * an endpoint where the owner can turn features on/off -/// * a method to check if feature is on or not -/// * a macro to make calling this method even more compact -/// -#[multiversx_sc::module] -pub trait FeaturesModule { - #[storage_mapper("feat:")] - fn feature_flag(&self, feature_name: &FeatureName) -> SingleValueMapper; - - fn check_feature_on(&self, feature_name: &'static [u8], default: bool) { - let flag = self.feature_flag(&FeatureName(feature_name.into())).get(); - let value = match flag { - FEATURE_NOT_SET => default, - FEATURE_ON => true, - _ => false, - }; - require!(value, "{} currently disabled", feature_name); - } - - #[only_owner] - #[endpoint(setFeatureFlag)] - fn set_feature_flag_endpoint(&self, feature_name: ManagedBuffer, value: bool) { - let feature_value = if value { FEATURE_ON } else { FEATURE_OFF }; - self.feature_flag(&FeatureName(feature_name)) - .set(feature_value); - } -} - -multiversx_sc::derive_imports!(); - -#[derive(TopEncode)] -pub struct FeatureName(ManagedBuffer) -where - M: ManagedTypeApi; - -use multiversx_sc::codec::*; -impl NestedEncode for FeatureName -where - M: ManagedTypeApi, -{ - #[inline] - fn dep_encode_or_handle_err(&self, dest: &mut O, h: H) -> Result<(), H::HandledErr> - where - O: NestedEncodeOutput, - H: EncodeErrorHandler, - { - dest.push_specialized((), &self.0, h) - } -} diff --git a/modules/src/governance/README.md b/modules/src/governance/README.md deleted file mode 100644 index 784a1350..00000000 --- a/modules/src/governance/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Elrond smart contract module - Governance - -This is a standard smart contract module, that when added to a smart contract offers governance features: -- proposing actions -- voting/downvoting a particular proposal -- after a voting period, either putting the action in a queue (if it reached quorum) or canceling - -Voting can only be done by depositing a specific token defined in the initial setup. - -## Configuration - -Initial configuration is done through the `init_governance_module` function. Usually, this should be called from the main contract's `#[init]` function. - -Arguments for the init function: - -- `governance_token_id` - the token that will be used for voting -- `quorum` - the minimum number of (`votes` minus `downvotes`) at the end of the voting period -- `min_token_balance_for_proposal` - Minimum numbers of tokens the proposer has to deposit. These automatically count as `votes` as well -- `voting_delay_in_blocks` - Number of blocks to wait after a block is proposed before being able to vote/downvote that proposal -- `voting_period_in_blocks` - Number of blocks the voting period lasts (voting delay does not count towards this) -- `lock_time_after_voting_ends_in_blocks` - Number of blocks to wait before a successful proposal can be executed - -All of the above parameters execpt the `governance_token_id` can be changed later through proposals. - -The module also provides events for most actions that happen: -- `proposalCreated` - triggers when a proposal is created. It also provides all the relevant information, like proposer, actions, etc. -- `voteCast` - user voted on a proposal -- `downvoteCast` - user downvoted a proposal -- `proposalCanceled`, `proposalQueued` and `proposalExecuted` - provides the ID of the specific proposal -- `userDeposit` - a user deposited some tokens needed for a future payable action - -Please note that although the main contract can modify the module's storage directly, it is not recommended to do so, as that defeats the whole purpose of having governance. These parameters should only be modified through actions. - -## Proposing actions - -Proposing actions is done through the `propose` endpoint. An action has the following format: - - gas limit for action execution - - destination address - - a vector of ESDT transfers, in the form of `ManagedVec` - - endpoint to be called on the destination - - a vector of arguments for the endpoint, in the form of `ManagedVec` - -A maximum of `MAX_GOVERNANCE_PROPOSAL_ACTIONS` may be proposed at a time. All actions are bundled into a single proposal, and a `proposal_id` is returned. This ID is further used for interacting with the proposal for the purpose of voting, downvoting, etc. - -Additionally, a minimum of `min_token_balance_for_proposal` governance tokens must be deposited at proposal time. - -Examples of actions that can be proposed: -- transfering ESDT tokens to user accounts -- calling other smart contracts, with or without sending tokens as well -- calling the goverance contract itself, for the purpose of changing configurable parameters - -## Voting/Downvoting - -After a period of `voting_delay_in_blocks` blocks, in which governance members can evaluate the proposal, the voting/downvoting period starts. - -To express their desire for the proposal to be executed, governance members should deposit governance tokens through the `vote` endpoint. If they do not wish for it to executed, they should use the `downvote` endpoint. - -This period lasts an amount of blocks equal to `voting_period_in_blocks`. - -## Executing proposals - -Once the voting period ends, proposals have to be queued, after which they're locked for another `lock_time_after_voting_ends_in_blocks` blocks. Then, they can be executed, which will launch all the proposed actions. - -After the execution, the governance tokens can be withdrawn by the voters and downvoters, to further use them for other proposals. diff --git a/modules/src/governance/governance_configurable.rs b/modules/src/governance/governance_configurable.rs deleted file mode 100644 index 407e4eab..00000000 --- a/modules/src/governance/governance_configurable.rs +++ /dev/null @@ -1,182 +0,0 @@ -multiversx_sc::imports!(); - -/// # Elrond smart contract module - Governance -/// -/// This is a standard smart contract module, that when added to a smart contract offers governance features: -/// - proposing actions -/// - voting/downvoting a certain proposal -/// - after a voting period, either putting the action in a queue (if it reached quorum), or canceling -/// -/// Voting can only be done by depositing a certain token, decided upon first time setup. -/// -/// The module provides the following configurable parameters: -/// - `quorum` - the minimum number of (`votes` minus `downvotes`) at the end of voting period -/// - `minTokenBalanceForProposing` - Minimum numbers of tokens the proposer has to deposit. These automatically count as `votes` as well -/// - `maxActionsPerProposal` - Maximum number of actions (transfers and/or smart contract calls) that a proposal may have -/// - `votingDelayInBlocks` - Number of blocks to wait after a block is proposed before being able to vote/downvote that proposal -/// - `votingPeriodInBlocks` - Number of blocks the voting period lasts (voting delay does not count towards this) -/// - `lockTimeAfterVotingEndsInBlocks` - Number of blocks to wait before a successful proposal can be executed -/// -/// The module also provides events for most actions that happen: -/// - `proposalCreated` - triggers when a proposal is created. Also provoides all the relevant information, like proposer, actions etc. -/// - `voteCast` - user voted on a proposal -/// - `downvoteCast` - user downvoted a proposal -/// - `proposalCanceled`, `proposalQueued` and `proposalExecuted` - provides the ID of the specific proposal -/// - `userDeposit` - a user deposited some tokens needed for a future payable action -/// -/// Please note that although the main contract can modify the module's storage directly, it is not recommended to do so, -/// as that defeats the whole purpose of having governance. These parameters should only be modified through actions. -/// -#[multiversx_sc::module] -pub trait GovernanceConfigurablePropertiesModule { - // endpoints - owner-only - - /// The module can't protect its storage from the main SC, so it's the developers responsibility - /// to not modify parameters manually - fn init_governance_module( - &self, - governance_token_id: TokenIdentifier, - quorum: BigUint, - min_token_balance_for_proposal: BigUint, - voting_delay_in_blocks: u64, - voting_period_in_blocks: u64, - lock_time_after_voting_ends_in_blocks: u64, - ) { - require!( - governance_token_id.is_valid_esdt_identifier(), - "Invalid ESDT token ID provided for governance_token_id" - ); - - self.governance_token_id() - .set_if_empty(&governance_token_id); - - self.try_change_quorum(quorum); - self.try_change_min_token_balance_for_proposing(min_token_balance_for_proposal); - self.try_change_voting_delay_in_blocks(voting_delay_in_blocks); - self.try_change_voting_period_in_blocks(voting_period_in_blocks); - self.try_change_lock_time_after_voting_ends_in_blocks( - lock_time_after_voting_ends_in_blocks, - ); - } - - // endpoints - these can only be called by the SC itself. - // i.e. only by proposing and executing an action with the SC as dest and the respective func name - - #[endpoint(changeQuorum)] - fn change_quorum(&self, new_value: BigUint) { - self.require_caller_self(); - - self.try_change_quorum(new_value); - } - - #[endpoint(changeMinTokenBalanceForProposing)] - fn change_min_token_balance_for_proposing(&self, new_value: BigUint) { - self.require_caller_self(); - - self.try_change_min_token_balance_for_proposing(new_value); - } - - #[endpoint(changeVotingDelayInBlocks)] - fn change_voting_delay_in_blocks(&self, new_value: u64) { - self.require_caller_self(); - - self.try_change_voting_delay_in_blocks(new_value); - } - - #[endpoint(changeVotingPeriodInBlocks)] - fn change_voting_period_in_blocks(&self, new_value: u64) { - self.require_caller_self(); - - self.try_change_voting_period_in_blocks(new_value); - } - - #[endpoint(changeLockTimeAfterVotingEndsInBlocks)] - fn change_lock_time_after_voting_ends_in_blocks(&self, new_value: u64) { - self.require_caller_self(); - - self.try_change_lock_time_after_voting_ends_in_blocks(new_value); - } - - // private - - fn require_caller_self(&self) { - let caller = self.blockchain().get_caller(); - let sc_address = self.blockchain().get_sc_address(); - - require!( - caller == sc_address, - "Only the SC itself may call this function" - ); - } - - fn try_change_quorum(&self, new_value: BigUint) { - require!(new_value != 0, "Quorum can't be set to 0"); - - self.quorum().set(&new_value); - } - - fn try_change_min_token_balance_for_proposing(&self, new_value: BigUint) { - require!( - new_value != 0, - "Min token balance for proposing can't be set to 0" - ); - - self.min_token_balance_for_proposing().set(&new_value); - } - - fn try_change_voting_delay_in_blocks(&self, new_value: u64) { - require!(new_value != 0, "Voting delay in blocks can't be set to 0"); - - self.voting_delay_in_blocks().set(new_value); - } - - fn try_change_voting_period_in_blocks(&self, new_value: u64) { - require!( - new_value != 0, - "Voting period (in blocks) can't be set to 0" - ); - - self.voting_period_in_blocks().set(new_value); - } - - fn try_change_lock_time_after_voting_ends_in_blocks(&self, new_value: u64) { - require!( - new_value != 0, - "Lock time after voting ends (in blocks) can't be set to 0" - ); - - self.lock_time_after_voting_ends_in_blocks().set(new_value); - } - - // storage - fixed parameters - - #[view(getGovernanceTokenId)] - #[storage_mapper("governance:governanceTokenId")] - fn governance_token_id(&self) -> SingleValueMapper; - - // storage - configurable parameters - - #[view(getQuorum)] - #[storage_mapper("governance:quorum")] - fn quorum(&self) -> SingleValueMapper; - - #[view(getMinFeeForPropose)] - #[storage_mapper("minFeeForPropose")] - fn min_fee_for_propose(&self) -> SingleValueMapper; - - #[view(getMinTokenBalanceForProposing)] - #[storage_mapper("governance:minTokenBalanceForProposing")] - fn min_token_balance_for_proposing(&self) -> SingleValueMapper; - - #[view(getVotingDelayInBlocks)] - #[storage_mapper("governance:votingDelayInBlocks")] - fn voting_delay_in_blocks(&self) -> SingleValueMapper; - - #[view(getVotingPeriodInBlocks)] - #[storage_mapper("governance:votingPeriodInBlocks")] - fn voting_period_in_blocks(&self) -> SingleValueMapper; - - #[view(getLockTimeAfterVotingEndsInBlocks)] - #[storage_mapper("governance:lockTimeAfterVotingEndsInBlocks")] - fn lock_time_after_voting_ends_in_blocks(&self) -> SingleValueMapper; -} diff --git a/modules/src/governance/governance_events.rs b/modules/src/governance/governance_events.rs deleted file mode 100644 index 5a70d6fb..00000000 --- a/modules/src/governance/governance_events.rs +++ /dev/null @@ -1,73 +0,0 @@ -multiversx_sc::imports!(); - -use super::governance_proposal::GovernanceProposal; -use crate::governance::ProposalId; - -#[multiversx_sc::module] -pub trait GovernanceEventsModule { - #[event("proposalCreated")] - fn proposal_created_event( - &self, - #[indexed] proposal_id: usize, - #[indexed] proposer: &ManagedAddress, - #[indexed] start_block: u64, - proposal: &GovernanceProposal, - ); - - #[event("upVoteCast")] - fn up_vote_cast_event( - &self, - #[indexed] up_voter: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - nr_votes: &BigUint, - ); - - #[event("downVoteCast")] - fn down_vote_cast_event( - &self, - #[indexed] down_voter: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - nr_downvotes: &BigUint, - ); - - #[event("downVetoVoteCast")] - fn down_veto_vote_cast_event( - &self, - #[indexed] down_veto_voter: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - nr_downvotes: &BigUint, - ); - - #[event("abstainVoteCast")] - fn abstain_vote_cast_event( - &self, - #[indexed] abstain_voter: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - nr_downvotes: &BigUint, - ); - - #[event("proposalCanceled")] - fn proposal_canceled_event(&self, #[indexed] proposal_id: usize); - - #[event("proposalQueued")] - fn proposal_queued_event(&self, #[indexed] proposal_id: usize, #[indexed] queued_block: u64); - - #[event("proposalExecuted")] - fn proposal_executed_event(&self, #[indexed] proposal_id: usize); - - #[event("userDeposit")] - fn user_deposit_event( - &self, - #[indexed] address: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - payment: &EsdtTokenPayment, - ); - - #[event("userClaimDepositedTokens")] - fn user_claim_event( - &self, - #[indexed] address: &ManagedAddress, - #[indexed] proposal_id: ProposalId, - payment: &EsdtTokenPayment, - ); -} diff --git a/modules/src/governance/governance_proposal.rs b/modules/src/governance/governance_proposal.rs deleted file mode 100644 index e251a438..00000000 --- a/modules/src/governance/governance_proposal.rs +++ /dev/null @@ -1,112 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub const MAX_GOVERNANCE_PROPOSAL_ACTIONS: usize = 4; -pub type ProposalId = usize; - -pub type GovernanceActionAsMultiArg = - MultiValue4, ManagedBuffer, ManagedVec>>; - -#[derive(TypeAbi, TopEncode, TopDecode)] -pub enum VoteType { - UpVote, - DownVote, - DownVetoVote, - AbstainVote, -} - -#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Eq)] -pub enum GovernanceProposalStatus { - None, - Pending, - Active, - Defeated, - Succeeded, - Queued, - WaitingForFees, -} - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, TypeAbi)] -pub struct ProposalFees { - pub total_amount: BigUint, - pub entries: ManagedVec>, -} - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, TypeAbi)] -pub struct FeeEntry { - pub depositor_addr: ManagedAddress, - pub tokens: EsdtTokenPayment, -} - -#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode)] -pub struct GovernanceAction { - pub gas_limit: u64, - pub dest_address: ManagedAddress, - pub function_name: ManagedBuffer, - pub arguments: ManagedVec>, -} - -impl GovernanceAction { - pub fn into_multiarg(self) -> GovernanceActionAsMultiArg { - ( - self.gas_limit, - self.dest_address, - self.function_name, - self.arguments, - ) - .into() - } -} - -#[derive(TypeAbi, TopEncode, TopDecode)] -pub struct GovernanceProposal { - pub proposer: ManagedAddress, - pub actions: ArrayVec, MAX_GOVERNANCE_PROPOSAL_ACTIONS>, - pub description: ManagedBuffer, - pub fees: ProposalFees, -} - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi)] -pub struct ProposalVotes { - pub up_votes: BigUint, - pub down_votes: BigUint, - pub down_veto_votes: BigUint, - pub abstain_votes: BigUint, -} - -impl Default for ProposalVotes { - fn default() -> Self { - Self::new() - } -} - -impl ProposalVotes { - pub fn new() -> Self { - ProposalVotes { - up_votes: BigUint::zero(), - down_votes: BigUint::zero(), - down_veto_votes: BigUint::zero(), - abstain_votes: BigUint::zero(), - } - } - - pub fn get_total_votes(&self) -> BigUint { - &self.up_votes + &self.down_votes + &self.down_veto_votes + &self.abstain_votes - } - pub fn get_up_votes_percentage(&self) -> BigUint { - let total_votes = self.get_total_votes(); - &self.up_votes / &total_votes - } - pub fn get_down_votes_percentage(&self) -> BigUint { - let total_votes = self.get_total_votes(); - &self.down_votes / &total_votes - } - pub fn get_down_veto_votes_percentage(&self) -> BigUint { - let total_votes = self.get_total_votes(); - &self.down_veto_votes / &total_votes - } - pub fn get_abstain_votes_percentage(&self) -> BigUint { - let total_votes = self.get_total_votes(); - &self.abstain_votes / &total_votes - } -} diff --git a/modules/src/governance/mod.rs b/modules/src/governance/mod.rs deleted file mode 100644 index ee343f71..00000000 --- a/modules/src/governance/mod.rs +++ /dev/null @@ -1,524 +0,0 @@ -multiversx_sc::imports!(); - -pub mod governance_configurable; -pub mod governance_events; -pub mod governance_proposal; - -use governance_proposal::*; - -const MAX_GAS_LIMIT_PER_BLOCK: u64 = 600_000_000; -const MIN_AMOUNT_PER_DEPOSIT: u64 = 1; -pub const ALREADY_VOTED_ERR_MSG: &[u8] = b"Already voted for this proposal"; -pub const MIN_FEES_REACHED: &[u8] = b"Propose already reached min threshold for fees"; -pub const MIN_AMOUNT_NOT_REACHED: &[u8] = b"Minimum amount not reached"; - -#[multiversx_sc::module] -pub trait GovernanceModule: - governance_configurable::GovernanceConfigurablePropertiesModule - + governance_events::GovernanceEventsModule -{ - // endpoints - - /// Used to deposit tokens for "payable" actions. - /// Funds will be returned if the proposal is defeated. - /// To keep the logic simple, all tokens have to be deposited at once - #[payable("*")] - #[endpoint(depositTokensForProposal)] - fn deposit_tokens_for_proposal(&self, proposal_id: ProposalId) { - self.require_caller_not_self(); - self.require_valid_proposal_id(proposal_id); - require!( - self.get_proposal_status(proposal_id) == GovernanceProposalStatus::WaitingForFees, - "Proposal has to be executed or canceled first" - ); - require!( - !self.proposal_reached_min_fees(proposal_id), - MIN_FEES_REACHED - ); - let additional_fee = self.require_payment_token_governance_token(); - require!( - additional_fee.amount >= MIN_AMOUNT_PER_DEPOSIT, - MIN_AMOUNT_NOT_REACHED - ); - - let caller = self.blockchain().get_caller(); - let mut proposal = self.proposals().get(proposal_id); - proposal.fees.entries.push(FeeEntry { - depositor_addr: caller.clone(), - tokens: additional_fee.clone(), - }); - proposal.fees.total_amount += additional_fee.amount.clone(); - self.proposals().set(proposal_id, &proposal); - - self.user_deposit_event(&caller, proposal_id, &additional_fee); - } - - // Used to withdraw the tokens after the action was executed or cancelled - #[endpoint(withdrawGovernanceTokens)] - fn claim_deposited_tokens(&self, proposal_id: usize) { - self.require_caller_not_self(); - self.require_valid_proposal_id(proposal_id); - require!( - self.get_proposal_status(proposal_id) == GovernanceProposalStatus::WaitingForFees, - "Cannot claim deposited tokens anymore; Proposal is not in WatingForFees state" - ); - - require!( - !self.proposal_reached_min_fees(proposal_id), - MIN_FEES_REACHED - ); - - let caller = self.blockchain().get_caller(); - let mut proposal = self.proposals().get(proposal_id); - let mut fees_to_send = ManagedVec::>::new(); - let mut i = 0; - while i < proposal.fees.entries.len() { - if proposal.fees.entries.get(i).depositor_addr == caller { - fees_to_send.push(proposal.fees.entries.get(i)); - proposal.fees.entries.remove(i); - } else { - i += 1; - } - } - - for fee_entry in fees_to_send.iter() { - let payment = fee_entry.tokens.clone(); - - self.send().direct_esdt( - &fee_entry.depositor_addr, - &payment.token_identifier, - payment.token_nonce, - &payment.amount, - ); - self.user_claim_event(&caller, proposal_id, &fee_entry.tokens); - } - } - - /// Propose a list of actions. - /// A maximum of MAX_GOVERNANCE_PROPOSAL_ACTIONS can be proposed at a time. - /// - /// An action has the following format: - /// - gas limit for action execution - /// - destination address - /// - a vector of ESDT transfers, in the form of ManagedVec - /// - endpoint to be called on the destination - /// - a vector of arguments for the endpoint, in the form of ManagedVec - /// - /// Returns the ID of the newly created proposal. - #[payable("*")] - #[endpoint] - fn propose( - &self, - description: ManagedBuffer, - actions: MultiValueEncoded>, - ) -> usize { - self.require_caller_not_self(); - - let payment = self.require_payment_token_governance_token(); - - require!( - payment.amount >= self.min_token_balance_for_proposing().get(), - "Not enough tokens for proposing action" - ); - require!(!actions.is_empty(), "Proposal has no actions"); - require!( - actions.len() <= MAX_GOVERNANCE_PROPOSAL_ACTIONS, - "Exceeded max actions per proposal" - ); - - let mut gov_actions = ArrayVec::new(); - for action in actions { - let (gas_limit, dest_address, function_name, arguments) = action.into_tuple(); - let gov_action = GovernanceAction { - gas_limit, - dest_address, - function_name, - arguments, - }; - - require!( - gas_limit < MAX_GAS_LIMIT_PER_BLOCK, - "A single action cannot use more than the max gas limit per block" - ); - - gov_actions.push(gov_action); - } - - require!( - self.total_gas_needed(&gov_actions) < MAX_GAS_LIMIT_PER_BLOCK, - "Actions require too much gas to be executed" - ); - - let proposer = self.blockchain().get_caller(); - let fees_entries = ManagedVec::from_single_item(FeeEntry { - depositor_addr: proposer.clone(), - tokens: payment.clone(), - }); - - let proposal = GovernanceProposal { - proposer: proposer.clone(), - description, - actions: gov_actions, - fees: ProposalFees { - total_amount: payment.amount, - entries: fees_entries, - }, - }; - - let proposal_id = self.proposals().push(&proposal); - self.proposal_votes(proposal_id).set(ProposalVotes::new()); - - let current_block = self.blockchain().get_block_nonce(); - self.proposal_start_block(proposal_id).set(current_block); - - self.proposal_created_event(proposal_id, &proposer, current_block, &proposal); - - proposal_id - } - - /// Vote on a proposal by depositing any amount of governance tokens - /// These tokens will be locked until the proposal is executed or cancelled. - #[payable("*")] - #[endpoint] - fn vote(&self, proposal_id: usize, vote: VoteType) { - self.require_caller_not_self(); - - let payment = self.require_payment_token_governance_token(); - self.require_valid_proposal_id(proposal_id); - require!( - self.get_proposal_status(proposal_id) == GovernanceProposalStatus::Active, - "Proposal is not active" - ); - - let voter = self.blockchain().get_caller(); - let new_user = self.user_voted_proposals(&voter).insert(proposal_id); - require!(new_user, ALREADY_VOTED_ERR_MSG); - - match vote { - VoteType::UpVote => { - self.proposal_votes(proposal_id).update(|total_votes| { - total_votes.up_votes += &payment.amount.clone(); - }); - self.up_vote_cast_event(&voter, proposal_id, &payment.amount); - }, - VoteType::DownVote => { - self.proposal_votes(proposal_id).update(|total_votes| { - total_votes.down_votes += &payment.amount.clone(); - }); - self.down_vote_cast_event(&voter, proposal_id, &payment.amount); - }, - VoteType::DownVetoVote => { - self.proposal_votes(proposal_id).update(|total_votes| { - total_votes.down_veto_votes += &payment.amount.clone(); - }); - self.down_veto_vote_cast_event(&voter, proposal_id, &payment.amount); - }, - VoteType::AbstainVote => { - self.proposal_votes(proposal_id).update(|total_votes| { - total_votes.abstain_votes += &payment.amount.clone(); - }); - self.abstain_vote_cast_event(&voter, proposal_id, &payment.amount); - }, - } - } - - /// Queue a proposal for execution. - /// This can be done only if the proposal has reached the quorum. - /// A proposal is considered successful and ready for queing if - /// total_votes - total_downvotes >= quorum - #[endpoint] - fn queue(&self, proposal_id: usize) { - self.require_caller_not_self(); - - require!( - self.get_proposal_status(proposal_id) == GovernanceProposalStatus::Succeeded, - "Can only queue succeeded proposals" - ); - - let current_block = self.blockchain().get_block_nonce(); - self.proposal_queue_block(proposal_id).set(current_block); - - self.proposal_queued_event(proposal_id, current_block); - } - - /// Execute a previously queued proposal. - /// This will clear the proposal and unlock the governance tokens. - /// Said tokens can then be withdrawn and used to vote/downvote other proposals. - #[endpoint] - fn execute(&self, proposal_id: usize) { - self.require_caller_not_self(); - - require!( - self.get_proposal_status(proposal_id) == GovernanceProposalStatus::Queued, - "Can only execute queued proposals" - ); - - let current_block = self.blockchain().get_block_nonce(); - let lock_blocks = self.lock_time_after_voting_ends_in_blocks().get(); - - let lock_start = self.proposal_queue_block(proposal_id).get(); - let lock_end = lock_start + lock_blocks; - - require!( - current_block >= lock_end, - "Proposal is in timelock status. Try again later" - ); - - let proposal = self.proposals().get(proposal_id); - let total_gas_needed = self.total_gas_needed(&proposal.actions); - let gas_left = self.blockchain().get_gas_left(); - - require!( - gas_left > total_gas_needed, - "Not enough gas to execute all proposals" - ); - - self.clear_proposal(proposal_id); - - for action in proposal.actions { - let mut contract_call = self - .send() - .contract_call::<()>(action.dest_address, action.function_name) - .with_gas_limit(action.gas_limit); - - for arg in &action.arguments { - contract_call.push_raw_argument(arg); - } - - contract_call.transfer_execute(); - } - - self.proposal_executed_event(proposal_id); - } - - /// Cancel a proposed action. This can be done: - /// - by the proposer, at any time - /// - by anyone, if the proposal was defeated - #[endpoint] - fn cancel(&self, proposal_id: usize) { - self.require_caller_not_self(); - - match self.get_proposal_status(proposal_id) { - GovernanceProposalStatus::None => { - sc_panic!("Proposal does not exist"); - }, - GovernanceProposalStatus::Pending => { - let proposal = self.proposals().get(proposal_id); - let caller = self.blockchain().get_caller(); - - require!( - caller == proposal.proposer, - "Only original proposer may cancel a pending proposal" - ); - }, - GovernanceProposalStatus::Defeated => {}, - GovernanceProposalStatus::WaitingForFees => { - self.refund_payments(proposal_id); - }, - _ => { - sc_panic!("Action may not be cancelled"); - }, - } - - self.clear_proposal(proposal_id); - self.proposal_canceled_event(proposal_id); - } - - // views - - #[view(getProposalStatus)] - fn get_proposal_status(&self, proposal_id: usize) -> GovernanceProposalStatus { - if !self.proposal_exists(proposal_id) { - return GovernanceProposalStatus::None; - } - - let queue_block = self.proposal_queue_block(proposal_id).get(); - if queue_block > 0 { - return GovernanceProposalStatus::Queued; - } - - let current_block = self.blockchain().get_block_nonce(); - let proposal_block = self.proposal_start_block(proposal_id).get(); - let voting_delay = self.voting_delay_in_blocks().get(); - let voting_period = self.voting_period_in_blocks().get(); - - let voting_start = proposal_block + voting_delay; - let voting_end = voting_start + voting_period; - - if current_block < voting_start { - return GovernanceProposalStatus::Pending; - } - if current_block >= voting_start && current_block < voting_end { - return GovernanceProposalStatus::Active; - } - - if self.quorum_and_vote_reached(proposal_id) { - GovernanceProposalStatus::Succeeded - } else { - GovernanceProposalStatus::Defeated - } - } - - fn quorum_and_vote_reached(&self, proposal_id: ProposalId) -> bool { - let proposal_votes = self.proposal_votes(proposal_id).get(); - let total_votes = proposal_votes.get_total_votes(); - let total_up_votes = proposal_votes.up_votes; - let total_down_votes = proposal_votes.down_votes; - let total_down_veto_votes = proposal_votes.down_veto_votes; - let third_total_votes = &total_votes / 3u64; - let quorum = self.quorum().get(); - - sc_print!("Total votes = {} quorum = {}", total_votes, quorum); - if total_down_veto_votes > third_total_votes { - false - } else { - total_votes >= quorum && total_up_votes > (total_down_votes + total_down_veto_votes) - } - } - - #[view(getProposer)] - fn get_proposer(&self, proposal_id: usize) -> OptionalValue { - if !self.proposal_exists(proposal_id) { - OptionalValue::None - } else { - OptionalValue::Some(self.proposals().get(proposal_id).proposer) - } - } - - #[view(getProposalDescription)] - fn get_proposal_description(&self, proposal_id: usize) -> OptionalValue { - if !self.proposal_exists(proposal_id) { - OptionalValue::None - } else { - OptionalValue::Some(self.proposals().get(proposal_id).description) - } - } - - #[view(getProposalActions)] - fn get_proposal_actions( - &self, - proposal_id: usize, - ) -> MultiValueEncoded> { - if !self.proposal_exists(proposal_id) { - return MultiValueEncoded::new(); - } - - let actions = self.proposals().get(proposal_id).actions; - let mut actions_as_multiarg = MultiValueEncoded::new(); - - for action in actions { - actions_as_multiarg.push(action.into_multiarg()); - } - - actions_as_multiarg - } - - // private - - fn refund_payments(&self, proposal_id: ProposalId) { - let payments = self.proposals().get(proposal_id).fees; - - for fee_entry in payments.entries.iter() { - let payment = fee_entry.tokens; - self.send().direct_esdt( - &fee_entry.depositor_addr, - &payment.token_identifier, - payment.token_nonce, - &payment.amount, - ); - } - } - - fn require_payment_token_governance_token(&self) -> EsdtTokenPayment { - let payment = self.call_value().single_esdt(); - require!( - payment.token_identifier == self.governance_token_id().get(), - "Only Governance token accepted as payment" - ); - payment - } - - fn require_valid_proposal_id(&self, proposal_id: usize) { - require!( - self.is_valid_proposal_id(proposal_id), - "Invalid proposal ID" - ); - } - - fn require_caller_not_self(&self) { - let caller = self.blockchain().get_caller(); - let sc_address = self.blockchain().get_sc_address(); - - require!( - caller != sc_address, - "Cannot call this endpoint through proposed action" - ); - } - - fn is_valid_proposal_id(&self, proposal_id: usize) -> bool { - proposal_id >= 1 && proposal_id <= self.proposals().len() - } - - fn proposal_reached_min_fees(&self, proposal_id: ProposalId) -> bool { - let accumulated_fees = self.proposals().get(proposal_id).fees.total_amount; - let min_fees = self.min_fee_for_propose().get(); - accumulated_fees >= min_fees - } - - fn proposal_exists(&self, proposal_id: usize) -> bool { - self.is_valid_proposal_id(proposal_id) && !self.proposals().item_is_empty(proposal_id) - } - - fn total_gas_needed( - &self, - actions: &ArrayVec, MAX_GOVERNANCE_PROPOSAL_ACTIONS>, - ) -> u64 { - let mut total = 0; - for action in actions { - total += action.gas_limit; - } - - total - } - - /// specific votes/downvotes are not cleared, - /// as they're used for reclaim tokens logic and cleared one by one - fn clear_proposal(&self, proposal_id: usize) { - self.proposals().clear_entry(proposal_id); - self.proposal_start_block(proposal_id).clear(); - self.proposal_queue_block(proposal_id).clear(); - - self.total_votes(proposal_id).clear(); - self.total_downvotes(proposal_id).clear(); - } - - // storage - general - - #[storage_mapper("governance:proposals")] - fn proposals(&self) -> VecMapper>; - - /// Not stored under "proposals", as that would require deserializing the whole struct - #[storage_mapper("governance:proposalStartBlock")] - fn proposal_start_block(&self, proposal_id: usize) -> SingleValueMapper; - - #[storage_mapper("governance:proposalQueueBlock")] - fn proposal_queue_block(&self, proposal_id: usize) -> SingleValueMapper; - - #[storage_mapper("governance:userVotedProposals")] - fn user_voted_proposals(&self, user: &ManagedAddress) -> UnorderedSetMapper; - - #[view(getProposalVotes)] - #[storage_mapper("proposalVotes")] - fn proposal_votes( - &self, - proposal_id: ProposalId, - ) -> SingleValueMapper>; - - #[view(getTotalVotes)] - #[storage_mapper("governance:totalVotes")] - fn total_votes(&self, proposal_id: usize) -> SingleValueMapper; - - #[view(getTotalDownvotes)] - #[storage_mapper("governance:totalDownvotes")] - fn total_downvotes(&self, proposal_id: usize) -> SingleValueMapper; -} diff --git a/modules/src/lib.rs b/modules/src/lib.rs deleted file mode 100644 index 3229e4f7..00000000 --- a/modules/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_std] -#![feature(trait_alias)] - -pub mod bonding_curve; -pub mod claim_developer_rewards; -pub mod default_issue_callbacks; -pub mod dns; -pub mod esdt; -pub mod features; -pub mod governance; -pub mod ongoing_operation; -pub mod only_admin; -pub mod pause; -pub mod staking; -pub mod token_merge; -pub mod transfer_role_proxy; -pub mod users; diff --git a/modules/src/ongoing_operation.rs b/modules/src/ongoing_operation.rs deleted file mode 100644 index 91537da0..00000000 --- a/modules/src/ongoing_operation.rs +++ /dev/null @@ -1,116 +0,0 @@ -multiversx_sc::imports!(); - -pub const DEFAULT_MIN_GAS_TO_SAVE_PROGRESS: u64 = 1_000_000; - -pub type LoopOp = bool; -pub const CONTINUE_OP: bool = true; -pub const STOP_OP: bool = false; - -#[multiversx_sc::module] -pub trait OngoingOperationModule { - /// Run the given lambda function until it's either completed or it runs out of gas. - /// min_gas_to_save_progress should be a reasonable value to save gas. - /// This can vary a lot based on the given ongoing operation data structures. - /// - /// # Usage example: Counting to 100 - /// ``` - /// # use multiversx_sc::types::OperationCompletionStatus; - /// # use multiversx_sc_modules::ongoing_operation::{ - /// # self, CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP, - /// # }; - /// # pub trait ExampleContract: multiversx_sc::contract_base::ContractBase + ongoing_operation::OngoingOperationModule - /// # { - /// fn count_to_100(&self) -> OperationCompletionStatus { - /// let mut current_number = self.load_operation::(); - /// let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || { - /// if current_number == 100 { - /// return STOP_OP; - /// } - /// - /// current_number += 1; - /// - /// CONTINUE_OP - /// }); - /// - /// if run_result == OperationCompletionStatus::InterruptedBeforeOutOfGas { - /// self.save_progress(¤t_number); - /// } - /// - /// run_result - /// } - /// # } - /// ``` - fn run_while_it_has_gas( - &self, - min_gas_to_save_progress: u64, - mut process: Process, - ) -> OperationCompletionStatus - where - Process: FnMut() -> LoopOp, - { - let mut gas_per_iteration = 0; - let mut gas_before = self.blockchain().get_gas_left(); - loop { - let loop_op = process(); - if loop_op == STOP_OP { - break; - } - - let gas_after = self.blockchain().get_gas_left(); - let current_iteration_cost = gas_before - gas_after; - if current_iteration_cost > gas_per_iteration { - gas_per_iteration = current_iteration_cost; - } - - if !self.can_continue_operation(gas_per_iteration, min_gas_to_save_progress) { - return OperationCompletionStatus::InterruptedBeforeOutOfGas; - } - - gas_before = gas_after; - } - - self.clear_operation(); - - OperationCompletionStatus::Completed - } - - #[inline] - fn can_continue_operation(&self, operation_cost: u64, min_gas_to_save_progress: u64) -> bool { - let gas_left = self.blockchain().get_gas_left(); - - gas_left > min_gas_to_save_progress + operation_cost - } - - /// Load the current ongoing operation. - /// Will return the default value if no operation is saved. - fn load_operation(&self) -> T { - let raw_buffer = self.current_ongoing_operation().get(); - if raw_buffer.is_empty() { - return T::default(); - } - - match T::top_decode(raw_buffer) { - Result::Ok(op) => op, - Result::Err(err) => sc_panic!(err.message_str()), - } - } - - /// Save progress for the current operation. The given value can be any serializable type. - fn save_progress(&self, op: &T) { - let mut encoded_op = ManagedBuffer::new(); - if let Result::Err(err) = op.top_encode(&mut encoded_op) { - sc_panic!(err.message_str()); - } - - self.current_ongoing_operation().set(&encoded_op); - } - - /// Clears the currently stored operation. This is for internal use. - #[inline] - fn clear_operation(&self) { - self.current_ongoing_operation().clear(); - } - - #[storage_mapper("ongoing_operation:currentOngoingOperation")] - fn current_ongoing_operation(&self) -> SingleValueMapper; -} diff --git a/modules/src/only_admin.rs b/modules/src/only_admin.rs deleted file mode 100644 index 989b1071..00000000 --- a/modules/src/only_admin.rs +++ /dev/null @@ -1,34 +0,0 @@ -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait OnlyAdminModule { - #[view(isAdmin)] - fn is_admin(&self, address: ManagedAddress) -> bool { - self.admins().contains(&address) - } - - #[only_owner] - #[endpoint(addAdmin)] - fn add_admin(&self, address: ManagedAddress) { - self.admins().insert(address); - // TODO: event - } - - #[only_owner] - #[endpoint(removeAdmin)] - fn remove_admin(&self, address: ManagedAddress) { - self.admins().swap_remove(&address); - // TODO: event - } - - #[view(getAdmins)] - #[storage_mapper("only_admin_module:admins")] - fn admins(&self) -> UnorderedSetMapper; - - fn require_caller_is_admin(&self) { - require!( - self.is_admin(self.blockchain().get_caller()), - "Endpoint can only be called by admins" - ); - } -} diff --git a/modules/src/pause.rs b/modules/src/pause.rs deleted file mode 100644 index a02be8b5..00000000 --- a/modules/src/pause.rs +++ /dev/null @@ -1,54 +0,0 @@ -multiversx_sc::imports!(); - -/// Standard smart contract module that, when added to a smart contract, offers pausability. -/// -/// It provides a flag that contracts can use to check if owner decided to pause the entire contract. -/// Use the features module for more granular on/off switches. -/// -/// It offers: -/// * an endpoint where the owner can pause/unpause contract -/// * a method to check if contract is paused or not -/// -#[multiversx_sc::module] -pub trait PauseModule { - #[inline] - fn is_paused(&self) -> bool { - self.paused_status().get() - } - - #[inline] - fn not_paused(&self) -> bool { - !self.is_paused() - } - - #[inline] - fn set_paused(&self, paused: bool) { - self.paused_status().set(paused); - } - - #[only_owner] - #[endpoint(pause)] - fn pause_endpoint(&self) { - self.set_paused(true); - // TODO: event - } - - #[only_owner] - #[endpoint(unpause)] - fn unpause_endpoint(&self) { - self.set_paused(false); - // TODO: event - } - - fn require_paused(&self) { - require!(self.is_paused(), "Contract is not paused"); - } - - fn require_not_paused(&self) { - require!(self.not_paused(), "Contract is paused"); - } - - #[view(isPaused)] - #[storage_mapper("pause_module:paused")] - fn paused_status(&self) -> SingleValueMapper; -} diff --git a/modules/src/staking.rs b/modules/src/staking.rs deleted file mode 100644 index 348739eb..00000000 --- a/modules/src/staking.rs +++ /dev/null @@ -1,174 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[derive(TopEncode, TopDecode)] -pub struct TokenAmountPair { - pub token_id: TokenIdentifier, - pub amount: BigUint, -} - -static NOT_ENOUGH_STAKE_ERR_MSG: &[u8] = b"Not enough stake"; - -#[multiversx_sc::module] -pub trait StakingModule { - fn init_staking_module( - &self, - staking_token: &EgldOrEsdtTokenIdentifier, - staking_amount: &BigUint, - slash_amount: &BigUint, - slash_quorum: usize, - user_whitelist: &ManagedVec, - ) { - let nr_board_members = user_whitelist.len(); - require!(nr_board_members > 0, "No board members"); - require!( - slash_quorum <= nr_board_members, - "Quorum higher than total possible board members" - ); - require!( - staking_amount > &0 && slash_amount > &0, - "Staking and slash amount cannot be 0" - ); - require!( - slash_amount <= staking_amount, - "Slash amount cannot be higher than required stake" - ); - - self.staking_token().set(staking_token); - self.required_stake_amount().set(staking_amount); - self.slash_amount().set(slash_amount); - self.slash_quorum().set(slash_quorum); - - for user in user_whitelist { - let _ = self.user_whitelist().insert(user); - } - } - - #[payable("*")] - #[endpoint] - fn stake(&self) { - let (payment_token, payment_amount) = self.call_value().egld_or_single_fungible_esdt(); - let staking_token = self.staking_token().get(); - require!(payment_token == staking_token, "Invalid payment token"); - - let caller = self.blockchain().get_caller(); - require!( - self.user_whitelist().contains(&caller), - "Only whitelisted members can stake" - ); - - self.staked_amount(&caller) - .update(|amt| *amt += payment_amount); - } - - #[endpoint] - fn unstake(&self, unstake_amount: BigUint) { - let caller = self.blockchain().get_caller(); - let staked_amount_mapper = self.staked_amount(&caller); - let staked_amount = staked_amount_mapper.get(); - require!(unstake_amount <= staked_amount, NOT_ENOUGH_STAKE_ERR_MSG); - - let leftover_amount = &staked_amount - &unstake_amount; - let required_stake_amount = self.required_stake_amount().get(); - if self.user_whitelist().contains(&caller) { - require!( - leftover_amount >= required_stake_amount, - NOT_ENOUGH_STAKE_ERR_MSG - ); - } - - staked_amount_mapper.set(&leftover_amount); - - let staking_token = self.staking_token().get(); - self.send() - .direct(&caller, &staking_token, 0, &unstake_amount); - } - - #[endpoint(voteSlashMember)] - fn vote_slash_member(&self, member_to_slash: ManagedAddress) { - require!( - self.is_staked_board_member(&member_to_slash), - "Voted user is not a staked board member" - ); - - let caller = self.blockchain().get_caller(); - require!( - self.is_staked_board_member(&caller), - NOT_ENOUGH_STAKE_ERR_MSG - ); - - let _ = self - .slashing_proposal_voters(&member_to_slash) - .insert(caller); - } - - #[endpoint(slashMember)] - fn slash_member(&self, member_to_slash: ManagedAddress) { - let quorum = self.slash_quorum().get(); - let mut slashing_voters_mapper = self.slashing_proposal_voters(&member_to_slash); - require!(slashing_voters_mapper.len() >= quorum, "Quorum not reached"); - - let slash_amount = self.slash_amount().get(); - self.staked_amount(&member_to_slash) - .update(|amt| *amt -= &slash_amount); - self.total_slashed_amount() - .update(|total| *total += slash_amount); - - slashing_voters_mapper.clear(); - } - - fn is_staked_board_member(&self, user: &ManagedAddress) -> bool { - let required_stake = self.required_stake_amount().get(); - let user_stake = self.staked_amount(user).get(); - - self.user_whitelist().contains(user) && user_stake >= required_stake - } - - #[inline] - fn add_board_member(&self, user: ManagedAddress) { - let _ = self.user_whitelist().insert(user); - } - - fn remove_board_member(&self, user: &ManagedAddress) { - let mut whitelist_mapper = self.user_whitelist(); - let was_whitelisted = whitelist_mapper.swap_remove(user); - if !was_whitelisted { - return; - } - - // remove user's votes as well - for board_member in whitelist_mapper.iter() { - let _ = self - .slashing_proposal_voters(&board_member) - .swap_remove(user); - } - self.slashing_proposal_voters(user).clear(); - } - - #[storage_mapper("staking_module:stakingToken")] - fn staking_token(&self) -> SingleValueMapper; - - #[storage_mapper("staking_module:requiredStakeAmount")] - fn required_stake_amount(&self) -> SingleValueMapper; - - #[storage_mapper("staking_module:userWhitelist")] - fn user_whitelist(&self) -> UnorderedSetMapper; - - #[storage_mapper("staking_module:stakedAmount")] - fn staked_amount(&self, user: &ManagedAddress) -> SingleValueMapper; - - #[storage_mapper("staking_module:slashingProposalVoters")] - fn slashing_proposal_voters( - &self, - slash_address: &ManagedAddress, - ) -> UnorderedSetMapper; - - #[storage_mapper("staking_module:slashQuorum")] - fn slash_quorum(&self) -> SingleValueMapper; - - #[storage_mapper("staking_module:slashAmount")] - fn slash_amount(&self) -> SingleValueMapper; - - #[storage_mapper("staking_module:totalSlashedAmount")] - fn total_slashed_amount(&self) -> SingleValueMapper; -} diff --git a/modules/src/token_merge/custom_merged_token_attributes.rs b/modules/src/token_merge/custom_merged_token_attributes.rs deleted file mode 100644 index 0585685f..00000000 --- a/modules/src/token_merge/custom_merged_token_attributes.rs +++ /dev/null @@ -1,65 +0,0 @@ -multiversx_sc::imports!(); - -use core::marker::PhantomData; - -use multiversx_sc::codec::Empty; - -use super::merged_token_instances::MergedTokenInstances; - -pub trait AllMergeScTraits = super::merged_token_setup::MergedTokenSetupModule - + crate::default_issue_callbacks::DefaultIssueCallbacksModule - + crate::pause::PauseModule; - -pub trait MergedTokenAttributesCreator { - type ScType: AllMergeScTraits; - type AttributesType: TopEncode + TopDecode; - - fn get_merged_token_attributes( - &self, - sc: &Self::ScType, - merged_token_id: &TokenIdentifier<::Api>, - merged_token_raw_attributes: &MergedTokenInstances<::Api>, - ) -> Self::AttributesType; -} - -pub struct DefaultMergedAttributesWrapper { - _phantom: PhantomData, -} - -impl DefaultMergedAttributesWrapper -where - Sc: AllMergeScTraits, -{ - #[inline] - pub fn new() -> Self { - Self { - _phantom: PhantomData, - } - } -} - -impl Default for DefaultMergedAttributesWrapper -where - Sc: AllMergeScTraits, -{ - fn default() -> Self { - Self::new() - } -} - -impl MergedTokenAttributesCreator for DefaultMergedAttributesWrapper -where - Sc: AllMergeScTraits, -{ - type ScType = Sc; - type AttributesType = Empty; - - fn get_merged_token_attributes( - &self, - _sc: &Self::ScType, - _merged_token_id: &TokenIdentifier<::Api>, - _merged_token_raw_attributes: &MergedTokenInstances<::Api>, - ) -> Self::AttributesType { - Empty - } -} diff --git a/modules/src/token_merge/merged_token_instances.rs b/modules/src/token_merge/merged_token_instances.rs deleted file mode 100644 index 473af1f5..00000000 --- a/modules/src/token_merge/merged_token_instances.rs +++ /dev/null @@ -1,115 +0,0 @@ -use core::ops::Deref; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub const MAX_MERGED_TOKENS: usize = 25; - -pub static TOO_MANY_TOKENS_ERR_MSG: &[u8] = b"Too many tokens to merge"; -pub static INSUFFICIENT_BALANCE_IN_MERGED_INST_ERR_MSG: &[u8] = - b"Insufficient token balance to deduct from merged instance"; - -pub type InstanceArray = ArrayVec, MAX_MERGED_TOKENS>; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MergedTokenInstances { - instances: InstanceArray, -} - -impl Default for MergedTokenInstances { - #[inline] - fn default() -> Self { - Self { - instances: ArrayVec::new(), - } - } -} - -impl MergedTokenInstances { - #[inline] - pub fn new() -> Self { - Self::default() - } - - #[inline] - pub fn new_from_instances(instances: InstanceArray) -> Self { - Self { instances } - } - - pub fn decode_from_first_uri(uris: &ManagedVec>) -> Self { - let first_uri = uris.get(0); - let decode_result = InstanceArray::::top_decode(first_uri.deref().clone()); - match decode_result { - core::result::Result::Ok(instances) => Self::new_from_instances(instances), - core::result::Result::Err(_) => { - M::error_api_impl().signal_error(b"Error decoding tokens from URI") - }, - } - } - - #[inline] - pub fn get_instances(&self) -> &InstanceArray { - &self.instances - } - - pub fn add_or_update_instance(&mut self, new_instance: EsdtTokenPayment) { - let search_result = - self.find_instance(&new_instance.token_identifier, new_instance.token_nonce); - match search_result { - Some(existing_index) => { - self.instances[existing_index].amount += new_instance.amount; - }, - None => { - if self.instances.len() >= MAX_MERGED_TOKENS { - M::error_api_impl().signal_error(TOO_MANY_TOKENS_ERR_MSG); - } - - unsafe { - self.instances.push_unchecked(new_instance); - } - }, - } - } - - pub fn merge_with_other(&mut self, other: Self) { - for inst in other.instances { - self.add_or_update_instance(inst); - } - } - - pub fn deduct_balance_for_instance(&mut self, tokens_to_deduct: &EsdtTokenPayment) { - let search_result = self.find_instance( - &tokens_to_deduct.token_identifier, - tokens_to_deduct.token_nonce, - ); - match search_result { - Some(index) => { - let found_instance = &mut self.instances[index]; - if tokens_to_deduct.amount == 0 || found_instance.amount < tokens_to_deduct.amount { - M::error_api_impl().signal_error(INSUFFICIENT_BALANCE_IN_MERGED_INST_ERR_MSG); - } - - found_instance.amount -= &tokens_to_deduct.amount; - if found_instance.amount == 0 { - let _ = self.instances.remove(index); - } - }, - None => M::error_api_impl().signal_error(INSUFFICIENT_BALANCE_IN_MERGED_INST_ERR_MSG), - } - } - - #[inline] - pub fn into_instances(self) -> InstanceArray { - self.instances - } - - fn find_instance( - &self, - original_token_id: &TokenIdentifier, - original_token_nonce: u64, - ) -> Option { - self.instances.iter().position(|item| { - &item.token_identifier == original_token_id && item.token_nonce == original_token_nonce - }) - } -} diff --git a/modules/src/token_merge/merged_token_setup.rs b/modules/src/token_merge/merged_token_setup.rs deleted file mode 100644 index f99618e0..00000000 --- a/modules/src/token_merge/merged_token_setup.rs +++ /dev/null @@ -1,149 +0,0 @@ -multiversx_sc::imports!(); - -use super::{ - custom_merged_token_attributes::MergedTokenAttributesCreator, - merged_token_instances::{MergedTokenInstances, MAX_MERGED_TOKENS}, -}; - -const NFT_AMOUNT: u64 = 1; -pub static DIFFERENT_CREATOR_ERR_MSG: &[u8] = b"All merged tokens must have the same creator"; - -#[multiversx_sc::module] -pub trait MergedTokenSetupModule { - #[only_owner] - #[payable("EGLD")] - #[endpoint(issueMergedToken)] - fn issue_merged_token(&self, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer) { - let payment_amount = self.call_value().egld_value(); - self.merged_token().issue_and_set_all_roles( - EsdtTokenType::NonFungible, - payment_amount.clone_value(), - token_display_name, - token_ticker, - 0, - None, - ); - } - - #[only_owner] - #[endpoint(addMergeableTokensToWhitelist)] - fn add_mergeable_tokens_to_whitelist(&self, tokens: MultiValueEncoded) { - let mut whitelist = self.mergeable_tokens_whitelist(); - for token in tokens { - let _ = whitelist.insert(token); - } - } - - #[only_owner] - #[endpoint(removeMergeableTokensFromWhitelist)] - fn remove_mergeable_tokens_from_whitelist(&self, tokens: MultiValueEncoded) { - let mut whitelist = self.mergeable_tokens_whitelist(); - for token in tokens { - let _ = whitelist.swap_remove(&token); - } - } - - fn create_merged_token>( - &self, - merged_token_id: TokenIdentifier, - merged_instances: &MergedTokenInstances, - attr_creator: &AttributesCreator, - ) -> EsdtTokenPayment { - let nft_amount = BigUint::from(NFT_AMOUNT); - let empty_buffer = ManagedBuffer::new(); - - let all_token_data = self.collect_token_data(merged_instances); - self.require_all_parts_same_creator(&all_token_data); - - let royalties = self.get_max_royalties(&all_token_data); - let uri = self.create_uri_for_merged_token(merged_instances); - let attributes = - attr_creator.get_merged_token_attributes(self, &merged_token_id, merged_instances); - let merged_token_nonce = self.send().esdt_nft_create( - &merged_token_id, - &nft_amount, - &empty_buffer, - &royalties, - &empty_buffer, - &attributes, - &ManagedVec::from_single_item(uri), - ); - - EsdtTokenPayment::new(merged_token_id, merged_token_nonce, nft_amount) - } - - fn create_uri_for_merged_token( - &self, - merged_instances: &MergedTokenInstances, - ) -> ManagedBuffer { - let mut tokens_list = ManagedVec::::new(); - for inst in merged_instances.get_instances() { - tokens_list.push(inst.clone()); - } - - let mut encoded = ManagedBuffer::new(); - let _ = tokens_list.top_encode(&mut encoded); - - encoded - } - - fn collect_token_data( - &self, - merged_instances: &MergedTokenInstances, - ) -> ArrayVec, MAX_MERGED_TOKENS> { - let mut all_token_data = ArrayVec::new(); - let own_sc_address = self.blockchain().get_sc_address(); - for inst in merged_instances.get_instances() { - let token_data = self.blockchain().get_esdt_token_data( - &own_sc_address, - &inst.token_identifier, - inst.token_nonce, - ); - unsafe { - all_token_data.push_unchecked(token_data); - } - } - - all_token_data - } - - fn require_all_parts_same_creator( - &self, - all_token_data: &ArrayVec, MAX_MERGED_TOKENS>, - ) { - if all_token_data.is_empty() { - return; - } - - let first_creator = unsafe { &all_token_data.get_unchecked(0).creator }; - for token_data in &all_token_data.as_slice()[1..] { - require!( - &token_data.creator == first_creator, - DIFFERENT_CREATOR_ERR_MSG - ); - } - } - - fn get_max_royalties( - &self, - all_token_data: &ArrayVec, MAX_MERGED_TOKENS>, - ) -> BigUint { - let zero = BigUint::zero(); - let mut max_ref = &zero; - for token_data in all_token_data { - if &token_data.royalties > max_ref { - max_ref = &token_data.royalties; - } - } - - max_ref.clone() - } - - #[view(getMergedTokenId)] - #[storage_mapper("mergedToken")] - fn merged_token(&self) -> NonFungibleTokenMapper; - - #[view(getMergeableTokensWhitelist)] - #[storage_mapper("mergeableTokensWhitelist")] - fn mergeable_tokens_whitelist(&self) -> UnorderedSetMapper; -} diff --git a/modules/src/token_merge/mod.rs b/modules/src/token_merge/mod.rs deleted file mode 100644 index 024626e2..00000000 --- a/modules/src/token_merge/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub mod custom_merged_token_attributes; -pub mod merged_token_instances; -pub mod merged_token_setup; - -use merged_token_instances::{MergedTokenInstances, MAX_MERGED_TOKENS}; - -use self::custom_merged_token_attributes::MergedTokenAttributesCreator; - -static SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG: &[u8] = b"NFT parts belong to another merging SC"; - -const MIN_MERGE_PAYMENTS: usize = 2; - -#[multiversx_sc::module] -pub trait TokenMergeModule: - merged_token_setup::MergedTokenSetupModule - + crate::default_issue_callbacks::DefaultIssueCallbacksModule - + crate::pause::PauseModule -{ - fn merge_tokens>( - &self, - payments: &ManagedVec, - attr_creator: &AttributesCreator, - ) -> EsdtTokenPayment { - self.require_not_paused(); - require!( - payments.len() >= MIN_MERGE_PAYMENTS, - "Must send at least 2 tokens" - ); - - let merged_token_id = self.merged_token().get_token_id(); - let sc_address = self.blockchain().get_sc_address(); - let token_whitelist = self.mergeable_tokens_whitelist(); - - let mut already_merged_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new(); - let mut single_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new(); - for token in payments { - if token.token_identifier == merged_token_id { - let token_data = self.blockchain().get_esdt_token_data( - &sc_address, - &token.token_identifier, - token.token_nonce, - ); - - require!( - token_data.creator == sc_address, - SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG - ); - - let merged_instances = - MergedTokenInstances::decode_from_first_uri(&token_data.uris); - already_merged_tokens.push(merged_instances); - } else { - require!( - token_whitelist.contains(&token.token_identifier), - "Token {} cannot be merged", - (token.token_identifier) - ); - - single_tokens.push(token); - } - } - - let mut all_merged_instances = MergedTokenInstances::new(); - for already_merged in already_merged_tokens { - all_merged_instances.merge_with_other(already_merged); - } - - for single_token_instance in single_tokens { - all_merged_instances.add_or_update_instance(single_token_instance); - } - - let merged_token_payment = - self.create_merged_token(merged_token_id, &all_merged_instances, attr_creator); - let caller = self.blockchain().get_caller(); - self.send() - .direct_non_zero_esdt_payment(&caller, &merged_token_payment); - - merged_token_payment - } - - fn split_tokens( - &self, - payments: &ManagedVec, - ) -> ManagedVec { - self.require_not_paused(); - require!(!payments.is_empty(), "No payments"); - - let merged_token_id = self.merged_token().get_token_id(); - let sc_address = self.blockchain().get_sc_address(); - - let mut output_payments = ManagedVec::new(); - for token in payments { - require!( - token.token_identifier == merged_token_id, - "Invalid token to split" - ); - - let token_data = self.blockchain().get_esdt_token_data( - &sc_address, - &token.token_identifier, - token.token_nonce, - ); - require!( - token_data.creator == sc_address, - SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG - ); - - let previously_merged_instance_attributes = - MergedTokenInstances::decode_from_first_uri(&token_data.uris); - for inst in previously_merged_instance_attributes.into_instances() { - output_payments.push(inst); - } - - self.send() - .esdt_local_burn(&token.token_identifier, token.token_nonce, &token.amount); - } - - let caller = self.blockchain().get_caller(); - self.send().direct_multi(&caller, &output_payments); - - output_payments - } - - fn split_token_partial>( - &self, - merged_token: EsdtTokenPayment, - mut tokens_to_remove: ManagedVec, - attr_creator: &AttributesCreator, - ) -> ManagedVec { - self.require_not_paused(); - self.merged_token() - .require_same_token(&merged_token.token_identifier); - - let sc_address = self.blockchain().get_sc_address(); - let merged_token_data = self.blockchain().get_esdt_token_data( - &sc_address, - &merged_token.token_identifier, - merged_token.token_nonce, - ); - require!( - merged_token_data.creator == sc_address, - SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG - ); - - let mut merged_attributes = - MergedTokenInstances::decode_from_first_uri(&merged_token_data.uris); - for token in &tokens_to_remove { - merged_attributes.deduct_balance_for_instance(&token); - } - - self.send().esdt_local_burn( - &merged_token.token_identifier, - merged_token.token_nonce, - &merged_token.amount, - ); - - // all removed tokens get sent to user, so we can re-use this as output payments - let new_merged_token = self.create_merged_token( - merged_token.token_identifier, - &merged_attributes, - attr_creator, - ); - tokens_to_remove.push(new_merged_token); - - let caller = self.blockchain().get_caller(); - self.send().direct_multi(&caller, &tokens_to_remove); - - tokens_to_remove - } -} diff --git a/modules/src/token_merge/readme.md b/modules/src/token_merge/readme.md deleted file mode 100644 index b5f5f33b..00000000 --- a/modules/src/token_merge/readme.md +++ /dev/null @@ -1,30 +0,0 @@ -# NFTs owning NFTs - -Currently MultiversX has a set of built in functions for NFTs: token manager, create, burn, update, mint. It implements transfer and execute, multi transfer. Smart contracts can own NFTs, they can create new NFTs, can update attributes, update URIs. - -Equipping. Our goal is to be able to equip NFTs with other NFTs, enabling ownership, attribute updates and some extra features. This can be done through a new SC standard - we will find some name for it - for now let’s call it MergeNFT. The basic contract should be as simple as possible, as it is a small standard. On top of that, developers and the market can create multiple usecases. So in the first place we will focus on building the MINIMUM VIABLE SMART CONTRACT to enable the NFTs owning NFTs feature. - -Let’s think about the first usecase: user owns a character and buys a sword for it. He wants to equip that sword to the character and have a new image with both of the things combined. There is no game involved, it is only about 2 NFTs, each of them having one image, a set of attributes, and royalties. - -The user makes a multiESDTNFTTransfer for those 2 NFTs to the new contract, the contract keeps the 2 NFTs and will send the user a new one, which contains some merged information on those 2. We do not need to duplicate the information of URIs, Attributes as those are already stored in the blockchain - we need to only create the smallest possible information through which every TOOL (marketplaces, indexer, xSpotLight, API, etc) will understand and can show the results of such merge. Furthermore, merging and selling a merged NFT should send the royalties back to the original owners - this is somewhat harder to do - but let’s try it - discussing a little bit later. If the merging of tokens is from the same artist there is no problem as royalties go to the same place. So in order to help creators we will check if the address of royalties for the selected NFTs is the same. To make it clear for users, we could even check that the address of royalties is the same as the owner of the SC. - -## MergeNFTs: -Take the list of NFTs from the multiESDTNFTTransfer, check that all NFTs have the same address for royalties (this can be eliminated in some usecases). Create a new NFT with the following information: -Royalties - it has to be the maximum of all the NFTs merged. -Hash - nothing -Attributes - open ended - up to developer to do what he wants -URIs - list of [TokenID, nonce and value] as first URI - -No need to keep any storage in the contract. - -## UnMergeNFTs: -User comes with a merged NFT. The contract will check if the tokenID is the one the contract generated. The contract takes the list of NFTs from the URIs of the merged NFT, burns the NFT and sends back as multiTransferESDT the list of tokens from the attributes. - -## Royalties: -This contract has to implement the claimRoyalties module - as will create NFTs and needs to claim royalties from marketplaces. -Distribute royalties: needs a new function to distribute the royalties to the original royalty owner. - -In a sense of integration, we need to register these contracts - using the contract address, hash, tokenID in API in order to make sure the mergedNFTs will appear as they should be. - -## NFTs owning SFTs: -As you can see from the above resolution, there is no limitation of NFTs owning actual SFTs. So your character could own 10 mana potions, 100 apples, or stuff like that - everything being encoded in the attributes. diff --git a/modules/src/transfer_role_proxy.rs b/modules/src/transfer_role_proxy.rs deleted file mode 100644 index 80995eff..00000000 --- a/modules/src/transfer_role_proxy.rs +++ /dev/null @@ -1,129 +0,0 @@ -use multiversx_sc::codec::TopEncodeMulti; - -multiversx_sc::imports!(); - -const CALLBACK_RESERVED_GAS_PER_TOKEN: u64 = 1_000_000; -static ERR_CALLBACK_MSG: &[u8] = b"Error received in callback:"; - -pub type PaymentsVec = ManagedVec>; - -#[multiversx_sc::module] -pub trait TransferRoleProxyModule { - fn transfer_to_user( - &self, - original_caller: ManagedAddress, - dest: ManagedAddress, - payments: PaymentsVec, - data: ManagedBuffer, - ) -> ! { - let contract_call = - ContractCallWithMultiEsdt::::new(dest, data, payments.clone()); - - self.execute_async_call(original_caller, payments, contract_call, None); - } - - fn transfer_to_contract_typed_call( - &self, - original_caller: ManagedAddress, - contract_call: ContractCallWithMultiEsdt, - opt_custom_callback: Option>, - ) -> ! - where - T: TopEncodeMulti, - { - self.execute_async_call( - original_caller, - contract_call.esdt_payments.clone(), - contract_call, - opt_custom_callback, - ); - } - - fn transfer_to_contract_raw( - &self, - original_caller: ManagedAddress, - dest: ManagedAddress, - payments: PaymentsVec, - endpoint_name: ManagedBuffer, - args: ManagedArgBuffer, - opt_custom_callback: Option>, - ) -> ! { - let contract_call = - ContractCallWithMultiEsdt::::new(dest, endpoint_name, payments.clone()) - .with_raw_arguments(args); - - self.execute_async_call( - original_caller, - payments, - contract_call, - opt_custom_callback, - ); - } - - fn execute_async_call( - &self, - original_caller: ManagedAddress, - initial_payments: PaymentsVec, - contract_call: ContractCallWithMultiEsdt, - opt_custom_callback: Option>, - ) -> ! - where - T: TopEncodeMulti, - { - require!( - self.destination_whitelist() - .contains(&contract_call.basic.to), - "Destination address not whitelisted" - ); - - let remaining_gas = self.blockchain().get_gas_left(); - let cb_gas_needed = - CALLBACK_RESERVED_GAS_PER_TOKEN * contract_call.esdt_payments.len() as u64; - require!( - remaining_gas > cb_gas_needed, - "Not enough gas to launch async call" - ); - - let async_call_gas = remaining_gas - cb_gas_needed; - let cb = match opt_custom_callback { - Some(custom_cb) => custom_cb, - None => TransferRoleProxyModule::callbacks(self) - .transfer_callback(original_caller, initial_payments), - }; - - contract_call - .with_gas_limit(async_call_gas) - .async_call() - .with_callback(cb) - .call_and_exit() - } - - #[callback] - fn transfer_callback( - &self, - original_caller: ManagedAddress, - initial_payments: ManagedVec>, - #[call_result] result: ManagedAsyncCallResult>, - ) -> MultiValueEncoded { - match result { - ManagedAsyncCallResult::Ok(return_values) => return_values, - ManagedAsyncCallResult::Err(err) => { - if !initial_payments.is_empty() { - self.send() - .direct_multi(&original_caller, &initial_payments); - } - - let mut err_result = MultiValueEncoded::new(); - err_result.push(ManagedBuffer::new_from_bytes(ERR_CALLBACK_MSG)); - err_result.push(err.err_msg.clone()); - - sc_print!("{}", err.err_msg); - - err_result - }, - } - } - - #[storage_mapper("transfer_role_proxy:destination_whitelist")] - fn destination_whitelist(&self) -> UnorderedSetMapper; -} diff --git a/modules/src/users.rs b/modules/src/users.rs deleted file mode 100644 index cf4d52a0..00000000 --- a/modules/src/users.rs +++ /dev/null @@ -1,63 +0,0 @@ -multiversx_sc::imports!(); - -/// Standard smart contract module that when added to a smart contract manages a list of users. -/// -/// It was created before there was such a thing as storage mappers. -/// The `UserMapper` is a more elegant solution nowadays that solves the same problem. -/// -/// It provides a bi-directional map: -/// * from user address to a unique user id -/// * from user id to address -#[multiversx_sc::module] -pub trait UsersModule { - /// Each user gets a user id. This is in order to be able to iterate over their data. - /// This is a mapping from user address to user id. - /// The key is the bytes "user_id" concatenated with their public key. - /// The value is the user id. - #[view(getUserId)] - #[storage_get("user_id")] - fn get_user_id(&self, address: &ManagedAddress) -> usize; - - #[storage_set("user_id")] - fn set_user_id(&self, address: &ManagedAddress, user_id: usize); - - #[view(getUserAddress)] - #[storage_get("user_address")] - fn get_user_address(&self, user_id: usize) -> ManagedAddress; - - #[storage_set("user_address")] - fn set_user_address(&self, user_id: usize, address: &ManagedAddress); - - /// Retrieves the number of delegtors, including the owner, - /// even if they no longer have anything in the contract. - #[view(getNumUsers)] - #[storage_get("num_users")] - fn get_num_users(&self) -> usize; - - /// Yields how accounts are registered in the contract. - /// Note that not all of them must have stakes greater than zero. - #[storage_set("num_users")] - fn set_num_users(&self, num_users: usize); - - fn get_or_create_user(&self, address: &ManagedAddress) -> usize { - let mut user_id = self.get_user_id(address); - if user_id == 0 { - let mut num_users = self.get_num_users(); - num_users += 1; - self.set_num_users(num_users); - user_id = num_users; - self.set_user_id(address, user_id); - self.set_user_address(user_id, address); - } - user_id - } - - #[endpoint(updateUserAddress)] - fn update_user_address(&self, addresses: MultiValueEncoded) { - for address in &addresses.to_vec() { - let user_id = self.get_user_id(&address); - require!(user_id > 0, "unknown address"); - self.set_user_address(user_id, &address); - } - } -}