1use std::{
3 cmp::Ordering,
4 collections::{BTreeMap, BTreeSet},
5 convert::TryInto,
6};
7
8use num_traits::Zero;
9use once_cell::sync::Lazy;
10use thiserror::Error;
11
12use crate::{
13 context::Context,
14 core::common::quantity::Quantity,
15 handler, migration,
16 module::{self, FeeProxyHandler, Module as _, Parameters as _},
17 modules::{
18 self,
19 core::{Error as CoreError, API as _},
20 },
21 runtime::Runtime,
22 sdk_derive,
23 sender::SenderMeta,
24 state::{CurrentState, Mode},
25 storage::{self, Prefix},
26 types::{
27 address::{Address, SignatureAddressSpec},
28 token,
29 transaction::{AuthInfo, Transaction},
30 },
31};
32
33pub mod fee;
34#[cfg(test)]
35pub(crate) mod test;
36pub mod types;
37
38const MODULE_NAME: &str = "accounts";
40
41const MAX_CHECK_NONCE_FUTURE_DELTA: u64 = 5;
44
45#[derive(Error, Debug, oasis_runtime_sdk_macros::Error)]
47pub enum Error {
48 #[error("invalid argument")]
49 #[sdk_error(code = 1)]
50 InvalidArgument,
51
52 #[error("insufficient balance")]
53 #[sdk_error(code = 2)]
54 InsufficientBalance,
55
56 #[error("forbidden by policy")]
57 #[sdk_error(code = 3)]
58 Forbidden,
59
60 #[error("not found")]
61 #[sdk_error(code = 4)]
62 NotFound,
63
64 #[error("core: {0}")]
65 #[sdk_error(transparent)]
66 Core(#[from] modules::core::Error),
67}
68
69#[derive(Debug, cbor::Encode, oasis_runtime_sdk_macros::Event)]
71#[cbor(untagged)]
72pub enum Event {
73 #[sdk_event(code = 1)]
74 Transfer {
75 from: Address,
76 to: Address,
77 amount: token::BaseUnits,
78 },
79
80 #[sdk_event(code = 2)]
81 Burn {
82 owner: Address,
83 amount: token::BaseUnits,
84 },
85
86 #[sdk_event(code = 3)]
87 Mint {
88 owner: Address,
89 amount: token::BaseUnits,
90 },
91}
92
93#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
95pub struct GasCosts {
96 pub tx_transfer: u64,
97}
98
99#[derive(Clone, Default, Debug, cbor::Encode, cbor::Decode)]
101pub struct Parameters {
102 pub transfers_disabled: bool,
103 pub gas_costs: GasCosts,
104
105 #[cbor(optional)]
106 pub debug_disable_nonce_check: bool,
107
108 #[cbor(optional)]
109 pub denomination_infos: BTreeMap<token::Denomination, types::DenominationInfo>,
110}
111
112#[derive(Error, Debug)]
114pub enum ParameterValidationError {
115 #[error("debug option used: {0}")]
116 DebugOptionUsed(String),
117}
118
119impl module::Parameters for Parameters {
120 type Error = ParameterValidationError;
121
122 #[cfg(not(feature = "unsafe-allow-debug"))]
123 fn validate_basic(&self) -> Result<(), Self::Error> {
124 if self.debug_disable_nonce_check {
125 return Err(ParameterValidationError::DebugOptionUsed(
126 "debug_disable_nonce_check".to_string(),
127 ));
128 }
129
130 Ok(())
131 }
132}
133
134#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
136pub struct Genesis {
137 pub parameters: Parameters,
138 pub accounts: BTreeMap<Address, types::Account>,
139 pub balances: BTreeMap<Address, BTreeMap<token::Denomination, u128>>,
140 pub total_supplies: BTreeMap<token::Denomination, u128>,
141}
142
143pub trait API {
145 fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
147
148 fn transfer_silent(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
150
151 fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
153
154 fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error>;
156
157 fn set_nonce(address: Address, nonce: u64);
159
160 fn get_nonce(address: Address) -> Result<u64, Error>;
162
163 fn inc_nonce(address: Address);
165
166 fn set_balance(address: Address, amount: &token::BaseUnits);
172
173 fn get_balance(address: Address, denomination: token::Denomination) -> Result<u128, Error>;
175
176 fn ensure_balance(address: Address, amount: &token::BaseUnits) -> Result<(), Error> {
178 let balance = Self::get_balance(address, amount.denomination().clone())?;
179 if balance < amount.amount() {
180 Err(Error::InsufficientBalance)
181 } else {
182 Ok(())
183 }
184 }
185
186 fn get_allowance(
194 owner: Address,
195 beneficiary: Address,
196 denomination: token::Denomination,
197 ) -> Result<u128, Error>;
198
199 fn set_allowance(owner: Address, beneficiary: Address, amount: &token::BaseUnits);
202
203 fn get_balances(address: Address) -> Result<types::AccountBalances, Error>;
205
206 fn get_addresses(denomination: token::Denomination) -> Result<Vec<Address>, Error>;
208
209 fn get_total_supplies() -> Result<BTreeMap<token::Denomination, u128>, Error>;
211
212 fn get_total_supply(denomination: token::Denomination) -> Result<u128, Error>;
214
215 fn set_total_supply(amount: &token::BaseUnits);
221
222 fn get_denomination_info(
224 denomination: &token::Denomination,
225 ) -> Result<types::DenominationInfo, Error>;
226
227 fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error>;
229
230 fn set_refund_unused_tx_fee(refund: bool);
233
234 fn take_refund_unused_tx_fee() -> bool;
239
240 fn check_signer_nonces<C: Context>(
243 ctx: &C,
244 tx_auth_info: &AuthInfo,
245 ) -> Result<Address, modules::core::Error>;
246
247 fn update_signer_nonces<C: Context>(
249 ctx: &C,
250 tx_auth_info: &AuthInfo,
251 ) -> Result<(), modules::core::Error>;
252}
253
254pub mod state {
256 pub const ACCOUNTS: &[u8] = &[0x01];
258 pub const BALANCES: &[u8] = &[0x02];
260 pub const TOTAL_SUPPLY: &[u8] = &[0x03];
262 pub const ALLOWANCES: &[u8] = &[0x04];
264}
265
266pub struct Module;
267
268pub static ADDRESS_COMMON_POOL: Lazy<Address> =
272 Lazy::new(|| Address::from_module(MODULE_NAME, "common-pool"));
273pub static ADDRESS_FEE_ACCUMULATOR: Lazy<Address> =
277 Lazy::new(|| Address::from_module(MODULE_NAME, "fee-accumulator"));
278
279#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
281struct AddressWithDenomination(Address, token::Denomination);
282
283#[derive(Error, Debug)]
284enum AWDError {
285 #[error("malformed address")]
286 MalformedAddress,
287
288 #[error("malformed denomination")]
289 MalformedDenomination,
290}
291
292impl std::convert::TryFrom<&[u8]> for AddressWithDenomination {
293 type Error = AWDError;
294
295 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
296 let address =
297 Address::try_from(&bytes[..Address::SIZE]).map_err(|_| AWDError::MalformedAddress)?;
298 let denomination = token::Denomination::try_from(&bytes[Address::SIZE..])
299 .map_err(|_| AWDError::MalformedDenomination)?;
300 Ok(AddressWithDenomination(address, denomination))
301 }
302}
303
304impl Module {
305 fn add_amount(addr: Address, amount: &token::BaseUnits) -> Result<(), Error> {
307 if amount.amount() == 0 {
308 return Ok(());
309 }
310
311 CurrentState::with_store(|store| {
312 let store = storage::PrefixStore::new(store, &MODULE_NAME);
313 let balances = storage::PrefixStore::new(store, &state::BALANCES);
314 let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr));
315 let mut value: u128 = account.get(amount.denomination()).unwrap_or_default();
316
317 value = value
318 .checked_add(amount.amount())
319 .ok_or(Error::InvalidArgument)?;
320 account.insert(amount.denomination(), value);
321 Ok(())
322 })
323 }
324
325 fn sub_amount(addr: Address, amount: &token::BaseUnits) -> Result<(), Error> {
327 if amount.amount() == 0 {
328 return Ok(());
329 }
330
331 CurrentState::with_store(|store| {
332 let store = storage::PrefixStore::new(store, &MODULE_NAME);
333 let balances = storage::PrefixStore::new(store, &state::BALANCES);
334 let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr));
335 let mut value: u128 = account.get(amount.denomination()).unwrap_or_default();
336
337 value = value
338 .checked_sub(amount.amount())
339 .ok_or(Error::InsufficientBalance)?;
340 account.insert(amount.denomination(), value);
341 Ok(())
342 })
343 }
344
345 fn inc_total_supply(amount: &token::BaseUnits) -> Result<(), Error> {
347 if amount.amount() == 0 {
348 return Ok(());
349 }
350
351 CurrentState::with_store(|store| {
352 let store = storage::PrefixStore::new(store, &MODULE_NAME);
353 let mut total_supplies =
354 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
355 let mut total_supply: u128 = total_supplies
356 .get(amount.denomination())
357 .unwrap_or_default();
358
359 total_supply = total_supply
360 .checked_add(amount.amount())
361 .ok_or(Error::InvalidArgument)?;
362 total_supplies.insert(amount.denomination(), total_supply);
363 Ok(())
364 })
365 }
366
367 fn dec_total_supply(amount: &token::BaseUnits) -> Result<(), Error> {
369 if amount.amount() == 0 {
370 return Ok(());
371 }
372
373 CurrentState::with_store(|store| {
374 let store = storage::PrefixStore::new(store, &MODULE_NAME);
375 let mut total_supplies =
376 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
377 let mut total_supply: u128 = total_supplies
378 .get(amount.denomination())
379 .unwrap_or_default();
380
381 total_supply = total_supply
382 .checked_sub(amount.amount())
383 .ok_or(Error::InsufficientBalance)?;
384 total_supplies.insert(amount.denomination(), total_supply);
385 Ok(())
386 })
387 }
388
389 fn get_all_balances() -> Result<BTreeMap<Address, BTreeMap<token::Denomination, u128>>, Error> {
391 CurrentState::with_store(|store| {
392 let store = storage::PrefixStore::new(store, &MODULE_NAME);
393 let balances =
394 storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES));
395
396 let balmap: BTreeMap<AddressWithDenomination, u128> = balances.iter().collect();
401
402 let mut b: BTreeMap<Address, BTreeMap<token::Denomination, u128>> = BTreeMap::new();
403
404 for (addrden, amt) in &balmap {
405 let addr = &addrden.0;
406 let den = &addrden.1;
407
408 let addr_bals = b.entry(*addr).or_default();
410
411 addr_bals
413 .entry(den.clone())
414 .and_modify(|a| *a += amt)
415 .or_insert_with(|| *amt);
416 }
417
418 Ok(b)
419 })
420 }
421}
422
423const CONTEXT_KEY_TX_FEE_REFUND_UNUSED: &str = "accounts.TxRefundUnusedFee";
425const CONTEXT_KEY_FEE_MANAGER: &str = "accounts.FeeManager";
427
428impl API for Module {
429 fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
430 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
431 return Ok(());
432 }
433
434 Self::transfer_silent(from, to, amount)?;
435
436 CurrentState::with(|state| {
438 state.emit_event(Event::Transfer {
439 from,
440 to,
441 amount: amount.clone(),
442 })
443 });
444
445 Ok(())
446 }
447
448 fn transfer_silent(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
449 Self::sub_amount(from, amount)?;
451 Self::add_amount(to, amount)?;
453
454 Ok(())
455 }
456
457 fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
458 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
459 return Ok(());
460 }
461
462 Self::add_amount(to, amount)?;
464
465 Self::inc_total_supply(amount)?;
467
468 CurrentState::with(|state| {
470 state.emit_event(Event::Mint {
471 owner: to,
472 amount: amount.clone(),
473 });
474 });
475
476 Ok(())
477 }
478
479 fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error> {
480 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
481 return Ok(());
482 }
483
484 Self::sub_amount(from, amount)?;
486
487 Self::dec_total_supply(amount)
489 .expect("target account had enough balance so total supply should not underflow");
490
491 CurrentState::with(|state| {
493 state.emit_event(Event::Burn {
494 owner: from,
495 amount: amount.clone(),
496 });
497 });
498
499 Ok(())
500 }
501
502 fn set_nonce(address: Address, nonce: u64) {
503 CurrentState::with_store(|store| {
504 let store = storage::PrefixStore::new(store, &MODULE_NAME);
505 let mut accounts =
506 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
507 let mut account: types::Account = accounts.get(address).unwrap_or_default();
508 account.nonce = nonce;
509 accounts.insert(address, account);
510 })
511 }
512
513 fn get_nonce(address: Address) -> Result<u64, Error> {
514 CurrentState::with_store(|store| {
515 let store = storage::PrefixStore::new(store, &MODULE_NAME);
516 let accounts =
517 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
518 let account: types::Account = accounts.get(address).unwrap_or_default();
519 Ok(account.nonce)
520 })
521 }
522
523 fn inc_nonce(address: Address) {
524 CurrentState::with_store(|store| {
525 let store = storage::PrefixStore::new(store, &MODULE_NAME);
526 let mut accounts =
527 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
528 let mut account: types::Account = accounts.get(address).unwrap_or_default();
529 account.nonce = account.nonce.saturating_add(1);
530 accounts.insert(address, account);
531 })
532 }
533
534 fn set_balance(address: Address, amount: &token::BaseUnits) {
535 CurrentState::with_store(|store| {
536 let store = storage::PrefixStore::new(store, &MODULE_NAME);
537 let balances = storage::PrefixStore::new(store, &state::BALANCES);
538 let mut account =
539 storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
540 account.insert(amount.denomination(), amount.amount());
541 });
542 }
543
544 fn get_balance(address: Address, denomination: token::Denomination) -> Result<u128, Error> {
545 CurrentState::with_store(|store| {
546 let store = storage::PrefixStore::new(store, &MODULE_NAME);
547 let balances = storage::PrefixStore::new(store, &state::BALANCES);
548 let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
549
550 Ok(account.get(denomination).unwrap_or_default())
551 })
552 }
553
554 fn get_balances(address: Address) -> Result<types::AccountBalances, Error> {
555 CurrentState::with_store(|store| {
556 let store = storage::PrefixStore::new(store, &MODULE_NAME);
557 let balances = storage::PrefixStore::new(store, &state::BALANCES);
558 let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
559
560 Ok(types::AccountBalances {
561 balances: account.iter().collect(),
562 })
563 })
564 }
565
566 fn get_allowance(
567 owner: Address,
568 beneficiary: Address,
569 denomination: token::Denomination,
570 ) -> Result<u128, Error> {
571 CurrentState::with_store(|store| {
572 let store = storage::PrefixStore::new(store, &MODULE_NAME);
573 let allowances = storage::PrefixStore::new(store, &state::ALLOWANCES);
574 let for_owner = storage::PrefixStore::new(allowances, &owner);
575 let for_beneficiary =
576 storage::TypedStore::new(storage::PrefixStore::new(for_owner, &beneficiary));
577
578 Ok(for_beneficiary.get(denomination).unwrap_or_default())
579 })
580 }
581
582 fn set_allowance(owner: Address, beneficiary: Address, amount: &token::BaseUnits) {
583 CurrentState::with_store(|store| {
584 let store = storage::PrefixStore::new(store, &MODULE_NAME);
585 let allowances = storage::PrefixStore::new(store, &state::ALLOWANCES);
586 let for_owner = storage::PrefixStore::new(allowances, &owner);
587 let mut for_beneficiary =
588 storage::TypedStore::new(storage::PrefixStore::new(for_owner, &beneficiary));
589
590 for_beneficiary.insert(amount.denomination(), amount.amount());
591 })
592 }
593
594 fn get_addresses(denomination: token::Denomination) -> Result<Vec<Address>, Error> {
595 CurrentState::with_store(|store| {
596 let store = storage::PrefixStore::new(store, &MODULE_NAME);
597 let balances: BTreeMap<AddressWithDenomination, Quantity> =
598 storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES))
599 .iter()
600 .collect();
601
602 Ok(balances
603 .into_keys()
604 .filter(|bal| bal.1 == denomination)
605 .map(|bal| bal.0)
606 .collect())
607 })
608 }
609
610 fn get_total_supplies() -> Result<BTreeMap<token::Denomination, u128>, Error> {
611 CurrentState::with_store(|store| {
612 let store = storage::PrefixStore::new(store, &MODULE_NAME);
613 let ts =
614 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
615
616 Ok(ts.iter().collect())
617 })
618 }
619
620 fn get_total_supply(denomination: token::Denomination) -> Result<u128, Error> {
621 CurrentState::with_store(|store| {
622 let store = storage::PrefixStore::new(store, &MODULE_NAME);
623 let ts =
624 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
625 Ok(ts.get(denomination).unwrap_or_default())
626 })
627 }
628
629 fn set_total_supply(amount: &token::BaseUnits) {
630 CurrentState::with_store(|store| {
631 let store = storage::PrefixStore::new(store, &MODULE_NAME);
632 let mut total_supplies =
633 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
634 total_supplies.insert(amount.denomination(), amount.amount());
635 });
636 }
637
638 fn get_denomination_info(
639 denomination: &token::Denomination,
640 ) -> Result<types::DenominationInfo, Error> {
641 Self::params()
642 .denomination_infos
643 .get(denomination)
644 .cloned()
645 .ok_or(Error::NotFound)
646 }
647
648 fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error> {
649 if CurrentState::with_env(|env| env.is_simulation()) {
650 return Ok(());
651 }
652
653 Self::sub_amount(from, amount).map_err(|_| modules::core::Error::InsufficientFeeBalance)?;
654
655 CurrentState::with(|state| {
656 state
657 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
658 .or_default()
659 .record_fee(from, amount);
660 });
661
662 Ok(())
663 }
664
665 fn set_refund_unused_tx_fee(refund: bool) {
666 CurrentState::with(|state| {
667 if state.env().is_simulation() {
668 return;
669 }
670
671 state
672 .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED)
673 .set(refund);
674 });
675 }
676
677 fn take_refund_unused_tx_fee() -> bool {
678 CurrentState::with(|state| {
679 if state.env().is_simulation() {
680 return false;
681 }
682
683 state
684 .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED)
685 .take()
686 .unwrap_or(false)
687 })
688 }
689
690 fn check_signer_nonces<C: Context>(
691 _ctx: &C,
692 auth_info: &AuthInfo,
693 ) -> Result<Address, modules::core::Error> {
694 let mode = CurrentState::with_env(|env| env.mode());
695
696 let params = Self::params();
699 let sender = CurrentState::with_store(|store| {
700 let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
702 let accounts =
703 storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::ACCOUNTS));
704 let mut sender = None;
705 for si in auth_info.signer_info.iter() {
706 let address = si.address_spec.address();
707 let account: types::Account = accounts.get(address).unwrap_or_default();
708
709 if sender.is_none() {
711 sender = Some(SenderMeta {
712 address,
713 tx_nonce: si.nonce,
714 state_nonce: account.nonce,
715 });
716 }
717
718 if params.debug_disable_nonce_check {
720 continue;
721 }
722
723 match si.nonce.cmp(&account.nonce) {
725 Ordering::Less => {
726 return Err(modules::core::Error::InvalidNonce);
728 }
729 Ordering::Equal => {} Ordering::Greater => {
731 match mode {
733 Mode::Check => {}
734 Mode::PreSchedule => {
735 return Err(modules::core::Error::FutureNonce);
738 }
739 _ => {
740 return Err(modules::core::Error::InvalidNonce);
741 }
742 }
743
744 if si.nonce - account.nonce > MAX_CHECK_NONCE_FUTURE_DELTA {
746 return Err(modules::core::Error::InvalidNonce);
747 }
748 }
749 }
750 }
751
752 Ok(sender)
753 })?;
754
755 let sender = sender.expect("at least one signer is always present");
757 let sender_address = sender.address;
758 if mode == Mode::Check {
759 <C::Runtime as Runtime>::Core::set_sender_meta(sender);
760 }
761
762 Ok(sender_address)
763 }
764
765 fn update_signer_nonces<C: Context>(
766 _ctx: &C,
767 auth_info: &AuthInfo,
768 ) -> Result<(), modules::core::Error> {
769 CurrentState::with_store(|store| {
770 let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
772 let mut accounts =
773 storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::ACCOUNTS));
774 for si in auth_info.signer_info.iter() {
775 let address = si.address_spec.address();
776 let mut account: types::Account = accounts.get(address).unwrap_or_default();
777
778 account.nonce = account
780 .nonce
781 .checked_add(1)
782 .ok_or(modules::core::Error::InvalidNonce)?; accounts.insert(address, account);
784 }
785 Ok(())
786 })
787 }
788}
789
790#[sdk_derive(Module)]
791impl Module {
792 const NAME: &'static str = MODULE_NAME;
793 type Error = Error;
794 type Event = Event;
795 type Parameters = Parameters;
796 type Genesis = Genesis;
797
798 #[migration(init)]
799 pub fn init(genesis: Genesis) {
800 CurrentState::with_store(|store| {
801 let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
803 let mut accounts =
804 storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::ACCOUNTS));
805 for (address, account) in genesis.accounts {
806 accounts.insert(address, account);
807 }
808
809 let mut balances = storage::PrefixStore::new(&mut store, &state::BALANCES);
811 let mut computed_total_supply: BTreeMap<token::Denomination, u128> = BTreeMap::new();
812 for (address, denominations) in genesis.balances.iter() {
813 let mut account =
814 storage::TypedStore::new(storage::PrefixStore::new(&mut balances, &address));
815 for (denomination, value) in denominations {
816 account.insert(denomination, value);
817
818 computed_total_supply
820 .entry(denomination.clone())
821 .and_modify(|v| *v += value)
822 .or_insert_with(|| *value);
823 }
824 }
825
826 let mut total_supplies = storage::TypedStore::new(storage::PrefixStore::new(
828 &mut store,
829 &state::TOTAL_SUPPLY,
830 ));
831 for (denomination, total_supply) in genesis.total_supplies.iter() {
832 let computed = computed_total_supply
833 .remove(denomination)
834 .expect("unexpected total supply");
835 assert!(
836 &computed == total_supply,
837 "unexpected total supply (expected: {total_supply} got: {computed})",
838 );
839
840 total_supplies.insert(denomination, total_supply);
841 }
842 if let Some((denomination, total_supply)) = computed_total_supply.iter().next() {
843 panic!("missing expected total supply: {total_supply} {denomination}",);
844 }
845 });
846
847 genesis
849 .parameters
850 .validate_basic()
851 .expect("invalid genesis parameters");
852
853 Self::set_params(genesis.parameters);
855 }
856
857 #[handler(prefetch = "accounts.Transfer")]
858 fn prefetch_transfer(
859 add_prefix: &mut dyn FnMut(Prefix),
860 body: cbor::Value,
861 auth_info: &AuthInfo,
862 ) -> Result<(), crate::error::RuntimeError> {
863 let args: types::Transfer = cbor::from_value(body).map_err(|_| Error::InvalidArgument)?;
864 let from = auth_info.signer_info[0].address_spec.address();
865
866 add_prefix(Prefix::from(
868 [MODULE_NAME.as_bytes(), state::ACCOUNTS, args.to.as_ref()].concat(),
869 ));
870 add_prefix(Prefix::from(
871 [MODULE_NAME.as_bytes(), state::BALANCES, args.to.as_ref()].concat(),
872 ));
873 add_prefix(Prefix::from(
875 [MODULE_NAME.as_bytes(), state::ACCOUNTS, from.as_ref()].concat(),
876 ));
877 add_prefix(Prefix::from(
878 [MODULE_NAME.as_bytes(), state::BALANCES, from.as_ref()].concat(),
879 ));
880
881 Ok(())
882 }
883
884 #[handler(call = "accounts.Transfer")]
885 fn tx_transfer<C: Context>(_ctx: &C, body: types::Transfer) -> Result<(), Error> {
886 let params = Self::params();
887
888 if params.transfers_disabled {
890 return Err(Error::Forbidden);
891 }
892
893 <C::Runtime as Runtime>::Core::use_tx_gas(params.gas_costs.tx_transfer)?;
894
895 let tx_caller_address = CurrentState::with_env(|env| env.tx_caller_address());
896 Self::transfer(tx_caller_address, body.to, &body.amount)?;
897
898 Ok(())
899 }
900
901 #[handler(query = "accounts.Nonce")]
902 fn query_nonce<C: Context>(_ctx: &C, args: types::NonceQuery) -> Result<u64, Error> {
903 Self::get_nonce(args.address)
904 }
905
906 #[handler(query = "accounts.Addresses", expensive)]
907 fn query_addresses<C: Context>(
908 _ctx: &C,
909 args: types::AddressesQuery,
910 ) -> Result<Vec<Address>, Error> {
911 Self::get_addresses(args.denomination)
912 }
913
914 #[handler(query = "accounts.Balances")]
915 fn query_balances<C: Context>(
916 _ctx: &C,
917 args: types::BalancesQuery,
918 ) -> Result<types::AccountBalances, Error> {
919 Self::get_balances(args.address)
920 }
921
922 #[handler(query = "accounts.DenominationInfo")]
923 fn query_denomination_info<C: Context>(
924 _ctx: &C,
925 args: types::DenominationInfoQuery,
926 ) -> Result<types::DenominationInfo, Error> {
927 Self::get_denomination_info(&args.denomination)
928 }
929}
930
931impl module::TransactionHandler for Module {
932 fn authenticate_tx<C: Context>(
933 ctx: &C,
934 tx: &Transaction,
935 ) -> Result<module::AuthDecision, modules::core::Error> {
936 let default_payer = Self::check_signer_nonces(ctx, &tx.auth_info)?;
938
939 let payer =
941 <C::Runtime as Runtime>::FeeProxy::resolve_payer(ctx, tx)?.unwrap_or(default_payer);
942
943 if !tx.auth_info.fee.amount.amount().is_zero() {
945 if CurrentState::with_env(|env| env.is_check_only()) {
946 Self::ensure_balance(payer, &tx.auth_info.fee.amount)
950 .map_err(|_| modules::core::Error::InsufficientFeeBalance)?;
951
952 CurrentState::with(|state| {
954 state
955 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
956 .or_default()
957 .record_fee(payer, &tx.auth_info.fee.amount);
958 });
959 } else {
960 Self::charge_tx_fee(payer, &tx.auth_info.fee.amount)?;
962 }
963
964 let gas_price = tx.auth_info.fee.gas_price();
965 <C::Runtime as Runtime>::Core::set_priority(gas_price.try_into().unwrap_or(u64::MAX));
967 }
968
969 if !CurrentState::with_env(|env| env.is_check_only()) {
973 Self::update_signer_nonces(ctx, &tx.auth_info)?;
974 }
975
976 Ok(module::AuthDecision::Continue)
977 }
978
979 fn after_handle_call<C: Context>(
980 _ctx: &C,
981 result: module::CallResult,
982 ) -> Result<module::CallResult, modules::core::Error> {
983 let refund_fee = if Self::take_refund_unused_tx_fee() {
985 let remaining_gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
986 let gas_price = CurrentState::with_env(|env| env.tx_auth_info().fee.gas_price());
987
988 gas_price.saturating_mul(remaining_gas.into())
989 } else {
990 0
991 };
992
993 CurrentState::with(|state| {
994 let mgr = state
995 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
996 .or_default();
997
998 mgr.record_refund(refund_fee);
1001
1002 let tx_fee = mgr.tx_fee().cloned().unwrap_or_default();
1004 if tx_fee.amount() > 0 {
1005 state.emit_unconditional_event(Event::Transfer {
1006 from: tx_fee.payer(),
1007 to: *ADDRESS_FEE_ACCUMULATOR,
1008 amount: token::BaseUnits::new(tx_fee.amount(), tx_fee.denomination()),
1009 });
1010 }
1011 });
1012
1013 Ok(result)
1014 }
1015
1016 fn after_dispatch_tx<C: Context>(
1017 ctx: &C,
1018 tx_auth_info: &AuthInfo,
1019 result: &module::CallResult,
1020 ) {
1021 let fee_updates = CurrentState::with(|state| {
1023 let mgr = state
1024 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
1025 .or_default();
1026 mgr.commit_tx()
1027 });
1028 Self::add_amount(fee_updates.payer, &fee_updates.refund).unwrap();
1030
1031 if !CurrentState::with_env(|env| env.is_check_only()) {
1032 return;
1034 }
1035 if !matches!(result, module::CallResult::Ok(_)) {
1036 return;
1038 }
1039
1040 Self::sub_amount(fee_updates.payer, &tx_auth_info.fee.amount).unwrap(); Self::update_signer_nonces(ctx, tx_auth_info).unwrap();
1045 }
1046}
1047
1048impl module::BlockHandler for Module {
1049 fn end_block<C: Context>(ctx: &C) {
1050 let mut previous_fees = Self::get_balances(*ADDRESS_FEE_ACCUMULATOR)
1052 .expect("get_balances must succeed")
1053 .balances;
1054
1055 for (denom, remainder) in &previous_fees {
1057 Self::sub_amount(
1058 *ADDRESS_FEE_ACCUMULATOR,
1059 &token::BaseUnits::new(*remainder, denom.clone()),
1060 )
1061 .expect("sub_amount must succeed");
1062 }
1063
1064 let addrs: Vec<Address> = ctx
1066 .runtime_round_results()
1067 .good_compute_entities
1068 .iter()
1069 .map(|pk| Address::from_sigspec(&SignatureAddressSpec::Ed25519(pk.into())))
1070 .collect();
1071
1072 if !addrs.is_empty() {
1073 let amounts: Vec<_> = previous_fees
1074 .iter()
1075 .filter_map(|(denom, fee)| {
1076 let fee = fee
1077 .checked_div(addrs.len() as u128)
1078 .expect("addrs is non-empty");
1079
1080 if fee.is_zero() {
1082 None
1083 } else {
1084 Some(token::BaseUnits::new(fee, denom.clone()))
1085 }
1086 })
1087 .collect();
1088
1089 for address in addrs {
1090 for amount in &amounts {
1091 let remaining = previous_fees
1092 .get_mut(amount.denomination())
1093 .expect("designated denomination should be there");
1094 *remaining = remaining
1095 .checked_sub(amount.amount())
1096 .expect("there should be enough to disburse");
1097
1098 Self::add_amount(address, amount)
1099 .expect("add_amount must succeed for fee disbursement");
1100
1101 CurrentState::with(|state| {
1103 state.emit_event(Event::Transfer {
1104 from: *ADDRESS_FEE_ACCUMULATOR,
1105 to: address,
1106 amount: amount.clone(),
1107 });
1108 });
1109 }
1110 }
1111 }
1112
1113 for (denom, remainder) in previous_fees.into_iter() {
1115 if remainder.is_zero() {
1116 continue;
1117 }
1118
1119 let amount = token::BaseUnits::new(remainder, denom);
1120 Self::add_amount(*ADDRESS_COMMON_POOL, &amount)
1121 .expect("add_amount must succeed for transfer to common pool");
1122
1123 CurrentState::with(|state| {
1125 state.emit_event(Event::Transfer {
1126 from: *ADDRESS_FEE_ACCUMULATOR,
1127 to: *ADDRESS_COMMON_POOL,
1128 amount,
1129 })
1130 });
1131 }
1132
1133 let block_fees = CurrentState::with(|state| {
1135 let mgr = state
1136 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
1137 .take()
1138 .unwrap_or_default();
1139 mgr.commit_block().into_iter()
1140 });
1141
1142 for (denom, amount) in block_fees {
1143 Self::add_amount(
1144 *ADDRESS_FEE_ACCUMULATOR,
1145 &token::BaseUnits::new(amount, denom),
1146 )
1147 .expect("add_amount must succeed for transfer to fee accumulator")
1148 }
1149 }
1150}
1151
1152impl module::InvariantHandler for Module {
1153 fn check_invariants<C: Context>(_ctx: &C) -> Result<(), CoreError> {
1155 #[allow(clippy::or_fun_call)]
1159 let balances = Self::get_all_balances().or(Err(CoreError::InvariantViolation(
1160 "unable to get balances of all accounts".to_string(),
1161 )))?;
1162 #[allow(clippy::or_fun_call)]
1163 let total_supplies = Self::get_total_supplies().or(Err(CoreError::InvariantViolation(
1164 "unable to get total supplies".to_string(),
1165 )))?;
1166
1167 let mut computed_ts: BTreeMap<token::Denomination, u128> = BTreeMap::new();
1169
1170 for bals in balances.values() {
1171 for (den, amt) in bals {
1172 computed_ts
1173 .entry(den.clone())
1174 .and_modify(|a| *a += amt)
1175 .or_insert_with(|| *amt);
1176 }
1177 }
1178
1179 for (den, ts) in &total_supplies {
1181 #[allow(clippy::or_fun_call)]
1185 let computed = computed_ts
1186 .remove(den)
1187 .ok_or(CoreError::InvariantViolation(
1188 "unexpected denomination".to_string(),
1189 ))?;
1190
1191 if &computed != ts {
1192 return Err(CoreError::InvariantViolation(format!(
1194 "computed and actual total supplies don't match (computed={computed}, actual={ts})",
1195 )));
1196 }
1197 }
1198
1199 if computed_ts.is_empty() {
1203 Ok(())
1204 } else {
1205 Err(CoreError::InvariantViolation(
1206 "encountered denomination that isn't present in total supplies table".to_string(),
1207 ))
1208 }
1209 }
1210}