diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8576a42..f0f5d0e 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 95 --out Lcov + args: --all-features --fail-under 90 --out Lcov - name: Upload to Coveralls uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # v2.2.3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4dca5..843da0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.20.0 (08.05.2024) +- Remove VCs amount limit. See [PR #200](https://github.com/kommitters/chaincerts-smart-contracts/pull/200) +- Migrate VCs storage in old contracts. See [PR #203](https://github.com/kommitters/chaincerts-smart-contracts/pull/203) + ## 0.19.1 (25.04.2024) - Add stale issues policy. See [PR #196](https://github.com/kommitters/chaincerts-smart-contracts/pull/196) diff --git a/Cargo.toml b/Cargo.toml index b01f1f9..5ef3b0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" members = ["deployer_contract", "did_contract", "vault_contract", "vc_issuance_contract"] [workspace.package] -version = "0.19.1" +version = "0.20.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/kommitters/chaincerts-smart-contracts" diff --git a/deployer_contract/did_contract.wasm b/deployer_contract/did_contract.wasm deleted file mode 100644 index df6c263..0000000 Binary files a/deployer_contract/did_contract.wasm and /dev/null differ diff --git a/deployer_contract/src/test.rs b/deployer_contract/src/test.rs index 740ba4f..60b6d7e 100644 --- a/deployer_contract/src/test.rs +++ b/deployer_contract/src/test.rs @@ -53,11 +53,9 @@ fn test_deploy_from_address() { fn vc_issuance_init_args(env: &Env) -> Vec { let admin = Address::generate(env); let issuer_did = String::from_str(env, "did:chaincerts:3mtjfbxad3wzh7qa4w5f7q4h"); - let amount: Option = Some(10); vec![ env, Val::from_val(env, &admin), Val::from_val(env, &issuer_did), - Val::from_val(env, &amount), ] } diff --git a/vault_contract/README.md b/vault_contract/README.md index c532818..ba18668 100644 --- a/vault_contract/README.md +++ b/vault_contract/README.md @@ -12,7 +12,7 @@ With this smart contract, you will be able to: - Revoke an issuer for a specific vault. - Store a verifiable credential in the recipient's vault. - Revoke the vault. -- Retrieve the list of stored vcs in the vault. +- Migrate the VCs key for contracts older than version v0.20.0. - Set the contract admin. - Upgrade the contract. - Get the contract version. @@ -28,7 +28,7 @@ Represents a digitally signed statement made by an issuer about a DID subject. | `id` | `String` | Unique identifier (e.g., `t5iwuct2njbbcdu2nfwr32ib`). | | `data` | `String` | VC data encrypted utilizing a key agreement algorithm for heightened security. | | `issuance_contract` | `Address` | Smart contract address responsible for verifiable credential issuance. | -| `issuer_did` | `String` | DID of the verifiable credential issuer. | +| `issuer_did` | `String` | DID of the verifiable credential issuer. | #### Example @@ -219,16 +219,13 @@ soroban contract invoke \ revoke_vault ``` -### Get VCs -Retrieve the list of stored vcs in the vault. +### Migrate VCs +Migrates the VCs from being stored in a single vector to multiple vectors. ```rust -fn get_vcs(e: Env) -> Vec; +fn migrate(e: Env); ``` -#### Output -Returns a list of vcs. - #### Example ```bash @@ -238,23 +235,7 @@ soroban contract invoke \ --rpc-url https://soroban-testnet.stellar.org:443 \ --network-passphrase 'Test SDF Network ; September 2015' \ -- \ - get_vcs - -# Output: VCs -[ - { - "id": "t5iwuct2njbbcdu2nfwr32ib", - "data": "gzLDVsdtPc6w8tOhyiaftVPu9gI8J+/8UKlIAmTVNkiV0QAAfahvqhgMY2ZNLHnksFA15XiLDiXb6Yam39rcif94XrsVnXZ7UKuhOFqgMew", - "issuance_contract": "CBCA3EDJOEHHVH3X2RGWQNUDWVHP2JZHFYVGSSCDWD3RI3IUYY4FKLD4", - "issuer_did": "did:chaincerts:5ppl9sm47frl0tpj7g3lp6eo" - }, - { - "id": "wqzrxs3eq2v90i5un1ph7k8l", - "data": "Pc1hVUB2Mz8jXw9rEk7NxF4Lg5vmB3rYscAItJfRqiD0dVxkpwZqXlO2eau7YcDIoZaVlqSRF7sQ1B2YnmfIY", - "issuance_contract": "CBRM3HA7GLEI6QQ3O55RUKVRDSQASARUPKK6NXKXKKPWEYLE533GDYQD", - "issuer_did": "did:chaincerts:pe4t2r94dftr1n1gf6jikt6a" - } -] + migrate ``` ### Set contract admin diff --git a/vault_contract/src/contract.rs b/vault_contract/src/contract.rs index d4061cb..9f086aa 100644 --- a/vault_contract/src/contract.rs +++ b/vault_contract/src/contract.rs @@ -4,7 +4,6 @@ use crate::issuer; use crate::storage; use crate::vault_trait::VaultTrait; use crate::verifiable_credential; -use crate::verifiable_credential::VerifiableCredential; use soroban_sdk::{ contract, contractimpl, contractmeta, panic_with_error, Address, BytesN, Env, IntoVal, String, Symbol, Val, Vec, @@ -17,6 +16,7 @@ contractmeta!( val = "Smart contract for Chaincerts Vault", ); +#[allow(dead_code)] #[contract] pub struct VaultContract; @@ -38,9 +38,9 @@ impl VaultTrait for VaultContract { storage::write_admin(&e, &admin); storage::write_did(&e, &did_uri); + storage::write_did_contract(&e, &did_contract_address); storage::write_revoked(&e, &false); storage::write_issuers(&e, &Vec::new(&e)); - storage::write_vcs(&e, &Vec::new(&e)); (did_contract_address, did_document.into_val(&e)) } @@ -75,7 +75,7 @@ impl VaultTrait for VaultContract { issuance_contract: Address, ) { validate_vault_revoked(&e); - validate_issuer(&e, &issuer, &vc_data, &issuance_contract); + validate_issuer(&e, &issuer); verifiable_credential::store_vc(&e, vc_id, vc_data, issuance_contract, issuer_did); } @@ -87,8 +87,26 @@ impl VaultTrait for VaultContract { storage::write_revoked(&e, &true); } - fn get_vcs(e: Env) -> Vec { - storage::read_vcs(&e) + fn migrate(e: Env) { + validate_admin(&e); + + let vcs = storage::read_old_vcs(&e); + + if vcs.is_none() { + panic_with_error!(e, ContractError::VCSAlreadyMigrated) + } + + for vc in vcs.unwrap().iter() { + verifiable_credential::store_vc( + &e, + vc.id.clone(), + vc.data.clone(), + vc.issuance_contract.clone(), + vc.issuer_did.clone(), + ); + } + + storage::remove_old_vcs(&e); } fn set_admin(e: Env, new_admin: Address) { @@ -114,16 +132,14 @@ fn validate_admin(e: &Env) { contract_admin.require_auth(); } -fn validate_issuer(e: &Env, issuer: &Address, vc_data: &String, issuance_contract: &Address) { +fn validate_issuer(e: &Env, issuer: &Address) { let issuers: Vec
= storage::read_issuers(e); if !issuer::is_authorized(&issuers, issuer) { panic_with_error!(e, ContractError::IssuerNotAuthorized) } - issuer.require_auth_for_args( - (vc_data.clone(), issuer.clone(), issuance_contract.clone()).into_val(e), - ); + issuer.require_auth(); } fn validate_vault_revoked(e: &Env) { diff --git a/vault_contract/src/error.rs b/vault_contract/src/error.rs index d82a489..acfde47 100644 --- a/vault_contract/src/error.rs +++ b/vault_contract/src/error.rs @@ -8,4 +8,5 @@ pub enum ContractError { IssuerNotAuthorized = 2, IssuerAlreadyAuthorized = 3, VaultRevoked = 4, + VCSAlreadyMigrated = 5, } diff --git a/vault_contract/src/storage.rs b/vault_contract/src/storage.rs index 923ebf2..d4b6e89 100644 --- a/vault_contract/src/storage.rs +++ b/vault_contract/src/storage.rs @@ -4,11 +4,13 @@ use soroban_sdk::{contracttype, Address, Env, String, Vec}; #[derive(Clone)] #[contracttype] pub enum DataKey { - Admin, // Address - Did, // String - Revoked, // Boolean - Issuers, // Vec
- VCs, // Vec + Admin, // Address + Did, // String + DidContract, // Address + Revoked, // Boolean + Issuers, // Vec
+ VC(String), // VerifiableCredential + VCs, // Vec } pub fn has_admin(e: &Env) -> bool { @@ -31,6 +33,11 @@ pub fn write_did(e: &Env, did: &String) { e.storage().instance().set(&key, did); } +pub fn write_did_contract(e: &Env, did_contract: &Address) { + let key = DataKey::DidContract; + e.storage().instance().set(&key, did_contract); +} + pub fn read_revoked(e: &Env) -> bool { let key = DataKey::Revoked; e.storage().instance().get(&key).unwrap() @@ -51,12 +58,17 @@ pub fn write_issuers(e: &Env, issuers: &Vec
) { e.storage().persistent().set(&key, issuers) } -pub fn read_vcs(e: &Env) -> Vec { +pub fn write_vc(e: &Env, vc_id: &String, vc: &VerifiableCredential) { + let key = DataKey::VC(vc_id.clone()); + e.storage().persistent().set(&key, vc) +} + +pub fn read_old_vcs(e: &Env) -> Option> { let key = DataKey::VCs; - e.storage().persistent().get(&key).unwrap() + e.storage().persistent().get(&key) } -pub fn write_vcs(e: &Env, vcs: &Vec) { +pub fn remove_old_vcs(e: &Env) { let key = DataKey::VCs; - e.storage().persistent().set(&key, vcs) + e.storage().persistent().remove(&key); } diff --git a/vault_contract/src/test/contract.rs b/vault_contract/src/test/contract.rs index 2728c94..fb4877b 100644 --- a/vault_contract/src/test/contract.rs +++ b/vault_contract/src/test/contract.rs @@ -1,7 +1,6 @@ use super::setup::{did_context, get_vc_setup, VCVaultContractTest}; use crate::did_contract; use crate::test::setup::VaultContractTest; -use crate::verifiable_credential::VerifiableCredential; use soroban_sdk::{testutils::Address as _, vec, Address, String}; #[test] @@ -332,61 +331,20 @@ fn test_revoke_vault() { } #[test] -fn test_get_vcs() { +#[should_panic(expected = "HostError: Error(Contract, #5)")] +fn test_migrate_should_fail_without_vcs() { let VaultContractTest { - env, + env: _, admin, - issuer, + issuer: _, did_init_args, did_wasm_hash, salt, contract, } = VaultContractTest::setup(); - let VCVaultContractTest { - vc_id, - vc_data, - issuance_contract_address, - issuer_did, - } = get_vc_setup(&env); - - let vc_id_2 = String::from_str(&env, "vc_id2"); - - let vc_1 = VerifiableCredential { - id: vc_id.clone(), - data: vc_data.clone(), - issuance_contract: issuance_contract_address.clone(), - issuer_did: issuer_did.clone(), - }; - - let vc_2 = VerifiableCredential { - id: vc_id_2.clone(), - data: vc_data.clone(), - issuance_contract: issuance_contract_address.clone(), - issuer_did: issuer_did.clone(), - }; - contract.initialize(&admin, &did_wasm_hash, &did_init_args, &salt); - contract.authorize_issuer(&issuer); - contract.store_vc( - &vc_id, - &vc_data, - &issuer, - &issuer_did, - &issuance_contract_address, - ); - contract.store_vc( - &vc_id_2, - &vc_data, - &issuer, - &issuer_did, - &issuance_contract_address, - ); - let vcs = contract.get_vcs(); - - assert_eq!(vcs.len(), 2); - assert_eq!(vcs.get_unchecked(1), vc_1); - assert_eq!(vcs.get_unchecked(0), vc_2); + contract.migrate(); } #[test] diff --git a/vault_contract/src/vault_trait.rs b/vault_contract/src/vault_trait.rs index 9f288a2..0361988 100644 --- a/vault_contract/src/vault_trait.rs +++ b/vault_contract/src/vault_trait.rs @@ -1,6 +1,6 @@ -use crate::verifiable_credential::VerifiableCredential; use soroban_sdk::{Address, BytesN, Env, String, Val, Vec}; +#[allow(dead_code)] pub trait VaultTrait { /// Initializes the vault contract by setting the admin and deploying the DID. fn initialize( @@ -33,8 +33,8 @@ pub trait VaultTrait { /// Revokes the vault. fn revoke_vault(e: Env); - /// Retrieves the vcs. - fn get_vcs(e: Env) -> Vec; + /// Migrates the VCs from being stored in a single vector to multiple vectors. + fn migrate(e: Env); /// Sets the new contract admin. fn set_admin(e: Env, new_admin: Address); diff --git a/vault_contract/src/verifiable_credential.rs b/vault_contract/src/verifiable_credential.rs index d99a6b6..70ba726 100644 --- a/vault_contract/src/verifiable_credential.rs +++ b/vault_contract/src/verifiable_credential.rs @@ -1,5 +1,5 @@ use crate::storage; -use soroban_sdk::{contracttype, Address, Env, String, Vec}; +use soroban_sdk::{contracttype, Address, Env, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] @@ -11,14 +11,12 @@ pub struct VerifiableCredential { } pub fn store_vc(e: &Env, id: String, data: String, issuance_contract: Address, issuer_did: String) { - let mut vcs: Vec = storage::read_vcs(e); let new_vc: VerifiableCredential = VerifiableCredential { - id, + id: id.clone(), data, issuance_contract, issuer_did, }; - vcs.push_front(new_vc); - storage::write_vcs(e, &vcs); + storage::write_vc(e, &id, &new_vc); } diff --git a/vc_issuance_contract/README.md b/vc_issuance_contract/README.md index ebb2f16..0651fc1 100644 --- a/vc_issuance_contract/README.md +++ b/vc_issuance_contract/README.md @@ -12,38 +12,18 @@ With this smart contract, you will be able to: - Issue a verifiable credential. - Verify a verifiable credential. - Revoke a verifiable credential. +- Migrate the VCs key for contracts older than version v0.20.0. - Set the contract admin. - Upgrade the contract. - Get the contract version. -## Types - -### Revocation -Represents a revoked verifiable credential. - -### Attributes - -| Name | Type | Values | -| ------------ | --------- | ------------------------------------------------- | -| `vc_id` | `String` | The verifiable credential id. | -| `date` | `String` | The date of revocation. | - -### Example - -```bash -{ - "vc_id": "a4tkzct2njbbcdu2nfwr32ib", - "date": "2023-12-05T21:37:44.389Z" -} -``` - ## Functions ### Initialize -Initializes the contract by setting the contract admin, the issuer DID and the limit amount of verifiable credentials that can be issued. The maximum amount allowed is **200**; if no amount is provided, the default value is **20**. An error will be triggered if the contract has already been initialized. +Initializes the contract by setting the contract admin and the issuer DID. An error will be triggered if the contract has already been initialized. ```rust -fn initialize(e: Env, admin: Address, issuer_did: String, amount: Option); +fn initialize(e: Env, admin: Address, issuer_did: String); ``` #### Example: @@ -101,7 +81,7 @@ soroban contract invoke \ ``` ### Verify -Verifies the verifiable credential status, returning a map indicating if it is **valid** or **revoked**. If the status is revoked, it additionally provides the date of revocation. An error will be triggered if the verifiable credential is not registered. +Verifies the verifiable credential status, returning a map indicating if it is **valid**, **revoked** or **invalid**. If the status is revoked, it additionally provides the date of revocation. An error will be triggered if the verifiable credential is not registered. ```rust fn verify(e: Env, vc_id: String) -> Map; @@ -175,6 +155,25 @@ soroban contract invoke \ --date "2023-12-05T21:37:44.389Z" ``` +### Migrate VCs +Migrates the VCs from being stored in a single vector to multiple vectors. + +```rust +fn migrate(e: Env); +``` + +#### Example + +```bash +soroban contract invoke \ + --id CONTRACT_ID \ + --source SOURCE_ACCOUNT_SECRET_KEY \ + --rpc-url https://soroban-testnet.stellar.org:443 \ + --network-passphrase 'Test SDF Network ; September 2015' \ + -- \ + migrate +``` + ### Set contract admin Replaces the current contract admin with a new one. @@ -247,10 +246,8 @@ soroban contract invoke \ | Code | Error | Description | | ---- | ----------------------- | ----------------------------------------------------------------------- | | 1 | `AlreadyInitialized` | Contract has already been initialized | -| 2 | `AmountLimitExceeded` | Provided amount exceeds the maximum allowed | | 3 | `VCNotFound` | Verifiable credential not found | | 4 | `VCAlreadyRevoked` | Verifiable credential already revoked | -| 5 | `IssuanceLimitExceeded` | Contract issuance limit exceeded | ## Development diff --git a/vc_issuance_contract/src/contract.rs b/vc_issuance_contract/src/contract.rs index 3bbf93f..af54b4b 100644 --- a/vc_issuance_contract/src/contract.rs +++ b/vc_issuance_contract/src/contract.rs @@ -1,49 +1,37 @@ +use crate::error::ContractError; use crate::storage; use crate::vc_issuance_trait::VCIssuanceTrait; -use crate::verifiable_credential; -use crate::{error::ContractError, revocation}; +use crate::verifiable_credential::{self, VCStatus}; use soroban_sdk::{ contract, contractimpl, contractmeta, map, panic_with_error, vec, Address, BytesN, Env, - FromVal, Map, String, Symbol, Val, Vec, + FromVal, Map, String, Symbol, Val, }; const VERSION: &str = env!("CARGO_PKG_VERSION"); -const DEFAULT_AMOUNT: u32 = 20; -const MAX_AMOUNT: u32 = 200; contractmeta!( key = "Description", val = "Smart Contract to issue, transfer, verify, and revoke Verifiable Credentials (VCs).", ); +#[allow(dead_code)] #[contract] pub struct VCIssuanceContract; #[contractimpl] impl VCIssuanceTrait for VCIssuanceContract { - fn initialize(e: Env, admin: Address, issuer_did: String, amount: Option) { + fn initialize(e: Env, admin: Address, issuer_did: String) { if storage::has_admin(&e) { panic_with_error!(e, ContractError::AlreadyInitialized); } - if amount.is_some() && amount.unwrap() > MAX_AMOUNT { - panic_with_error!(e, ContractError::AmountLimitExceeded); - } storage::write_admin(&e, &admin); storage::write_issuer_did(&e, &issuer_did); - storage::write_amount(&e, &amount.unwrap_or(DEFAULT_AMOUNT)); - - // set initial empty values - storage::write_vcs(&e, &Vec::new(&e)); - storage::write_vcs_revocations(&e, &Map::new(&e)); } fn issue(e: Env, vc_id: String, vc_data: String, vault_contract: Address) -> String { let admin = validate_admin(&e); - let vcs = storage::read_vcs(&e); - validate_vc_amount(&e, &vcs); - let contract_address = e.current_contract_address(); let issuer_did = storage::read_issuer_did(&e); @@ -58,24 +46,26 @@ impl VCIssuanceTrait for VCIssuanceContract { let store_vc_fn = Symbol::new(&e, "store_vc"); e.invoke_contract::<()>(&vault_contract, &store_vc_fn, store_vc_args); - - verifiable_credential::add_vc(&e, &vc_id, vcs); + storage::write_vc(&e, &vc_id, &VCStatus::Valid); vc_id } fn verify(e: Env, vc_id: String) -> Map { - validate_vc(&e, &vc_id); - let revocations = storage::read_vcs_revocations(&e); + let vc_status = storage::read_vc(&e, &vc_id); let status_str = String::from_str(&e, "status"); let since_str = String::from_str(&e, "since"); let revoked_str = String::from_str(&e, "revoked"); let valid_str = String::from_str(&e, "valid"); - - match revocations.get(vc_id) { - Some(revocation) => map![&e, (status_str, revoked_str), (since_str, revocation.date)], - None => map![&e, (status_str, valid_str)], + let invalid_str = String::from_str(&e, "invalid"); + + match vc_status { + VCStatus::Invalid => map![&e, (status_str, invalid_str)], + VCStatus::Valid => map![&e, (status_str, valid_str)], + VCStatus::Revoked(revocation_date) => { + map![&e, (status_str, revoked_str), (since_str, revocation_date)] + } } } @@ -83,12 +73,35 @@ impl VCIssuanceTrait for VCIssuanceContract { validate_admin(&e); validate_vc(&e, &vc_id); - revocation::revoke_vc(&e, vc_id, date); + verifiable_credential::revoke_vc(&e, vc_id, date); + } + + fn migrate(e: Env) { + validate_admin(&e); + + let vcs = storage::read_old_vcs(&e); + + if vcs.is_none() { + panic_with_error!(e, ContractError::VCSAlreadyMigrated) + } + + let revocations = storage::read_old_revocations(&e); + + for vc_id in vcs.unwrap().iter() { + match revocations.get(vc_id.clone()) { + Some(revocation) => { + storage::write_vc(&e, &vc_id.clone(), &VCStatus::Revoked(revocation.date)) + } + None => storage::write_vc(&e, &vc_id, &VCStatus::Valid), + } + } + + storage::remove_old_vcs(&e); + storage::remove_old_revocations(&e); } fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { - let admin = storage::read_admin(&e); - admin.require_auth(); + validate_admin(&e); e.deployer().update_current_contract_wasm(new_wasm_hash); } @@ -111,17 +124,10 @@ fn validate_admin(e: &Env) -> Address { contract_admin } -fn validate_vc_amount(e: &Env, vcs: &Vec) { - let amount = storage::read_amount(e); - if amount == vcs.len() { - panic_with_error!(e, ContractError::IssuanceLimitExceeded); - } -} - fn validate_vc(e: &Env, vc_id: &String) { - let vcs = storage::read_vcs(e); + let vc_status = storage::read_vc(e, vc_id); - if !vcs.contains(vc_id) { + if vc_status == VCStatus::Invalid { panic_with_error!(e, ContractError::VCNotFound) } } diff --git a/vc_issuance_contract/src/error.rs b/vc_issuance_contract/src/error.rs index 48cda85..771bd52 100644 --- a/vc_issuance_contract/src/error.rs +++ b/vc_issuance_contract/src/error.rs @@ -5,8 +5,7 @@ use soroban_sdk::contracterror; #[repr(u32)] pub enum ContractError { AlreadyInitialized = 1, - AmountLimitExceeded = 2, - VCNotFound = 3, - VCAlreadyRevoked = 4, - IssuanceLimitExceeded = 5, + VCNotFound = 2, + VCAlreadyRevoked = 3, + VCSAlreadyMigrated = 4, } diff --git a/vc_issuance_contract/src/lib.rs b/vc_issuance_contract/src/lib.rs index 322821d..760839f 100644 --- a/vc_issuance_contract/src/lib.rs +++ b/vc_issuance_contract/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] mod contract; mod error; -mod revocation; mod storage; mod vc_issuance_trait; mod verifiable_credential; diff --git a/vc_issuance_contract/src/revocation.rs b/vc_issuance_contract/src/revocation.rs deleted file mode 100644 index 079b1d9..0000000 --- a/vc_issuance_contract/src/revocation.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::error::ContractError; -use crate::storage; -use soroban_sdk::{contracttype, panic_with_error, Env, String}; - -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -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 bf31772..d831e1f 100644 --- a/vc_issuance_contract/src/storage.rs +++ b/vc_issuance_contract/src/storage.rs @@ -1,4 +1,4 @@ -use crate::revocation::Revocation; +use crate::verifiable_credential::VCStatus; use soroban_sdk::{contracttype, Address, Env, Map, String, Vec}; #[derive(Clone)] @@ -6,9 +6,16 @@ use soroban_sdk::{contracttype, Address, Env, Map, String, Vec}; pub enum DataKey { Admin, // Address IssuerDID, // String - Amount, // U32 - VCs, // Vec + VC(String), // VCStatus Revocations, // Map + VCs, // Vec +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Revocation { + pub vc_id: String, + pub date: String, } pub fn has_admin(e: &Env) -> bool { @@ -36,32 +43,35 @@ pub fn write_issuer_did(e: &Env, issuer_did: &String) { e.storage().instance().set(&key, issuer_did); } -pub fn read_amount(e: &Env) -> u32 { - let key = DataKey::Amount; - e.storage().instance().get(&key).unwrap() +pub fn write_vc(e: &Env, vc_id: &String, status: &VCStatus) { + let key = DataKey::VC(vc_id.clone()); + e.storage().persistent().set(&key, status) } -pub fn write_amount(e: &Env, amount: &u32) { - let key = DataKey::Amount; - e.storage().instance().set(&key, amount) +pub fn read_vc(e: &Env, vc_id: &String) -> VCStatus { + let key = DataKey::VC(vc_id.clone()); + e.storage() + .persistent() + .get(&key) + .unwrap_or(VCStatus::Invalid) } -pub fn write_vcs(e: &Env, vc: &Vec) { +pub fn read_old_vcs(e: &Env) -> Option> { let key = DataKey::VCs; - e.storage().persistent().set(&key, vc) + e.storage().persistent().get(&key) } -pub fn read_vcs(e: &Env) -> Vec { +pub fn remove_old_vcs(e: &Env) { let key = DataKey::VCs; - e.storage().persistent().get(&key).unwrap() + e.storage().persistent().remove(&key); } -pub fn write_vcs_revocations(e: &Env, revocations: &Map) { +pub fn read_old_revocations(e: &Env) -> Map { let key = DataKey::Revocations; - e.storage().persistent().set(&key, revocations) + e.storage().persistent().get(&key).unwrap() } -pub fn read_vcs_revocations(e: &Env) -> Map { +pub fn remove_old_revocations(e: &Env) { let key = DataKey::Revocations; - e.storage().persistent().get(&key).unwrap() + e.storage().persistent().remove(&key); } diff --git a/vc_issuance_contract/src/test/contract.rs b/vc_issuance_contract/src/test/contract.rs index c4189d5..8ab47ce 100644 --- a/vc_issuance_contract/src/test/contract.rs +++ b/vc_issuance_contract/src/test/contract.rs @@ -2,50 +2,17 @@ use crate::test::setup::{create_vc, get_revoked_vc_map, get_valid_vc_map, VCIssu use soroban_sdk::{testutils::Address as _, Address, String}; #[test] -fn test_initialize_with_amount() { +fn test_initialize() { let VCIssuanceContractTest { env: _env, admin, - amount, vc_id: _, vc_data: _, issuer_did, contract, } = VCIssuanceContractTest::setup(); - contract.initialize(&admin, &issuer_did, &amount); -} - -#[test] -fn test_initialize_without_amount() { - let VCIssuanceContractTest { - env: _env, - admin, - amount: _, - vc_id: _, - vc_data: _, - issuer_did, - contract, - } = VCIssuanceContractTest::setup(); - - contract.initialize(&admin, &issuer_did, &None); -} - -#[test] -#[should_panic(expected = "HostError: Error(Contract, #2)")] -fn test_initialize_with_too_high_amount() { - let VCIssuanceContractTest { - env: _env, - admin, - amount: _, - vc_id: _, - vc_data: _, - issuer_did, - contract, - } = VCIssuanceContractTest::setup(); - let high_amount = Some(201); - - contract.initialize(&admin, &issuer_did, &high_amount); + contract.initialize(&admin, &issuer_did); } #[test] @@ -54,15 +21,14 @@ fn test_initialize_an_already_initialized_contract() { let VCIssuanceContractTest { env: _env, admin, - amount, vc_id: _, vc_data: _, issuer_did, contract, } = VCIssuanceContractTest::setup(); - contract.initialize(&admin, &issuer_did, &amount); - contract.initialize(&admin, &issuer_did, &amount); + contract.initialize(&admin, &issuer_did); + contract.initialize(&admin, &issuer_did); } #[test] @@ -70,31 +36,13 @@ fn test_issue() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id, vc_data, issuer_did, contract, } = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); - contract.issue(&vc_id, &vc_data, &vault_contract_id); -} - -#[test] -#[should_panic(expected = "HostError: Error(Contract, #5)")] -fn test_issue_when_amount_is_exceeded() { - let VCIssuanceContractTest { - env, - admin, - amount: _, - vc_id, - vc_data, - issuer_did, - contract, - }: VCIssuanceContractTest<'_> = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &Some(1)); - contract.issue(&vc_id, &vc_data, &vault_contract_id); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did); contract.issue(&vc_id, &vc_data, &vault_contract_id); } @@ -103,13 +51,12 @@ fn test_revoke_vc() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id, vc_data, issuer_did, contract, } = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did); let vc_id = contract.issue(&vc_id, &vc_data, &vault_contract_id); let date = String::from_str(&env, "2023-12-05T21:37:44.389Z"); @@ -118,18 +65,17 @@ fn test_revoke_vc() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #3)")] +#[should_panic(expected = "HostError: Error(Contract, #2)")] fn test_revoke_vc_with_invalid_vc() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id: _, vc_data: _, issuer_did, contract, } = VCIssuanceContractTest::setup(); - contract.initialize(&admin, &issuer_did, &Some(10)); + contract.initialize(&admin, &issuer_did); let vc_id = String::from_str(&env, "vc_id1"); let date = String::from_str(&env, "2023-12-05T21:37:44.389Z"); @@ -138,18 +84,17 @@ fn test_revoke_vc_with_invalid_vc() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #4)")] +#[should_panic(expected = "HostError: Error(Contract, #3)")] fn test_revoke_vc_when_it_was_already_revoked() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id, vc_data, issuer_did, contract, } = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did); let vc_id = contract.issue(&vc_id, &vc_data, &vault_contract_id); let date_1 = String::from_str(&env, "2023-12-05T21:37:44.389Z"); @@ -164,13 +109,12 @@ fn test_verify_vc() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id, vc_data, issuer_did, contract, } = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did); let vc_id = contract.issue(&vc_id, &vc_data, &vault_contract_id); let valid_vc_map = get_valid_vc_map(&env); @@ -182,13 +126,12 @@ fn test_verify_vc_with_revoked_vc() { let VCIssuanceContractTest { env, admin, - amount: _, vc_id, vc_data, issuer_did, contract, } = VCIssuanceContractTest::setup(); - let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did, &None); + let vault_contract_id = create_vc(&env, &admin, &contract, &issuer_did); let vc_id = contract.issue(&vc_id, &vc_data, &vault_contract_id); let date = String::from_str(&env, "2023-12-05T21:37:44.389Z"); @@ -198,19 +141,34 @@ fn test_verify_vc_with_revoked_vc() { assert_eq!(contract.verify(&vc_id), revoked_vc_map) } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #4)")] +fn test_migrate_should_fail_without_vcs() { + let VCIssuanceContractTest { + env: _, + admin, + vc_id: _, + vc_data: _, + issuer_did, + contract, + } = VCIssuanceContractTest::setup(); + + contract.initialize(&admin, &issuer_did); + contract.migrate(); +} + #[test] fn test_set_admin() { let VCIssuanceContractTest { env, admin, - amount, vc_id: _, vc_data: _, issuer_did, contract, } = VCIssuanceContractTest::setup(); - contract.initialize(&admin, &issuer_did, &amount); + contract.initialize(&admin, &issuer_did); let new_admin = Address::generate(&env); @@ -222,7 +180,6 @@ fn test_version() { let VCIssuanceContractTest { env, admin: _, - amount: _, vc_id: _, vc_data: _, issuer_did: _, diff --git a/vc_issuance_contract/src/test/setup.rs b/vc_issuance_contract/src/test/setup.rs index e1c67a6..79a93ae 100644 --- a/vc_issuance_contract/src/test/setup.rs +++ b/vc_issuance_contract/src/test/setup.rs @@ -20,7 +20,6 @@ mod vault_contract { pub struct VCIssuanceContractTest<'a> { pub env: Env, pub admin: Address, - pub amount: Option, pub vc_id: String, pub vc_data: String, pub issuer_did: String, @@ -34,7 +33,6 @@ impl<'a> VCIssuanceContractTest<'a> { let admin = Address::generate(&env); let contract = VCIssuanceContractClient::new(&env, &env.register_contract(None, VCIssuanceContract)); - let amount = Some(10); let vc_id = String::from_str(&env, "iwvkdjquj3fscmafrgeeqblw"); let vc_data = String::from_str(&env, "eoZXggNeVDW2g5GeA0G2s0QJBn3SZWzWSE3fXM9V6IB5wWIfFJRxPrTLQRMHulCF62bVQNmZkj7zbSa39fVjAUTtfm6JMio75uMxoDlAN/Y"); let issuer_did = String::from_str(&env, "did:chaincerts:7dotwpyzo2weqj6oto6liic6"); @@ -42,7 +40,6 @@ impl<'a> VCIssuanceContractTest<'a> { VCIssuanceContractTest { env, admin, - amount, vc_id, vc_data, issuer_did, @@ -56,7 +53,6 @@ pub fn create_vc( admin: &Address, contract: &VCIssuanceContractClient, issuer_did: &String, - amount: &Option, ) -> Address { let vault_admin = Address::generate(env); let vault_contract_address = env.register_contract_wasm(None, vault_contract::WASM); @@ -69,7 +65,7 @@ pub fn create_vc( vault_client.initialize(&vault_admin, &did_wasm_hash, &did_init_args, &salt); vault_client.authorize_issuer(admin); - contract.initialize(admin, issuer_did, amount); + contract.initialize(admin, issuer_did); vault_contract_address } diff --git a/vc_issuance_contract/src/vc_issuance_trait.rs b/vc_issuance_contract/src/vc_issuance_trait.rs index d580c29..7fb318c 100644 --- a/vc_issuance_contract/src/vc_issuance_trait.rs +++ b/vc_issuance_contract/src/vc_issuance_trait.rs @@ -1,8 +1,9 @@ use soroban_sdk::{Address, BytesN, Env, Map, String}; +#[allow(dead_code)] pub trait VCIssuanceTrait { - /// Initializes the Verifiable Credentials Issuance Contract by setting the admin, the issuer_did and an optional amount. - fn initialize(e: Env, admin: Address, issuer_did: String, amount: Option); + /// Initializes the Verifiable Credentials Issuance Contract by setting the admin and the issuer_did. + fn initialize(e: Env, admin: Address, issuer_did: String); /// Issues a new Verifiable Credential and returns the Verifiable Credential id fn issue(e: Env, vc_id: String, vc_data: String, vault_contract: Address) -> String; @@ -16,6 +17,9 @@ pub trait VCIssuanceTrait { /// Sets the new contract admin. fn set_admin(e: Env, new_admin: Address); + /// Migrates the VCs from being stored in a single vector to multiple vectors. + fn migrate(e: Env); + /// Upgrades WASM code. fn upgrade(e: Env, new_wasm_hash: BytesN<32>); diff --git a/vc_issuance_contract/src/verifiable_credential.rs b/vc_issuance_contract/src/verifiable_credential.rs index 04aa5b5..a5e0bab 100644 --- a/vc_issuance_contract/src/verifiable_credential.rs +++ b/vc_issuance_contract/src/verifiable_credential.rs @@ -1,9 +1,20 @@ -use soroban_sdk::{Env, String, Vec}; - +use crate::error::ContractError; use crate::storage; +use soroban_sdk::{contracttype, panic_with_error, Env, String}; + +#[derive(PartialEq)] +#[contracttype] +pub enum VCStatus { + Valid, + Invalid, + Revoked(String), +} -pub fn add_vc(e: &Env, vc_id: &String, mut vcs: Vec) { - vcs.push_front(vc_id.clone()); +pub fn revoke_vc(e: &Env, vc_id: String, date: String) { + let vc_status = storage::read_vc(e, &vc_id); - storage::write_vcs(e, &vcs); + if vc_status != VCStatus::Valid { + panic_with_error!(e, ContractError::VCAlreadyRevoked) + } + storage::write_vc(e, &vc_id, &VCStatus::Revoked(date)) }