diff --git a/precompiles/did/src/lib.rs b/precompiles/did/src/lib.rs index fb496381..9d8f08f8 100644 --- a/precompiles/did/src/lib.rs +++ b/precompiles/did/src/lib.rs @@ -184,15 +184,15 @@ where let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)); let did = R::AddressMapping::into_account_id(input.read::
()?.into()); let service_details = input.read::>()?; - let mut services: BoundedVec<::Hash, R::MaxServices> = - BoundedVec::with_bounded_capacity(service_details.len()); - for service in service_details.into_iter() { + // The maximum capacity is the minimum between [`service_details.len()`] and [`R::MaxServices`] + let mut services = BoundedVec::with_bounded_capacity(service_details.len()); + for service in service_details { if service.0.len() != H256::len_bytes() { return Err(revert("Service length different than 32 bytes")); } let hash = H256::from_slice(service.0.as_slice()); let endpoint = ::Hash::from(hash); - services.try_push(endpoint).map_err(|_| revert("failed to parse to service"))?; + services.try_push(endpoint).map_err(|_| revert("failed to parse service"))?; } RuntimeHelper::::try_dispatch( handle, @@ -206,6 +206,7 @@ where fn parse_credentials( raw_credentials: Vec, ) -> EvmResult, R::MaxCredentialsTypes>> { + // The maximum capacity is the minimum between [`raw_credentials.len()`] and [`R::MaxCredentialsTypes`] let mut credentials = BoundedVec::with_bounded_capacity(raw_credentials.len()); for raw_credential in raw_credentials { raw_credential @@ -215,7 +216,7 @@ where .map_err(|_| revert("Credential too long"))?; credentials .try_push(credential) - .map_err(|_| revert("failed to parse to credential"))?; + .map_err(|_| revert("failed to parse credential"))?; } Ok(credentials) } @@ -272,10 +273,9 @@ where fn parse_services( raw_services: Vec<(u8, Bytes)>, ) -> Result, R::MaxServices>, PrecompileFailure> { - let mut services: BoundedVec, R::MaxServices> = - BoundedVec::with_bounded_capacity(raw_services.len()); - let s = raw_services.iter(); - for service in s { + // The maximum capacity is the minimum between [`raw_services.len()`] and [`R::MaxServices`] + let mut services = BoundedVec::with_bounded_capacity(raw_services.len()); + for service in raw_services { let service_type: ServiceType = match service.0 { 0u8 => ServiceType::VerifiableCredentialFileStorage, _ => ServiceType::default(), @@ -285,7 +285,7 @@ where service.1.clone().0.try_into().map_err(|_| revert("Services string too long"))?; services .try_push(ServiceInfo { type_id: service_type, service_endpoint: endpoint }) - .map_err(|_| revert("failed to parse to service"))?; + .map_err(|_| revert("failed to parse service"))?; } Ok(services) } diff --git a/precompiles/did/src/mock.rs b/precompiles/did/src/mock.rs index 839ca0c3..3fe9866b 100644 --- a/precompiles/did/src/mock.rs +++ b/precompiles/did/src/mock.rs @@ -37,9 +37,9 @@ parameter_types! { pub const SS58Prefix: u8 = 19; pub const DidDeposit: u64 = 5; pub const MaxString: u8 = 100; - pub const MaxCredentialsTypes: u8 = 50; + pub const MaxCredentialsTypes: u8 = 5; pub const MaxCredentialTypeLength: u32 = 32; - pub const MaxServices: u8 = 10; + pub const MaxServices: u8 = 5; pub const MaxHash: u32 = 512; } diff --git a/precompiles/did/src/tests.rs b/precompiles/did/src/tests.rs index 5006fff8..0a61e791 100644 --- a/precompiles/did/src/tests.rs +++ b/precompiles/did/src/tests.rs @@ -20,6 +20,7 @@ use pallet_did::{ }; use precompile_utils::testing::PrecompileTesterExt; use sp_core::{bounded_vec, H160}; +use sp_std::vec::Vec; use super::*; use crate::mock::*; @@ -40,9 +41,7 @@ fn events() -> Vec> { result } -fn hash_services( - services: &BoundedVec, ::MaxServices>, -) -> ServiceKeysOf { +fn hash_services(services: &Vec>) -> ServiceKeysOf { let mut services_keys: ServiceKeysOf = BoundedVec::default(); for service in services { let _ = services_keys @@ -109,7 +108,7 @@ fn default_services( bounded_vec![ServiceInfo { type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, service_endpoint: bounded_vec![b's', b'0'] - },] + }] } #[test] @@ -128,8 +127,7 @@ fn it_creates_did_without_assertion() { .build(), ) .execute_returns(EvmDataWriter::new().write(true).build()); - let events = events(); - assert!(events.contains(&pallet_did::Event::::DidCreated { + assert!(events().contains(&pallet_did::Event::::DidCreated { did: TestAccount::Alice, document: expected_document, })); @@ -159,6 +157,36 @@ fn it_creates_did_with_assertion() { }); } +#[test] +fn reverts_creates_did_if_too_many_services() { + new_test_ext().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::CreateDID) + .write(Address(TestAccount::Alice.into())) + .write(Address(H160::from([0u8; 20]))) + .write((false, Address(H160::from([0u8; 20])))) + .write(vec![ + (1u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (2u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (3u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (4u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (5u8, Bytes(default_services()[0].service_endpoint.to_vec())), + // the 6th should fail + (6u8, Bytes(default_services()[0].service_endpoint.to_vec())), + ]) + .build(), + ) + .execute_reverts(|err| { + let reason = sp_std::str::from_utf8(err).unwrap(); + assert_eq!(reason, "failed to parse service"); + true + }); + }); +} + #[test] fn it_updates_nothing_from_did() { new_test_ext().execute_with(|| { @@ -187,6 +215,40 @@ fn it_updates_nothing_from_did() { }); } +#[test] +fn reverts_updates_did_if_too_many_services() { + new_test_ext().execute_with(|| { + insert_default_did(TestAccount::Alice); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::UpdateDID) + .write(Address(TestAccount::Alice.into())) + .write((false, Address(TestAccount::Alice.into()))) + .write((false, Address(H160::from([0u8; 20])))) + .write((false, Address(H160::from([0u8; 20])))) + .write(( + true, + vec![ + (2u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (3u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (4u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (5u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (6u8, Bytes(default_services()[0].service_endpoint.to_vec())), + (7u8, Bytes(default_services()[0].service_endpoint.to_vec())), + ], + )) + .build(), + ) + .execute_reverts(|err| { + let reason = sp_std::str::from_utf8(err).unwrap(); + assert_eq!(reason, "failed to parse service"); + true + }); + }); +} + #[test] fn it_updates_all_from_did() { new_test_ext().execute_with(|| { @@ -278,7 +340,7 @@ fn can_add_did_services() { service_keys.sort(); let mut raw_services: Vec<(u8, Bytes)> = Vec::with_capacity(services.len()); - for service in services.iter() { + for service in services { match service.type_id { ServiceType::VerifiableCredentialFileStorage => { raw_services.push((0u8, Bytes(service.service_endpoint.to_vec()))) @@ -292,7 +354,7 @@ fn can_add_did_services() { PRECOMPILE_ADDRESS, EvmDataWriter::new_with_selector(Action::AddDIDServices) .write(Address(TestAccount::Alice.into())) - .write::>(raw_services) + .write(raw_services) .build(), ) .execute_returns(EvmDataWriter::new().write(true).build()); @@ -303,6 +365,66 @@ fn can_add_did_services() { }); } +#[test] +fn reverts_add_did_services_if_too_many() { + new_test_ext().execute_with(|| { + let services = vec![ + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'1'], + }, + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'2'], + }, + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'3'], + }, + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'4'], + }, + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'5'], + }, + // the 6th one should fail + ServiceInfo { + type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage, + service_endpoint: bounded_vec![b's', b'6'], + }, + ]; + insert_default_did(TestAccount::Alice); + let mut service_keys = hash_services(&services); + service_keys.sort(); + let mut raw_services: Vec<(u8, Bytes)> = Vec::with_capacity(services.len()); + + for service in services { + match service.type_id { + ServiceType::VerifiableCredentialFileStorage => { + raw_services.push((0u8, Bytes(service.service_endpoint.to_vec()))) + }, + } + } + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AddDIDServices) + .write(Address(TestAccount::Alice.into())) + .write(raw_services) + .build(), + ) + .execute_reverts(|err| { + let reason = sp_std::str::from_utf8(err).unwrap(); + assert_eq!(reason, "failed to parse service"); + true + }); + }); +} + #[test] fn can_remove_did_services() { new_test_ext().execute_with(|| { @@ -362,6 +484,43 @@ fn it_issues_credentials() { }); } +#[test] +fn revert_issues_credentials_if_too_many() { + new_test_ext().execute_with(|| { + let credentials: BoundedVec< + BoundedVec::MaxCredentialTypeLength>, + ::MaxCredentialsTypes, + > = bounded_vec![bounded_vec![1u8; 32]]; + insert_default_credential_types(credentials.clone()); + insert_default_did(TestAccount::Alice); + insert_default_issuer(TestAccount::Alice); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::IssueCredentials) + .write(Address(TestAccount::Alice.into())) + .write(Address(TestAccount::Alice.into())) + .write(vec![ + Bytes(vec![1u8; 32]), + Bytes(vec![2u8; 32]), + Bytes(vec![3u8; 32]), + Bytes(vec![4u8; 32]), + Bytes(vec![5u8; 32]), + // the 6th one should fail + Bytes(vec![6u8; 32]), + ]) + .write(Bytes(vec![5u8; 32])) + .build(), + ) + .execute_reverts(|err| { + let reason = sp_std::str::from_utf8(err).unwrap(); + assert_eq!(reason, "failed to parse credential"); + true + }); + }); +} + #[test] fn it_revokes_credentials() { new_test_ext().execute_with(|| { @@ -397,3 +556,46 @@ fn it_revokes_credentials() { })); }); } + +#[test] +fn revert_revoke_credentials_if_too_many() { + new_test_ext().execute_with(|| { + let credentials: BoundedVec< + BoundedVec::MaxCredentialTypeLength>, + ::MaxCredentialsTypes, + > = bounded_vec![bounded_vec![1u8; 32]]; + insert_default_credential_types(credentials.clone()); + insert_default_did(TestAccount::Alice); + insert_default_issuer(TestAccount::Alice); + assert_ok!(DID::issue_credentials( + RuntimeOrigin::signed(TestAccount::Alice), + TestAccount::Alice, + TestAccount::Alice, + bounded_vec![bounded_vec![1u8; 32]], + bounded_vec![5u8; 32], + )); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::RevokeCredentials) + .write(Address(TestAccount::Alice.into())) + .write(Address(TestAccount::Alice.into())) + .write(vec![ + Bytes(vec![1u8; 32]), + Bytes(vec![2u8; 32]), + Bytes(vec![3u8; 32]), + Bytes(vec![4u8; 32]), + Bytes(vec![5u8; 32]), + // if we insert a 6th it should crash + Bytes(vec![6u8; 32]), + ]) + .build(), + ) + .execute_reverts(|err| { + let reason = sp_std::str::from_utf8(err).unwrap(); + assert_eq!(reason, "failed to parse credential"); + true + }); + }); +}