Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: draft implementation of NEP-366 #7497

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ce474eb
Draft implementation of DelegateAction (NEP-366)
Aug 29, 2022
3eb7f02
Rename 'publisher_id' to 'relayer_id'
Aug 31, 2022
433b158
Updated according to the discussion in NEP-366
Aug 31, 2022
a7bcf89
Add fees for DelegateAction.
Oct 26, 2022
529ca2c
Remove redundant code. Add DelegateActionMustBeOnlyOne error
Nov 1, 2022
cfb9780
Add the inner actions' send fees to the total send fee
Nov 1, 2022
602baca
Charge the send fee for the inner actions twice
Nov 2, 2022
3b78721
Refactor deserialization the inner actions
Oct 31, 2022
106dc67
Applied max_block_height
Nov 1, 2022
ee124a6
Fix delegate_cost.exec_fee todo
Nov 3, 2022
11c29fc
Charge DelegateAction's 'send_fee' in two steps
Nov 8, 2022
b344298
Fixed review issues. Added a test for DelegateAction deserialization
Nov 9, 2022
d6ef8ba
Use `signer_public_key` instead of `delegate_action.public_key` in a …
Nov 9, 2022
df86e3a
Add tests to actions.rs. Fix tests in transaction.rs
Nov 10, 2022
59a5986
Return `AccountDoesNotExist` error if `sender` doesn't exist
Nov 10, 2022
cd289d4
Fix review issues
Nov 10, 2022
9a9c0da
Add protocol_feature_delegate_action
Nov 17, 2022
f34acda
Add "protocol_feature_delegate_action" to "nightly" features
Nov 17, 2022
704c524
Create test_delegate_action_deserialization for stable build
Nov 17, 2022
88973ce
Applied cargo fmt
Nov 17, 2022
335537e
Updated snapshots for tests::test_json_unchanged test
Nov 17, 2022
7575c90
Merge branch 'master' into NEP-366
Nov 17, 2022
010d718
Update DelegateAction feature version number
Nov 17, 2022
1935499
Apply cargo fmt
Nov 17, 2022
79915fd
Update snapshots and error schema for tests
Nov 17, 2022
155054f
Use `safe_add_gas` in `apply_delegate_action` function
Nov 18, 2022
81f4eb7
Add comments which explain what's going on
Nov 25, 2022
d0c3787
Rename protocol_feature_delegate_action with protocol_feature_nep366_…
Jan 12, 2023
e9e1f8f
Add comments and remove `new_delegate_actions`
Jan 12, 2023
d5cb1a0
Add "test_delegate_action_must_be_only_one" test
Jan 12, 2023
c9eb505
Add a test case for ACTION_DELEGATE_NUMBER
Jan 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chain/chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ impl RuntimeAdapter for KeyValueRuntime {
receipt_id: create_receipt_nonce(from.clone(), to.clone(), amount, nonce),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: from.clone(),
relayer_id: None,
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price,
output_data_receivers: vec![],
Expand Down
1 change: 1 addition & 0 deletions chain/rosetta-rpc/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ impl From<NearActions> for Vec<crate::models::Operation> {
);
operations.push(deploy_contract_operation);
}
near_primitives::transaction::Action::Delegate(_) => todo!(),
Copy link
Author

@e-uleyskiy e-uleyskiy Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jakmeier
Should this todo be fixed for commiting this patch or it can be done in another patch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep this as a todo for the initial PR, this should not stop us from getting through the NEP approval.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it should be fixed before submitting (otherwise this code path could cause panic in the node)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we currently don't have a way to parse DelegateAction without the feature enabled. So it would only be possible to panic on nightly builds. But if you want to fix this before we merge, @firatNEAR told me they will take care of this one.

}
}
operations
Expand Down
30 changes: 29 additions & 1 deletion core/primitives/src/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::borsh::maybestd::collections::HashMap;
use crate::hash::CryptoHash;
use crate::logging;
use crate::serialize::{dec_format, option_base64_format};
use crate::transaction::{Action, TransferAction};
use crate::transaction::{Action, DelegateAction, TransferAction};
use crate::types::{AccountId, Balance, ShardId};

/// Receipts are used for a cross-shard communication.
Expand Down Expand Up @@ -52,6 +52,7 @@ impl Receipt {

receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "system".parse().unwrap(),
relayer_id: None,
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: 0,
output_data_receivers: vec![],
Expand Down Expand Up @@ -79,6 +80,7 @@ impl Receipt {

receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: receiver_id.clone(),
relayer_id: None,
signer_public_key,
gas_price: 0,
output_data_receivers: vec![],
Expand All @@ -87,6 +89,30 @@ impl Receipt {
}),
}
}

pub fn new_delegate_actions(
jakmeier marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment - and explain why do we need a special function for this case.

relayer_id: &AccountId,
predecessor_id: &AccountId,
delegate_action: &DelegateAction,
public_key: &PublicKey,
gas_price: Balance,
) -> Self {
Receipt {
predecessor_id: predecessor_id.clone(),
receiver_id: delegate_action.reciever_id.clone(),
receipt_id: CryptoHash::default(),

receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: predecessor_id.clone(),
relayer_id: Some(relayer_id.clone()),
signer_public_key: public_key.clone(),
gas_price: gas_price,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: delegate_action.actions.clone(),
}),
}
}
}

/// Receipt could be either ActionReceipt or DataReceipt
Expand All @@ -103,6 +129,8 @@ pub enum ReceiptEnum {
pub struct ActionReceipt {
/// A signer of the original transaction
pub signer_id: AccountId,
/// A relayer's identifier in case of a delgated action
pub relayer_id: Option<AccountId>,
/// An access key which was used to sign the original transaction
pub signer_public_key: PublicKey,
/// A gas_price which has been used to buy gas in the original transaction
Expand Down
40 changes: 40 additions & 0 deletions core/primitives/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Borrow;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::io::Error;

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -70,6 +71,7 @@ pub enum Action {
AddKey(AddKeyAction),
DeleteKey(DeleteKeyAction),
DeleteAccount(DeleteAccountAction),
Delegate(SignedDelegateAction),
}

impl Action {
Expand All @@ -83,6 +85,10 @@ impl Action {
match self {
Action::FunctionCall(a) => a.deposit,
Action::Transfer(a) => a.deposit,
Action::Delegate(a) => {
let delegate_action = a.get_delegate_action().unwrap();
delegate_action.deposit
}
_ => 0,
}
}
Expand Down Expand Up @@ -220,6 +226,40 @@ impl From<DeleteAccountAction> for Action {
}
}

#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct DelegateAction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment

pub reciever_id: AccountId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The receiver in this case is the delegator, right? (Alice in your example) Maybe write that in a comment.

(Also, there is a typo in receiver)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receiver_id is who the actions in this Action will be targeted toward after it has been delegated.
Think of DelegateAction/SignedDelegateAction as Transaction and SignedTransaction respectively.

Adding description comment to DelegateAction with a link to NEP and moving diagram there will be useful indeed.

pub deposit: Balance,
pub nonce: u64,
pub actions: Vec<Action>,
}
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct SignedDelegateAction {
// Borsh doesn't support recursive types. Therefore this field
// is deserialized to DelegateAction in runtime
pub delegate_action_serde: Vec<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand why not just use delegate_action: DelegateAction here?
See SignedTransaction for example -

pub transaction: Transaction,

You can add borsh_init to initialize whatever data is needed.

Copy link
Author

@e-uleyskiy e-uleyskiy Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand why not just use delegate_action: DelegateAction here?
The comment is wrong. The issue is that there will be a type recursion:

pub struct Transaction {
   ....
   pub actions: Vec<Action>, // <--- Recursion
}

pub enum Action {
   ...
   Delegate(SignedDelegateAction),
}

pub struct SignedDelegateAction {
   pub delegate_action: DelegateAction,
   ....
}

pub struct DelegateAction {
   ...
   pub actions: Vec<Action>, /// <--- Recursion
}

pub public_key: PublicKey,
pub signature: Signature,
jakmeier marked this conversation as resolved.
Show resolved Hide resolved
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments on public_key and signature would be helpful. Whose signature is it? Whose PK? My guess is both belong to the delegator, which would be Alice in your example.


impl SignedDelegateAction {
pub fn get_delegate_action(&self) -> Result<DelegateAction, Error> {
DelegateAction::try_from_slice(&self.delegate_action_serde)
}

pub fn get_hash(&self) -> CryptoHash {
hash(&self.delegate_action_serde)
}
}

impl From<SignedDelegateAction> for Action {
fn from(delegate_action: SignedDelegateAction) -> Self {
Self::Delegate(delegate_action)
}
}

#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Eq, Debug, Clone)]
#[borsh_init(init)]
Expand Down
23 changes: 22 additions & 1 deletion core/primitives/src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ use crate::sharding::{
use crate::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
DeployContractAction, ExecutionMetadata, ExecutionOutcome, ExecutionOutcomeWithIdAndProof,
ExecutionStatus, FunctionCallAction, SignedTransaction, StakeAction, TransferAction,
ExecutionStatus, FunctionCallAction, SignedDelegateAction, SignedTransaction, StakeAction,
TransferAction,
};
use crate::types::{
AccountId, AccountWithPublicKey, Balance, BlockHeight, CompiledContractCache, EpochHeight,
Expand Down Expand Up @@ -954,6 +955,11 @@ pub enum ActionView {
DeleteAccount {
beneficiary_id: AccountId,
},
Delegate {
delegate_action_serde: Vec<u8>,
signature: Signature,
public_key: PublicKey,
},
}

impl From<Action> for ActionView {
Expand Down Expand Up @@ -982,6 +988,11 @@ impl From<Action> for ActionView {
Action::DeleteAccount(action) => {
ActionView::DeleteAccount { beneficiary_id: action.beneficiary_id }
}
Action::Delegate(action) => ActionView::Delegate {
delegate_action_serde: action.delegate_action_serde,
signature: action.signature,
public_key: action.public_key,
},
}
}
}
Expand Down Expand Up @@ -1011,6 +1022,15 @@ impl TryFrom<ActionView> for Action {
ActionView::DeleteAccount { beneficiary_id } => {
Action::DeleteAccount(DeleteAccountAction { beneficiary_id })
}
ActionView::Delegate {
delegate_action_serde: delegate_action,
signature,
public_key,
} => Action::Delegate(SignedDelegateAction {
delegate_action_serde: delegate_action,
signature,
public_key,
}),
})
}
}
Expand Down Expand Up @@ -1478,6 +1498,7 @@ impl TryFrom<ReceiptView> for Receipt {
actions,
} => ReceiptEnum::Action(ActionReceipt {
signer_id,
relayer_id: None,
signer_public_key,
gas_price,
output_data_receivers: output_data_receivers
Expand Down
1 change: 1 addition & 0 deletions genesis-tools/genesis-csv-to-json/src/csv_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ fn account_records(row: &Row, gas_price: Balance) -> Vec<StateRecord> {
receipt_id: hash(row.account_id.as_ref().as_bytes()),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: row.account_id.clone(),
relayer_id: None,
// `signer_public_key` can be anything because the key checks are not applied when
// a transaction is already converted to a receipt.
signer_public_key: PublicKey::empty(KeyType::ED25519),
Expand Down
1 change: 1 addition & 0 deletions integration-tests/src/tests/standard_cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,7 @@ fn make_write_key_value_action(key: Vec<u64>, value: Vec<u64>) -> Action {
fn make_receipt(node: &impl Node, actions: Vec<Action>, receiver_id: AccountId) -> Receipt {
let receipt_enum = ReceiptEnum::Action(ActionReceipt {
signer_id: alice_account(),
relayer_id: None,
signer_public_key: node.signer().as_ref().public_key(),
gas_price: 0,
output_data_receivers: vec![],
Expand Down
50 changes: 48 additions & 2 deletions runtime/runtime/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use near_primitives::runtime::config::AccountCreationConfig;
use near_primitives::runtime::fees::RuntimeFeesConfig;
use near_primitives::transaction::{
Action, AddKeyAction, DeleteAccountAction, DeleteKeyAction, DeployContractAction,
FunctionCallAction, StakeAction, TransferAction,
FunctionCallAction, SignedDelegateAction, StakeAction, TransferAction,
};
use near_primitives::types::validator_stake::ValidatorStake;
use near_primitives::types::{AccountId, BlockHeight, EpochInfoProvider, TrieCacheMode};
Expand All @@ -30,6 +30,7 @@ use near_vm_errors::{
use near_vm_logic::types::PromiseResult;
use near_vm_logic::VMContext;

use crate::balance_checker::receipt_cost;
use crate::config::{safe_add_gas, RuntimeConfig};
use crate::ext::{ExternalError, RuntimeExt};
use crate::{ActionResult, ApplyState};
Expand Down Expand Up @@ -253,6 +254,7 @@ pub(crate) fn action_function_call(
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: action_receipt.signer_id.clone(),
relayer_id: action_receipt.relayer_id.clone(),
signer_public_key: action_receipt.signer_public_key.clone(),
gas_price: action_receipt.gas_price,
output_data_receivers: receipt.output_data_receivers,
Expand Down Expand Up @@ -613,6 +615,46 @@ pub(crate) fn action_add_key(
Ok(())
}

pub(crate) fn action_delegate_action(
apply_state: &ApplyState,
action_receipt: &ActionReceipt,
predecessor_id: &AccountId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

predecessor_id is ambiguous here and confused me a lot.

If I'm understanding it right, this is the predecessor of the actions that will be spawned now, which is going to be the current account that unpacks the delegated actions. In your example, this should be Alice. Whereas the predecessor of the current action is the publisher.

Maybe rename to something like delegator_id.

signed_delegate_action: &SignedDelegateAction,
result: &mut ActionResult,
) -> Result<(), RuntimeError> {
match signed_delegate_action.get_delegate_action() {
Ok(delegate_action) => {
let new_receipt = Receipt::new_delegate_actions(
&action_receipt.signer_id,
predecessor_id,
&delegate_action,
&signed_delegate_action.public_key,
action_receipt.gas_price,
);

let transaction_costs = &apply_state.config.transaction_costs;
let current_protocol_version = apply_state.current_protocol_version;
let cost = receipt_cost(transaction_costs, current_protocol_version, &new_receipt)?;

if let Some(refund) = delegate_action.deposit.checked_sub(cost.clone()) {
let refund_receipt = Receipt::new_balance_refund(&action_receipt.signer_id, refund);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that we have to handle deposit refunds specially?
I would have expected that we treat it like existing deposit refunds. That is, towards the end of apply_action_receipt we call generate_refund_receipts and give a refund to the predecessor of the action. The predecessor of the delegation action should be the publisher, right? So that seems correct to me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that we have to handle deposit refunds specially? I would have expected that we treat it like existing deposit refunds. That is, towards the end of apply_action_receipt we call generate_refund_receipts and give a refund to the predecessor of the action. The predecessor of the delegation action should be the publisher, right? So that seems correct to me.

This deposit covers the total cost (gas and deposits) of all actions in DelegateAction. To run actions from DelegateAction, some prepaid gas is required. Relayer doesn't prepaid gas but attaches deposit instead. If Relayer attaches deposit which is less than the total cost of actions, need to generate a refund.
generate_refund_receipts generates a refund only when the receipt fails. This is not a fail case.

Why just do not pre-pay gas? Then I will play with result.gas_used counter here (and maybe deposit, if we want to refund it). result.gas_used will not be actually used but will be "reserved" for actions in DelegateAction. Otherwise generate_refund_receipts will refund all prepaid gas and check_balance call panic.

Both variants are tricky and I chose the first one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for explaining.

Then, there is one issue with the current design. The gas price for the initial receipts is computed based on attached gas = 0 because the delegate action has no attached gas. This gas price is then passed on to the inner actions receipt produced by the relayer. This circumvents the pessimistic price calculation that is meant to ensure that no receipt ever executed at a gas price lower than the current gas price, even if the gas price is increased on every block.

Prepaying all the gas upfront would avoid such trickiness. And it would be more in line with the existing bahaviour.

The protocol currently has two situations where gas is burnt later than when it is purchased.

  1. In general, action receipts are created in one block and executed in the next. In ActionResult, gas_burnt covers the cost for executing the current action and for creating new receipts without executing them. gas_used covers the full cost of executing the current action and all new action receipts. In other words, gas_used is gas_burnt + gas reserved for later execution.
    I don't see a reason why this model cannot be used here. The used_gas of the delegate action result after is has been unwrapped includes the usual exec fees of all created actions plus the gas burnt.
  2. For function calls, where the exact amount of gas is unknown at the time of gas purchasing, we attach gas. This is treated separately because the user defines this number and it is not fixed in the protocol. But in the ActionResult, the attached gas is still included in used_gas.
    For delegate actions, you could define attached gas to be determined by the inner actions, instead of an explicit field.

Unless there is a good reason for it, is is my opinion that we should avoid introducing the new concept of attached deposit that is used to buy gas a block later.

Instead, I suggest to prepay gas. Just include the gas as prepaid gas here.

pub fn get_prepaid_gas(&self) -> Gas {
match self {
Action::FunctionCall(a) => a.gas,
_ => 0,
}
}

The value for a delegate action should be the sum of all send+exec fees for inner actions. This should balance out used_gas for the new receipts, such that check_balance does no panic.


result.new_receipts.push(new_receipt);
result.new_receipts.push(refund_receipt);
} else {
result.result = Err(ActionErrorKind::LackBalanceForState {
account_id: action_receipt.signer_id.clone(),
amount: cost.clone(),
}
.into());
}
}
Err(_) => todo!(),
}

Ok(())
}

pub(crate) fn check_actor_permissions(
action: &Action,
account: &Option<Account>,
Expand Down Expand Up @@ -645,7 +687,10 @@ pub(crate) fn check_actor_permissions(
.into());
}
}
Action::CreateAccount(_) | Action::FunctionCall(_) | Action::Transfer(_) => (),
Action::CreateAccount(_)
| Action::FunctionCall(_)
| Action::Transfer(_)
| Action::Delegate(_) => (),
};
Ok(())
}
Expand Down Expand Up @@ -720,6 +765,7 @@ pub(crate) fn check_account_existence(
.into());
}
}
Action::Delegate(_) => (),
};
Ok(())
}
Expand Down
4 changes: 3 additions & 1 deletion runtime/runtime/src/balance_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn get_delayed_receipts(
}

/// Calculates and returns cost of a receipt.
fn receipt_cost(
pub(crate) fn receipt_cost(
transaction_costs: &RuntimeFeesConfig,
current_protocol_version: ProtocolVersion,
receipt: &Receipt,
Expand Down Expand Up @@ -422,6 +422,7 @@ mod tests {
receipt_id: Default::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: tx.transaction.signer_id.clone(),
relayer_id: None,
signer_public_key: tx.transaction.public_key.clone(),
gas_price,
output_data_receivers: vec![],
Expand Down Expand Up @@ -477,6 +478,7 @@ mod tests {
receipt_id: Default::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: tx.transaction.signer_id.clone(),
relayer_id: None,
signer_public_key: tx.transaction.public_key.clone(),
gas_price,
output_data_receivers: vec![],
Expand Down
2 changes: 2 additions & 0 deletions runtime/runtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub fn total_send_fees(
},
DeleteKey(_) => cfg.delete_key_cost.send_fee(sender_is_receiver),
DeleteAccount(_) => cfg.delete_account_cost.send_fee(sender_is_receiver),
Delegate(_) => 0, // TODO: Set some fee
};
result = safe_add_gas(result, delta)?;
}
Expand Down Expand Up @@ -170,6 +171,7 @@ pub fn exec_fee(
},
DeleteKey(_) => cfg.delete_key_cost.exec_fee(),
DeleteAccount(_) => cfg.delete_account_cost.exec_fee(),
Delegate(_) => cfg.delete_account_cost.exec_fee(), // TODO: Add another fee for Delegate action.
}
}

Expand Down
Loading