diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ceca8b2..52078c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: version: 0.22.0 # Due to a bug with cargo-tarpaulin crate with multi-line chain calls, the test coverage was reduced. # https://github.com/xd009642/tarpaulin/issues/949 - args: --all-features --fail-under 98.0 --out Lcov + args: --all-features --fail-under 97.8 --out Lcov - name: Upload to Coveralls uses: coverallsapp/github-action@master with: diff --git a/vc_issuance_contract/README.md b/vc_issuance_contract/README.md index 0a90d01..4f0fa0a 100644 --- a/vc_issuance_contract/README.md +++ b/vc_issuance_contract/README.md @@ -152,6 +152,7 @@ A contract error will be triggered if: - Invoker is not the contract admin. - Verifiable credential is not registered. +- Verifiable credential is already revoked. ```rust fn revoke(e: Env, admin: Address, vc_id: String, date: String); @@ -179,8 +180,9 @@ soroban contract invoke \ | 1 | `AlreadyInitialized` | Contract has already been initialized | | 2 | `NotAuthorized` | Invoker is not the contract admin | | 3 | `AmountLimitExceeded` | Provided amount exceeds the maximum allowed | -| 4 | `VCNotFound` | Verifiable credential not found | -| 5 | `IssuanceLimitExceeded` | Contract issuance limit exceeded | +| 4 | `VCNotFound` | Verifiable credential not found | +| 5 | `VCAlreadyRevoked` | Verifiable credential already revoked | +| 6 | `IssuanceLimitExceeded` | Contract issuance limit exceeded | ## Development diff --git a/vc_issuance_contract/src/contract.rs b/vc_issuance_contract/src/contract.rs index fe82a27..2c71aa7 100644 --- a/vc_issuance_contract/src/contract.rs +++ b/vc_issuance_contract/src/contract.rs @@ -1,17 +1,12 @@ -use crate::error::ContractError; -use crate::revocation::Revocation; use crate::storage; use crate::vc_issuance_trait::VCIssuanceTrait; use crate::verifiable_credential; +use crate::{error::ContractError, revocation}; use soroban_sdk::{ contract, contractimpl, contractmeta, map, panic_with_error, vec, Address, Env, FromVal, Map, String, Symbol, Val, Vec, }; -// MAXIMUM ENTRY TTL: -// 31 days, 12 ledger close per minute. -// (12 * 60 * 24 * 31) - 1 -const LEDGERS_TO_EXTEND: u32 = 535_679; const DEFAULT_AMOUNT: u32 = 20; const MAX_AMOUNT: u32 = 100; @@ -41,9 +36,8 @@ impl VCIssuanceTrait for VCIssuanceContract { storage::write_vcs(&e, &Vec::new(&e)); storage::write_vcs_revocations(&e, &Map::new(&e)); - e.storage() - .instance() - .extend_ttl(LEDGERS_TO_EXTEND, LEDGERS_TO_EXTEND); + storage::extend_ttl_to_instance(&e); + storage::extend_ttl_to_persistent(&e); } fn issue(e: Env, admin: Address, vc_data: String, vault_contract: Address) -> String { validate_admin(&e, &admin); @@ -91,11 +85,7 @@ impl VCIssuanceTrait for VCIssuanceContract { validate_admin(&e, &admin); validate_vc(&e, &vc_id); - let mut revocations = storage::read_vcs_revocations(&e); - - revocations.set(vc_id.clone(), Revocation { vc_id, date }); - - storage::write_vcs_revocations(&e, &revocations); + revocation::revoke_vc(&e, vc_id, date); } } diff --git a/vc_issuance_contract/src/error.rs b/vc_issuance_contract/src/error.rs index de56caa..4c37dde 100644 --- a/vc_issuance_contract/src/error.rs +++ b/vc_issuance_contract/src/error.rs @@ -8,5 +8,6 @@ pub enum ContractError { NotAuthorized = 2, AmountLimitExceeded = 3, VCNotFound = 4, - IssuanceLimitExceeded = 5, + VCAlreadyRevoked = 5, + IssuanceLimitExceeded = 6, } diff --git a/vc_issuance_contract/src/revocation.rs b/vc_issuance_contract/src/revocation.rs index e13cd5f..079b1d9 100644 --- a/vc_issuance_contract/src/revocation.rs +++ b/vc_issuance_contract/src/revocation.rs @@ -1,4 +1,6 @@ -use soroban_sdk::{contracttype, String}; +use crate::error::ContractError; +use crate::storage; +use soroban_sdk::{contracttype, panic_with_error, Env, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] @@ -6,3 +8,13 @@ pub struct Revocation { pub vc_id: String, pub date: String, } + +pub fn revoke_vc(e: &Env, vc_id: String, date: String) { + let mut revocations = storage::read_vcs_revocations(e); + + if revocations.contains_key(vc_id.clone()) { + panic_with_error!(e, ContractError::VCAlreadyRevoked) + } + revocations.set(vc_id.clone(), Revocation { vc_id, date }); + storage::write_vcs_revocations(e, &revocations); +} diff --git a/vc_issuance_contract/src/storage.rs b/vc_issuance_contract/src/storage.rs index 133e4a1..4d8fb17 100644 --- a/vc_issuance_contract/src/storage.rs +++ b/vc_issuance_contract/src/storage.rs @@ -1,14 +1,19 @@ use crate::revocation::Revocation; use soroban_sdk::{contracttype, Address, Env, Map, String, Vec}; +// MAXIMUM ENTRY TTL: +// 31 days, 12 ledger close per minute. +// (12 * 60 * 24 * 31) - 1 +const LEDGERS_TO_EXTEND: u32 = 535_679; + #[derive(Clone)] #[contracttype] pub enum DataKey { - Admin, // Address - IssuerDID, // String - Amount, // U32 - VerifiableCredentials, // Vec - RevocationList, // Map + Admin, // Address + IssuerDID, // String + Amount, // U32 + VCs, // Vec + Revocations, // Map } pub fn has_admin(e: &Env) -> bool { @@ -47,21 +52,39 @@ pub fn write_amount(e: &Env, amount: &u32) { } pub fn write_vcs(e: &Env, vc: &Vec) { - let key = DataKey::VerifiableCredentials; - e.storage().instance().set(&key, vc) + let key = DataKey::VCs; + e.storage().persistent().set(&key, vc) } pub fn read_vcs(e: &Env) -> Vec { - let key = DataKey::VerifiableCredentials; - e.storage().instance().get(&key).unwrap() + let key = DataKey::VCs; + e.storage().persistent().get(&key).unwrap() } pub fn write_vcs_revocations(e: &Env, revocations: &Map) { - let key = DataKey::RevocationList; - e.storage().instance().set(&key, revocations) + let key = DataKey::Revocations; + e.storage().persistent().set(&key, revocations) } pub fn read_vcs_revocations(e: &Env) -> Map { - let key = DataKey::RevocationList; - e.storage().instance().get(&key).unwrap() + let key = DataKey::Revocations; + e.storage().persistent().get(&key).unwrap() +} + +pub fn extend_ttl_to_instance(e: &Env) { + e.storage() + .instance() + .extend_ttl(LEDGERS_TO_EXTEND, LEDGERS_TO_EXTEND); +} + +pub fn extend_ttl_to_persistent(e: &Env) { + let vcs_key = DataKey::VCs; + let revocations_key = DataKey::Revocations; + + e.storage() + .persistent() + .extend_ttl(&vcs_key, LEDGERS_TO_EXTEND, LEDGERS_TO_EXTEND); + e.storage() + .persistent() + .extend_ttl(&revocations_key, LEDGERS_TO_EXTEND, LEDGERS_TO_EXTEND); } diff --git a/vc_issuance_contract/src/test/contract.rs b/vc_issuance_contract/src/test/contract.rs index 26b428c..4a6f2b1 100644 --- a/vc_issuance_contract/src/test/contract.rs +++ b/vc_issuance_contract/src/test/contract.rs @@ -94,7 +94,7 @@ fn test_issue_with_invalid_admin() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #5)")] +#[should_panic(expected = "HostError: Error(Contract, #6)")] fn test_issue_when_amount_is_exceeded() { let VCIssuanceContractTest { env, @@ -109,6 +109,24 @@ fn test_issue_when_amount_is_exceeded() { contract.issue(&admin, &vc_data, &vault_contract_id); } +#[test] +fn test_revoke_vc() { + let VCIssuanceContractTest { + env, + admin, + amount: _, + vc_data, + issuer_did, + contract, + } = VCIssuanceContractTest::setup(); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); + let vc_id = contract.issue(&admin, &vc_data, &vault_contract_id); + + let date = String::from_str(&env, "2023-12-05T21:37:44.389Z"); + + contract.revoke(&admin, &vc_id, &date); +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #4)")] fn test_revoke_vc_with_invalid_vc() { @@ -129,7 +147,8 @@ fn test_revoke_vc_with_invalid_vc() { } #[test] -fn test_revoke_vc() { +#[should_panic(expected = "HostError: Error(Contract, #5)")] +fn test_revoke_vc_when_it_was_already_revoked() { let VCIssuanceContractTest { env, admin, @@ -141,9 +160,11 @@ fn test_revoke_vc() { let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); let vc_id = contract.issue(&admin, &vc_data, &vault_contract_id); - let date = String::from_str(&env, "2023-12-05T21:37:44.389Z"); + let date_1 = String::from_str(&env, "2023-12-05T21:37:44.389Z"); + let date_2 = String::from_str(&env, "2023-21-05T21:37:44.389Z"); - contract.revoke(&admin, &vc_id, &date); + contract.revoke(&admin, &vc_id, &date_1); + contract.revoke(&admin, &vc_id, &date_2); } #[test]