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 modules::core::{Error as CoreError, API as _},
19 runtime::Runtime,
20 sdk_derive,
21 sender::SenderMeta,
22 state::CurrentState,
23 storage,
24 storage::Prefix,
25 types::{
26 address::{Address, SignatureAddressSpec},
27 token,
28 transaction::{AuthInfo, Transaction},
29 },
30};
31
32pub mod fee;
33#[cfg(test)]
34pub(crate) mod test;
35pub mod types;
36
37const MODULE_NAME: &str = "accounts";
39
40const MAX_CHECK_NONCE_FUTURE_DELTA: u64 = 0; #[derive(Error, Debug, oasis_runtime_sdk_macros::Error)]
46pub enum Error {
47 #[error("invalid argument")]
48 #[sdk_error(code = 1)]
49 InvalidArgument,
50
51 #[error("insufficient balance")]
52 #[sdk_error(code = 2)]
53 InsufficientBalance,
54
55 #[error("forbidden by policy")]
56 #[sdk_error(code = 3)]
57 Forbidden,
58
59 #[error("not found")]
60 #[sdk_error(code = 4)]
61 NotFound,
62
63 #[error("core: {0}")]
64 #[sdk_error(transparent)]
65 Core(#[from] modules::core::Error),
66}
67
68#[derive(Debug, cbor::Encode, oasis_runtime_sdk_macros::Event)]
70#[cbor(untagged)]
71pub enum Event {
72 #[sdk_event(code = 1)]
73 Transfer {
74 from: Address,
75 to: Address,
76 amount: token::BaseUnits,
77 },
78
79 #[sdk_event(code = 2)]
80 Burn {
81 owner: Address,
82 amount: token::BaseUnits,
83 },
84
85 #[sdk_event(code = 3)]
86 Mint {
87 owner: Address,
88 amount: token::BaseUnits,
89 },
90}
91
92#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
94pub struct GasCosts {
95 pub tx_transfer: u64,
96}
97
98#[derive(Clone, Default, Debug, cbor::Encode, cbor::Decode)]
100pub struct Parameters {
101 pub transfers_disabled: bool,
102 pub gas_costs: GasCosts,
103
104 #[cbor(optional)]
105 pub debug_disable_nonce_check: bool,
106
107 #[cbor(optional)]
108 pub denomination_infos: BTreeMap<token::Denomination, types::DenominationInfo>,
109}
110
111#[derive(Error, Debug)]
113pub enum ParameterValidationError {
114 #[error("debug option used: {0}")]
115 DebugOptionUsed(String),
116}
117
118impl module::Parameters for Parameters {
119 type Error = ParameterValidationError;
120
121 #[cfg(not(feature = "unsafe-allow-debug"))]
122 fn validate_basic(&self) -> Result<(), Self::Error> {
123 if self.debug_disable_nonce_check {
124 return Err(ParameterValidationError::DebugOptionUsed(
125 "debug_disable_nonce_check".to_string(),
126 ));
127 }
128
129 Ok(())
130 }
131}
132
133#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
135pub struct Genesis {
136 pub parameters: Parameters,
137 pub accounts: BTreeMap<Address, types::Account>,
138 pub balances: BTreeMap<Address, BTreeMap<token::Denomination, u128>>,
139 pub total_supplies: BTreeMap<token::Denomination, u128>,
140}
141
142pub trait API {
144 fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
146
147 fn transfer_silent(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
149
150 fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error>;
152
153 fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error>;
155
156 fn set_nonce(address: Address, nonce: u64);
158
159 fn get_nonce(address: Address) -> Result<u64, Error>;
161
162 fn inc_nonce(address: Address);
164
165 fn set_balance(address: Address, amount: &token::BaseUnits);
171
172 fn get_balance(address: Address, denomination: token::Denomination) -> Result<u128, Error>;
174
175 fn ensure_balance(address: Address, amount: &token::BaseUnits) -> Result<(), Error> {
177 let balance = Self::get_balance(address, amount.denomination().clone())?;
178 if balance < amount.amount() {
179 Err(Error::InsufficientBalance)
180 } else {
181 Ok(())
182 }
183 }
184
185 fn get_allowance(
193 owner: Address,
194 beneficiary: Address,
195 denomination: token::Denomination,
196 ) -> Result<u128, Error>;
197
198 fn set_allowance(owner: Address, beneficiary: Address, amount: &token::BaseUnits);
201
202 fn get_balances(address: Address) -> Result<types::AccountBalances, Error>;
204
205 fn get_addresses(denomination: token::Denomination) -> Result<Vec<Address>, Error>;
207
208 fn get_total_supplies() -> Result<BTreeMap<token::Denomination, u128>, Error>;
210
211 fn get_total_supply(denomination: token::Denomination) -> Result<u128, Error>;
213
214 fn set_total_supply(amount: &token::BaseUnits);
220
221 fn get_denomination_info(
223 denomination: &token::Denomination,
224 ) -> Result<types::DenominationInfo, Error>;
225
226 fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error>;
228
229 fn set_refund_unused_tx_fee(refund: bool);
232
233 fn take_refund_unused_tx_fee() -> bool;
238
239 fn check_signer_nonces<C: Context>(
242 ctx: &C,
243 tx_auth_info: &AuthInfo,
244 ) -> Result<Address, modules::core::Error>;
245
246 fn update_signer_nonces<C: Context>(
248 ctx: &C,
249 tx_auth_info: &AuthInfo,
250 ) -> Result<(), modules::core::Error>;
251}
252
253pub mod state {
255 pub const ACCOUNTS: &[u8] = &[0x01];
257 pub const BALANCES: &[u8] = &[0x02];
259 pub const TOTAL_SUPPLY: &[u8] = &[0x03];
261 pub const ALLOWANCES: &[u8] = &[0x04];
263}
264
265pub struct Module;
266
267pub static ADDRESS_COMMON_POOL: Lazy<Address> =
271 Lazy::new(|| Address::from_module(MODULE_NAME, "common-pool"));
272pub static ADDRESS_FEE_ACCUMULATOR: Lazy<Address> =
276 Lazy::new(|| Address::from_module(MODULE_NAME, "fee-accumulator"));
277
278#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
280struct AddressWithDenomination(Address, token::Denomination);
281
282#[derive(Error, Debug)]
283enum AWDError {
284 #[error("malformed address")]
285 MalformedAddress,
286
287 #[error("malformed denomination")]
288 MalformedDenomination,
289}
290
291impl std::convert::TryFrom<&[u8]> for AddressWithDenomination {
292 type Error = AWDError;
293
294 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
295 let address =
296 Address::try_from(&bytes[..Address::SIZE]).map_err(|_| AWDError::MalformedAddress)?;
297 let denomination = token::Denomination::try_from(&bytes[Address::SIZE..])
298 .map_err(|_| AWDError::MalformedDenomination)?;
299 Ok(AddressWithDenomination(address, denomination))
300 }
301}
302
303impl Module {
304 fn add_amount(addr: Address, amount: &token::BaseUnits) -> Result<(), Error> {
306 if amount.amount() == 0 {
307 return Ok(());
308 }
309
310 CurrentState::with_store(|store| {
311 let store = storage::PrefixStore::new(store, &MODULE_NAME);
312 let balances = storage::PrefixStore::new(store, &state::BALANCES);
313 let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr));
314 let mut value: u128 = account.get(amount.denomination()).unwrap_or_default();
315
316 value = value
317 .checked_add(amount.amount())
318 .ok_or(Error::InvalidArgument)?;
319 account.insert(amount.denomination(), value);
320 Ok(())
321 })
322 }
323
324 fn sub_amount(addr: Address, amount: &token::BaseUnits) -> Result<(), Error> {
326 if amount.amount() == 0 {
327 return Ok(());
328 }
329
330 CurrentState::with_store(|store| {
331 let store = storage::PrefixStore::new(store, &MODULE_NAME);
332 let balances = storage::PrefixStore::new(store, &state::BALANCES);
333 let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr));
334 let mut value: u128 = account.get(amount.denomination()).unwrap_or_default();
335
336 value = value
337 .checked_sub(amount.amount())
338 .ok_or(Error::InsufficientBalance)?;
339 account.insert(amount.denomination(), value);
340 Ok(())
341 })
342 }
343
344 fn inc_total_supply(amount: &token::BaseUnits) -> Result<(), Error> {
346 if amount.amount() == 0 {
347 return Ok(());
348 }
349
350 CurrentState::with_store(|store| {
351 let store = storage::PrefixStore::new(store, &MODULE_NAME);
352 let mut total_supplies =
353 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
354 let mut total_supply: u128 = total_supplies
355 .get(amount.denomination())
356 .unwrap_or_default();
357
358 total_supply = total_supply
359 .checked_add(amount.amount())
360 .ok_or(Error::InvalidArgument)?;
361 total_supplies.insert(amount.denomination(), total_supply);
362 Ok(())
363 })
364 }
365
366 fn dec_total_supply(amount: &token::BaseUnits) -> Result<(), Error> {
368 if amount.amount() == 0 {
369 return Ok(());
370 }
371
372 CurrentState::with_store(|store| {
373 let store = storage::PrefixStore::new(store, &MODULE_NAME);
374 let mut total_supplies =
375 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
376 let mut total_supply: u128 = total_supplies
377 .get(amount.denomination())
378 .unwrap_or_default();
379
380 total_supply = total_supply
381 .checked_sub(amount.amount())
382 .ok_or(Error::InsufficientBalance)?;
383 total_supplies.insert(amount.denomination(), total_supply);
384 Ok(())
385 })
386 }
387
388 fn get_all_balances() -> Result<BTreeMap<Address, BTreeMap<token::Denomination, u128>>, Error> {
390 CurrentState::with_store(|store| {
391 let store = storage::PrefixStore::new(store, &MODULE_NAME);
392 let balances =
393 storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES));
394
395 let balmap: BTreeMap<AddressWithDenomination, u128> = balances.iter().collect();
400
401 let mut b: BTreeMap<Address, BTreeMap<token::Denomination, u128>> = BTreeMap::new();
402
403 for (addrden, amt) in &balmap {
404 let addr = &addrden.0;
405 let den = &addrden.1;
406
407 let addr_bals = b.entry(*addr).or_default();
409
410 addr_bals
412 .entry(den.clone())
413 .and_modify(|a| *a += amt)
414 .or_insert_with(|| *amt);
415 }
416
417 Ok(b)
418 })
419 }
420}
421
422const CONTEXT_KEY_TX_FEE_REFUND_UNUSED: &str = "accounts.TxRefundUnusedFee";
424const CONTEXT_KEY_FEE_MANAGER: &str = "accounts.FeeManager";
426
427impl API for Module {
428 fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
429 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
430 return Ok(());
431 }
432
433 Self::transfer_silent(from, to, amount)?;
434
435 CurrentState::with(|state| {
437 state.emit_event(Event::Transfer {
438 from,
439 to,
440 amount: amount.clone(),
441 })
442 });
443
444 Ok(())
445 }
446
447 fn transfer_silent(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
448 Self::sub_amount(from, amount)?;
450 Self::add_amount(to, amount)?;
452
453 Ok(())
454 }
455
456 fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error> {
457 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
458 return Ok(());
459 }
460
461 Self::add_amount(to, amount)?;
463
464 Self::inc_total_supply(amount)?;
466
467 CurrentState::with(|state| {
469 state.emit_event(Event::Mint {
470 owner: to,
471 amount: amount.clone(),
472 });
473 });
474
475 Ok(())
476 }
477
478 fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error> {
479 if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 {
480 return Ok(());
481 }
482
483 Self::sub_amount(from, amount)?;
485
486 Self::dec_total_supply(amount)
488 .expect("target account had enough balance so total supply should not underflow");
489
490 CurrentState::with(|state| {
492 state.emit_event(Event::Burn {
493 owner: from,
494 amount: amount.clone(),
495 });
496 });
497
498 Ok(())
499 }
500
501 fn set_nonce(address: Address, nonce: u64) {
502 CurrentState::with_store(|store| {
503 let store = storage::PrefixStore::new(store, &MODULE_NAME);
504 let mut accounts =
505 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
506 let mut account: types::Account = accounts.get(address).unwrap_or_default();
507 account.nonce = nonce;
508 accounts.insert(address, account);
509 })
510 }
511
512 fn get_nonce(address: Address) -> Result<u64, Error> {
513 CurrentState::with_store(|store| {
514 let store = storage::PrefixStore::new(store, &MODULE_NAME);
515 let accounts =
516 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
517 let account: types::Account = accounts.get(address).unwrap_or_default();
518 Ok(account.nonce)
519 })
520 }
521
522 fn inc_nonce(address: Address) {
523 CurrentState::with_store(|store| {
524 let store = storage::PrefixStore::new(store, &MODULE_NAME);
525 let mut accounts =
526 storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS));
527 let mut account: types::Account = accounts.get(address).unwrap_or_default();
528 account.nonce = account.nonce.saturating_add(1);
529 accounts.insert(address, account);
530 })
531 }
532
533 fn set_balance(address: Address, amount: &token::BaseUnits) {
534 CurrentState::with_store(|store| {
535 let store = storage::PrefixStore::new(store, &MODULE_NAME);
536 let balances = storage::PrefixStore::new(store, &state::BALANCES);
537 let mut account =
538 storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
539 account.insert(amount.denomination(), amount.amount());
540 });
541 }
542
543 fn get_balance(address: Address, denomination: token::Denomination) -> Result<u128, Error> {
544 CurrentState::with_store(|store| {
545 let store = storage::PrefixStore::new(store, &MODULE_NAME);
546 let balances = storage::PrefixStore::new(store, &state::BALANCES);
547 let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
548
549 Ok(account.get(denomination).unwrap_or_default())
550 })
551 }
552
553 fn get_balances(address: Address) -> Result<types::AccountBalances, Error> {
554 CurrentState::with_store(|store| {
555 let store = storage::PrefixStore::new(store, &MODULE_NAME);
556 let balances = storage::PrefixStore::new(store, &state::BALANCES);
557 let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address));
558
559 Ok(types::AccountBalances {
560 balances: account.iter().collect(),
561 })
562 })
563 }
564
565 fn get_allowance(
566 owner: Address,
567 beneficiary: Address,
568 denomination: token::Denomination,
569 ) -> Result<u128, Error> {
570 CurrentState::with_store(|store| {
571 let store = storage::PrefixStore::new(store, &MODULE_NAME);
572 let allowances = storage::PrefixStore::new(store, &state::ALLOWANCES);
573 let for_owner = storage::PrefixStore::new(allowances, &owner);
574 let for_beneficiary =
575 storage::TypedStore::new(storage::PrefixStore::new(for_owner, &beneficiary));
576
577 Ok(for_beneficiary.get(denomination).unwrap_or_default())
578 })
579 }
580
581 fn set_allowance(owner: Address, beneficiary: Address, amount: &token::BaseUnits) {
582 CurrentState::with_store(|store| {
583 let store = storage::PrefixStore::new(store, &MODULE_NAME);
584 let allowances = storage::PrefixStore::new(store, &state::ALLOWANCES);
585 let for_owner = storage::PrefixStore::new(allowances, &owner);
586 let mut for_beneficiary =
587 storage::TypedStore::new(storage::PrefixStore::new(for_owner, &beneficiary));
588
589 for_beneficiary.insert(amount.denomination(), amount.amount());
590 })
591 }
592
593 fn get_addresses(denomination: token::Denomination) -> Result<Vec<Address>, Error> {
594 CurrentState::with_store(|store| {
595 let store = storage::PrefixStore::new(store, &MODULE_NAME);
596 let balances: BTreeMap<AddressWithDenomination, Quantity> =
597 storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES))
598 .iter()
599 .collect();
600
601 Ok(balances
602 .into_keys()
603 .filter(|bal| bal.1 == denomination)
604 .map(|bal| bal.0)
605 .collect())
606 })
607 }
608
609 fn get_total_supplies() -> Result<BTreeMap<token::Denomination, u128>, Error> {
610 CurrentState::with_store(|store| {
611 let store = storage::PrefixStore::new(store, &MODULE_NAME);
612 let ts =
613 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
614
615 Ok(ts.iter().collect())
616 })
617 }
618
619 fn get_total_supply(denomination: token::Denomination) -> Result<u128, Error> {
620 CurrentState::with_store(|store| {
621 let store = storage::PrefixStore::new(store, &MODULE_NAME);
622 let ts =
623 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
624 Ok(ts.get(denomination).unwrap_or_default())
625 })
626 }
627
628 fn set_total_supply(amount: &token::BaseUnits) {
629 CurrentState::with_store(|store| {
630 let store = storage::PrefixStore::new(store, &MODULE_NAME);
631 let mut total_supplies =
632 storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY));
633 total_supplies.insert(amount.denomination(), amount.amount());
634 });
635 }
636
637 fn get_denomination_info(
638 denomination: &token::Denomination,
639 ) -> Result<types::DenominationInfo, Error> {
640 Self::params()
641 .denomination_infos
642 .get(denomination)
643 .cloned()
644 .ok_or(Error::NotFound)
645 }
646
647 fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error> {
648 if CurrentState::with_env(|env| env.is_simulation()) {
649 return Ok(());
650 }
651
652 Self::sub_amount(from, amount).map_err(|_| modules::core::Error::InsufficientFeeBalance)?;
653
654 CurrentState::with(|state| {
655 state
656 .block_value::<fee::FeeManager>(CONTEXT_KEY_FEE_MANAGER)
657 .or_default()
658 .record_fee(from, amount);
659 });
660
661 Ok(())
662 }
663
664 fn set_refund_unused_tx_fee(refund: bool) {
665 CurrentState::with(|state| {
666 if state.env().is_simulation() {
667 return;
668 }
669
670 state
671 .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED)
672 .set(refund);
673 });
674 }
675
676 fn take_refund_unused_tx_fee() -> bool {
677 CurrentState::with(|state| {
678 if state.env().is_simulation() {
679 return false;
680 }
681
682 state
683 .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED)
684 .take()
685 .unwrap_or(false)
686 })
687 }
688
689 fn check_signer_nonces<C: Context>(
690 _ctx: &C,
691 auth_info: &AuthInfo,
692 ) -> Result<Address, modules::core::Error> {
693 let is_pre_schedule = CurrentState::with_env(|env| env.is_pre_schedule());
694 let is_check_only = CurrentState::with_env(|env| env.is_check_only());
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 if si.nonce - account.nonce > MAX_CHECK_NONCE_FUTURE_DELTA {
733 return Err(modules::core::Error::InvalidNonce);
734 }
735
736 if is_pre_schedule {
739 return Err(modules::core::Error::FutureNonce);
740 }
741
742 if !is_check_only {
744 return Err(modules::core::Error::InvalidNonce);
745 }
746
747 }
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 is_check_only {
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}