casper_storage/system/
mint.rs

1pub(crate) mod detail;
2/// Provides native mint processing.
3mod mint_native;
4/// Provides runtime logic for mint processing.
5pub mod runtime_provider;
6/// Provides storage logic for mint processing.
7pub mod storage_provider;
8/// Provides system logic for mint processing.
9pub mod system_provider;
10
11use num_rational::Ratio;
12use num_traits::CheckedMul;
13
14use casper_types::{
15    account::AccountHash,
16    system::{
17        mint::{Error, ROUND_SEIGNIORAGE_RATE_KEY, TOTAL_SUPPLY_KEY},
18        Caller,
19    },
20    Key, PublicKey, URef, U512,
21};
22
23use crate::system::mint::{
24    runtime_provider::RuntimeProvider, storage_provider::StorageProvider,
25    system_provider::SystemProvider,
26};
27
28/// Mint trait.
29pub trait Mint: RuntimeProvider + StorageProvider + SystemProvider {
30    /// Mint new token with given `initial_balance` balance. Returns new purse on success, otherwise
31    /// an error.
32    fn mint(&mut self, initial_balance: U512) -> Result<URef, Error> {
33        let caller = self.get_caller();
34        let is_empty_purse = initial_balance.is_zero();
35        if !is_empty_purse && caller != PublicKey::System.to_account_hash() {
36            return Err(Error::InvalidNonEmptyPurseCreation);
37        }
38
39        let purse_uref: URef = self.new_uref(())?;
40        self.write_balance(purse_uref, initial_balance)?;
41
42        if !is_empty_purse {
43            // get total supply uref if exists, otherwise error
44            let total_supply_uref = match self.get_key(TOTAL_SUPPLY_KEY) {
45                None => {
46                    // total supply URef should exist due to genesis
47                    return Err(Error::TotalSupplyNotFound);
48                }
49                Some(Key::URef(uref)) => uref,
50                Some(_) => return Err(Error::MissingKey),
51            };
52            // increase total supply
53            self.add(total_supply_uref, initial_balance)?;
54        }
55
56        Ok(purse_uref)
57    }
58
59    /// Burns native tokens.
60    fn burn(&mut self, purse: URef, amount: U512) -> Result<(), Error> {
61        if !purse.is_writeable() {
62            return Err(Error::InvalidAccessRights);
63        }
64        if !self.is_valid_uref(&purse) {
65            return Err(Error::ForgedReference);
66        }
67
68        let source_available_balance: U512 = match self.balance(purse)? {
69            Some(source_balance) => source_balance,
70            None => return Err(Error::PurseNotFound),
71        };
72
73        let new_balance = source_available_balance
74            .checked_sub(amount)
75            .unwrap_or_else(U512::zero);
76        // change balance
77        self.write_balance(purse, new_balance)?;
78        // reduce total supply AFTER changing balance in case changing balance errors
79        let burned_amount = source_available_balance.saturating_sub(new_balance);
80        detail::reduce_total_supply_unsafe(self, burned_amount)
81    }
82
83    /// Reduce total supply by `amount`. Returns unit on success, otherwise
84    /// an error.
85    fn reduce_total_supply(&mut self, amount: U512) -> Result<(), Error> {
86        // only system may reduce total supply
87        let caller = self.get_caller();
88        if caller != PublicKey::System.to_account_hash() {
89            return Err(Error::InvalidTotalSupplyReductionAttempt);
90        }
91
92        detail::reduce_total_supply_unsafe(self, amount)
93    }
94
95    /// Read balance of given `purse`.
96    fn balance(&mut self, purse: URef) -> Result<Option<U512>, Error> {
97        match self.available_balance(purse)? {
98            some @ Some(_) => Ok(some),
99            None => Err(Error::PurseNotFound),
100        }
101    }
102
103    /// Transfers `amount` of tokens from `source` purse to a `target` purse.
104    fn transfer(
105        &mut self,
106        maybe_to: Option<AccountHash>,
107        source: URef,
108        target: URef,
109        amount: U512,
110        id: Option<u64>,
111    ) -> Result<(), Error> {
112        if !self.allow_unrestricted_transfers() {
113            let registry = self
114                .get_system_entity_registry()
115                .map_err(|_| Error::UnableToGetSystemRegistry)?;
116            let immediate_caller = self.get_immediate_caller();
117            match immediate_caller {
118                Some(Caller::Entity { entity_addr, .. })
119                    if registry.exists(&entity_addr.value()) =>
120                {
121                    // System contract calling a mint is fine (i.e. standard payment calling mint's
122                    // transfer)
123                }
124
125                Some(Caller::Initiator { account_hash: _ })
126                    if self.is_called_from_standard_payment() =>
127                {
128                    // Standard payment acts as a session without separate stack frame and calls
129                    // into mint's transfer.
130                }
131
132                Some(Caller::Initiator { account_hash })
133                    if account_hash == PublicKey::System.to_account_hash() =>
134                {
135                    // System calls a session code.
136                }
137
138                Some(Caller::Initiator { account_hash }) => {
139                    // For example: a session using transfer host functions, or calling the mint's
140                    // entrypoint directly
141                    let is_source_admin = self.is_administrator(&account_hash);
142                    match maybe_to {
143                        Some(to) => {
144                            let maybe_account = self.runtime_footprint_by_account_hash(to);
145
146                            match maybe_account {
147                                Ok(Some(runtime_footprint)) => {
148                                    // This can happen when user tries to transfer funds by
149                                    // calling mint
150                                    // directly but tries to specify wrong account hash.
151                                    let addr = if let Some(uref) = runtime_footprint.main_purse() {
152                                        uref.addr()
153                                    } else {
154                                        return Err(Error::InvalidContext);
155                                    };
156
157                                    if addr != target.addr() {
158                                        return Err(Error::DisabledUnrestrictedTransfers);
159                                    }
160                                    let is_target_system_account =
161                                        to == PublicKey::System.to_account_hash();
162                                    let is_target_administrator = self.is_administrator(&to);
163                                    if !(is_source_admin
164                                        || is_target_system_account
165                                        || is_target_administrator)
166                                    {
167                                        return Err(Error::DisabledUnrestrictedTransfers);
168                                    }
169                                }
170                                Ok(None) => {
171                                    // `to` is specified, but no new account is persisted
172                                    // yet. Only
173                                    // administrators can do that and it is also validated
174                                    // at the host function level.
175                                    if !is_source_admin {
176                                        return Err(Error::DisabledUnrestrictedTransfers);
177                                    }
178                                }
179                                Err(_) => {
180                                    return Err(Error::Storage);
181                                }
182                            }
183                        }
184                        None => {
185                            if !is_source_admin {
186                                return Err(Error::DisabledUnrestrictedTransfers);
187                            }
188                        }
189                    }
190                }
191
192                Some(Caller::Entity {
193                    package_hash: _,
194                    entity_addr: _,
195                }) => {
196                    if self.get_caller() != PublicKey::System.to_account_hash()
197                        && !self.is_administrator(&self.get_caller())
198                    {
199                        return Err(Error::DisabledUnrestrictedTransfers);
200                    }
201                }
202
203                Some(Caller::SmartContract {
204                    contract_package_hash: _,
205                    contract_hash: _,
206                }) => {
207                    if self.get_caller() != PublicKey::System.to_account_hash()
208                        && !self.is_administrator(&self.get_caller())
209                    {
210                        return Err(Error::DisabledUnrestrictedTransfers);
211                    }
212                }
213
214                None => {
215                    // There's always an immediate caller, but we should return something.
216                    return Err(Error::DisabledUnrestrictedTransfers);
217                }
218            }
219        }
220
221        if !source.is_writeable() || !target.is_addable() {
222            // TODO: I don't think we should enforce is addable on the target
223            // Unlike other uses of URefs (such as a counter), in this context the value represents
224            // a deposit of token. Generally, deposit of a desirable resource is permissive.
225            return Err(Error::InvalidAccessRights);
226        }
227        let source_available_balance: U512 = match self.available_balance(source)? {
228            Some(source_balance) => source_balance,
229            None => return Err(Error::SourceNotFound),
230        };
231        if amount > source_available_balance {
232            // NOTE: we use AVAILABLE balance to check sufficient funds
233            return Err(Error::InsufficientFunds);
234        }
235        let source_total_balance = self.total_balance(source)?;
236        if source_available_balance > source_total_balance {
237            panic!("available balance can never be greater than total balance");
238        }
239        if self.available_balance(target)?.is_none() {
240            return Err(Error::DestNotFound);
241        }
242        let addr = match self.get_main_purse() {
243            None => return Err(Error::InvalidURef),
244            Some(uref) => uref.addr(),
245        };
246        if self.get_caller() != PublicKey::System.to_account_hash() && addr == source.addr() {
247            if amount > self.get_approved_spending_limit() {
248                return Err(Error::UnapprovedSpendingAmount);
249            }
250            self.sub_approved_spending_limit(amount);
251        }
252
253        // NOTE: we use TOTAL balance to determine new balance
254        let new_balance = source_total_balance.saturating_sub(amount);
255        self.write_balance(source, new_balance)?;
256        self.add_balance(target, amount)?;
257        self.record_transfer(maybe_to, source, target, amount, id)?;
258        Ok(())
259    }
260
261    /// Retrieves the base round reward.
262    fn read_base_round_reward(&mut self) -> Result<U512, Error> {
263        let total_supply_uref = match self.get_key(TOTAL_SUPPLY_KEY) {
264            Some(Key::URef(uref)) => uref,
265            Some(_) => return Err(Error::MissingKey),
266            None => return Err(Error::MissingKey),
267        };
268        let total_supply: U512 = self
269            .read(total_supply_uref)?
270            .ok_or(Error::TotalSupplyNotFound)?;
271
272        let round_seigniorage_rate_uref = match self.get_key(ROUND_SEIGNIORAGE_RATE_KEY) {
273            Some(Key::URef(uref)) => uref,
274            Some(_) => return Err(Error::MissingKey),
275            None => return Err(Error::MissingKey),
276        };
277        let round_seigniorage_rate: Ratio<U512> = self
278            .read(round_seigniorage_rate_uref)?
279            .ok_or(Error::TotalSupplyNotFound)?;
280
281        round_seigniorage_rate
282            .checked_mul(&Ratio::from(total_supply))
283            .map(|ratio| ratio.to_integer())
284            .ok_or(Error::ArithmeticOverflow)
285    }
286
287    /// Mint `amount` new token into `existing_purse`.
288    /// Returns unit on success, otherwise an error.
289    fn mint_into_existing_purse(
290        &mut self,
291        existing_purse: URef,
292        amount: U512,
293    ) -> Result<(), Error> {
294        let caller = self.get_caller();
295        if caller != PublicKey::System.to_account_hash() {
296            return Err(Error::InvalidContext);
297        }
298        if amount.is_zero() {
299            // treat as noop
300            return Ok(());
301        }
302        if !self.purse_exists(existing_purse)? {
303            return Err(Error::PurseNotFound);
304        }
305        self.add_balance(existing_purse, amount)?;
306        // get total supply uref if exists, otherwise error.
307        let total_supply_uref = match self.get_key(TOTAL_SUPPLY_KEY) {
308            None => {
309                // total supply URef should exist due to genesis
310                // which obviously must have been called
311                // before new rewards are minted at the end of an era
312                return Err(Error::TotalSupplyNotFound);
313            }
314            Some(Key::URef(uref)) => uref,
315            Some(_) => return Err(Error::MissingKey),
316        };
317        // increase total supply
318        self.add(total_supply_uref, amount)?;
319        Ok(())
320    }
321
322    /// Check if a purse exists.
323    fn purse_exists(&mut self, uref: URef) -> Result<bool, Error>;
324}