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