extract Coin and Denom
This commit is contained in:
@@ -483,15 +483,15 @@ impl<C> NymdClient<C> {
|
|||||||
/// Delegates specified amount of stake to particular mixnode.
|
/// Delegates specified amount of stake to particular mixnode.
|
||||||
pub async fn delegate_to_mixnode(
|
pub async fn delegate_to_mixnode(
|
||||||
&self,
|
&self,
|
||||||
mix_identity: IdentityKey,
|
mix_identity: &str,
|
||||||
amount: Coin,
|
amount: &Coin,
|
||||||
) -> Result<ExecuteResult, NymdError>
|
) -> Result<ExecuteResult, NymdError>
|
||||||
where
|
where
|
||||||
C: SigningCosmWasmClient + Sync,
|
C: SigningCosmWasmClient + Sync,
|
||||||
{
|
{
|
||||||
let fee = self.get_fee(Operation::DelegateToMixnode);
|
let fee = self.get_fee(Operation::DelegateToMixnode);
|
||||||
|
|
||||||
let req = ExecuteMsg::DelegateToMixnode { mix_identity };
|
let req = ExecuteMsg::DelegateToMixnode { mix_identity: mix_identity.to_string() };
|
||||||
self.client
|
self.client
|
||||||
.execute(
|
.execute(
|
||||||
self.address(),
|
self.address(),
|
||||||
@@ -499,7 +499,7 @@ impl<C> NymdClient<C> {
|
|||||||
&req,
|
&req,
|
||||||
fee,
|
fee,
|
||||||
"Delegating to mixnode from rust!",
|
"Delegating to mixnode from rust!",
|
||||||
vec![cosmwasm_coin_to_cosmos_coin(amount)],
|
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -648,3 +648,11 @@ fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
|
|||||||
amount: (coin.amount.u128() as u64).into(),
|
amount: (coin.amount.u128() as u64).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cosmwasm_coin_ptr_to_cosmos_coin(coin: &Coin) -> CosmosCoin {
|
||||||
|
CosmosCoin {
|
||||||
|
denom: coin.denom.parse().unwrap(),
|
||||||
|
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
|
||||||
|
amount: (coin.amount.u128() as u64).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
// This should be moved out of the wallet, and used as a primary coin type throughout the codebase
|
||||||
|
|
||||||
|
use ::config::defaults::DENOM;
|
||||||
|
use cosmos_sdk::Coin as CosmosCoin;
|
||||||
|
use cosmos_sdk::Decimal;
|
||||||
|
use cosmos_sdk::Denom as CosmosDenom;
|
||||||
|
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt;
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use ts_rs::TS;
|
||||||
|
use validator_client::nymd::GasPrice;
|
||||||
|
|
||||||
|
use crate::format_err;
|
||||||
|
|
||||||
|
#[derive(TS, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum Denom {
|
||||||
|
Major,
|
||||||
|
Minor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Denom {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Denom::Major => f.write_str(&DENOM[1..].to_uppercase()),
|
||||||
|
Denom::Minor => f.write_str(DENOM),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Denom {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Denom, String> {
|
||||||
|
if s.to_lowercase() == DENOM.to_lowercase() {
|
||||||
|
Ok(Denom::Minor)
|
||||||
|
} else if s.to_lowercase() == DENOM[1..].to_lowercase() {
|
||||||
|
Ok(Denom::Major)
|
||||||
|
} else {
|
||||||
|
Err(format_err!(format!(
|
||||||
|
"{} is not a valid denomination string",
|
||||||
|
s
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(TS, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Coin {
|
||||||
|
amount: String,
|
||||||
|
denom: Denom,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO convert to TryFrom
|
||||||
|
impl From<GasPrice> for Coin {
|
||||||
|
fn from(g: GasPrice) -> Coin {
|
||||||
|
Coin {
|
||||||
|
amount: g.amount.to_string(),
|
||||||
|
denom: Denom::from_str(&g.denom.to_string()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Coin {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&format!("{} {}", self.amount, self.denom))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows adding minor and major denominations, output will have the LHS denom.
|
||||||
|
impl Add for Coin {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
let denom = self.denom.clone();
|
||||||
|
let lhs = self.to_minor();
|
||||||
|
let rhs = rhs.to_minor();
|
||||||
|
let lhs_amount = lhs.amount.parse::<u64>().unwrap();
|
||||||
|
let rhs_amount = rhs.amount.parse::<u64>().unwrap();
|
||||||
|
let amount = lhs_amount + rhs_amount;
|
||||||
|
let coin = Coin {
|
||||||
|
amount: amount.to_string(),
|
||||||
|
denom: Denom::Minor,
|
||||||
|
};
|
||||||
|
match denom {
|
||||||
|
Denom::Major => coin.to_major(),
|
||||||
|
Denom::Minor => coin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coin {
|
||||||
|
pub fn to_major(&self) -> Coin {
|
||||||
|
match self.denom {
|
||||||
|
Denom::Major => self.clone(),
|
||||||
|
Denom::Minor => Coin {
|
||||||
|
amount: (self.amount.parse::<f64>().unwrap() / 1_000_000.).to_string(),
|
||||||
|
denom: Denom::Major,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_minor(&self) -> Coin {
|
||||||
|
match self.denom {
|
||||||
|
Denom::Minor => self.clone(),
|
||||||
|
Denom::Major => Coin {
|
||||||
|
amount: (self.amount.parse::<f64>().unwrap() * 1_000_000.).to_string(),
|
||||||
|
denom: Denom::Minor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Coin> for CosmWasmCoin {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(coin: Coin) -> Result<CosmWasmCoin, String> {
|
||||||
|
match serde_json::to_value(coin) {
|
||||||
|
Ok(value) => match serde_json::from_value(value) {
|
||||||
|
Ok(coin) => Ok(coin),
|
||||||
|
Err(e) => Err(format_err!(e)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format_err!(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Coin> for CosmosCoin {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(coin: Coin) -> Result<CosmosCoin, String> {
|
||||||
|
let coin = coin.to_minor();
|
||||||
|
match Decimal::from_str(&coin.amount) {
|
||||||
|
Ok(d) => Ok(CosmosCoin {
|
||||||
|
amount: d,
|
||||||
|
denom: CosmosDenom::from_str(&coin.denom.to_string()).unwrap(),
|
||||||
|
}),
|
||||||
|
Err(e) => Err(format_err!(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CosmosCoin> for Coin {
|
||||||
|
fn from(c: CosmosCoin) -> Coin {
|
||||||
|
Coin {
|
||||||
|
amount: c.amount.to_string(),
|
||||||
|
denom: Denom::from_str(&c.denom.to_string()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CosmWasmCoin> for Coin {
|
||||||
|
fn from(c: CosmWasmCoin) -> Coin {
|
||||||
|
Coin {
|
||||||
|
amount: c.amount.to_string(),
|
||||||
|
denom: Denom::from_str(&c.denom.to_string()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,32 +3,28 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use ::config::defaults::DENOM;
|
|
||||||
use bip39::{Language, Mnemonic};
|
use bip39::{Language, Mnemonic};
|
||||||
use cosmos_sdk::Coin as CosmosCoin;
|
use cosmos_sdk::Coin as CosmosCoin;
|
||||||
use cosmos_sdk::Denom as CosmosDenom;
|
use cosmos_sdk::AccountId;
|
||||||
use cosmos_sdk::{AccountId, Decimal};
|
|
||||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||||
use error::BackendError;
|
use error::BackendError;
|
||||||
use mixnet_contract::{Gateway, MixNode};
|
use mixnet_contract::{Gateway, MixNode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops::Add;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tendermint_rpc::endpoint::broadcast::tx_commit::Response;
|
use tendermint_rpc::endpoint::broadcast::tx_commit::Response;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use ts_rs::{export, TS};
|
use ts_rs::{export, TS};
|
||||||
use validator_client::nymd::fee_helpers::Operation;
|
use validator_client::nymd::fee_helpers::Operation;
|
||||||
use validator_client::nymd::GasPrice;
|
|
||||||
use validator_client::nymd::{NymdClient, SigningNymdClient};
|
use validator_client::nymd::{NymdClient, SigningNymdClient};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
mod coconut;
|
mod coconut;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod coin;
|
||||||
|
|
||||||
use crate::coconut::{
|
use crate::coconut::{
|
||||||
delete_credential, get_credential, list_credentials, randomise_credential, verify_credential,
|
delete_credential, get_credential, list_credentials, randomise_credential, verify_credential,
|
||||||
@@ -36,6 +32,7 @@ use crate::coconut::{
|
|||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::coin::{Coin, Denom};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! format_err {
|
macro_rules! format_err {
|
||||||
@@ -44,43 +41,19 @@ macro_rules! format_err {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(TS, Serialize, Deserialize)]
|
||||||
|
struct DelegationResult {
|
||||||
|
source_address: String,
|
||||||
|
target_address: String,
|
||||||
|
amount: Option<Coin>
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(TS, Serialize, Deserialize)]
|
#[derive(TS, Serialize, Deserialize)]
|
||||||
struct Balance {
|
struct Balance {
|
||||||
coin: Coin,
|
coin: Coin,
|
||||||
printable_balance: String,
|
printable_balance: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Denom {
|
|
||||||
Major,
|
|
||||||
Minor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Denom {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Denom::Major => f.write_str(&DENOM[1..].to_uppercase()),
|
|
||||||
Denom::Minor => f.write_str(DENOM),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Denom {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Denom, String> {
|
|
||||||
if s.to_lowercase() == DENOM.to_lowercase() {
|
|
||||||
Ok(Denom::Minor)
|
|
||||||
} else if s.to_lowercase() == DENOM[1..].to_lowercase() {
|
|
||||||
Ok(Denom::Major)
|
|
||||||
} else {
|
|
||||||
Err(format_err!(format!(
|
|
||||||
"{} is not a valid denomination string",
|
|
||||||
s
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
struct TauriTxResult {
|
struct TauriTxResult {
|
||||||
code: u32,
|
code: u32,
|
||||||
@@ -109,124 +82,12 @@ impl TauriTxResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy types to allow TS generation
|
// TODO these should be more explicit
|
||||||
#[derive(TS, Serialize, Deserialize, Clone)]
|
|
||||||
struct Coin {
|
|
||||||
amount: String,
|
|
||||||
denom: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GasPrice> for Coin {
|
|
||||||
fn from(g: GasPrice) -> Coin {
|
|
||||||
Coin {
|
|
||||||
amount: g.amount.to_string(),
|
|
||||||
denom: g.denom.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Coin {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str(&format!("{} {}", self.amount, self.denom))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allows adding minor and major denominations, output will have the LHS denom.
|
|
||||||
impl Add for Coin {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self {
|
|
||||||
let denom = Denom::from_str(&self.denom).unwrap();
|
|
||||||
let lhs = self.to_minor();
|
|
||||||
let rhs = rhs.to_minor();
|
|
||||||
let lhs_amount = lhs.amount.parse::<u64>().unwrap();
|
|
||||||
let rhs_amount = rhs.amount.parse::<u64>().unwrap();
|
|
||||||
let amount = lhs_amount + rhs_amount;
|
|
||||||
let coin = Coin {
|
|
||||||
amount: amount.to_string(),
|
|
||||||
denom: Denom::Minor.to_string(),
|
|
||||||
};
|
|
||||||
match denom {
|
|
||||||
Denom::Major => coin.to_major(),
|
|
||||||
Denom::Minor => coin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Coin {
|
|
||||||
fn to_major(&self) -> Coin {
|
|
||||||
if let Ok(denom) = Denom::from_str(&self.denom) {
|
|
||||||
match denom {
|
|
||||||
Denom::Major => self.clone(),
|
|
||||||
Denom::Minor => Coin {
|
|
||||||
amount: (self.amount.parse::<f64>().unwrap() / 1_000_000.).to_string(),
|
|
||||||
denom: Denom::Major.to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_minor(&self) -> Coin {
|
|
||||||
if let Ok(denom) = Denom::from_str(&self.denom) {
|
|
||||||
match denom {
|
|
||||||
Denom::Minor => self.clone(),
|
|
||||||
Denom::Major => Coin {
|
|
||||||
amount: (self.amount.parse::<f64>().unwrap() * 1_000_000.).to_string(),
|
|
||||||
denom: Denom::Minor.to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Coin> for CosmWasmCoin {
|
|
||||||
type Error = String;
|
|
||||||
|
|
||||||
fn try_from(coin: Coin) -> Result<CosmWasmCoin, String> {
|
|
||||||
match serde_json::to_value(coin) {
|
|
||||||
Ok(value) => match serde_json::from_value(value) {
|
|
||||||
Ok(coin) => Ok(coin),
|
|
||||||
Err(e) => Err(format_err!(e)),
|
|
||||||
},
|
|
||||||
Err(e) => Err(format_err!(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is some confusion here over coins and denoms, it feels like we should use types to differentiate between the two
|
|
||||||
impl TryFrom<Coin> for CosmosCoin {
|
|
||||||
type Error = String;
|
|
||||||
|
|
||||||
fn try_from(coin: Coin) -> Result<CosmosCoin, String> {
|
|
||||||
let coin = coin.to_minor();
|
|
||||||
match Decimal::from_str(&coin.amount) {
|
|
||||||
Ok(d) => Ok(CosmosCoin {
|
|
||||||
amount: d,
|
|
||||||
denom: CosmosDenom::from_str(&coin.denom).unwrap(),
|
|
||||||
}),
|
|
||||||
Err(e) => Err(format_err!(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CosmosCoin> for Coin {
|
|
||||||
fn from(c: CosmosCoin) -> Coin {
|
|
||||||
Coin {
|
|
||||||
amount: c.amount.to_string(),
|
|
||||||
denom: c.denom.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn major_to_minor(amount: String) -> Result<Coin, String> {
|
fn major_to_minor(amount: String) -> Result<Coin, String> {
|
||||||
let coin = Coin {
|
let coin = Coin {
|
||||||
amount,
|
amount,
|
||||||
denom: Denom::Major.to_string(),
|
denom: Denom::Major,
|
||||||
};
|
};
|
||||||
Ok(coin.to_minor())
|
Ok(coin.to_minor())
|
||||||
}
|
}
|
||||||
@@ -235,7 +96,7 @@ fn major_to_minor(amount: String) -> Result<Coin, String> {
|
|||||||
fn minor_to_major(amount: String) -> Result<Coin, String> {
|
fn minor_to_major(amount: String) -> Result<Coin, String> {
|
||||||
let coin = Coin {
|
let coin = Coin {
|
||||||
amount,
|
amount,
|
||||||
denom: Denom::Minor.to_string(),
|
denom: Denom::Minor,
|
||||||
};
|
};
|
||||||
Ok(coin.to_major())
|
Ok(coin.to_major())
|
||||||
}
|
}
|
||||||
@@ -285,7 +146,7 @@ async fn get_balance(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<Bala
|
|||||||
Ok(Some(coin)) => {
|
Ok(Some(coin)) => {
|
||||||
let coin = Coin {
|
let coin = Coin {
|
||||||
amount: coin.amount.to_string(),
|
amount: coin.amount.to_string(),
|
||||||
denom: coin.denom.to_string(),
|
denom: Denom::from_str(&coin.denom.to_string())?,
|
||||||
};
|
};
|
||||||
Ok(Balance {
|
Ok(Balance {
|
||||||
coin: coin.clone(),
|
coin: coin.clone(),
|
||||||
@@ -353,15 +214,19 @@ async fn delegate_to_mixnode(
|
|||||||
identity: String,
|
identity: String,
|
||||||
amount: Coin,
|
amount: Coin,
|
||||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<DelegationResult, String> {
|
||||||
let r_state = state.read().await;
|
let r_state = state.read().await;
|
||||||
let bond: CosmWasmCoin = match amount.try_into() {
|
let bond: CosmWasmCoin = match amount.try_into() {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) => return Err(format_err!(e)),
|
Err(e) => return Err(format_err!(e)),
|
||||||
};
|
};
|
||||||
let client = r_state.client()?;
|
let client = r_state.client()?;
|
||||||
match client.delegate_to_mixnode(identity, bond).await {
|
match client.delegate_to_mixnode(&identity, &bond).await {
|
||||||
Ok(_result) => Ok(()),
|
Ok(_result) => Ok(DelegationResult {
|
||||||
|
source_address: client.address().to_string(),
|
||||||
|
target_address: identity,
|
||||||
|
amount: Some(bond.into())
|
||||||
|
}),
|
||||||
Err(e) => Err(format_err!(e)),
|
Err(e) => Err(format_err!(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,7 +343,7 @@ async fn get_fee(
|
|||||||
let fee = client.get_fee(operation);
|
let fee = client.get_fee(operation);
|
||||||
let mut coin = Coin {
|
let mut coin = Coin {
|
||||||
amount: "0".to_string(),
|
amount: "0".to_string(),
|
||||||
denom: "upunk".to_string(),
|
denom: Denom::Major,
|
||||||
};
|
};
|
||||||
for f in fee.amount {
|
for f in fee.amount {
|
||||||
coin = coin + f.into();
|
coin = coin + f.into();
|
||||||
|
|||||||
Reference in New Issue
Block a user