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
+ });
+ });
+}