@@ -7,15 +7,15 @@ use mpl_core::{
77 accounts:: BaseAssetV1 ,
88 fetch_external_plugin_adapter_data_info,
99 instructions:: {
10- AddExternalPluginAdapterV1Builder , UpdatePluginV1Builder ,
11- WriteExternalPluginAdapterDataV1Builder ,
10+ AddExternalPluginAdapterV1Builder , UpdateCollectionV1Builder , UpdatePluginV1Builder ,
11+ UpdateV1Builder , WriteExternalPluginAdapterDataV1Builder ,
1212 } ,
1313 types:: {
14- AppDataInitInfo , Attribute , Attributes , ExternalPluginAdapterInitInfo ,
14+ AppDataInitInfo , Attribute , Attributes , Creator , ExternalPluginAdapterInitInfo ,
1515 ExternalPluginAdapterKey , ExternalPluginAdapterSchema , FreezeDelegate , Plugin ,
16- PluginAuthority , PluginAuthorityPair ,
16+ PluginAuthority , PluginAuthorityPair , Royalties , RuleSet ,
1717 } ,
18- Asset ,
18+ Asset , Collection ,
1919} ;
2020pub use setup:: * ;
2121
@@ -790,3 +790,217 @@ async fn test_update_plugin_shrink_attributes_preserves_external_plugin() {
790790 ) ;
791791 }
792792}
793+
794+ // Regression: shrinking asset name + uri must preserve attached plugin data.
795+ #[ tokio:: test]
796+ async fn test_update_v1_shrink_name_uri_preserves_plugin ( ) {
797+ let mut context = program_test ( ) . start_with_context ( ) . await ;
798+
799+ let asset = Keypair :: new ( ) ;
800+
801+ let long_name = "x" . repeat ( 100 ) ;
802+ let long_uri = format ! ( "https://example.com/{}" , "y" . repeat( 200 ) ) ;
803+
804+ create_asset (
805+ & mut context,
806+ CreateAssetHelperArgs {
807+ owner : None ,
808+ payer : None ,
809+ asset : & asset,
810+ data_state : None ,
811+ name : Some ( long_name. clone ( ) ) ,
812+ uri : Some ( long_uri. clone ( ) ) ,
813+ authority : None ,
814+ update_authority : None ,
815+ collection : None ,
816+ plugins : vec ! [ PluginAuthorityPair {
817+ plugin: Plugin :: FreezeDelegate ( FreezeDelegate { frozen: false } ) ,
818+ authority: None ,
819+ } ] ,
820+ external_plugin_adapters : vec ! [ ] ,
821+ } ,
822+ )
823+ . await
824+ . unwrap ( ) ;
825+
826+ let account_before = context
827+ . banks_client
828+ . get_account ( asset. pubkey ( ) )
829+ . await
830+ . unwrap ( )
831+ . unwrap ( ) ;
832+ let size_before = account_before. data . len ( ) ;
833+ println ! ( "Account size before name/uri shrink: {}" , size_before) ;
834+
835+ let asset_before = Asset :: from_bytes ( & account_before. data ) . unwrap ( ) ;
836+ assert_eq ! ( asset_before. base. name, long_name) ;
837+ assert_eq ! ( asset_before. base. uri, long_uri) ;
838+ assert ! ( asset_before. plugin_list. freeze_delegate. is_some( ) ) ;
839+
840+ let short_name = "a" . to_string ( ) ;
841+ let short_uri = "b" . to_string ( ) ;
842+
843+ let ix = UpdateV1Builder :: new ( )
844+ . asset ( asset. pubkey ( ) )
845+ . payer ( context. payer . pubkey ( ) )
846+ . new_name ( short_name. clone ( ) )
847+ . new_uri ( short_uri. clone ( ) )
848+ . instruction ( ) ;
849+
850+ let tx = Transaction :: new_signed_with_payer (
851+ & [ ix] ,
852+ Some ( & context. payer . pubkey ( ) ) ,
853+ & [ & context. payer ] ,
854+ context. last_blockhash ,
855+ ) ;
856+
857+ context
858+ . banks_client
859+ . process_transaction ( tx)
860+ . await
861+ . expect ( "Asset name/uri shrink transaction should succeed" ) ;
862+
863+ let account_after = context
864+ . banks_client
865+ . get_account ( asset. pubkey ( ) )
866+ . await
867+ . unwrap ( )
868+ . unwrap ( ) ;
869+ let size_after = account_after. data . len ( ) ;
870+ assert ! (
871+ size_after < size_before,
872+ "Expected account to shrink from {} to {}, but it did not" ,
873+ size_before,
874+ size_after
875+ ) ;
876+
877+ let asset_after = Asset :: from_bytes ( & account_after. data ) . expect (
878+ "Asset deserialization should succeed after name/uri shrink; registry must remain intact" ,
879+ ) ;
880+
881+ assert_eq ! ( asset_after. base. name, short_name, "Name should be updated" ) ;
882+ assert_eq ! ( asset_after. base. uri, short_uri, "URI should be updated" ) ;
883+
884+ let fd = asset_after
885+ . plugin_list
886+ . freeze_delegate
887+ . as_ref ( )
888+ . expect ( "FreezeDelegate must still be present after name/uri shrink" ) ;
889+ assert_eq ! (
890+ fd. freeze_delegate,
891+ FreezeDelegate { frozen: false } ,
892+ "FreezeDelegate state should be unchanged"
893+ ) ;
894+ }
895+
896+ // Regression: shrinking collection name + uri must preserve attached plugin data.
897+ #[ tokio:: test]
898+ async fn test_update_collection_v1_shrink_name_uri_preserves_plugin ( ) {
899+ let mut context = program_test ( ) . start_with_context ( ) . await ;
900+
901+ let collection = Keypair :: new ( ) ;
902+
903+ let long_name = "c" . repeat ( 100 ) ;
904+ let long_uri = format ! ( "https://example.com/collection/{}" , "d" . repeat( 200 ) ) ;
905+
906+ let royalties = Royalties {
907+ basis_points : 500 ,
908+ creators : vec ! [ Creator {
909+ address: context. payer. pubkey( ) ,
910+ percentage: 100 ,
911+ } ] ,
912+ rule_set : RuleSet :: None ,
913+ } ;
914+
915+ create_collection (
916+ & mut context,
917+ CreateCollectionHelperArgs {
918+ collection : & collection,
919+ update_authority : None ,
920+ payer : None ,
921+ name : Some ( long_name. clone ( ) ) ,
922+ uri : Some ( long_uri. clone ( ) ) ,
923+ plugins : vec ! [ PluginAuthorityPair {
924+ plugin: Plugin :: Royalties ( royalties. clone( ) ) ,
925+ authority: None ,
926+ } ] ,
927+ external_plugin_adapters : vec ! [ ] ,
928+ } ,
929+ )
930+ . await
931+ . unwrap ( ) ;
932+
933+ let account_before = context
934+ . banks_client
935+ . get_account ( collection. pubkey ( ) )
936+ . await
937+ . unwrap ( )
938+ . unwrap ( ) ;
939+ let size_before = account_before. data . len ( ) ;
940+ println ! ( "Collection size before name/uri shrink: {}" , size_before) ;
941+
942+ let collection_before = Collection :: from_bytes ( & account_before. data ) . unwrap ( ) ;
943+ assert_eq ! ( collection_before. base. name, long_name) ;
944+ assert_eq ! ( collection_before. base. uri, long_uri) ;
945+ assert ! ( collection_before. plugin_list. royalties. is_some( ) ) ;
946+
947+ let short_name = "e" . to_string ( ) ;
948+ let short_uri = "f" . to_string ( ) ;
949+
950+ let ix = UpdateCollectionV1Builder :: new ( )
951+ . collection ( collection. pubkey ( ) )
952+ . payer ( context. payer . pubkey ( ) )
953+ . new_name ( short_name. clone ( ) )
954+ . new_uri ( short_uri. clone ( ) )
955+ . instruction ( ) ;
956+
957+ let tx = Transaction :: new_signed_with_payer (
958+ & [ ix] ,
959+ Some ( & context. payer . pubkey ( ) ) ,
960+ & [ & context. payer ] ,
961+ context. last_blockhash ,
962+ ) ;
963+
964+ context
965+ . banks_client
966+ . process_transaction ( tx)
967+ . await
968+ . expect ( "Collection name/uri shrink transaction should succeed" ) ;
969+
970+ let account_after = context
971+ . banks_client
972+ . get_account ( collection. pubkey ( ) )
973+ . await
974+ . unwrap ( )
975+ . unwrap ( ) ;
976+ let size_after = account_after. data . len ( ) ;
977+ assert ! (
978+ size_after < size_before,
979+ "Expected collection to shrink from {} to {}, but it did not" ,
980+ size_before,
981+ size_after
982+ ) ;
983+
984+ let collection_after = Collection :: from_bytes ( & account_after. data ) . expect (
985+ "Collection deserialization should succeed after name/uri shrink; registry must remain intact" ,
986+ ) ;
987+
988+ assert_eq ! (
989+ collection_after. base. name, short_name,
990+ "Collection name should be updated"
991+ ) ;
992+ assert_eq ! (
993+ collection_after. base. uri, short_uri,
994+ "Collection URI should be updated"
995+ ) ;
996+
997+ let royalties_after = collection_after
998+ . plugin_list
999+ . royalties
1000+ . as_ref ( )
1001+ . expect ( "Royalties plugin must still be present after name/uri shrink" ) ;
1002+ assert_eq ! (
1003+ royalties_after. royalties, royalties,
1004+ "Royalties plugin contents should be unchanged"
1005+ ) ;
1006+ }
0 commit comments