From 224f6191bf8c65220b1e1cc735fc0df38eecdf82 Mon Sep 17 00:00:00 2001 From: Ozgun Ozerk Date: Sat, 1 Nov 2025 13:50:11 +0300 Subject: [PATCH 1/4] doc recommendations by stellar --- .../access/access-control.mdx | 118 ++++++++++++++++++ content/stellar-contracts/utils/pausable.mdx | 23 ++++ 2 files changed, 141 insertions(+) diff --git a/content/stellar-contracts/access/access-control.mdx b/content/stellar-contracts/access/access-control.mdx index a1fdde80..7d8f7828 100644 --- a/content/stellar-contracts/access/access-control.mdx +++ b/content/stellar-contracts/access/access-control.mdx @@ -31,6 +31,52 @@ The module supports a hierarchical role system where each role can have an "admi This allows for creating complex organizational structures with chains of command and delegated authority. +#### Setting Up Role Hierarchies + +Here's how to establish and use role hierarchies in practice: + +```rust +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env}; +use stellar_access::access_control::{self as access_control, AccessControl}; + +#[contract] +pub struct MyContract; + +#[contractimpl] +impl MyContract { + pub fn __constructor(e: &Env, admin: Address) { + // Set the contract admin + access_control::set_admin(e, &admin); + + // Define role hierarchy: MANAGER_ROLE can manage USER_ROLE + let manager_role = symbol_short!("manager"); + let user_role = symbol_short!("user"); + + // Set MANAGER_ROLE as the admin role for USER_ROLE + access_control::set_role_admin(e, &admin, &user_role, &manager_role); + } + + pub fn setup_roles(e: &Env, admin: Address, manager: Address, user: Address) { + let manager_role = symbol_short!("manager"); + let user_role = symbol_short!("user"); + + // Admin grants MANAGER_ROLE to the manager account + access_control::grant_role(e, &admin, &manager, &manager_role); + + // Now the manager can grant USER_ROLE to other accounts + access_control::grant_role(e, &manager, &user, &user_role); + + // Manager can also revoke USER_ROLE + access_control::revoke_role(e, &manager, &user, &user_role); + } +} +``` + +In this example: +1. The contract admin sets `manager` as the admin role for `user` using `set_role_admin()` +2. The admin grants the `manager` role to a manager account +3. The manager can now grant/revoke the `user` role to other accounts without requiring admin intervention + ### Role Enumeration The system tracks account-role pairs in storage with additional enumeration logic: @@ -41,6 +87,78 @@ The system tracks account-role pairs in storage with additional enumeration logi Roles exist only through their relationships with accounts, so a role with zero accounts is indistinguishable from a role that never existed. +## Procedural Macros + +The module includes several procedural macros to simplify authorization checks in your contract functions. These macros are divided into two categories: + +### Authorization-Enforcing Macros + +These macros automatically call `require_auth()` on the specified account before executing the function: + +#### @only_admin + +Restricts access to the contract admin only: + +```rust +#[only_admin] +pub fn admin_function(e: &Env) { + // Only the admin can call this function + // require_auth() is automatically called +} +``` + +#### @only_role + +Restricts access to accounts with a specific role: + +```rust +#[only_role(caller, "minter")] +pub fn mint(e: &Env, caller: Address, to: Address, token_id: u32) { + // Only accounts with the "minter" role can call this + // require_auth() is automatically called on caller +} +``` + +#### @only_any_role + +Restricts access to accounts with any of the specified roles: + +```rust +#[only_any_role(caller, ["minter", "burner"])] +pub fn multi_role_action(e: &Env, caller: Address) { + // Accounts with either "minter" or "burner" role can call this + // require_auth() is automatically called on caller +} +``` + +### Role-Checking Macros + +These macros check role membership but do **not** enforce authorization. You must manually call `require_auth()` if needed: + +#### @has_role + +Checks if an account has a specific role: + +```rust +#[has_role(caller, "minter")] +pub fn conditional_mint(e: &Env, caller: Address, to: Address, token_id: u32) { + // Checks if caller has "minter" role, but doesn't call require_auth() + caller.require_auth(); // Must manually authorize if needed +} +``` + +#### @has_any_role + +Checks if an account has any of the specified roles: + +```rust +#[has_any_role(caller, ["minter", "burner"])] +pub fn multi_role_check(e: &Env, caller: Address) { + // Checks if caller has either role, but doesn't call require_auth() + caller.require_auth(); // Must manually authorize if needed +} +``` + ## Usage Example Here’s a simple example of using the Access Control module: diff --git a/content/stellar-contracts/utils/pausable.mdx b/content/stellar-contracts/utils/pausable.mdx index a462ba09..fc741f70 100644 --- a/content/stellar-contracts/utils/pausable.mdx +++ b/content/stellar-contracts/utils/pausable.mdx @@ -31,3 +31,26 @@ pub fn emergency_reset(e: &Env) { e.storage().instance().set(&DataKey::Counter, &0); } ``` + +## Implementing Pausable Trait + +```rust +#[contractimpl] +impl Pausable for ExampleContract { + fn paused(e: &Env) -> bool { + pausable::paused(e) + } + + #[only_owner] + fn pause(e: &Env, _caller: Address) { + pausable::pause(e); + } + + #[only_owner] + fn unpause(e: &Env, _caller: Address) { + pausable::unpause(e); + } +} +``` + + From 590d67117a7de6837495b3775e752a881fdb7cce Mon Sep 17 00:00:00 2001 From: brozorec <9572072+brozorec@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:10:34 +0100 Subject: [PATCH 2/4] role hierarchy with guardians --- .../access/access-control.mdx | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/content/stellar-contracts/access/access-control.mdx b/content/stellar-contracts/access/access-control.mdx index 7d8f7828..092b8d9d 100644 --- a/content/stellar-contracts/access/access-control.mdx +++ b/content/stellar-contracts/access/access-control.mdx @@ -36,46 +36,47 @@ This allows for creating complex organizational structures with chains of comman Here's how to establish and use role hierarchies in practice: ```rust -use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env}; +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, Symbol}; use stellar_access::access_control::{self as access_control, AccessControl}; +const MANAGER_ROLE: Symbol = symbol_short!("manager"); +const GUARDIAN_ROLE: Symbol = symbol_short!("guardian"); + #[contract] pub struct MyContract; #[contractimpl] impl MyContract { - pub fn __constructor(e: &Env, admin: Address) { + pub fn __constructor(e: &Env, admin: Address, manager: Address) { // Set the contract admin access_control::set_admin(e, &admin); - // Define role hierarchy: MANAGER_ROLE can manage USER_ROLE - let manager_role = symbol_short!("manager"); - let user_role = symbol_short!("user"); - - // Set MANAGER_ROLE as the admin role for USER_ROLE - access_control::set_role_admin(e, &admin, &user_role, &manager_role); + // 1. Set MANAGER_ROLE as the admin role for GUARDIAN_ROLE: + // accounts with MANAGER_ROLE can manage accounts with GUARDIAN_ROLE + access_control::set_role_admin_no_auth(e, &admin, &GUARDIAN_ROLE, &MANAGER_ROLE); + + // 2. Admin grants MANAGER_ROLE to the manager account + access_control::grant_role_no_auth(e, &admin, &manager, &MANAGER_ROLE); } - - pub fn setup_roles(e: &Env, admin: Address, manager: Address, user: Address) { - let manager_role = symbol_short!("manager"); - let user_role = symbol_short!("user"); - - // Admin grants MANAGER_ROLE to the manager account - access_control::grant_role(e, &admin, &manager, &manager_role); + + pub fn manage_guardians(e: &Env, manager: Address, guardian1: Address, guardian2: Address) { + // Manager must be authorized + manager.require_auth(); - // Now the manager can grant USER_ROLE to other accounts - access_control::grant_role(e, &manager, &user, &user_role); + // 3. Now the manager can grant GUARDIAN_ROLE to other accounts + access_control::grant_role_no_auth(e, &manager, &guardian1, &GUARDIAN_ROLE); + access_control::grant_role_no_auth(e, &manager, &guardian2, &GUARDIAN_ROLE); - // Manager can also revoke USER_ROLE - access_control::revoke_role(e, &manager, &user, &user_role); + // Manager can also revoke GUARDIAN_ROLE + access_control::revoke_role_no_auth(e, &manager, &guardian1, &GUARDIAN_ROLE); } } ``` In this example: -1. The contract admin sets `manager` as the admin role for `user` using `set_role_admin()` -2. The admin grants the `manager` role to a manager account -3. The manager can now grant/revoke the `user` role to other accounts without requiring admin intervention +1. The `admin` sets `MANAGER_ROLE` as the admin role for `GUARDIAN_ROLE` using `set_role_admin()` +2. The `admin` grants the `MANAGER_ROLE` role to the `manager` account +3. The `manager` can now grant/revoke the `GUARDIAN_ROLE` role to other accounts without requiring admin intervention ### Role Enumeration From 5ded462fa0edd1096d3aab6a49be2e2a24331ccc Mon Sep 17 00:00:00 2001 From: brozorec <9572072+brozorec@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:40:02 +0100 Subject: [PATCH 3/4] surface merkle distributor --- content/stellar-contracts/utils/crypto.mdx | 201 ------------------ .../stellar-contracts/utils/crypto/crypto.mdx | 109 ++++++++++ .../utils/crypto/merkle-distributor.mdx | 109 ++++++++++ src/navigation/stellar.json | 15 +- 4 files changed, 231 insertions(+), 203 deletions(-) delete mode 100644 content/stellar-contracts/utils/crypto.mdx create mode 100644 content/stellar-contracts/utils/crypto/crypto.mdx create mode 100644 content/stellar-contracts/utils/crypto/merkle-distributor.mdx diff --git a/content/stellar-contracts/utils/crypto.mdx b/content/stellar-contracts/utils/crypto.mdx deleted file mode 100644 index b50af954..00000000 --- a/content/stellar-contracts/utils/crypto.mdx +++ /dev/null @@ -1,201 +0,0 @@ ---- -title: Cryptography Utilities ---- - -[Crypto Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/crypto) | -[Merkle Distributor Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/merkle-distributor) - -## Overview - -The Cryptography Utilities provide a set of cryptographic tools for Soroban smart contracts, -including hash functions, Merkle tree verification, and Merkle-based distribution systems. -These utilities enable secure data verification and efficient token distribution mechanisms. -The Cryptography Utilities consist of two main packages: - -* Crypto: A set of cryptographic primitives and utilities for Soroban contracts. -* Merkle Distributor: A system for distributing tokens or other assets using Merkle proofs for verification. - -## Crypto Package - -The crypto package provides fundamental cryptographic primitives and utilities for Soroban contracts, -with a focus on hashing and Merkle tree operations. - -### Key Components - -#### Hashers - -Provides a generic `Hasher` trait and implementations for common hash functions: - -* `Sha256`: Implementation of the SHA-256 hash function -* `Keccak256`: Implementation of the Keccak-256 hash function (used in Ethereum) - -Each hasher follows the same interface: - -```rust -pub trait Hasher { - type Output; - - fn new(e: &Env) -> Self; - fn update(&mut self, input: Bytes); - fn finalize(self) -> Self::Output; -} -``` - -#### Hashable - -The `Hashable` trait allows types to be hashed with any `Hasher` implementation: - -```rust -pub trait Hashable { - fn hash(&self, hasher: &mut H); -} -``` - -Built-in implementations are provided for `BytesN<32>` and `Bytes`. - -#### Utility Functions - -* `hash_pair`: Hashes two values together -* `commutative_hash_pair`: Hashes two values in a deterministic order (important for Merkle trees) - -#### Merkle Tree Verification - -The `Verifier` struct provides functionality to verify Merkle proofs: - -```rust -impl Verifier -where - H: Hasher, -{ - pub fn verify(e: &Env, proof: Vec, root: Bytes32, leaf: Bytes32) -> bool { - // Implementation verifies that the leaf is part of the tree defined by root - } -} -``` - -### Usage Examples - -#### Hashing Data - -```rust -use soroban_sdk::{Bytes, Env}; -use stellar_contract_utils::crypto::keccak::Keccak256; -use stellar_contract_utils::crypto::hasher::Hasher; - -// Hash some data with Keccak256 -let e = Env::default(); -let data = Bytes::from_slice(&e, "Hello, world!".as_bytes()); - -let mut hasher = Keccak256::new(&e); -hasher.update(data); -let hash = hasher.finalize(); -``` - -#### Verifying a Merkle Proof - -```rust -use soroban_sdk::{BytesN, Env, Vec}; -use stellar_crypto::keccak::Keccak256; -use stellar_crypto::merkle::Verifier; - -// Verify that a leaf is part of a Merkle tree -let e = Env::default(); -let root = /* merkle root as BytesN<32> */; -let leaf = /* leaf to verify as BytesN<32> */; -let proof = /* proof as Vec> */; - -let is_valid = Verifier::::verify(&e, proof, root, leaf); -``` - -## Merkle Distributor - -The Merkle Distributor package builds on the crypto package to provide a system for distributing tokens or -other assets using Merkle proofs for verification. - -### Key Concepts - -#### IndexableLeaf - -The `IndexableLeaf` trait defines the structure for nodes in the Merkle tree: - -```rust -pub trait IndexableLeaf { - fn index(&self) -> u32; -} -``` - -Each node must include a unique index that identifies its position in the Merkle tree. - -#### MerkleDistributor - -The `MerkleDistributor` struct provides functionality for: - -* Setting a Merkle root -* Checking if an index has been claimed -* Verifying proofs and marking indices as claimed - -### Usage Example - -```rust -use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; -use stellar_contract_utils::crypto::keccak::Keccak256; -use stellar_contract_utils::merkle_distributor::{IndexableLeaf, MerkleDistributor}; - -// Define a leaf node structure -#[contracttype] -struct LeafData { - pub index: u32, - pub address: Address, - pub amount: i128, -} - -// Implement IndexableLeaf for the leaf structure -impl IndexableLeaf for LeafData { - fn index(&self) -> u32 { - self.index - } -} - -#[contract] -pub struct TokenDistributor; - -#[contractimpl] -impl TokenDistributor { - // Initialize the distributor with a Merkle root - pub fn initialize(e: &Env, root: BytesN<32>) { - MerkleDistributor::::set_root(e, root); - } - - // Claim tokens by providing a proof - pub fn claim(e: &Env, leaf: LeafData, proof: Vec>) { - // Verify the proof and mark as claimed - MerkleDistributor::::verify_and_set_claimed(e, leaf.clone(), proof); - - // Transfer tokens or perform other actions based on leaf data - // ... - } - - // Check if an index has been claimed - pub fn is_claimed(e: &Env, index: u32) -> bool { - MerkleDistributor::::is_claimed(e, index) - } -} -``` - -## Use Cases - -### Token Airdrops - -Efficiently distribute tokens to a large number of recipients without requiring individual transactions for each recipient. - -### NFT Distributions - -Distribute NFTs to a whitelist of addresses, with each address potentially receiving different NFTs. - -### Off-chain Allowlists - -Maintain a list of eligible addresses off-chain and allow them to claim tokens or other assets on-chain. - -### Snapshot-based Voting - -Create a snapshot of token holders at a specific block and allow them to vote based on their holdings. diff --git a/content/stellar-contracts/utils/crypto/crypto.mdx b/content/stellar-contracts/utils/crypto/crypto.mdx new file mode 100644 index 00000000..bf3cf746 --- /dev/null +++ b/content/stellar-contracts/utils/crypto/crypto.mdx @@ -0,0 +1,109 @@ +--- +title: Cryptography Utilities +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/crypto) + +## Overview + +The Cryptography Utilities provide a set of cryptographic tools for Soroban smart contracts, +including hash functions, Merkle tree verification, and Merkle-based distribution systems. +These utilities enable secure data verification and efficient token distribution mechanisms. +The Cryptography Utilities consist of two main packages: + +* Crypto: A set of cryptographic primitives and utilities for Soroban contracts. +* [Merkle Distributor](./merkle-distributor): A system for distributing tokens or other assets using Merkle proofs for verification. + +## Crypto Package + +The crypto package provides fundamental cryptographic primitives and utilities for Soroban contracts, +with a focus on hashing and Merkle tree operations. + +### Key Components + +#### Hashers + +Provides a generic `Hasher` trait and implementations for common hash functions: + +* `Sha256`: Implementation of the SHA-256 hash function +* `Keccak256`: Implementation of the Keccak-256 hash function (used in Ethereum) + +Each hasher follows the same interface: + +```rust +pub trait Hasher { + type Output; + + fn new(e: &Env) -> Self; + fn update(&mut self, input: Bytes); + fn finalize(self) -> Self::Output; +} +``` + +#### Hashable + +The `Hashable` trait allows types to be hashed with any `Hasher` implementation: + +```rust +pub trait Hashable { + fn hash(&self, hasher: &mut H); +} +``` + +Built-in implementations are provided for `BytesN<32>` and `Bytes`. + +#### Utility Functions + +* `hash_pair`: Hashes two values together +* `commutative_hash_pair`: Hashes two values in a deterministic order (important for Merkle trees) + +#### Merkle Tree Verification + +The `Verifier` struct provides functionality to verify Merkle proofs: + +```rust +impl Verifier +where + H: Hasher, +{ + pub fn verify(e: &Env, proof: Vec, root: Bytes32, leaf: Bytes32) -> bool { + // Implementation verifies that the leaf is part of the tree defined by root + } +} +``` + +### Usage Examples + +#### Hashing Data + +```rust +use soroban_sdk::{Bytes, Env}; +use stellar_contract_utils::crypto::keccak::Keccak256; +use stellar_contract_utils::crypto::hasher::Hasher; + +// Hash some data with Keccak256 +let e = Env::default(); +let data = Bytes::from_slice(&e, "Hello, world!".as_bytes()); + +let mut hasher = Keccak256::new(&e); +hasher.update(data); +let hash = hasher.finalize(); +``` + +#### Verifying a Merkle Proof + +```rust +use soroban_sdk::{BytesN, Env, Vec}; +use stellar_crypto::keccak::Keccak256; +use stellar_crypto::merkle::Verifier; + +// Verify that a leaf is part of a Merkle tree +let e = Env::default(); +let root = /* merkle root as BytesN<32> */; +let leaf = /* leaf to verify as BytesN<32> */; +let proof = /* proof as Vec> */; + +let is_valid = Verifier::::verify(&e, proof, root, leaf); +``` + + diff --git a/content/stellar-contracts/utils/crypto/merkle-distributor.mdx b/content/stellar-contracts/utils/crypto/merkle-distributor.mdx new file mode 100644 index 00000000..75a28e7d --- /dev/null +++ b/content/stellar-contracts/utils/crypto/merkle-distributor.mdx @@ -0,0 +1,109 @@ +--- +title: Merkle Distributor +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/merkle-distributor) + +The Merkle Distributor package builds on the [crypto package](./crypto) to provide a system for distributing tokens or other assets using Merkle proofs for verification. + +## Key Components + +### IndexableLeaf + +The `IndexableLeaf` trait defines the interface that leaf nodes must implement to be used with the Merkle Distributor: + +```rust +pub trait IndexableLeaf { + fn index(&self) -> u32; +} +``` + +Each leaf in the merkle tree requires a unique index to track whether it has been claimed to prevent double-claiming. The custom leaf struct should implement this trait by returning a unique `u32` value for each recipient. This index serves as the key for storing claim status on-chain. + +### MerkleDistributor + +The `MerkleDistributor` struct is a generic component that manages the verification and claiming process. It is parameterized by a hash function (e.g. `Keccak256`) and provides the following core functionality: + +#### Setting the Merkle Root + +`set_root(env: &Env, root: BytesN<32>)` stores the Merkle tree root on-chain. This is typically called once during contract initialization or when updating the distribution list. + +#### Checking Claim Status + +`is_claimed(env: &Env, index: u32) -> bool` queries whether a specific index has already been claimed. This allows you to check claim status before attempting verification. + +#### Verifying and Claiming + +`verify_and_set_claimed(env: &Env, leaf: impl IndexableLeaf, proof: Vec>)` performs two operations atomically: + +1. **Verification**: Validates that the provided leaf and proof correctly reconstruct the stored Merkle root +2. **Claiming**: Marks the leaf's index as claimed to prevent duplicate claims + +This function will panic if the proof is invalid or if the index has already been claimed, ensuring the integrity of the distribution process. + +## Usage Example + +```rust +use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; +use stellar_contract_utils::crypto::keccak::Keccak256; +use stellar_contract_utils::merkle_distributor::{IndexableLeaf, MerkleDistributor}; + +// Define a leaf node structure +#[contracttype] +struct LeafData { + pub index: u32, + pub address: Address, + pub amount: i128, +} + +// Implement IndexableLeaf for the leaf structure +impl IndexableLeaf for LeafData { + fn index(&self) -> u32 { + self.index + } +} + +#[contract] +pub struct TokenDistributor; + +#[contractimpl] +impl TokenDistributor { + // Initialize the distributor with a Merkle root + pub fn initialize(e: &Env, root: BytesN<32>) { + MerkleDistributor::::set_root(e, root); + } + + // Claim tokens by providing a proof + pub fn claim(e: &Env, leaf: LeafData, proof: Vec>) { + // Verify the proof and mark as claimed + MerkleDistributor::::verify_and_set_claimed(e, leaf.clone(), proof); + + // Transfer tokens or perform other actions based on leaf data + // ... + } + + // Check if an index has been claimed + pub fn is_claimed(e: &Env, index: u32) -> bool { + MerkleDistributor::::is_claimed(e, index) + } +} +``` + +## Use Cases + +### Token Airdrops + +Efficiently distribute tokens to a large number of recipients without requiring individual transactions for each recipient. + +### NFT Distributions + +Distribute NFTs to a whitelist of addresses, with each address potentially receiving different NFTs. + +### Off-chain Allowlists + +Maintain a list of eligible addresses off-chain and allow them to claim tokens or other assets on-chain. + +### Snapshot-based Voting + +Create a snapshot of token holders at a specific block and allow them to vote based on their holdings. + diff --git a/src/navigation/stellar.json b/src/navigation/stellar.json index 6387c3d4..0a7dcb1c 100644 --- a/src/navigation/stellar.json +++ b/src/navigation/stellar.json @@ -60,9 +60,20 @@ "url": "/stellar-contracts/utils/upgradeable" }, { - "type": "page", + "type": "folder", "name": "Cryptography", - "url": "/stellar-contracts/utils/crypto" + "index": { + "type": "page", + "name": "Cryptography", + "url": "/stellar-contracts/utils/crypto/crypto" + }, + "children": [ + { + "type": "page", + "name": "Merkle Distributor", + "url": "/stellar-contracts/utils/crypto/merkle-distributor" + } + ] } ] }, From 24067fb30ed90e6f30c78b83a81c4e7fa8c82e0c Mon Sep 17 00:00:00 2001 From: brozorec <9572072+brozorec@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:30:01 +0100 Subject: [PATCH 4/4] fix link --- content/stellar-contracts/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/stellar-contracts/index.mdx b/content/stellar-contracts/index.mdx index bb0c5382..6d5438d1 100644 --- a/content/stellar-contracts/index.mdx +++ b/content/stellar-contracts/index.mdx @@ -18,7 +18,7 @@ Explore our comprehensive suite of secure and scalable smart contract utilities * **[Pausable](/stellar-contracts/utils/pausable)**: Pause and unpause contract functions, useful for emergency response. * **[Upgradeable](/stellar-contracts/utils/upgradeable)**: Manage contract upgrades and data migrations seamlessly. -* **[Cryptography](/stellar-contracts/utils/crypto)**: A set of cryptographic primitives and utilities for Soroban contracts. +* **[Cryptography](/stellar-contracts/utils/crypto/crypto)**: A set of cryptographic primitives and utilities for Soroban contracts. ## Security and Audits