@@ -497,11 +497,17 @@ impl State {
497497
498498#[ cfg( test) ]
499499mod tests {
500+ use std:: collections:: BTreeMap ;
501+
500502 use crate :: {
501503 asset_registry:: { AssetId , AssetRegistry } ,
502504 state:: { AssetsStorageConfig , State } ,
503505 } ;
504- use acropolis_common:: { AssetName , NativeAssetDelta , PolicyId , TxHash } ;
506+ use acropolis_common:: {
507+ queries:: assets:: { AssetInfoRecord , AssetMetadataStandard } ,
508+ AssetName , Datum , NativeAsset , NativeAssetDelta , PolicyId , ShelleyAddress , TxHash , TxInput ,
509+ TxOutput , UTXODelta , Value ,
510+ } ;
505511
506512 fn dummy_policy ( byte : u8 ) -> PolicyId {
507513 [ byte; 28 ]
@@ -737,4 +743,358 @@ mod tests {
737743 assert ! ( state. get_asset_transactions( & fake_id) . is_err( ) ) ;
738744 assert ! ( state. get_policy_assets( & dummy_policy( 1 ) , & AssetRegistry :: new( ) ) . is_err( ) ) ;
739745 }
746+
747+ // CIP-25 tests
748+ fn setup_state_with_asset (
749+ registry : & mut AssetRegistry ,
750+ policy_id : PolicyId ,
751+ asset_name_bytes : & [ u8 ] ,
752+ seed_info : bool ,
753+ ) -> ( State , AssetId , AssetName ) {
754+ let asset_name = AssetName :: new ( asset_name_bytes) . unwrap ( ) ;
755+ let asset_id = registry. get_or_insert ( policy_id, asset_name. clone ( ) ) ;
756+
757+ let mut state = State :: new ( AssetsStorageConfig {
758+ store_info : true ,
759+ ..Default :: default ( )
760+ } ) ;
761+
762+ if seed_info {
763+ state
764+ . info
765+ . get_or_insert_with ( Default :: default)
766+ . insert ( asset_id, AssetInfoRecord :: default ( ) ) ;
767+ }
768+
769+ ( state, asset_id, asset_name)
770+ }
771+
772+ fn build_cip25_metadata (
773+ policy_id : PolicyId ,
774+ asset_name : & AssetName ,
775+ value : & str ,
776+ version : Option < & str > ,
777+ ) -> Vec < u8 > {
778+ let policy_hex = hex:: encode ( policy_id) ;
779+ let asset_hex = hex:: encode ( asset_name. as_slice ( ) ) ;
780+ let metadata_value = serde_cbor:: Value :: Text ( value. to_string ( ) ) ;
781+
782+ let mut asset_map = BTreeMap :: new ( ) ;
783+ asset_map. insert ( serde_cbor:: Value :: Text ( asset_hex) , metadata_value) ;
784+
785+ let mut policy_map = BTreeMap :: new ( ) ;
786+ policy_map. insert (
787+ serde_cbor:: Value :: Text ( policy_hex) ,
788+ serde_cbor:: Value :: Map ( asset_map) ,
789+ ) ;
790+
791+ if let Some ( ver) = version {
792+ policy_map. insert (
793+ serde_cbor:: Value :: Text ( "version" . to_string ( ) ) ,
794+ serde_cbor:: Value :: Text ( ver. to_string ( ) ) ,
795+ ) ;
796+ }
797+
798+ serde_cbor:: to_vec ( & serde_cbor:: Value :: Map ( policy_map) ) . unwrap ( )
799+ }
800+
801+ #[ test]
802+ fn handle_cip25_metadata_updates_correct_asset ( ) {
803+ let mut registry = AssetRegistry :: new ( ) ;
804+ let policy_id: PolicyId = [ 0u8 ; 28 ] . into ( ) ;
805+
806+ let ( state, asset_id, asset_name) =
807+ setup_state_with_asset ( & mut registry, policy_id, b"TestAsset" , true ) ;
808+
809+ let metadata_cbor = build_cip25_metadata ( policy_id, & asset_name, "hello world" , None ) ;
810+
811+ let new_state = state. handle_cip25_metadata ( & mut registry, & [ metadata_cbor] ) . unwrap ( ) ;
812+ let info = new_state. info . expect ( "info should be Some" ) ;
813+ let record = info. get ( & asset_id) . unwrap ( ) ;
814+
815+ // Onchain metadata has been set
816+ assert ! ( record. onchain_metadata. is_some( ) ) ;
817+ // Metadata standard defaults to v1 if not present in map
818+ assert_eq ! (
819+ record. metadata_standard,
820+ Some ( AssetMetadataStandard :: CIP25v1 )
821+ ) ;
822+ }
823+
824+ #[ test]
825+ fn handle_cip25_metadata_version_field_sets_v2 ( ) {
826+ let mut registry = AssetRegistry :: new ( ) ;
827+ let policy_id: PolicyId = [ 1u8 ; 28 ] . into ( ) ;
828+
829+ let ( state, asset_id, asset_name) =
830+ setup_state_with_asset ( & mut registry, policy_id, b"VersionedAsset" , true ) ;
831+
832+ let metadata_cbor =
833+ build_cip25_metadata ( policy_id, & asset_name, "metadata for v2" , Some ( "2.0" ) ) ;
834+
835+ let new_state = state. handle_cip25_metadata ( & mut registry, & [ metadata_cbor] ) . unwrap ( ) ;
836+ let info = new_state. info . expect ( "info should be Some" ) ;
837+ let record = info. get ( & asset_id) . unwrap ( ) ;
838+
839+ // Onchain metadata has been set
840+ assert ! ( record. onchain_metadata. is_some( ) ) ;
841+ // Metadata standard set to v2 when present in map
842+ assert_eq ! (
843+ record. metadata_standard,
844+ Some ( AssetMetadataStandard :: CIP25v2 )
845+ ) ;
846+ }
847+
848+ #[ test]
849+ fn handle_cip25_metadata_unknown_asset_is_ignored ( ) {
850+ let mut registry = AssetRegistry :: new ( ) ;
851+ let policy_id: PolicyId = [ 2u8 ; 28 ] . into ( ) ;
852+ let ( state, asset_id, _) =
853+ setup_state_with_asset ( & mut registry, policy_id, b"KnownAsset" , true ) ;
854+
855+ let other_asset_name = AssetName :: new ( b"UnknownAsset" ) . unwrap ( ) ;
856+ let metadata_cbor =
857+ build_cip25_metadata ( policy_id, & other_asset_name, "ignored metadata" , None ) ;
858+
859+ let new_state = state. handle_cip25_metadata ( & mut registry, & [ metadata_cbor] ) . unwrap ( ) ;
860+ let info = new_state. info . expect ( "info should be Some" ) ;
861+ let record = info. get ( & asset_id) . unwrap ( ) ;
862+
863+ // Metadata for known asset unchanged by unknown asset
864+ assert ! (
865+ record. onchain_metadata. is_none( ) ,
866+ "unknown asset should not update records"
867+ ) ;
868+ }
869+
870+ #[ test]
871+ fn handle_cip25_metadata_invalid_cbor_is_skipped ( ) {
872+ let mut registry = AssetRegistry :: new ( ) ;
873+ let policy_id: PolicyId = [ 3u8 ; 28 ] . into ( ) ;
874+ let ( state, asset_id, _) =
875+ setup_state_with_asset ( & mut registry, policy_id, b"BadAsset" , true ) ;
876+
877+ let metadata_cbor = vec ! [ 0xff , 0x00 , 0x13 , 0x37 ] ;
878+
879+ let new_state = state. handle_cip25_metadata ( & mut registry, & [ metadata_cbor] ) . unwrap ( ) ;
880+ let info = new_state. info . expect ( "info should be Some" ) ;
881+ let record = info. get ( & asset_id) . unwrap ( ) ;
882+
883+ // Metadata not set when CBOR is invalid
884+ assert ! (
885+ record. onchain_metadata. is_none( ) ,
886+ "invalid CBOR should be ignored"
887+ ) ;
888+ // Metadata standard not set when CBOR is invalid
889+ assert ! (
890+ record. metadata_standard. is_none( ) ,
891+ "invalid CBOR should not set a standard"
892+ ) ;
893+ }
894+
895+ // CIP-68 tests
896+ fn dummy_address ( ) -> acropolis_common:: Address {
897+ acropolis_common:: Address :: Shelley (
898+ ShelleyAddress :: from_string (
899+ "addr1q9g0u0aeuyvrn8ptc6yesgj6dtfgw2gadnc9y2p9cs8pneejrkwtdvk97yp2zayhvmm3wu0v672psdg2xn0temkz83ds7qfxdt" ,
900+ )
901+ . unwrap ( ) ,
902+ )
903+ }
904+
905+ fn make_output ( policy_id : PolicyId , asset_name : AssetName , datum : Option < Vec < u8 > > ) -> TxOutput {
906+ TxOutput {
907+ tx_hash : [ 0u8 ; 32 ] . into ( ) ,
908+ index : 0 ,
909+ address : dummy_address ( ) ,
910+ value : Value {
911+ lovelace : 0 ,
912+ assets : vec ! [ (
913+ policy_id,
914+ vec![ NativeAsset {
915+ name: asset_name,
916+ amount: 1 ,
917+ } ] ,
918+ ) ] ,
919+ } ,
920+ datum : datum. map ( Datum :: Inline ) ,
921+ }
922+ }
923+
924+ #[ test]
925+ fn handle_cip68_metadata_updates_onchain_metadata ( ) {
926+ let mut registry = AssetRegistry :: new ( ) ;
927+ let policy_id: PolicyId = [ 9u8 ; 28 ] . into ( ) ;
928+
929+ let ( state, reference_id, reference_name) = setup_state_with_asset (
930+ & mut registry,
931+ policy_id,
932+ & [ 0x00 , 0x06 , 0x43 , 0xb0 , 0x01 ] ,
933+ true ,
934+ ) ;
935+
936+ let datum_blob = vec ! [ 1 , 2 , 3 , 4 ] ;
937+ let output = make_output ( policy_id, reference_name. clone ( ) , Some ( datum_blob. clone ( ) ) ) ;
938+
939+ let new_state =
940+ state. handle_cip68_metadata ( & [ UTXODelta :: Output ( output) ] , & mut registry) . unwrap ( ) ;
941+ let info = new_state. info . expect ( "info should be Some" ) ;
942+ let record = info. get ( & reference_id) . expect ( "record should exist" ) ;
943+
944+ // Onchain metadata set when asset already exists and TxOutput with inline datum is processed
945+ assert_eq ! ( record. onchain_metadata, Some ( datum_blob) ) ;
946+ }
947+
948+ #[ test]
949+ fn handle_cip68_metadata_ignores_non_reference_assets ( ) {
950+ let mut registry = AssetRegistry :: new ( ) ;
951+ let policy_id: PolicyId = [ 9u8 ; 28 ] . into ( ) ;
952+
953+ let ( state, normal_id, normal_name) =
954+ setup_state_with_asset ( & mut registry, policy_id, & [ 0xAA , 0xBB , 0xCC ] , true ) ;
955+
956+ let datum_blob = vec ! [ 1 , 2 , 3 , 4 ] ;
957+ let output = make_output ( policy_id, normal_name. clone ( ) , Some ( datum_blob. clone ( ) ) ) ;
958+
959+ let delta = UTXODelta :: Output ( output) ;
960+ let new_state = state. handle_cip68_metadata ( & [ delta] , & mut registry) . unwrap ( ) ;
961+
962+ let info = new_state. info . expect ( "info should be Some" ) ;
963+ let record = info. get ( & normal_id) . expect ( "non reference asset should exist" ) ;
964+
965+ // Onchain metadata not updated for non reference asset
966+ assert_eq ! ( record. onchain_metadata, None ) ;
967+ }
968+
969+ #[ test]
970+ fn handle_cip68_metadata_ignores_unknown_reference_assets ( ) {
971+ let mut registry = AssetRegistry :: new ( ) ;
972+ let policy_id: PolicyId = [ 9u8 ; 28 ] . into ( ) ;
973+
974+ let ( state, asset_id, name) = setup_state_with_asset (
975+ & mut registry,
976+ policy_id,
977+ & [ 0x00 , 0x06 , 0x43 , 0xb0 , 0x02 ] ,
978+ false ,
979+ ) ;
980+
981+ let datum_blob = vec ! [ 1 , 2 , 3 , 4 ] ;
982+ let output = make_output ( policy_id, name, Some ( datum_blob) ) ;
983+
984+ let delta = UTXODelta :: Output ( output) ;
985+ let new_state = state. handle_cip68_metadata ( & [ delta] , & mut registry) . unwrap ( ) ;
986+
987+ let info = new_state. info . expect ( "info should be Some" ) ;
988+
989+ // Metadata not populated if asset does not exist
990+ assert ! (
991+ info. get( & asset_id) . is_none( ) ,
992+ "unknown reference assets should be ignored"
993+ ) ;
994+ }
995+
996+ #[ test]
997+ fn handle_cip68_metadata_ignores_inputs_and_outputs_without_datum ( ) {
998+ let mut registry = AssetRegistry :: new ( ) ;
999+ let policy_id: PolicyId = [ 7u8 ; 28 ] . into ( ) ;
1000+
1001+ let ( state, asset_id, name) = setup_state_with_asset (
1002+ & mut registry,
1003+ policy_id,
1004+ & [ 0x00 , 0x06 , 0x43 , 0xb0 , 0x02 ] ,
1005+ true ,
1006+ ) ;
1007+
1008+ let input_delta = UTXODelta :: Input ( TxInput {
1009+ tx_hash : [ 1u8 ; 32 ] . into ( ) ,
1010+ index : 0 ,
1011+ } ) ;
1012+ let output = make_output ( policy_id, name, None ) ;
1013+ let output_delta = UTXODelta :: Output ( output) ;
1014+
1015+ let new_state =
1016+ state. handle_cip68_metadata ( & [ input_delta, output_delta] , & mut registry) . unwrap ( ) ;
1017+
1018+ let info = new_state. info . expect ( "info should be Some" ) ;
1019+ let record = info. get ( & asset_id) . unwrap ( ) ;
1020+
1021+ // Metadata not populated for inputs or outputs without inline datum
1022+ assert ! (
1023+ record. onchain_metadata. is_none( ) ,
1024+ "inputs and outputs without datums should both be ignored"
1025+ ) ;
1026+ }
1027+
1028+ #[ test]
1029+ fn get_asset_info_reference_nft_strips_metadata ( ) {
1030+ let mut registry = AssetRegistry :: new ( ) ;
1031+ let policy_id: PolicyId = [ 9u8 ; 28 ] . into ( ) ;
1032+
1033+ let ( mut state, ref_id, _) =
1034+ setup_state_with_asset ( & mut registry, policy_id, & [ 0x00 , 0x06 , 0x43 , 0xb0 ] , true ) ;
1035+
1036+ let mut info = state. info . take ( ) . unwrap ( ) ;
1037+ let rec = info. get_mut ( & ref_id) . unwrap ( ) ;
1038+ rec. onchain_metadata = Some ( vec ! [ 1 , 2 , 3 ] ) ;
1039+ rec. metadata_standard = Some ( AssetMetadataStandard :: CIP68v1 ) ;
1040+ state. info = Some ( info) ;
1041+
1042+ state. supply = Some ( imbl:: HashMap :: new ( ) ) ;
1043+ state. supply . as_mut ( ) . unwrap ( ) . insert ( ref_id, 42 ) ;
1044+
1045+ let result = state. get_asset_info ( & ref_id, & registry) . unwrap ( ) . unwrap ( ) ;
1046+ let ( supply, rec) = result;
1047+
1048+ // Supply unchanged
1049+ assert_eq ! ( supply, 42 ) ;
1050+ // Metadata removed for reference asset
1051+ assert ! ( rec. onchain_metadata. is_none( ) ) ;
1052+ // Metadata standard removed for reference asset
1053+ assert ! ( rec. metadata_standard. is_none( ) ) ;
1054+ }
1055+
1056+ #[ test]
1057+ fn resolve_cip68_metadata_overwrites_cip25_user_token_metadata ( ) {
1058+ let mut registry = AssetRegistry :: new ( ) ;
1059+ let policy_id: PolicyId = [ 10u8 ; 28 ] . into ( ) ;
1060+
1061+ let user_name = AssetName :: new ( & [ 0x00 , 0x0d , 0xe1 , 0x40 , 0xaa ] ) . unwrap ( ) ;
1062+ let user_id = registry. get_or_insert ( policy_id, user_name. clone ( ) ) ;
1063+
1064+ let mut ref_bytes = user_name. as_slice ( ) . to_vec ( ) ;
1065+ ref_bytes[ 0 ..4 ] . copy_from_slice ( & [ 0x00 , 0x06 , 0x43 , 0xb0 ] ) ;
1066+ let ref_name = AssetName :: new ( & ref_bytes) . unwrap ( ) ;
1067+ let ref_id = registry. get_or_insert ( policy_id, ref_name) ;
1068+
1069+ let mut state = State :: new ( AssetsStorageConfig {
1070+ store_info : true ,
1071+ ..Default :: default ( )
1072+ } ) ;
1073+ let mut info_map = imbl:: HashMap :: new ( ) ;
1074+
1075+ let mut user_record = AssetInfoRecord :: default ( ) ;
1076+ user_record. onchain_metadata = Some ( vec ! [ 1 , 2 , 3 ] ) ;
1077+ user_record. metadata_standard = Some ( AssetMetadataStandard :: CIP25v1 ) ;
1078+ info_map. insert ( user_id, user_record) ;
1079+
1080+ let mut ref_record = AssetInfoRecord :: default ( ) ;
1081+ ref_record. onchain_metadata = Some ( vec ! [ 9 , 9 , 9 ] ) ;
1082+ ref_record. metadata_standard = Some ( AssetMetadataStandard :: CIP68v2 ) ;
1083+ info_map. insert ( ref_id, ref_record) ;
1084+
1085+ state. info = Some ( info_map) ;
1086+
1087+ state. supply = Some ( imbl:: HashMap :: new ( ) ) ;
1088+ state. supply . as_mut ( ) . unwrap ( ) . insert ( user_id, 100 ) ;
1089+
1090+ let result = state. get_asset_info ( & user_id, & registry) . unwrap ( ) . unwrap ( ) ;
1091+ let ( supply, rec) = result;
1092+
1093+ // User asset supply unchanged
1094+ assert_eq ! ( supply, 100 ) ;
1095+ // User asset metadata overwritten with reference token metadata
1096+ assert_eq ! ( rec. onchain_metadata, Some ( vec![ 9 , 9 , 9 ] ) ) ;
1097+ // User asset metadata standard overwritten with reference token metadata standard
1098+ assert_eq ! ( rec. metadata_standard, Some ( AssetMetadataStandard :: CIP68v2 ) ) ;
1099+ }
7401100}
0 commit comments