Skip to content

Commit eb05372

Browse files
committed
test: add version detection and CIP68 user token metadata resolution tests
Signed-off-by: William Hankins <[email protected]>
1 parent 414bf82 commit eb05372

File tree

2 files changed

+182
-8
lines changed

2 files changed

+182
-8
lines changed

modules/assets_state/src/state.rs

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,6 @@ impl State {
145145
if let Some(info_mut) = info.as_mut() {
146146
info_mut.metadata.cip68_metadata = ref_info.metadata.cip68_metadata;
147147
info_mut.metadata.cip68_version = ref_info.metadata.cip68_version;
148-
} else {
149-
info = Some(ref_info);
150148
}
151149
}
152150

@@ -596,7 +594,7 @@ impl State {
596594
continue;
597595
};
598596

599-
let mut cip68_version = None;
597+
let mut cip68_version = Some(AssetMetadataStandard::CIP68v1);
600598

601599
if let Ok(serde_cbor::Value::Map(m)) =
602600
serde_cbor::from_slice::<serde_cbor::Value>(blob)
@@ -619,7 +617,6 @@ impl State {
619617
continue;
620618
}
621619

622-
// NOTE: CIP68 metadata version is included in the blob and is decoded in REST handler
623620
match registry.lookup_id(policy_id, name) {
624621
Some(asset_id) => {
625622
if let Some(record) =
@@ -689,13 +686,14 @@ mod tests {
689686

690687
use crate::{
691688
asset_registry::{AssetId, AssetRegistry},
692-
state::{AssetsStorageConfig, State, StoreTransactions},
689+
state::{AssetsStorageConfig, State, StoreTransactions, CIP67_LABEL_222, CIP68_LABEL_100},
693690
};
694691
use acropolis_common::{
695-
Address, AddressDelta, AssetInfoRecord, AssetMetadataStandard, AssetName, Datum,
696-
NativeAsset, NativeAssetDelta, PolicyId, ShelleyAddress, TxIdentifier, TxOutput,
692+
Address, AddressDelta, AssetInfoRecord, AssetMetadata, AssetMetadataStandard, AssetName,
693+
Datum, NativeAsset, NativeAssetDelta, PolicyId, ShelleyAddress, TxIdentifier, TxOutput,
697694
TxUTxODeltas, UTxOIdentifier, Value,
698695
};
696+
use serde_cbor::Value as CborValue;
699697

700698
fn dummy_policy(byte: u8) -> PolicyId {
701699
[byte; 28]
@@ -1286,6 +1284,46 @@ mod tests {
12861284
);
12871285
}
12881286

1287+
#[test]
1288+
fn handle_cip68_version_detection() {
1289+
let mut registry = AssetRegistry::new();
1290+
let policy_id: PolicyId = [7u8; 28];
1291+
1292+
let (state, asset_id, name) = setup_state_with_asset(
1293+
&mut registry,
1294+
policy_id,
1295+
&[0x00, 0x06, 0x43, 0xb0, 0xAA],
1296+
true,
1297+
false,
1298+
StoreTransactions::None,
1299+
);
1300+
1301+
let mut map = BTreeMap::new();
1302+
map.insert(
1303+
CborValue::Text("version".to_string()),
1304+
CborValue::Text("2.0".to_string()),
1305+
);
1306+
1307+
let datum = serde_cbor::to_vec(&CborValue::Map(map)).unwrap();
1308+
1309+
let output = make_output(policy_id, name, Some(datum.clone()));
1310+
1311+
let tx = TxUTxODeltas {
1312+
tx_identifier: TxIdentifier::new(0, 0),
1313+
inputs: vec![],
1314+
outputs: vec![output],
1315+
};
1316+
let new_state = state.handle_cip68_metadata(&[tx], &registry).unwrap();
1317+
let record = new_state.info.as_ref().unwrap().get(&asset_id).unwrap();
1318+
1319+
// CIP68 version should be v2
1320+
assert_eq!(
1321+
record.metadata.cip68_version,
1322+
Some(AssetMetadataStandard::CIP68v2),
1323+
"CIP68 version should be set as CIP68v2"
1324+
);
1325+
}
1326+
12891327
#[test]
12901328
fn get_asset_info_reference_nft_strips_metadata() {
12911329
let mut registry = AssetRegistry::new();
@@ -1320,6 +1358,54 @@ mod tests {
13201358
assert!(rec.metadata.cip68_version.is_none());
13211359
}
13221360

1361+
#[test]
1362+
fn get_asset_info_resolves_user_token_metadata_from_reference_nft() {
1363+
let mut registry = AssetRegistry::new();
1364+
let policy_id: PolicyId = [5u8; 28];
1365+
let asset_name = [0x53, 0x4E, 0x45, 0x4B];
1366+
1367+
let mut user_name = CIP67_LABEL_222.to_vec();
1368+
user_name.extend_from_slice(&asset_name);
1369+
let user_token_name = AssetName::new(&user_name).unwrap();
1370+
let user_token_id = registry.get_or_insert(policy_id, user_token_name);
1371+
1372+
let mut reference_name = CIP68_LABEL_100.to_vec();
1373+
reference_name.extend_from_slice(&asset_name);
1374+
let reference_nft_name = AssetName::new(&reference_name).unwrap();
1375+
let reference_id = registry.get_or_insert(policy_id, reference_nft_name);
1376+
1377+
let mut state = State::new(full_config());
1378+
state.info.as_mut().unwrap().insert(
1379+
reference_id,
1380+
AssetInfoRecord {
1381+
initial_mint_tx: dummy_tx_identifier(0),
1382+
mint_or_burn_count: 0,
1383+
metadata: AssetMetadata {
1384+
cip25_metadata: None,
1385+
cip25_version: None,
1386+
cip68_metadata: Some(vec![1, 2, 3]),
1387+
cip68_version: Some(AssetMetadataStandard::CIP68v1),
1388+
},
1389+
},
1390+
);
1391+
1392+
let resolved = state.resolve_cip68_metadata(&user_token_id, &registry);
1393+
1394+
let record = resolved.expect("User token should resolve to reference NFT metadata");
1395+
1396+
assert_eq!(
1397+
record.metadata.cip68_metadata,
1398+
Some(vec![1, 2, 3]),
1399+
"User token should inherit CIP68 metadata from reference NFT"
1400+
);
1401+
1402+
assert_eq!(
1403+
record.metadata.cip68_version,
1404+
Some(AssetMetadataStandard::CIP68v1),
1405+
"User token should inherit CIP68 version from reference NFT"
1406+
);
1407+
}
1408+
13231409
#[test]
13241410
fn handle_transactions_duplicate_tx_ignored() {
13251411
let mut registry = AssetRegistry::new();

modules/rest_blockfrost/src/handlers/addresses.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,18 @@ impl AmountListExtended {
349349
let meta = &metadata[idx];
350350
idx += 1;
351351

352-
let decimals = if let Some(raw) = meta.cip68_metadata.as_ref() {
352+
// Blockfrost priority
353+
// 1. Set decimals to null if CIP25 metadata exists (This is an NFT)
354+
// 2. Set decimals based on CIP68 metadata if exists
355+
// 3. Set decimals based on off-chain registry
356+
// 4. Set decimals to null if no CIP68 metadata or off-chain registry entry
357+
358+
let decimals = if meta.cip25_metadata.is_some() {
359+
None
360+
} else if let Some(raw) = meta.cip68_metadata.as_ref() {
353361
extract_cip68_decimals(raw)
354362
} else {
363+
// TODO: off-chain registry lookup once caching exists
355364
None
356365
};
357366

@@ -407,3 +416,82 @@ pub fn extract_cip68_decimals(raw: &[u8]) -> Option<u64> {
407416

408417
None
409418
}
419+
420+
#[cfg(test)]
421+
mod tests {
422+
use super::*;
423+
use acropolis_common::{AssetName, NativeAsset, NativeAssets, Value};
424+
425+
fn make_value(policy_id: [u8; 28], name: Vec<u8>, amount: u64) -> Value {
426+
let assets: NativeAssets = vec![(
427+
policy_id,
428+
vec![NativeAsset {
429+
name: AssetName::new(&name).expect("Invalid asset name"),
430+
amount,
431+
}],
432+
)];
433+
434+
Value::new(0, assets)
435+
}
436+
437+
fn make_metadata(cip25: Option<Vec<u8>>, cip68: Option<Vec<u8>>) -> AssetMetadata {
438+
AssetMetadata {
439+
cip25_metadata: cip25,
440+
cip68_metadata: cip68,
441+
..Default::default()
442+
}
443+
}
444+
445+
#[test]
446+
fn cip25_existance_overrides_decimals() {
447+
use serde_cbor::Value as CborValue;
448+
use std::collections::BTreeMap;
449+
450+
let mut map = BTreeMap::new();
451+
map.insert(
452+
CborValue::Text("decimals".to_string()),
453+
CborValue::Integer(18),
454+
);
455+
456+
let cbor = serde_cbor::to_vec(&vec![CborValue::Map(map), CborValue::Null]).unwrap();
457+
458+
let policy_id = [1u8; 28];
459+
let value = make_value(policy_id, vec![0x41, 0x42], 100);
460+
461+
let metadata = vec![make_metadata(Some(vec![1, 2, 3]), Some(cbor))];
462+
463+
let list = AmountListExtended::from_value_and_metadata(value, &metadata);
464+
let asset = &list.0[1];
465+
466+
// Decimals set to none when CIP25 present
467+
assert!(asset.decimals.is_none());
468+
469+
// Onchain metadata flag is set
470+
assert!(asset.has_nft_onchain_metadata);
471+
}
472+
473+
#[test]
474+
fn cip68_decimals_are_extracted_when_no_cip25() {
475+
use serde_cbor::Value as CborValue;
476+
use std::collections::BTreeMap;
477+
478+
let mut map = BTreeMap::new();
479+
map.insert(CborValue::Text("decimals".into()), CborValue::Integer(18));
480+
481+
let cbor = serde_cbor::to_vec(&vec![CborValue::Map(map), CborValue::Null]).unwrap();
482+
483+
let policy_id = [3u8; 28];
484+
let value = make_value(policy_id, b"\x99".to_vec(), 999);
485+
486+
let metadata = vec![make_metadata(None, Some(cbor))];
487+
488+
let list = AmountListExtended::from_value_and_metadata(value, &metadata);
489+
let asset = &list.0[1];
490+
491+
// Decimals set to value in CIP68 metadata
492+
assert_eq!(asset.decimals, Some(18));
493+
494+
// Onchain metadata is false
495+
assert!(!asset.has_nft_onchain_metadata);
496+
}
497+
}

0 commit comments

Comments
 (0)