Skip to content

Commit c044d85

Browse files
gregnazarioJoshLind
authored andcommitted
[rosetta] Track Rosetta secondary store balances (#19954)
Add secondary fungible-store account identifiers so Rosetta can attribute non-primary store changes and query their balances directly. (cherry picked from commit 627b9fd)
1 parent 46e6e90 commit c044d85

4 files changed

Lines changed: 396 additions & 30 deletions

File tree

crates/aptos-rosetta/src/account.rs

Lines changed: 148 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use aptos_rest_client::{
2020
error::{AptosErrorResponse, RestError},
2121
Client,
2222
};
23-
use aptos_types::{account_address::AccountAddress, account_config::AccountResource};
23+
use aptos_types::{
24+
account_address::AccountAddress,
25+
account_config::{fungible_store::FungibleStoreResource, AccountResource},
26+
};
2427
use move_core_types::{
2528
ident_str,
2629
identifier::IdentStr,
@@ -126,18 +129,18 @@ async fn get_balances(
126129
if account.is_base_account() {
127130
balances =
128131
get_base_balances(&rest_client, owner_address, version, currencies_to_lookup).await?;
129-
} else if pool_address.is_some() {
132+
} else if let Some(currency) = account.secondary_store_currency() {
133+
if currencies_to_lookup.contains(currency) {
134+
balances =
135+
get_secondary_store_balance(&rest_client, owner_address, version, currency).await?;
136+
}
137+
} else if let Some(pool_address) = pool_address {
130138
// Lookup the delegation pool, if it's provided in the account information
131139
// Filter appropriately, must have native coin
132140
if currencies_to_lookup.contains(&native_coin()) {
133-
(balances, lockup_expiration) = get_delegation_info(
134-
&rest_client,
135-
&account,
136-
owner_address,
137-
pool_address.unwrap(),
138-
version,
139-
)
140-
.await?;
141+
(balances, lockup_expiration) =
142+
get_delegation_info(&rest_client, &account, owner_address, pool_address, version)
143+
.await?;
141144
}
142145
} else {
143146
// Retrieve staking information (if it applies)
@@ -159,6 +162,88 @@ async fn get_balances(
159162
))
160163
}
161164

165+
async fn get_secondary_store_balance(
166+
rest_client: &Client,
167+
store_address: AccountAddress,
168+
version: u64,
169+
currency: &Currency,
170+
) -> ApiResult<Vec<Amount>> {
171+
match rest_client
172+
.get_account_resource_at_version_bcs(
173+
store_address,
174+
"0x1::fungible_asset::FungibleStore",
175+
version,
176+
)
177+
.await
178+
{
179+
Ok(response) => {
180+
let store: FungibleStoreResource = response.into_inner();
181+
Ok(vec![secondary_store_amount(&store, currency)?])
182+
},
183+
Err(RestError::Api(AptosErrorResponse {
184+
error:
185+
AptosError {
186+
error_code: AptosErrorCode::AccountNotFound,
187+
..
188+
},
189+
..
190+
}))
191+
| Err(RestError::Api(AptosErrorResponse {
192+
error:
193+
AptosError {
194+
error_code: AptosErrorCode::ResourceNotFound,
195+
..
196+
},
197+
..
198+
})) => Ok(vec![Amount {
199+
value: 0.to_string(),
200+
currency: currency.clone(),
201+
}]),
202+
Err(err) => Err(ApiError::InternalError(Some(format!(
203+
"Failed to retrieve secondary store balance: {}",
204+
err
205+
)))),
206+
}
207+
}
208+
209+
fn secondary_store_amount(store: &FungibleStoreResource, currency: &Currency) -> ApiResult<Amount> {
210+
let expected_metadata = currency_fa_metadata_address(currency)?.ok_or_else(|| {
211+
ApiError::InvalidInput(Some(format!(
212+
"Currency {} does not identify a fungible asset metadata address",
213+
currency.symbol
214+
)))
215+
})?;
216+
217+
if store.metadata() != expected_metadata {
218+
return Err(ApiError::InvalidInput(Some(format!(
219+
"Secondary store metadata {} does not match requested currency metadata {}",
220+
store.metadata(),
221+
expected_metadata
222+
))));
223+
}
224+
225+
Ok(Amount {
226+
value: store.balance().to_string(),
227+
currency: currency.clone(),
228+
})
229+
}
230+
231+
fn currency_fa_metadata_address(currency: &Currency) -> ApiResult<Option<AccountAddress>> {
232+
if let Some(metadata) = currency.metadata.as_ref() {
233+
if let Some(fa_address) = metadata.fa_address.as_ref() {
234+
return AccountAddress::from_str(fa_address)
235+
.map(Some)
236+
.map_err(|err| ApiError::InvalidInput(Some(err.to_string())));
237+
}
238+
}
239+
240+
if currency == &native_coin() {
241+
Ok(Some(AccountAddress::TEN))
242+
} else {
243+
Ok(None)
244+
}
245+
}
246+
162247
async fn get_sequence_number(
163248
rest_client: &Client,
164249
owner_address: AccountAddress,
@@ -403,3 +488,56 @@ pub async fn view<T: DeserializeOwned>(
403488
.await?
404489
.into_inner())
405490
}
491+
492+
#[cfg(test)]
493+
mod tests {
494+
use super::*;
495+
use crate::types::CurrencyMetadata;
496+
497+
#[test]
498+
fn test_secondary_store_amount_for_native_coin() {
499+
let store = FungibleStoreResource::new(AccountAddress::TEN, 50_000_000, false);
500+
let amount = secondary_store_amount(&store, &native_coin()).unwrap();
501+
502+
assert_eq!(amount.value, "50000000");
503+
assert_eq!(amount.currency, native_coin());
504+
}
505+
506+
#[test]
507+
fn test_secondary_store_amount_preserves_fa_currency() {
508+
let metadata_address =
509+
AccountAddress::from_str("0x12341234123412341234123412341234").unwrap();
510+
let currency = Currency {
511+
symbol: "FUN".to_string(),
512+
decimals: 2,
513+
metadata: Some(CurrencyMetadata {
514+
move_type: None,
515+
fa_address: Some(metadata_address.to_string()),
516+
}),
517+
};
518+
let store = FungibleStoreResource::new(metadata_address, 123, false);
519+
let amount = secondary_store_amount(&store, &currency).unwrap();
520+
521+
assert_eq!(amount.value, "123");
522+
assert_eq!(amount.currency, currency);
523+
}
524+
525+
#[test]
526+
fn test_secondary_store_amount_rejects_mismatched_currency() {
527+
let store_metadata =
528+
AccountAddress::from_str("0x12341234123412341234123412341234").unwrap();
529+
let requested_metadata =
530+
AccountAddress::from_str("0x56785678567856785678567856785678").unwrap();
531+
let currency = Currency {
532+
symbol: "FUN".to_string(),
533+
decimals: 2,
534+
metadata: Some(CurrencyMetadata {
535+
move_type: None,
536+
fa_address: Some(requested_metadata.to_string()),
537+
}),
538+
};
539+
let store = FungibleStoreResource::new(store_metadata, 123, false);
540+
541+
assert!(secondary_store_amount(&store, &currency).is_err());
542+
}
543+
}

0 commit comments

Comments
 (0)