diff --git a/crates/antelope/src/api/v1/chain.rs b/crates/antelope/src/api/v1/chain.rs index c6d0fc3..372730e 100644 --- a/crates/antelope/src/api/v1/chain.rs +++ b/crates/antelope/src/api/v1/chain.rs @@ -1,15 +1,18 @@ use crate::api::client::Provider; use crate::api::v1::structs::{ - ClientError, GetInfoResponse, ProcessedTransaction, ProcessedTransactionReceipt, - SendTransactionResponse, SendTransactionResponseError, + ClientError, GetInfoResponse, GetTableRowsParams, GetTableRowsResponse, ProcessedTransaction, + ProcessedTransactionReceipt, SendTransactionResponse, SendTransactionResponseError, + TableIndexType, }; use crate::chain::block_id::BlockId; use crate::chain::checksum::Checksum256; use crate::chain::name::Name; use crate::chain::time::TimePoint; use crate::chain::transaction::{CompressionType, PackedTransaction, SignedTransaction}; +use crate::chain::{Decoder, Packer}; use crate::name; -use crate::serializer::formatter::JSONObject; +use crate::serializer::formatter::{JSONObject, ValueTo}; +use crate::util::hex_to_bytes; use serde_json::Value; pub struct ChainAPI { @@ -103,4 +106,38 @@ impl ChainAPI { }, }) } + + pub fn get_table_rows( + &self, + params: GetTableRowsParams, + ) -> Result, ClientError<()>> { + let result = self.provider.post( + String::from("/v1/chain/get_table_rows"), + Some(params.to_json()), + ); + + let json: Value = serde_json::from_str(result.unwrap().as_str()).unwrap(); + let response_obj = JSONObject::new(json); + let more = response_obj.get_bool("more")?; + let next_key_str = response_obj.get_string("next_key")?; + let rows_value = response_obj.get_vec("rows")?; + let mut rows: Vec = Vec::with_capacity(rows_value.len()); + for encoded_row in rows_value { + let row_bytes_hex = &ValueTo::string(Some(encoded_row))?; + let row_bytes = hex_to_bytes(row_bytes_hex); + let mut decoder = Decoder::new(&row_bytes); + let mut row = T::default(); + decoder.unpack(&mut row); + rows.push(row); + } + + let next_key = TableIndexType::NAME(name!(next_key_str.as_str())); + + Ok(GetTableRowsResponse { + rows, + more, + ram_payers: None, + next_key: Some(next_key), + }) + } } diff --git a/crates/antelope/src/api/v1/structs.rs b/crates/antelope/src/api/v1/structs.rs index e15c787..1e50ab5 100644 --- a/crates/antelope/src/api/v1/structs.rs +++ b/crates/antelope/src/api/v1/structs.rs @@ -1,3 +1,4 @@ +use crate::chain::checksum::Checksum160; use crate::chain::{ block_id::BlockId, checksum::Checksum256, @@ -6,6 +7,8 @@ use crate::chain::{ transaction::TransactionHeader, varint::VarUint32, }; +use serde_json::{json, Value}; +use std::collections::HashMap; #[derive(Debug)] pub enum ClientError { @@ -169,3 +172,58 @@ pub struct SendTransactionResponse { pub transaction_id: String, pub processed: ProcessedTransaction, } + +pub enum IndexPosition { + PRIMARY, + SECONDARY, + TERTIARY, + FOURTH, + FIFTH, + SIXTH, + SEVENTH, + EIGHTH, + NINTH, + TENTH, +} + +pub enum TableIndexType { + NAME(Name), + UINT64(u64), + UINT128(u128), + FLOAT64(f64), + CHECKSUM256(Checksum256), + CHECKSUM160(Checksum160), +} + +pub struct GetTableRowsParams { + pub code: Name, + pub table: Name, + pub scope: Option, + pub lower_bound: Option, + pub upper_bound: Option, + pub limit: Option, + pub reverse: Option, + pub index_position: Option, + pub show_payer: Option, +} + +impl GetTableRowsParams { + pub fn to_json(&self) -> String { + let mut req: HashMap<&str, Value> = HashMap::new(); + req.insert("json", Value::Bool(false)); + req.insert("code", Value::String(self.code.to_string())); + req.insert("table", Value::String(self.table.to_string())); + + let scope = self.scope.unwrap_or(self.code); + req.insert("scope", Value::String(scope.to_string())); + + json!(req).to_string() + } +} + +pub struct GetTableRowsResponse { + pub rows: Vec, + pub more: bool, + pub ram_payers: Option>, + pub next_key: Option, +} diff --git a/crates/antelope/src/serializer/formatter.rs b/crates/antelope/src/serializer/formatter.rs index d97f15b..a568dfb 100644 --- a/crates/antelope/src/serializer/formatter.rs +++ b/crates/antelope/src/serializer/formatter.rs @@ -25,6 +25,26 @@ impl ValueTo { Ok(value.as_str().unwrap().to_string()) } + pub fn bool(v: Option<&Value>) -> Result { + check_some(v, "bool")?; + let value = v.unwrap(); + if !value.is_boolean() { + return Err(EncodingError::new("Value is not bool".into())); + } + + Ok(value.as_bool().unwrap()) + } + + pub fn vec(v: Option<&Value>) -> Result<&Vec, EncodingError> { + check_some(v, "Vec")?; + let value = v.unwrap(); + if !value.is_array() { + return Err(EncodingError::new("Value is not Vec".into())); + } + + Ok(value.as_array().unwrap()) + } + pub fn hex_bytes(v: Option<&Value>) -> Result, EncodingError> { let value = Self::string(v)?; return Ok(hex_to_bytes(value.as_str())); @@ -84,6 +104,14 @@ impl JSONObject { ValueTo::string(self.value.get(property)) } + pub fn get_bool(&self, property: &str) -> Result { + ValueTo::bool(self.value.get(property)) + } + + pub fn get_vec(&self, property: &str) -> Result<&Vec, EncodingError> { + ValueTo::vec(self.value.get(property)) + } + pub fn get_hex_bytes(&self, property: &str) -> Result, EncodingError> { ValueTo::hex_bytes(self.value.get(property)) } diff --git a/crates/antelope/tests/client.rs b/crates/antelope/tests/client.rs index b2c060c..7908b95 100644 --- a/crates/antelope/tests/client.rs +++ b/crates/antelope/tests/client.rs @@ -1,10 +1,12 @@ use antelope::api::client::APIClient; -use antelope::api::v1::structs::ClientError; +use antelope::api::v1::structs::{ClientError, GetTableRowsParams}; use antelope::chain::asset::Asset; use antelope::chain::block_id::BlockId; use antelope::chain::name::Name; use antelope::name; +use antelope::serializer::{Decoder, Encoder, Packer}; use antelope::util::hex_to_bytes; +use antelope::StructPacker; mod utils; use crate::utils::mock_provider; use utils::mock_provider::MockProvider; @@ -82,3 +84,57 @@ fn chain_send_transaction() { } } } + +#[test] +pub fn chain_get_table_rows() { + #[derive(StructPacker, Default)] + struct UserRow { + balance: Asset, + } + + let mock_provider = MockProvider {}; + let client = APIClient::custom_provider(Box::new(mock_provider)).unwrap(); + //let client = APIClient::default_provider(String::from("https://testnet.telos.caleos.io")).unwrap(); + + let res1 = client + .v1_chain + .get_table_rows::(GetTableRowsParams { + code: name!("eosio.token"), + table: name!("accounts"), + scope: Some(name!("corecorecore")), + lower_bound: None, + upper_bound: None, + limit: None, + reverse: None, + index_position: None, + show_payer: None, + }) + .unwrap(); + + assert_eq!(res1.rows.len(), 1, "Should get 1 row back"); + assert_eq!( + res1.rows[0].balance.symbol().code().to_string(), + "TLOS", + "Should get TLOS symbol back" + ); + + // const res1 = await eos.v1.chain.get_table_rows({ + // code: 'fuel.gm', + // table: 'users', + // type: User, + // limit: 1, + // }) + // assert.equal(res1.rows[0].account instanceof Name, true) + // assert.equal(res1.more, true) + // assert.equal(String(res1.rows[0].account), 'aaaa') + // const res2 = await eos.v1.chain.get_table_rows({ + // code: 'fuel.gm', + // table: 'users', + // type: User, + // limit: 2, + // lower_bound: res1.next_key, + // }) + // assert.equal(String(res2.rows[0].account), 'atomichub') + // assert.equal(String(res2.next_key), 'boidservices') + // assert.equal(Number(res2.rows[1].balance).toFixed(6), (0.02566).toFixed(6)) +} diff --git a/crates/antelope/tests/utils/mock_provider_data/4dfe6343221c263889f5291f9929049cda782755.json b/crates/antelope/tests/utils/mock_provider_data/4dfe6343221c263889f5291f9929049cda782755.json new file mode 100644 index 0000000..0b8a438 --- /dev/null +++ b/crates/antelope/tests/utils/mock_provider_data/4dfe6343221c263889f5291f9929049cda782755.json @@ -0,0 +1 @@ +{"rows":["ccd8f5050000000004544c4f53000000"],"more":false,"next_key":""} \ No newline at end of file