diff --git a/rest/Cargo.lock b/rest/Cargo.lock index 32eda13c..5bde88d9 100644 --- a/rest/Cargo.lock +++ b/rest/Cargo.lock @@ -627,6 +627,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets", ] @@ -2354,6 +2355,7 @@ name = "nomic-rest" version = "0.1.0" dependencies = [ "base64 0.13.1", + "chrono", "hex", "lazy_static", "nomic", diff --git a/rest/Cargo.toml b/rest/Cargo.toml index 318c923c..2649a440 100644 --- a/rest/Cargo.toml +++ b/rest/Cargo.toml @@ -20,3 +20,4 @@ serde = "1.0.136" serde_json = "1.0.78" lazy_static = "1.4.0" tokio = "1.19.2" +chrono = { version = "0.4.31", features = ["serde"] } diff --git a/rest/src/main.rs b/rest/src/main.rs index f9f28b6e..17b552d2 100644 --- a/rest/src/main.rs +++ b/rest/src/main.rs @@ -2,14 +2,14 @@ extern crate rocket; use nomic::{ - app::{App, InnerApp, Nom}, + app::{InnerApp, Nom}, + bitcoin::Nbtc, orga::{ client::{wallet::Unsigned, AppClient}, - coins::{Address, Amount, Decimal}, - plugins::*, - query::Query, + coins::{Address, Amount, Decimal, DelegationInfo, Symbol, ValidatorQueryInfo}, tendermint::client::HttpClient, }, + utils::DeclareInfo, }; use rocket::response::status::BadRequest; use rocket::serde::json::{json, Value}; @@ -28,30 +28,195 @@ fn app_client() -> AppClient { nomic::app_client("http://localhost:26657") } +// DONE /cosmos/bank/v1beta1/balances/{address} +// DONE /cosmos/distribution/v1beta1/delegators/{address}/rewards +// TODO /cosmos/staking/v1beta1/delegations/{address} +// DONE /cosmos/staking/v1beta1/validators +// DONE /cosmos/staking/v1beta1/delegators/{address}/unbonding_delegations +// /cosmos/staking/v1beta1/validators/{address} +// /cosmos/gov/v1beta1/proposals +// /cosmos/gov/v1beta1/proposals/{proposalId} +// /cosmos/gov/v1beta1/proposals/{proposalId}/votes/{address} +// /cosmos/gov/v1beta1/proposals/{proposalId}/tally +// /ibc/apps/transfer/v1/denom_traces/{hash} +// /ibc/core/channel/v1/channels/{channelId}/ports/{portId}/client_state + +#[get("/cosmos/staking/v1beta1/validators")] +async fn validators() -> Value { + let all_validators: Vec = app_client() + .query(|app: InnerApp| app.staking.all_validators()) + .await + .unwrap(); + + let mut validators = vec![]; + for validator in all_validators { + let cons_key = app_client() + .query(|app: InnerApp| app.staking.consensus_key(validator.address.into())) + .await + .unwrap(); // TODO: cache + + let status = if validator.unbonding { + "BOND_STATUS_UNBONDING" + } else if validator.in_active_set { + "BOND_STATUS_BONDED" + } else { + "BOND_STATUS_UNBONDED" + }; + + let info: DeclareInfo = + serde_json::from_str(String::from_utf8(validator.info.to_vec()).unwrap().as_str()) + .unwrap_or(DeclareInfo { + details: "".to_string(), + identity: "".to_string(), + moniker: "".to_string(), + website: "".to_string(), + }); + + validators.push(json!( + { + "operator_address": validator.address.to_string(), + "consensus_pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": base64::encode(cons_key) + }, + "jailed": validator.jailed, + "status": status, + "tokens": validator.amount_staked.to_string(), + "delegator_shares": validator.amount_staked.to_string(), + "description": { + "moniker": info.moniker, + "identity": info.identity, + "website": info.website, + "security_contact": "", + "details": info.details + }, + "unbonding_height": "0", // TODO + "unbonding_time": "1970-01-01T00:00:00Z", // TODO + "commission": { + "commission_rates": { + "rate": validator.commission.rate, + "max_rate": validator.commission.max, + "max_change_rate": validator.commission.max_change + }, + "update_time": "2023-08-04T06:00:00.000000000Z" // TODO + }, + "min_self_delegation": validator.min_self_delegation.to_string() + })); + } + + json!({ + "validators": validators, + "pagination": { + "next_key": null, + "total": validators.len().to_string() + } + }) +} + +#[get("/cosmos/staking/v1beta1/validators/
")] +async fn validator(address: &str) -> Value { + let address: Address = address.parse().unwrap(); + + // TODO: cache + let all_validators: Vec = app_client() + .query(|app: InnerApp| app.staking.all_validators()) + .await + .unwrap(); + + let mut validators = vec![]; + for validator in all_validators { + if validator.address != address.into() { + continue; + } + let cons_key = app_client() + .query(|app: InnerApp| app.staking.consensus_key(validator.address.into())) + .await + .unwrap(); + + let status = if validator.unbonding { + "BOND_STATUS_UNBONDING" + } else if validator.in_active_set { + "BOND_STATUS_BONDED" + } else { + "BOND_STATUS_UNBONDED" + }; + + let info: DeclareInfo = + serde_json::from_str(String::from_utf8(validator.info.to_vec()).unwrap().as_str()) + .unwrap_or(DeclareInfo { + details: "".to_string(), + identity: "".to_string(), + moniker: "".to_string(), + website: "".to_string(), + }); + + validators.push(json!( + { + "operator_address": validator.address.to_string(), + "consensus_pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": base64::encode(cons_key) + }, + "jailed": validator.jailed, + "status": status, + "tokens": validator.amount_staked.to_string(), + "delegator_shares": validator.amount_staked.to_string(), + "description": { + "moniker": info.moniker, + "identity": info.identity, + "website": info.website, + "security_contact": "", + "details": info.details + }, + "unbonding_height": "0", // TODO + "unbonding_time": "1970-01-01T00:00:00Z", // TODO + "commission": { + "commission_rates": { + "rate": validator.commission.rate, + "max_rate": validator.commission.max, + "max_change_rate": validator.commission.max_change + }, + "update_time": "2023-08-04T06:00:00.000000000Z" // TODO + }, + "min_self_delegation": validator.min_self_delegation.to_string() + })); + } + let validator = validators.first().unwrap(); + + json!({ + "validator": validator, + }) +} + #[get("/cosmos/bank/v1beta1/balances/
")] async fn bank_balances(address: &str) -> Result> { let address: Address = address.parse().unwrap(); - let balance: u64 = app_client() + let nom_balance: u64 = app_client() .query(|app| app.accounts.balance(address)) .await .map_err(|e| BadRequest(Some(format!("{:?}", e))))? .into(); + let nbtc_balance: u64 = app_client() + .query(|app| app.bitcoin.accounts.balance(address)) + .await + .map_err(|e| BadRequest(Some(format!("{:?}", e))))? + .into(); Ok(json!({ "balances": [ { "denom": "unom", - "amount": balance.to_string(), + "amount": nom_balance.to_string(), }, { - "denom": "nsat", - "amount": balance.to_string(), + "denom": "usat", + "amount": nbtc_balance.to_string(), } ], "pagination": { "next_key": null, - "total": "0" + "total": "2" } })) } @@ -90,8 +255,7 @@ async fn auth_accounts(addr_str: &str) -> Result> { let mut nonce: u64 = app_client() .query_root(|app| app.inner.inner.borrow().inner.inner.inner.nonce(address)) .await - .map_err(|e| BadRequest(Some(format!("{:?}", e))))? - .into(); + .map_err(|e| BadRequest(Some(format!("{:?}", e))))?; nonce += 1; Ok(json!({ @@ -116,7 +280,7 @@ async fn auth_accounts(addr_str: &str) -> Result> { async fn auth_accounts2(addr_str: &str) -> Result> { let address: Address = addr_str.parse().unwrap(); - let balance: u64 = app_client() + let _balance: u64 = app_client() .query(|app| app.accounts.balance(address)) .await .map_err(|e| BadRequest(Some(format!("{:?}", e))))? @@ -125,8 +289,7 @@ async fn auth_accounts2(addr_str: &str) -> Result> { let mut nonce: u64 = app_client() .query_root(|app| app.inner.inner.borrow().inner.inner.inner.nonce(address)) .await - .map_err(|e| BadRequest(Some(format!("{:?}", e))))? - .into(); + .map_err(|e| BadRequest(Some(format!("{:?}", e))))?; nonce += 1; Ok(json!({ @@ -288,20 +451,21 @@ async fn query(query: &str, height: Option) -> Result")] -async fn staking_delegators_delegations(address: &str) -> Result> { +async fn staking_delegators_delegations(address: &str) -> Value { let address: Address = address.parse().unwrap(); let delegations = app_client() .query(|app| app.staking.delegations(address)) .await - .map_err(|e| BadRequest(Some(format!("{:?}", e))))?; + .unwrap(); let total_staked: u64 = delegations .iter() .map(|(_, d)| -> u64 { d.staked.into() }) .sum(); - Ok(json!({ "delegation_responses": [ + json!({ + "delegation_responses": [ { "delegation": { "delegator_address": "", @@ -313,7 +477,7 @@ async fn staking_delegators_delegations(address: &str) -> Result/delegations")] @@ -344,8 +508,35 @@ async fn staking_delegators_delegations_2(address: &str) -> Result/unbonding_delegations")] -fn staking_delegators_unbonding_delegations(address: &str) -> Value { - json!({ "unbonding_responses": [], "pagination": { "next_key": null, "total": "0" } }) +async fn staking_delegators_unbonding_delegations(address: &str) -> Value { + use chrono::{TimeZone, Utc}; + let address: Address = address.parse().unwrap(); + let delegations: Vec<(Address, DelegationInfo)> = app_client() + .query(|app: InnerApp| app.staking.delegations(address)) + .await + .unwrap(); + + let mut unbonds = vec![]; + + for (val_address, delegation) in delegations { + let mut entries = vec![]; + for unbond in delegation.unbonding { + let t = Utc.timestamp_opt(unbond.start_seconds, 0).unwrap(); + entries.push(json!({ + "creation_height": "0", // TODO + "completion_time": t, // TODO + "initial_balance": "0", // TODO + "balance": "0" // TODO + })) + } + unbonds.push(json!({ + "delegator_address": address, + "validator_address": val_address, + "entries": entries + })) + } + + json!({ "unbonding_responses": unbonds, "pagination": { "next_key": null, "total": unbonds.len().to_string() } }) } #[get("/staking/delegators/<_address>/unbonding_delegations")] @@ -353,93 +544,119 @@ fn staking_delegators_unbonding_delegations_2(_address: &str) -> Value { json!({ "height": "0", "result": [] }) } -#[get("/staking/delegators/<_address>/delegations")] -fn staking_delegations_2(_address: &str) -> Value { - json!({ "height": "0", "result": [] }) -} +#[get("/cosmos/distribution/v1beta1/delegators/
/rewards")] +async fn distribution_delegators_rewards(address: &str) -> Value { + let address: Address = address.parse().unwrap(); + let delegations: Vec<(Address, DelegationInfo)> = app_client() + .query(|app: InnerApp| app.staking.delegations(address)) + .await + .unwrap(); + + let mut rewards = vec![]; + let mut total_nom = 0; + let mut total_nbtc = 0; + for (validator, delegation) in delegations { + let mut reward = vec![]; + let liquid: u64 = delegation + .liquid + .iter() + .map(|(_, amount)| -> u64 { (*amount).into() }) + .sum(); + if liquid == 0 { + continue; + } -#[get("/cosmos/distribution/v1beta1/delegators/<_address>/rewards")] -async fn distribution_delegatrs_rewards(_address: &str) -> Value { - // let address = address.parse().unwrap(); + let liquid_nom: u64 = delegation + .liquid + .iter() + .find(|(denom, _)| *denom == Nom::INDEX) + .unwrap_or(&(0, 0.into())) + .1 + .into(); + total_nom += liquid_nom; + reward.push(json!({ + "denom": "unom", + "amount": liquid_nom.to_string(), + })); + let liquid_nbtc: u64 = delegation + .liquid + .iter() + .find(|(denom, _)| *denom == Nbtc::INDEX) + .unwrap_or(&(0, 0.into())) + .1 + .into(); + reward.push(json!({ + "denom": "usat", + "amount": liquid_nbtc.to_string(), + })); + total_nbtc += liquid_nbtc; + + rewards.push(json!({ + "validator_address": validator.to_string(), + "reward": reward, + })); + } + json!({ + "rewards": rewards, + "total": [ + { + "denom": "unom", + "amount": total_nom.to_string(), + }, + { + "denom": "usat", + "amount": total_nbtc.to_string(), + } + ] + }) +} - // type AppQuery = ::Query; - // type StakingQuery = as Query>::Query; +#[get("/cosmos/distribution/v1beta1/delegators/
/rewards/")] +async fn distribution_delegators_rewards_for_validator(address: &str, validator_address: &str) -> Value { + let address: Address = address.parse().unwrap(); + let validator_address: Address = validator_address.parse().unwrap(); - // let delegations = app_client() - // .query( - // AppQuery::FieldStaking(StakingQuery::MethodDelegations(address, vec![])), - // |state| state.staking.delegations(address), - // ) - // .await - // .unwrap(); + let delegations: Vec<(Address, DelegationInfo)> = app_client() + .query(|app: InnerApp| app.staking.delegations(address)) + .await + .unwrap(); - // let reward = (delegations - // .iter() - // .map(|(_, d)| -> u64 { d.liquid.into() }) - // .sum::()) - // .to_string(); + let delegation: &DelegationInfo = delegations.iter(). + find(|(validator, _delegation)| *validator == validator_address). + map(|(_validator, delegation)| delegation). + unwrap(); - json!({ "height": "0", "result": { - "rewards": [ - // { - // "validator_address": "cosmosvaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l", - // "reward": [ - // { - // "denom": "unom", - // "amount": reward - // } - // ] - // } - ], - "total": [ - // { - // "denom": "unom", - // "amount": reward - // } - ] - } }) -} + let mut rewards = vec![]; -#[get("/distribution/delegators/<_address>/rewards")] -async fn distribution_delegatrs_rewards_2(_address: &str) -> Value { - // let address = address.parse().unwrap(); + let liquid_nom: u64 = delegation + .liquid + .iter() + .find(|(denom, _)| *denom == Nom::INDEX) + .unwrap_or(&(0, 0.into())) + .1 + .into(); - // type AppQuery = ::Query; - // type StakingQuery = as Query>::Query; + rewards.push(json!({ + "denom": "unom", + "amount": liquid_nom.to_string(), + })); - // let delegations = app_client() - // .query( - // AppQuery::FieldStaking(StakingQuery::MethodDelegations(address, vec![])), - // |state| state.staking.delegations(address), - // ) - // .await - // .unwrap(); + let liquid_nbtc: u64 = delegation + .liquid + .iter() + .find(|(denom, _)| *denom == Nbtc::INDEX) + .unwrap_or(&(0, 0.into())) + .1 + .into(); - // let reward = (delegations - // .iter() - // .map(|(_, d)| -> u64 { d.liquid.into() }) - // .sum::()) - // .to_string(); + rewards.push(json!({ + "denom": "usat", + "amount": liquid_nbtc.to_string(), + })); - json!({ "height": "0", "result": { - "rewards": [ - // { - // "validator_address": "cosmosvaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l", - // "reward": [ - // { - // "denom": "unom", - // "amount": reward - // } - // ] - // } - ], - "total": [ - // { - // "denom": "unom", - // "amount": reward - // } - ] - } }) + json!({ + "rewards": rewards + }) } #[get("/cosmos/mint/v1beta1/inflation")] @@ -576,19 +793,21 @@ fn rocket() -> _ { txs2, query, staking_delegators_delegations, - // staking_delegators_delegations_2, + staking_delegators_delegations_2, staking_delegators_unbonding_delegations, staking_delegators_unbonding_delegations_2, - distribution_delegatrs_rewards, - distribution_delegatrs_rewards_2, - staking_delegations_2, + distribution_delegators_rewards, + distribution_delegators_rewards_for_validator, minting_inflation, + minting_inflation_2, staking_pool, staking_pool_2, bank_total, ibc_apps_transfer_params, ibc_applications_transfer_params, bank_supply_unom, + validators, + validator, ], ) } diff --git a/src/app.rs b/src/app.rs index 99a4a4df..69e11e45 100644 --- a/src/app.rs +++ b/src/app.rs @@ -341,6 +341,11 @@ impl InnerApp { Ok(()) } + // TODO: temporary workaround, will be exposed by client soon + pub fn height(&self) -> u64 { + self.ibc.ctx.query_height().unwrap() + } + #[call] pub fn app_noop(&mut self) -> Result<()> { Ok(())