Skip to content

Commit

Permalink
Merge pull request #33 from telosnetwork/mo/json_fix
Browse files Browse the repository at this point in the history
Fix the deserialization of some `get_account` responses

This PR includes some fixes to prevent the crash of antelope-rs during the deserialization of some get_account responses. The affected fields are:

- AccountVoterInfo/last_stake
- AccountResourceLimit/available
- AccountResourceLimit/max
- AccountObject/core_liquid_balance

Some unit tests have been added to cover these edge cases.
  • Loading branch information
ptagl committed Sep 11, 2024
2 parents 59f7e5c + f483c55 commit c1760e6
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 4 deletions.
318 changes: 315 additions & 3 deletions crates/antelope/src/api/v1/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::chain::public_key::PublicKey;
use crate::chain::signature::Signature;
use crate::chain::{
action::{Action, PermissionLevel},
asset::{deserialize_asset, Asset},
asset::{deserialize_asset, deserialize_optional_asset, Asset},
authority::Authority,
block_id::{deserialize_block_id, deserialize_optional_block_id, BlockId},
checksum::{deserialize_checksum256, Checksum160, Checksum256},
Expand Down Expand Up @@ -459,8 +459,12 @@ pub struct AccountObject {
pub last_code_update: TimePoint,
#[serde(deserialize_with = "deserialize_timepoint")]
pub created: TimePoint,
#[serde(deserialize_with = "deserialize_asset")]
pub core_liquid_balance: Asset,
#[serde(
deserialize_with = "deserialize_optional_asset",
default,
skip_serializing_if = "Option::is_none"
)]
pub core_liquid_balance: Option<Asset>,
pub ram_quota: i64,
pub net_weight: i64,
pub cpu_weight: i64,
Expand Down Expand Up @@ -529,7 +533,9 @@ pub struct AccountRexInfoMaturities {
#[derive(Debug, Serialize, Deserialize)]
pub struct AccountResourceLimit {
used: i64,
#[serde(deserialize_with = "deserialize_i64_from_string_or_i64")]
available: i64,
#[serde(deserialize_with = "deserialize_i64_from_string_or_i64")]
max: i64,
#[serde(
deserialize_with = "deserialize_optional_timepoint",
Expand Down Expand Up @@ -650,6 +656,7 @@ pub struct AccountVoterInfo {
#[serde(deserialize_with = "deserialize_vec_name")]
producers: Vec<Name>,
staked: Option<i64>,
last_stake: Option<i64>,
#[serde(deserialize_with = "deserialize_f64_from_string")]
last_vote_weight: f64,
#[serde(deserialize_with = "deserialize_f64_from_string")]
Expand Down Expand Up @@ -946,3 +953,308 @@ where

deserializer.deserialize_any(StringOrI64Visitor)
}

#[cfg(test)]
mod tests {
use crate::api::v1::structs::AccountObject;

#[test]
fn deserialize_simple_account() {
// This simple account response doesn't contain details about `total_resources`, `self_delegated_bandwidth`,
// `refund_request`, `voter_info`, and `rex_info`.
// Such fields are null.
let simple_account_json = r#"
{
"account_name": "eosio",
"head_block_num": 56,
"head_block_time": "2024-08-29T15:27:24.500",
"privileged": true,
"last_code_update": "2024-08-29T14:06:02.000",
"created": "2019-08-07T12:00:00.000",
"core_liquid_balance": "99986000.0000 TLOS",
"ram_quota": -1,
"net_weight": -1,
"cpu_weight": -1,
"net_limit": {
"used": -1,
"available": -1,
"max": -1,
"last_usage_update_time": "2024-08-29T15:27:25.000",
"current_used": -1
},
"cpu_limit": {
"used": -1,
"available": -1,
"max": -1,
"last_usage_update_time": "2024-08-29T15:27:25.000",
"current_used": -1
},
"ram_usage": 3485037,
"permissions": [
{
"perm_name": "active",
"parent": "owner",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS5uHeBsURAT6bBXNtvwKtWaiDSDJSdSmc96rHVws5M1qqVCkAm6",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
},
{
"perm_name": "owner",
"parent": "",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS5uHeBsURAT6bBXNtvwKtWaiDSDJSdSmc96rHVws5M1qqVCkAm6",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
}
],
"total_resources": null,
"self_delegated_bandwidth": null,
"refund_request": null,
"voter_info": null,
"rex_info": null,
"subjective_cpu_bill_limit": {
"used": 0,
"available": 0,
"max": 0,
"last_usage_update_time": "2000-01-01T00:00:00.000",
"current_used": 0
},
"eosio_any_linked_actions": []
}
"#;

let res = serde_json::from_str::<AccountObject>(&simple_account_json).unwrap();
println!("{:#?}", res);
}

#[test]
fn deserialize_detailed_account() {
// This detailed account response contains additional fields compared to the simple account (see test above),
// in particular `total_resources`, `self_delegated_bandwidth`, `refund_request`, `voter_info`, and `rex_info`.
let detailed_account_json = r#"
{
"account_name": "alice",
"head_block_num": 56,
"head_block_time": "2024-08-29T15:27:24.500",
"privileged": false,
"last_code_update": "1970-01-01T00:00:00.000",
"created": "2024-08-29T14:06:02.000",
"core_liquid_balance": "100.0000 TLOS",
"ram_quota": 610645714,
"net_weight": 10000000,
"cpu_weight": 10000000,
"net_limit": {
"used": 0,
"available": "95719449600",
"max": "95719449600",
"last_usage_update_time": "2024-08-29T14:06:02.000",
"current_used": 0
},
"cpu_limit": {
"used": 0,
"available": "364783305600",
"max": "364783305600",
"last_usage_update_time": "2024-08-29T14:06:02.000",
"current_used": 0
},
"ram_usage": 3566,
"permissions": [
{
"perm_name": "active",
"parent": "owner",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS77jzbmLuakAHpm2Q5ew8EL7Y7gGkfSzqJCmCNDDXWEsBP3xnDc",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
},
{
"perm_name": "owner",
"parent": "",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS77jzbmLuakAHpm2Q5ew8EL7Y7gGkfSzqJCmCNDDXWEsBP3xnDc",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
}
],
"total_resources": {
"owner": "alice",
"net_weight": "1000.0000 TLOS",
"cpu_weight": "1000.0000 TLOS",
"ram_bytes": 610644314
},
"self_delegated_bandwidth": {
"from": "alice",
"to": "alice",
"net_weight": "1000.0000 TLOS",
"cpu_weight": "1000.0000 TLOS"
},
"refund_request": null,
"voter_info": {
"owner": "alice",
"proxy": "",
"producers": [],
"staked": 20000000,
"last_stake": 0,
"last_vote_weight": "0.00000000000000000",
"proxied_vote_weight": "0.00000000000000000",
"is_proxy": 0,
"flags1": 0,
"reserved2": 0,
"reserved3": "0 "
},
"rex_info": null,
"subjective_cpu_bill_limit": {
"used": 0,
"available": 0,
"max": 0,
"last_usage_update_time": "2000-01-01T00:00:00.000",
"current_used": 0
},
"eosio_any_linked_actions": []
}
"#;

let res = serde_json::from_str::<AccountObject>(&detailed_account_json).unwrap();
println!("{:#?}", res);
}

#[test]
fn deserialize_account_without_core_liquid_balance() {
// This simple account response doesn't contain details about `total_resources`, `self_delegated_bandwidth`,
// `refund_request`, `voter_info`, and `rex_info`.
// Such fields are null.
let detailed_account_json = r#"
{
"account_name": "alice",
"head_block_num": 56,
"head_block_time": "2024-08-29T15:46:42.000",
"privileged": false,
"last_code_update": "1970-01-01T00:00:00.000",
"created": "2024-08-29T14:06:02.000",
"ram_quota": 610645714,
"net_weight": 10000000,
"cpu_weight": 10000000,
"net_limit": {
"used": 0,
"available": "95719449600",
"max": "95719449600",
"last_usage_update_time": "2024-08-29T14:06:02.000",
"current_used": 0
},
"cpu_limit": {
"used": 0,
"available": "364783305600",
"max": "364783305600",
"last_usage_update_time": "2024-08-29T14:06:02.000",
"current_used": 0
},
"ram_usage": 3566,
"permissions": [
{
"perm_name": "active",
"parent": "owner",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS77jzbmLuakAHpm2Q5ew8EL7Y7gGkfSzqJCmCNDDXWEsBP3xnDc",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
},
{
"perm_name": "owner",
"parent": "",
"required_auth": {
"threshold": 1,
"keys": [
{
"key": "EOS77jzbmLuakAHpm2Q5ew8EL7Y7gGkfSzqJCmCNDDXWEsBP3xnDc",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
}
],
"total_resources": {
"owner": "alice",
"net_weight": "1000.0000 TLOS",
"cpu_weight": "1000.0000 TLOS",
"ram_bytes": 610644314
},
"self_delegated_bandwidth": {
"from": "alice",
"to": "alice",
"net_weight": "1000.0000 TLOS",
"cpu_weight": "1000.0000 TLOS"
},
"refund_request": null,
"voter_info": {
"owner": "alice",
"proxy": "",
"producers": [],
"staked": 20000000,
"last_stake": 0,
"last_vote_weight": "0.00000000000000000",
"proxied_vote_weight": "0.00000000000000000",
"is_proxy": 0,
"flags1": 0,
"reserved2": 0,
"reserved3": "0 "
},
"rex_info": null,
"subjective_cpu_bill_limit": {
"used": 0,
"available": 0,
"max": 0,
"last_usage_update_time": "2000-01-01T00:00:00.000",
"current_used": 0
},
"eosio_any_linked_actions": []
}
"#;

let res = serde_json::from_str::<AccountObject>(&detailed_account_json).unwrap();
println!("{:#?}", res);
}
}
26 changes: 26 additions & 0 deletions crates/antelope/src/chain/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,32 @@ where
deserializer.deserialize_str(AssetVisitor)
}

pub(crate) fn deserialize_optional_asset<'de, D>(deserializer: D) -> Result<Option<Asset>, D::Error>
where
D: Deserializer<'de>,
{
struct OptionalAssetVisitor;

impl<'de> de::Visitor<'de> for OptionalAssetVisitor {
type Value = Option<Asset>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
"an optional string representing an asset in the format 'amount symbol_code'",
)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(deserialize_asset(deserializer)?))
}
}

deserializer.deserialize_option(OptionalAssetVisitor)
}

#[derive(Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct ExtendedAsset {
quantity: Asset,
Expand Down
2 changes: 1 addition & 1 deletion crates/antelope/tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async fn chan_get_account() {

assert_eq!(
account.core_liquid_balance,
Asset::from_string("128559.5000 TLOS")
Some(Asset::from_string("128559.5000 TLOS"))
);
}
Err(e) => {
Expand Down

0 comments on commit c1760e6

Please sign in to comment.