use crate::crypto::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display};
use near_primitives_core::{
hash::CryptoHash,
serialize::dec_format,
types::{AccountId, Balance, Gas, Nonce},
};
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum TxExecutionError {
ActionError(ActionError),
InvalidTxError(InvalidTxError),
}
impl std::error::Error for TxExecutionError {}
impl Display for TxExecutionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
TxExecutionError::ActionError(e) => write!(f, "{e}"),
TxExecutionError::InvalidTxError(e) => write!(f, "{e}"),
}
}
}
impl From<ActionError> for TxExecutionError {
fn from(error: ActionError) -> Self {
TxExecutionError::ActionError(error)
}
}
impl From<InvalidTxError> for TxExecutionError {
fn from(error: InvalidTxError) -> Self {
TxExecutionError::InvalidTxError(error)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RuntimeError {
UnexpectedIntegerOverflow,
InvalidTxError(InvalidTxError),
StorageError(StorageError),
BalanceMismatchError(BalanceMismatchError),
ReceiptValidationError(ReceiptValidationError),
ValidatorError(EpochError),
}
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&format!("{self:?}"))
}
}
impl std::error::Error for RuntimeError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StorageError {
StorageInternalError,
TrieNodeMissing,
StorageInconsistentState(String),
FlatStorageError(String),
}
impl std::fmt::Display for StorageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&format!("{self:?}"))
}
}
impl std::error::Error for StorageError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum InvalidTxError {
InvalidAccessKeyError(InvalidAccessKeyError),
InvalidSignerId { signer_id: String },
SignerDoesNotExist { signer_id: AccountId },
InvalidNonce { tx_nonce: Nonce, ak_nonce: Nonce },
NonceTooLarge { tx_nonce: Nonce, upper_bound: Nonce },
InvalidReceiverId { receiver_id: String },
InvalidSignature,
NotEnoughBalance {
signer_id: AccountId,
#[serde(with = "dec_format")]
balance: Balance,
#[serde(with = "dec_format")]
cost: Balance,
},
LackBalanceForState {
signer_id: AccountId,
#[serde(with = "dec_format")]
amount: Balance,
},
CostOverflow,
InvalidChain,
Expired,
ActionsValidation(ActionsValidationError),
TransactionSizeExceeded { size: u64, limit: u64 },
}
impl std::error::Error for InvalidTxError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum InvalidAccessKeyError {
AccessKeyNotFound {
account_id: AccountId,
public_key: Ed25519PublicKey,
},
ReceiverMismatch {
tx_receiver: AccountId,
ak_receiver: String,
},
MethodNameMismatch { method_name: String },
RequiresFullAccess,
NotEnoughAllowance {
account_id: AccountId,
public_key: Ed25519PublicKey,
#[serde(with = "dec_format")]
allowance: Balance,
#[serde(with = "dec_format")]
cost: Balance,
},
DepositWithFunctionCall,
}
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum ActionsValidationError {
DeleteActionMustBeFinal,
TotalPrepaidGasExceeded { total_prepaid_gas: Gas, limit: Gas },
TotalNumberOfActionsExceeded {
total_number_of_actions: u64,
limit: u64,
},
AddKeyMethodNamesNumberOfBytesExceeded {
total_number_of_bytes: u64,
limit: u64,
},
AddKeyMethodNameLengthExceeded { length: u64, limit: u64 },
IntegerOverflow,
InvalidAccountId { account_id: String },
ContractSizeExceeded { size: u64, limit: u64 },
FunctionCallMethodNameLengthExceeded { length: u64, limit: u64 },
FunctionCallArgumentsLengthExceeded { length: u64, limit: u64 },
UnsuitableStakingKey { public_key: Ed25519PublicKey },
FunctionCallZeroAttachedGas,
}
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum ReceiptValidationError {
InvalidPredecessorId { account_id: String },
InvalidReceiverId { account_id: String },
InvalidSignerId { account_id: String },
InvalidDataReceiverId { account_id: String },
ReturnedValueLengthExceeded { length: u64, limit: u64 },
NumberInputDataDependenciesExceeded {
number_of_input_data_dependencies: u64,
limit: u64,
},
ActionsValidation(ActionsValidationError),
}
impl Display for ReceiptValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
ReceiptValidationError::InvalidPredecessorId { account_id } => {
write!(f, "The predecessor_id `{account_id}` of a Receipt is not valid.")
}
ReceiptValidationError::InvalidReceiverId { account_id } => {
write!(f, "The receiver_id `{account_id}` of a Receipt is not valid.")
}
ReceiptValidationError::InvalidSignerId { account_id } => {
write!(f, "The signer_id `{account_id}` of an ActionReceipt is not valid.")
}
ReceiptValidationError::InvalidDataReceiverId { account_id } => write!(
f,
"The receiver_id `{account_id}` of a DataReceiver within an ActionReceipt is not valid."
),
ReceiptValidationError::ReturnedValueLengthExceeded { length, limit } => write!(
f,
"The length of the returned data {length} exceeded the limit {limit} in a DataReceipt"
),
ReceiptValidationError::NumberInputDataDependenciesExceeded { number_of_input_data_dependencies, limit } => write!(
f,
"The number of input data dependencies {number_of_input_data_dependencies} exceeded the limit {limit} in an ActionReceipt"
),
ReceiptValidationError::ActionsValidation(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for ReceiptValidationError {}
impl Display for ActionsValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
ActionsValidationError::DeleteActionMustBeFinal => {
write!(f, "The delete action must be the last action in transaction")
}
ActionsValidationError::TotalPrepaidGasExceeded { total_prepaid_gas, limit } => {
write!(f, "The total prepaid gas {total_prepaid_gas} exceeds the limit {limit}")
}
ActionsValidationError::TotalNumberOfActionsExceeded {total_number_of_actions, limit } => {
write!(
f,
"The total number of actions {total_number_of_actions} exceeds the limit {limit}"
)
}
ActionsValidationError::AddKeyMethodNamesNumberOfBytesExceeded { total_number_of_bytes, limit } => write!(
f,
"The total number of bytes in allowed method names {total_number_of_bytes} exceeds the maximum allowed number {limit} in a AddKey action"
),
ActionsValidationError::AddKeyMethodNameLengthExceeded { length, limit } => write!(
f,
"The length of some method name {length} exceeds the maximum allowed length {limit} in a AddKey action"
),
ActionsValidationError::IntegerOverflow => write!(
f,
"Integer overflow during a compute",
),
ActionsValidationError::InvalidAccountId { account_id } => write!(
f,
"Invalid account ID `{account_id}`"
),
ActionsValidationError::ContractSizeExceeded { size, limit } => write!(
f,
"The length of the contract size {size} exceeds the maximum allowed size {limit} in a DeployContract action"
),
ActionsValidationError::FunctionCallMethodNameLengthExceeded { length, limit } => write!(
f,
"The length of the method name {length} exceeds the maximum allowed length {limit} in a FunctionCall action"
),
ActionsValidationError::FunctionCallArgumentsLengthExceeded { length, limit } => write!(
f,
"The length of the arguments {length} exceeds the maximum allowed length {limit} in a FunctionCall action"
),
ActionsValidationError::UnsuitableStakingKey { public_key } => write!(
f,
"The staking key must be ristretto compatible ED25519 key. {public_key} is provided instead."
),
ActionsValidationError::FunctionCallZeroAttachedGas => write!(
f,
"The attached amount of gas in a FunctionCall action has to be a positive number",
),
}
}
}
impl std::error::Error for ActionsValidationError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ActionError {
pub index: Option<u64>,
pub kind: ActionErrorKind,
}
impl std::error::Error for ActionError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum ActionErrorKind {
AccountAlreadyExists { account_id: AccountId },
AccountDoesNotExist { account_id: AccountId },
CreateAccountOnlyByRegistrar {
account_id: AccountId,
registrar_account_id: AccountId,
predecessor_id: AccountId,
},
CreateAccountNotAllowed {
account_id: AccountId,
predecessor_id: AccountId,
},
ActorNoPermission {
account_id: AccountId,
actor_id: AccountId,
},
DeleteKeyDoesNotExist {
account_id: AccountId,
public_key: Ed25519PublicKey,
},
AddKeyAlreadyExists {
account_id: AccountId,
public_key: Ed25519PublicKey,
},
DeleteAccountStaking { account_id: AccountId },
LackBalanceForState {
account_id: AccountId,
#[serde(with = "dec_format")]
amount: Balance,
},
TriesToUnstake { account_id: AccountId },
TriesToStake {
account_id: AccountId,
#[serde(with = "dec_format")]
stake: Balance,
#[serde(with = "dec_format")]
locked: Balance,
#[serde(with = "dec_format")]
balance: Balance,
},
InsufficientStake {
account_id: AccountId,
#[serde(with = "dec_format")]
stake: Balance,
#[serde(with = "dec_format")]
minimum_stake: Balance,
},
FunctionCallError(near_vm_errors::FunctionCallErrorSer),
NewReceiptValidationError(ReceiptValidationError),
OnlyImplicitAccountCreationAllowed { account_id: AccountId },
DeleteAccountWithLargeState { account_id: AccountId },
}
impl From<ActionErrorKind> for ActionError {
fn from(e: ActionErrorKind) -> ActionError {
ActionError {
index: None,
kind: e,
}
}
}
impl Display for InvalidTxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
InvalidTxError::InvalidSignerId { signer_id } => {
write!(
f,
"Invalid signer account ID {signer_id:?} according to requirements"
)
}
InvalidTxError::SignerDoesNotExist { signer_id } => {
write!(f, "Signer {signer_id:?} does not exist")
}
InvalidTxError::InvalidAccessKeyError(access_key_error) => {
Display::fmt(&access_key_error, f)
}
InvalidTxError::InvalidNonce { tx_nonce, ak_nonce } => write!(
f,
"Transaction nonce {tx_nonce} must be larger than nonce of the used access key {ak_nonce}"
),
InvalidTxError::InvalidReceiverId { receiver_id } => {
write!(
f,
"Invalid receiver account ID {receiver_id:?} according to requirements"
)
}
InvalidTxError::InvalidSignature => {
write!(f, "Transaction is not signed with the given public key")
}
InvalidTxError::NotEnoughBalance {
signer_id,
balance,
cost,
} => write!(
f,
"Sender {signer_id:?} does not have enough balance {balance} for operation costing {cost}"
),
InvalidTxError::LackBalanceForState { signer_id, amount } => {
write!(f, "Failed to execute, because the account {signer_id:?} wouldn't have enough balance to cover storage, required to have {amount} yoctoNEAR more")
}
InvalidTxError::CostOverflow => {
write!(f, "Transaction gas or balance cost is too high")
}
InvalidTxError::InvalidChain => {
write!(
f,
"Transaction parent block hash doesn't belong to the current chain"
)
}
InvalidTxError::Expired => {
write!(f, "Transaction has expired")
}
InvalidTxError::ActionsValidation(error) => {
write!(f, "Transaction actions validation error: {error}")
}
InvalidTxError::NonceTooLarge {
tx_nonce,
upper_bound,
} => {
write!(
f,
"Transaction nonce {tx_nonce} must be smaller than the access key nonce upper bound {upper_bound}"
)
}
InvalidTxError::TransactionSizeExceeded { size, limit } => {
write!(
f,
"Size of serialized transaction {size} exceeded the limit {limit}"
)
}
}
}
}
impl From<InvalidAccessKeyError> for InvalidTxError {
fn from(error: InvalidAccessKeyError) -> Self {
InvalidTxError::InvalidAccessKeyError(error)
}
}
impl Display for InvalidAccessKeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
InvalidAccessKeyError::AccessKeyNotFound {
account_id,
public_key,
} => write!(
f,
"Signer {account_id:?} doesn't have access key with the given public_key {public_key}"
),
InvalidAccessKeyError::ReceiverMismatch {
tx_receiver,
ak_receiver,
} => write!(
f,
"Transaction receiver_id {tx_receiver:?} doesn't match the access key receiver_id {ak_receiver:?}",
),
InvalidAccessKeyError::MethodNameMismatch { method_name } => write!(
f,
"Transaction method name {method_name:?} isn't allowed by the access key"
),
InvalidAccessKeyError::RequiresFullAccess => {
write!(f, "Invalid access key type. Full-access keys are required for transactions that have multiple or non-function-call actions")
}
InvalidAccessKeyError::NotEnoughAllowance {
account_id,
public_key,
allowance,
cost,
} => write!(
f,
"Access Key {account_id:?}:{public_key} does not have enough balance {allowance} for transaction costing {cost}"
),
InvalidAccessKeyError::DepositWithFunctionCall => {
write!(f, "Having a deposit with a function call action is not allowed with a function call access key.")
}
}
}
}
impl std::error::Error for InvalidAccessKeyError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct BalanceMismatchError {
#[serde(with = "dec_format")]
pub incoming_validator_rewards: Balance,
#[serde(with = "dec_format")]
pub initial_accounts_balance: Balance,
#[serde(with = "dec_format")]
pub incoming_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub processed_delayed_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub initial_postponed_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub final_accounts_balance: Balance,
#[serde(with = "dec_format")]
pub outgoing_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub new_delayed_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub final_postponed_receipts_balance: Balance,
#[serde(with = "dec_format")]
pub tx_burnt_amount: Balance,
#[serde(with = "dec_format")]
pub slashed_burnt_amount: Balance,
#[serde(with = "dec_format")]
pub other_burnt_amount: Balance,
}
impl Display for BalanceMismatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let initial_balance = self
.incoming_validator_rewards
.saturating_add(self.initial_accounts_balance)
.saturating_add(self.incoming_receipts_balance)
.saturating_add(self.processed_delayed_receipts_balance)
.saturating_add(self.initial_postponed_receipts_balance);
let final_balance = self
.final_accounts_balance
.saturating_add(self.outgoing_receipts_balance)
.saturating_add(self.new_delayed_receipts_balance)
.saturating_add(self.final_postponed_receipts_balance)
.saturating_add(self.tx_burnt_amount)
.saturating_add(self.slashed_burnt_amount)
.saturating_add(self.other_burnt_amount);
write!(
f,
"Balance Mismatch Error. The input balance {} doesn't match output balance {}\n\
Inputs:\n\
\tIncoming validator rewards sum: {}\n\
\tInitial accounts balance sum: {}\n\
\tIncoming receipts balance sum: {}\n\
\tProcessed delayed receipts balance sum: {}\n\
\tInitial postponed receipts balance sum: {}\n\
Outputs:\n\
\tFinal accounts balance sum: {}\n\
\tOutgoing receipts balance sum: {}\n\
\tNew delayed receipts balance sum: {}\n\
\tFinal postponed receipts balance sum: {}\n\
\tTx fees burnt amount: {}\n\
\tSlashed amount: {}\n\
\tOther burnt amount: {}",
initial_balance,
final_balance,
self.incoming_validator_rewards,
self.initial_accounts_balance,
self.incoming_receipts_balance,
self.processed_delayed_receipts_balance,
self.initial_postponed_receipts_balance,
self.final_accounts_balance,
self.outgoing_receipts_balance,
self.new_delayed_receipts_balance,
self.final_postponed_receipts_balance,
self.tx_burnt_amount,
self.slashed_burnt_amount,
self.other_burnt_amount,
)
}
}
impl std::error::Error for BalanceMismatchError {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
pub struct IntegerOverflowError;
impl std::fmt::Display for IntegerOverflowError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&format!("{self:?}"))
}
}
impl std::error::Error for IntegerOverflowError {}
impl From<IntegerOverflowError> for InvalidTxError {
fn from(_: IntegerOverflowError) -> Self {
InvalidTxError::CostOverflow
}
}
impl From<IntegerOverflowError> for RuntimeError {
fn from(_: IntegerOverflowError) -> Self {
RuntimeError::UnexpectedIntegerOverflow
}
}
impl From<StorageError> for RuntimeError {
fn from(e: StorageError) -> Self {
RuntimeError::StorageError(e)
}
}
impl From<BalanceMismatchError> for RuntimeError {
fn from(e: BalanceMismatchError) -> Self {
RuntimeError::BalanceMismatchError(e)
}
}
impl From<InvalidTxError> for RuntimeError {
fn from(e: InvalidTxError) -> Self {
RuntimeError::InvalidTxError(e)
}
}
impl From<EpochError> for RuntimeError {
fn from(e: EpochError) -> Self {
RuntimeError::ValidatorError(e)
}
}
impl Display for ActionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"Action #{}: {}",
self.index.unwrap_or_default(),
self.kind
)
}
}
impl Display for ActionErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
ActionErrorKind::AccountAlreadyExists { account_id } => {
write!(f, "Can't create a new account {account_id:?}, because it already exists")
}
ActionErrorKind::AccountDoesNotExist { account_id } => write!(
f,
"Can't complete the action because account {account_id:?} doesn't exist"
),
ActionErrorKind::ActorNoPermission { actor_id, account_id } => write!(
f,
"Actor {actor_id:?} doesn't have permission to account {account_id:?} to complete the action"
),
ActionErrorKind::LackBalanceForState { account_id, amount } => write!(
f,
"The account {account_id} wouldn't have enough balance to cover storage, required to have {amount} yoctoNEAR more"
),
ActionErrorKind::TriesToUnstake { account_id } => {
write!(f, "Account {account_id:?} is not yet staked, but tries to unstake")
}
ActionErrorKind::TriesToStake { account_id, stake, locked, balance } => write!(
f,
"Account {account_id:?} tries to stake {stake}, but has staked {locked} and only has {balance}"
),
ActionErrorKind::CreateAccountOnlyByRegistrar { account_id, registrar_account_id, predecessor_id } => write!(
f,
"A top-level account ID {account_id:?} can't be created by {predecessor_id:?}, short top-level account IDs can only be created by {registrar_account_id:?}"
),
ActionErrorKind::CreateAccountNotAllowed { account_id, predecessor_id } => write!(
f,
"A sub-account ID {account_id:?} can't be created by account {predecessor_id:?}"
),
ActionErrorKind::DeleteKeyDoesNotExist { account_id, .. } => write!(
f,
"Account {account_id:?} tries to remove an access key that doesn't exist"
),
ActionErrorKind::AddKeyAlreadyExists { public_key, .. } => write!(
f,
"The public key {public_key:?} is already used for an existing access key"
),
ActionErrorKind::DeleteAccountStaking { account_id } => {
write!(f, "Account {account_id:?} is staking and can not be deleted")
}
ActionErrorKind::FunctionCallError(s) => write!(f, "{s:?}"),
ActionErrorKind::NewReceiptValidationError(e) => {
write!(f, "An new action receipt created during a FunctionCall is not valid: {e}")
}
ActionErrorKind::InsufficientStake { account_id, stake, minimum_stake } => write!(f, "Account {account_id} tries to stake {stake} but minimum required stake is {minimum_stake}"),
ActionErrorKind::OnlyImplicitAccountCreationAllowed { account_id } => write!(f, "CreateAccount action is called on hex-characters account of length 64 {account_id}"),
ActionErrorKind::DeleteAccountWithLargeState { account_id } => write!(f, "The state of account {account_id} is too large and therefore cannot be deleted"),
}
}
}
#[derive(Eq, PartialEq, Clone)]
pub enum EpochError {
ThresholdError { stake_sum: Balance, num_seats: u64 },
MissingBlock(CryptoHash),
IOErr(String),
ShardingError(String),
NotEnoughValidators {
num_validators: u64,
num_shards: u64,
},
}
impl std::error::Error for EpochError {}
impl Display for EpochError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EpochError::ThresholdError {
stake_sum,
num_seats,
} => write!(
f,
"Total stake {stake_sum} must be higher than the number of seats {num_seats}"
),
EpochError::MissingBlock(hash) => write!(f, "Missing block {hash}"),
EpochError::IOErr(err) => write!(f, "IO: {err}"),
EpochError::ShardingError(err) => write!(f, "Sharding Error: {err}"),
EpochError::NotEnoughValidators {
num_shards,
num_validators,
} => {
write!(f, "There were not enough validator proposals to fill all shards. num_proposals: {num_validators}, num_shards: {num_shards}")
}
}
}
}
impl Debug for EpochError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EpochError::ThresholdError {
stake_sum,
num_seats,
} => {
write!(f, "ThresholdError({stake_sum}, {num_seats})")
}
EpochError::MissingBlock(hash) => write!(f, "MissingBlock({hash})"),
EpochError::IOErr(err) => write!(f, "IOErr({err})"),
EpochError::ShardingError(err) => write!(f, "ShardingError({err})"),
EpochError::NotEnoughValidators {
num_shards,
num_validators,
} => {
write!(f, "NotEnoughValidators({num_validators}, {num_shards})")
}
}
}
}
impl From<std::io::Error> for EpochError {
fn from(error: std::io::Error) -> Self {
EpochError::IOErr(error.to_string())
}
}