Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
104 changes: 67 additions & 37 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ pub type BalanceResult<T> = Result<T, MmError<BalanceError>>;
pub type BalanceFut<T> = Box<dyn Future<Item = T, Error = MmError<BalanceError>> + Send>;
pub type NonZeroBalanceFut<T> = Box<dyn Future<Item = T, Error = MmError<GetNonZeroBalance>> + Send>;
pub type NumConversResult<T> = Result<T, MmError<NumConversError>>;
pub type StakingInfosResult = Result<StakingInfos, MmError<StakingInfosError>>;
pub type StakingInfosFut = Box<dyn Future<Item = StakingInfos, Error = MmError<StakingInfosError>> + Send>;
pub type StakingInfosFut = Box<dyn Future<Item = StakingInfos, Error = MmError<StakingInfoError>> + Send>;
pub type DelegationResult = Result<TransactionDetails, MmError<DelegationError>>;
pub type DelegationFut = Box<dyn Future<Item = TransactionDetails, Error = MmError<DelegationError>> + Send>;
pub type WithdrawResult = Result<TransactionDetails, MmError<WithdrawError>>;
Expand Down Expand Up @@ -2234,8 +2233,27 @@ pub struct ClaimStakingRewardsRequest {
}

#[derive(Deserialize)]
pub struct GetStakingInfosRequest {
pub struct DelegationsInfo {
pub coin: String,
info_details: DelegationsInfoDetails,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum DelegationsInfoDetails {
Qtum,
}

#[derive(Deserialize)]
pub struct ValidatorsInfo {
pub coin: String,
info_details: ValidatorsInfoDetails,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum ValidatorsInfoDetails {
Cosmos(rpc_command::tendermint::staking::ValidatorsRPC),
}

#[derive(Serialize, Deserialize)]
Expand Down Expand Up @@ -2764,59 +2782,59 @@ impl From<UnexpectedDerivationMethod> for BalanceError {

#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum StakingInfosError {
#[display(fmt = "Staking infos not available for: {}", coin)]
CoinDoesntSupportStakingInfos { coin: String },
pub enum StakingInfoError {
#[display(fmt = "No such coin {}", coin)]
NoSuchCoin { coin: String },
#[from_stringify("UnexpectedDerivationMethod")]
#[display(fmt = "Derivation method is not supported: {}", _0)]
UnexpectedDerivationMethod(String),
#[display(fmt = "Invalid payload: {}", reason)]
InvalidPayload { reason: String },
#[display(fmt = "Transport error: {}", _0)]
Transport(String),
#[display(fmt = "Internal error: {}", _0)]
Internal(String),
}

impl From<UtxoRpcError> for StakingInfosError {
impl From<UtxoRpcError> for StakingInfoError {
fn from(e: UtxoRpcError) -> Self {
match e {
UtxoRpcError::Transport(rpc) | UtxoRpcError::ResponseParseError(rpc) => {
StakingInfosError::Transport(rpc.to_string())
StakingInfoError::Transport(rpc.to_string())
},
UtxoRpcError::InvalidResponse(error) => StakingInfosError::Transport(error),
UtxoRpcError::Internal(error) => StakingInfosError::Internal(error),
UtxoRpcError::InvalidResponse(error) => StakingInfoError::Transport(error),
UtxoRpcError::Internal(error) => StakingInfoError::Internal(error),
}
}
}

impl From<Qrc20AddressError> for StakingInfosError {
impl From<Qrc20AddressError> for StakingInfoError {
fn from(e: Qrc20AddressError) -> Self {
match e {
Qrc20AddressError::UnexpectedDerivationMethod(e) => StakingInfosError::UnexpectedDerivationMethod(e),
Qrc20AddressError::UnexpectedDerivationMethod(e) => StakingInfoError::UnexpectedDerivationMethod(e),
Qrc20AddressError::ScriptHashTypeNotSupported { script_hash_type } => {
StakingInfosError::Internal(format!("Script hash type '{}' is not supported", script_hash_type))
StakingInfoError::Internal(format!("Script hash type '{}' is not supported", script_hash_type))
},
}
}
}

impl HttpStatusCode for StakingInfosError {
impl HttpStatusCode for StakingInfoError {
fn status_code(&self) -> StatusCode {
match self {
StakingInfosError::NoSuchCoin { .. }
| StakingInfosError::CoinDoesntSupportStakingInfos { .. }
| StakingInfosError::UnexpectedDerivationMethod(_) => StatusCode::BAD_REQUEST,
StakingInfosError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
StakingInfosError::Transport(_) => StatusCode::BAD_GATEWAY,
StakingInfoError::NoSuchCoin { .. }
| StakingInfoError::InvalidPayload { .. }
| StakingInfoError::UnexpectedDerivationMethod(_) => StatusCode::BAD_REQUEST,
StakingInfoError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
StakingInfoError::Transport(_) => StatusCode::BAD_GATEWAY,
}
}
}

impl From<CoinFindError> for StakingInfosError {
impl From<CoinFindError> for StakingInfoError {
fn from(e: CoinFindError) -> Self {
match e {
CoinFindError::NoSuchCoin { coin } => StakingInfosError::NoSuchCoin { coin },
CoinFindError::NoSuchCoin { coin } => StakingInfoError::NoSuchCoin { coin },
}
}
}
Expand Down Expand Up @@ -2897,18 +2915,16 @@ impl From<UtxoRpcError> for DelegationError {
}
}

impl From<StakingInfosError> for DelegationError {
fn from(e: StakingInfosError) -> Self {
impl From<StakingInfoError> for DelegationError {
fn from(e: StakingInfoError) -> Self {
match e {
StakingInfosError::CoinDoesntSupportStakingInfos { coin } => {
DelegationError::CoinDoesntSupportDelegation { coin }
},
StakingInfosError::NoSuchCoin { coin } => DelegationError::NoSuchCoin { coin },
StakingInfosError::Transport(e) => DelegationError::Transport(e),
StakingInfosError::UnexpectedDerivationMethod(reason) => {
StakingInfoError::NoSuchCoin { coin } => DelegationError::NoSuchCoin { coin },
StakingInfoError::Transport(e) => DelegationError::Transport(e),
StakingInfoError::UnexpectedDerivationMethod(reason) => {
DelegationError::DelegationOpsNotSupported { reason }
},
StakingInfosError::Internal(e) => DelegationError::InternalError(e),
StakingInfoError::Internal(e) => DelegationError::InternalError(e),
StakingInfoError::InvalidPayload { reason } => DelegationError::InvalidPayload { reason },
}
}
}
Expand Down Expand Up @@ -4969,18 +4985,32 @@ pub async fn remove_delegation(ctx: MmArc, req: RemoveDelegateRequest) -> Delega
}
}

pub async fn get_staking_infos(ctx: MmArc, req: GetStakingInfosRequest) -> StakingInfosResult {
pub async fn delegations_info(ctx: MmArc, req: DelegationsInfo) -> Result<Json, MmError<StakingInfoError>> {
let coin = lp_coinfind_or_err(&ctx, &req.coin).await?;
match coin {
MmCoinEnum::QtumCoin(qtum) => qtum.get_delegation_infos().compat().await,
_ => {
return MmError::err(StakingInfosError::CoinDoesntSupportStakingInfos {
coin: coin.ticker().to_string(),
})

match req.info_details {
DelegationsInfoDetails::Qtum => {
let MmCoinEnum::QtumCoin(qtum) = coin else {
return MmError::err(StakingInfoError::InvalidPayload {
reason: format!("{} is not a Qtum coin", req.coin)
});
};

qtum.get_delegation_infos().compat().await.map(|v| json!(v))
},
}
}

pub async fn validators_info(ctx: MmArc, req: ValidatorsInfo) -> Result<Json, MmError<StakingInfoError>> {
let coin = lp_coinfind_or_err(&ctx, &req.coin).await?;

match req.info_details {
ValidatorsInfoDetails::Cosmos(payload) => rpc_command::tendermint::staking::validators_rpc(coin, payload)
.await
.map(|v| json!(v)),
}
}

pub async fn add_delegation(ctx: MmArc, req: AddDelegateRequest) -> DelegationResult {
let coin = lp_coinfind_or_err(&ctx, &req.coin).await?;

Expand Down
60 changes: 18 additions & 42 deletions mm2src/coins/rpc_command/tendermint/staking.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use common::{HttpStatusCode, PagingOptions, StatusCode};
use common::PagingOptions;
use cosmrs::staking::{Commission, Description, Validator};
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::MmError;
use mm2_number::BigDecimal;

use crate::{hd_wallet::WithdrawFrom, lp_coinfind_or_err, tendermint::TendermintCoinRpcError, MmCoinEnum, WithdrawFee};
use crate::{hd_wallet::WithdrawFrom, tendermint::TendermintCoinRpcError, MmCoinEnum, StakingInfoError, WithdrawFee};

/// Represents current status of the validator.
#[derive(Default, Deserialize)]
#[derive(Debug, Default, Deserialize)]
pub(crate) enum ValidatorStatus {
All,
/// Validator is in the active set and participates in consensus.
Expand All @@ -30,10 +29,8 @@ impl ToString for ValidatorStatus {
}
}

#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
pub struct ValidatorsRPC {
#[serde(rename = "ticker")]
coin: String,
#[serde(flatten)]
paging: PagingOptions,
#[serde(default)]
Expand All @@ -45,38 +42,14 @@ pub struct ValidatorsRPCResponse {
validators: Vec<serde_json::Value>,
}

#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)]
#[serde(tag = "error_type", content = "error_data")]
pub enum ValidatorsRPCError {
#[display(fmt = "Coin '{ticker}' could not be found in coins configuration.")]
CoinNotFound { ticker: String },
#[display(fmt = "'{ticker}' is not a Cosmos coin.")]
UnexpectedCoinType { ticker: String },
#[display(fmt = "Transport error: {}", _0)]
Transport(String),
#[display(fmt = "Internal error: {}", _0)]
InternalError(String),
}

impl HttpStatusCode for ValidatorsRPCError {
fn status_code(&self) -> common::StatusCode {
match self {
ValidatorsRPCError::Transport(_) => StatusCode::SERVICE_UNAVAILABLE,
ValidatorsRPCError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
ValidatorsRPCError::CoinNotFound { .. } => StatusCode::NOT_FOUND,
ValidatorsRPCError::UnexpectedCoinType { .. } => StatusCode::BAD_REQUEST,
}
}
}

impl From<TendermintCoinRpcError> for ValidatorsRPCError {
impl From<TendermintCoinRpcError> for StakingInfoError {
fn from(e: TendermintCoinRpcError) -> Self {
match e {
TendermintCoinRpcError::InvalidResponse(e)
| TendermintCoinRpcError::PerformError(e)
| TendermintCoinRpcError::RpcClientError(e) => ValidatorsRPCError::Transport(e),
TendermintCoinRpcError::Prost(e) | TendermintCoinRpcError::InternalError(e) => ValidatorsRPCError::InternalError(e),
TendermintCoinRpcError::UnexpectedAccountType { .. } => ValidatorsRPCError::InternalError(
| TendermintCoinRpcError::RpcClientError(e) => StakingInfoError::Transport(e),
TendermintCoinRpcError::Prost(e) | TendermintCoinRpcError::InternalError(e) => StakingInfoError::Internal(e),
TendermintCoinRpcError::UnexpectedAccountType { .. } => StakingInfoError::Internal(
"RPC client got an unexpected error 'TendermintCoinRpcError::UnexpectedAccountType', this isn't normal."
.into(),
),
Expand All @@ -85,9 +58,9 @@ impl From<TendermintCoinRpcError> for ValidatorsRPCError {
}

pub async fn validators_rpc(
ctx: MmArc,
coin: MmCoinEnum,
req: ValidatorsRPC,
) -> Result<ValidatorsRPCResponse, MmError<ValidatorsRPCError>> {
) -> Result<ValidatorsRPCResponse, MmError<StakingInfoError>> {
fn maybe_jsonize_description(description: Option<Description>) -> Option<serde_json::Value> {
description.map(|d| {
json!({
Expand Down Expand Up @@ -133,16 +106,19 @@ pub async fn validators_rpc(
})
}

let validators = match lp_coinfind_or_err(&ctx, &req.coin).await {
Ok(MmCoinEnum::Tendermint(coin)) => coin.validators_list(req.filter_by_status, req.paging).await?,
Ok(MmCoinEnum::TendermintToken(token)) => {
let validators = match coin {
MmCoinEnum::Tendermint(coin) => coin.validators_list(req.filter_by_status, req.paging).await?,
MmCoinEnum::TendermintToken(token) => {
token
.platform_coin
.validators_list(req.filter_by_status, req.paging)
.await?
},
Ok(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }),
Err(_) => return MmError::err(ValidatorsRPCError::CoinNotFound { ticker: req.coin }),
other => {
return MmError::err(StakingInfoError::InvalidPayload {
reason: format!("{} is not a Cosmos coin", other.ticker()),
})
},
};

Ok(ValidatorsRPCResponse {
Expand Down
17 changes: 9 additions & 8 deletions mm2src/coins/utxo/qtum_delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use crate::utxo::rpc_clients::UtxoRpcClientEnum;
use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, UtxoTxBuilder};
use crate::utxo::{qtum, utxo_common, Address, GetUtxoListOps, UtxoCommonOps};
use crate::utxo::{PrivKeyPolicyNotAllowed, UTXO_LOCK};
use crate::{DelegationError, DelegationFut, DelegationResult, MarketCoinOps, StakingInfos, StakingInfosError,
StakingInfosFut, StakingInfosResult, TransactionData, TransactionDetails, TransactionType};
use crate::{DelegationError, DelegationFut, DelegationResult, MarketCoinOps, StakingInfoError, StakingInfos,
StakingInfosFut, TransactionData, TransactionDetails, TransactionType};
use bitcrypto::dhash256;
use common::now_sec;
use derive_more::Display;
Expand Down Expand Up @@ -39,6 +39,7 @@ lazy_static! {
}

pub type QtumStakingAbiResult<T> = Result<T, MmError<QtumStakingAbiError>>;
type StakingInfosResult = Result<StakingInfos, MmError<StakingInfoError>>;

#[derive(Debug, Display)]
pub enum QtumStakingAbiError {
Expand Down Expand Up @@ -132,12 +133,12 @@ impl QtumCoin {
.await
}

async fn am_i_currently_staking(&self) -> Result<Option<String>, MmError<StakingInfosError>> {
async fn am_i_currently_staking(&self) -> Result<Option<String>, MmError<StakingInfoError>> {
let utxo = self.as_ref();
let contract_address = contract_addr_into_rpc_format(&QTUM_DELEGATE_CONTRACT_ADDRESS);
let client = match &utxo.rpc_client {
UtxoRpcClientEnum::Native(_) => {
return MmError::err(StakingInfosError::Internal("Native not supported".to_string()))
return MmError::err(StakingInfoError::Internal("Native not supported".to_string()))
},
UtxoRpcClientEnum::Electrum(electrum) => electrum,
};
Expand All @@ -147,20 +148,20 @@ impl QtumCoin {
.blockchain_contract_event_get_history(&address_rpc, &contract_address, QTUM_ADD_DELEGATION_TOPIC)
.compat()
.await
.map_to_mm(|e| StakingInfosError::Transport(e.to_string()))?;
.map_to_mm(|e| StakingInfoError::Transport(e.to_string()))?;
let remove_delegation_history = client
.blockchain_contract_event_get_history(&address_rpc, &contract_address, QTUM_REMOVE_DELEGATION_TOPIC)
.compat()
.await
.map_to_mm(|e| StakingInfosError::Transport(e.to_string()))?;
.map_to_mm(|e| StakingInfoError::Transport(e.to_string()))?;
let am_i_staking = add_delegation_history.len() > remove_delegation_history.len();
if am_i_staking {
let last_tx_add = some_or_return_ok_none!(add_delegation_history.last());
let res = &client
.blockchain_transaction_get_receipt(&last_tx_add.tx_hash)
.compat()
.await
.map_to_mm(|e| StakingInfosError::Transport(e.to_string()))?;
.map_to_mm(|e| StakingInfoError::Transport(e.to_string()))?;
// there is only 3 topics for an add_delegation
// the first entry is the operation (add_delegation / remove_delegation),
// the second entry is always the staker as hexadecimal 32 byte padded
Expand All @@ -185,7 +186,7 @@ impl QtumCoin {
.and_then(|log_entry| log_entry.topics.get(1))
.map(|padded_staker_address_hex| padded_staker_address_hex.trim_start_matches('0'))
}) {
let hash = H160::from_str(raw).map_to_mm(|e| StakingInfosError::Internal(e.to_string()))?;
let hash = H160::from_str(raw).map_to_mm(|e| StakingInfoError::Internal(e.to_string()))?;
let address = self.utxo_addr_from_contract_addr(hash);
Ok(Some(address.to_string()))
} else {
Expand Down
Loading