diff --git a/README.md b/README.md index 0ceb0a0..9f4d0ee 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ The P-Assets Standard is a framework for issuing and managing permissioned balan - **Permissioned Transfers**: All transfers must go through chests and be approved by custom transfer rules - **Chest-Based Architecture**: Tokens can only be held in chests, with automatic balance tracking -- **Flexible Rules System**: Each token type has associated rules that govern transfers with jurisdiction-specific compliance +- **Flexible Policies System**: Each token type has associated rules that govern transfers with jurisdiction-specific compliance - **Optional Clawback**: Regulatory compliance feature that allows token recovery when legally required ## How It Works 1. **Setup**: Registry is created as a shared object, token issuers register their tokens with rules 2. **Chest Creation**: Chests are derived for each address that needs to hold tokens -3. **Transfers**: Initiated from source chest, creating a transfer request that must be resolved by the rule +3. **Transfers**: Initiated from source chest, creating a transfer request that must be resolved by the policy 4. **Resolution**: Token-specific smart contracts validate and approve transfers based on compliance rules ## Wallet & SDK Integration @@ -35,12 +35,12 @@ The P-Assets Standard is a framework for issuing and managing permissioned balan ### Simple Discovery The standard uses derived objects for predictable addresses: - **Single chest per user** which holds the balances of the user -- **No indexing required** - chest and rule addresses are deterministically computable +- **No indexing required** - chest and policy addresses are deterministically computable - **One query** to see all user balances via dynamic fields on their chest ### Easy Resolution -Each rule contains `Command` instructions that tell SDKs exactly how to resolve transfers - no need to understand complex on-chain logic. SDKs simply read the command and construct the appropriate transaction. +Each policy contains `Command` instructions that tell SDKs exactly how to resolve transfers - no need to understand complex on-chain logic. SDKs simply read the command and construct the appropriate transaction. ## Security Features diff --git a/packages/pas/sources/chest.move b/packages/pas/sources/chest.move index 28bc9d1..9c823b9 100644 --- a/packages/pas/sources/chest.move +++ b/packages/pas/sources/chest.move @@ -66,7 +66,7 @@ public fun create_and_share(namespace: &mut Namespace, owner: address) { } /// Enables a fund unlock flow. -/// This is useful for assets that are not managed by a Rule within the system, or +/// This is useful for assets that are not managed by a Policy within the system, or /// if there's a special case where an issuer allows balances to flow out of the system. public fun unlock_funds( chest: &mut Chest, @@ -95,7 +95,7 @@ public fun transfer_funds( /// Initiate a clawback request for an amount of funds. /// This takes no `Auth`, as it's an admin action. /// -/// This can only ever finalize if clawback is enabled in the rule. +/// This can only ever finalize if clawback is enabled in the policy. public fun clawback_funds( from: &mut Chest, amount: u64, diff --git a/packages/pas/sources/keys.move b/packages/pas/sources/keys.move index 7f2ea7e..8fa3184 100644 --- a/packages/pas/sources/keys.move +++ b/packages/pas/sources/keys.move @@ -3,8 +3,8 @@ module pas::keys; use std::string::String; use sui::vec_set::{Self, VecSet}; -/// Key for deriving `Rule` from the namespace -public struct RuleKey() has copy, drop, store; +/// Key for deriving `Policy` from the namespace +public struct PolicyKey() has copy, drop, store; /// Key for deriving `Chest` from the namespace public struct ChestKey(address) has copy, drop, store; @@ -13,7 +13,7 @@ public struct ChestKey(address) has copy, drop, store; public struct TemplateKey() has copy, drop, store; /// WARNING: these should only be used internally. -public(package) fun rule_key(): RuleKey { RuleKey() } +public(package) fun policy_key(): PolicyKey { PolicyKey() } public(package) fun chest_key(owner: address): ChestKey { ChestKey(owner) } diff --git a/packages/pas/sources/namespace.move b/packages/pas/sources/namespace.move index 7c6dffe..c621780 100644 --- a/packages/pas/sources/namespace.move +++ b/packages/pas/sources/namespace.move @@ -2,7 +2,7 @@ /// /// Namespace is responsible for creating objects that are easy to query & find: /// 1. Chests -/// 2. Rules +/// 2. Policies /// ... any other module we might add in the future module pas::namespace; @@ -19,7 +19,7 @@ const EUpgradeCapPackageMismatch: vector = const EUpgradeCapNotSet: vector = b"The upgrade cap is not set for this namespace, making it unusable."; -/// The namespace is only used for address derivation of chests, rules, etc. +/// The namespace is only used for address derivation of chests, policies, etc. /// /// Namespace is a singleton -- there's one global version for it. public struct Namespace has key { @@ -69,14 +69,14 @@ public fun unblock_version(namespace: &mut Namespace, cap: &UpgradeCap, version: namespace.versioning.unblock_version(version); } -/// Check if `Rule` exists in the namespace -public fun rule_exists(namespace: &Namespace): bool { - derived_object::exists(&namespace.id, keys::rule_key()) +/// Check if `Policy` exists in the namespace +public fun policy_exists(namespace: &Namespace): bool { + derived_object::exists(&namespace.id, keys::policy_key()) } -/// The derived address for `Rule` -public fun rule_address(namespace: &Namespace): address { - derived_object::derive_address(namespace.id.to_inner(), keys::rule_key()) +/// The derived address for `Policy` +public fun policy_address(namespace: &Namespace): address { + derived_object::derive_address(namespace.id.to_inner(), keys::policy_key()) } public fun chest_exists(namespace: &Namespace, owner: address): bool { diff --git a/packages/pas/sources/policy.move b/packages/pas/sources/policy.move new file mode 100644 index 0000000..fab1a0c --- /dev/null +++ b/packages/pas/sources/policy.move @@ -0,0 +1,155 @@ +module pas::policy; + +use pas::{keys, namespace::Namespace, versioning::Versioning}; +use std::{string::String, type_name::{Self, TypeName}}; +use sui::{ + coin::TreasuryCap, + derived_object, + dynamic_field, + vec_map::{Self, VecMap}, + vec_set::{Self, VecSet} +}; + +#[error(code = 1)] +const EPolicyAlreadyExists: vector = b"A policy for this token type already exists."; +#[error(code = 2)] +const EFundManagementNotEnabled: vector = b"Fund management is not enabled for this policy."; +#[error(code = 3)] +const EFundManagementAlreadyEnabled: vector = + b"Fund management is already enabled for this policy."; +#[error(code = 4)] +const EInvalidAction: vector = b"Invalid action type."; +#[error(code = 5)] +const ENotSupportedAction: vector = + b"The requested action type is not supported by the issuer."; + +/// A policy is set by the owner of `T`, and points to a `TypeName` that needs +/// to be verified by the entity's contract. +/// +/// This is derived from `namespace, TypeName` +public struct Policy has key { + id: UID, + /// The required approvals per request type. + /// The key must be one of the request types (e.g. `transfer_funds`, `unlock_funds` or `clawback_funds`). + /// + /// The value is a vector of approvals that need to be gather to resolve the request. + required_approvals: VecMap>, + /// Block versions to break backwards compatibility -- only used in case of emergency. + versioning: Versioning, +} + +/// Capability for managing a `Policy`. It's 1:1. +public struct PolicyCap has key, store { + id: UID, +} + +/// Key that is used to derive the PolicyCap ID from `Policy` +public struct PolicyCapKey() has copy, drop, store; + +/// A flag saved as to check if claw-backs are enabled +/// for a given asset. +public struct FundsClawbackState() has copy, drop, store; + +/// Create a new `Policy` for `T`. +/// We use `Permit` as the proof of ownership for `T`. +public fun new(namespace: &mut Namespace, _: internal::Permit): (Policy, PolicyCap) { + assert!(!namespace.policy_exists(), EPolicyAlreadyExists); + + let versioning = namespace.versioning(); + versioning.assert_is_valid_version(); + + let mut policy = Policy { + id: derived_object::claim(namespace.uid_mut(), keys::policy_key()), + required_approvals: vec_map::empty(), + versioning, + }; + + let policy_cap = PolicyCap { + id: derived_object::claim(&mut policy.id, PolicyCapKey()), + }; + + (policy, policy_cap) +} + +public fun share(policy: Policy) { + transfer::share_object(policy); +} + +/// Enables funds management for a given `T`, adding a DF that tracks the clawback status (true/false). +/// This can only be called once. After calling it, the clawback status can never change! +public fun enable_funds_management( + policy: &mut Policy, + _: &mut TreasuryCap, + clawback_allowed: bool, +) { + assert!(!policy.is_fund_management_enabled(), EFundManagementAlreadyEnabled); + policy.versioning.assert_is_valid_version(); + dynamic_field::add(&mut policy.id, FundsClawbackState(), clawback_allowed); +} + +/// Get the set of required approvals for a given action. +public fun required_approvals(policy: &Policy, action_type: String): VecSet { + assert!(policy.required_approvals.contains(&action_type), ENotSupportedAction); + *policy.required_approvals.get(&action_type) +} + +/// For a set of actions, set the approvals required to conclude the action. +/// +/// Supported actions: ["transfer_funds", "unlock_funds", "clawback_funds"] +public(package) fun set_required_approvals( + policy: &mut Policy, + _: &PolicyCap, + action: String, + approvals: VecSet, +) { + policy.versioning.assert_is_valid_version(); + assert!(keys::is_valid_action(action), EInvalidAction); + + if (policy.required_approvals.contains(&action)) { + policy.required_approvals.remove(&action); + }; + policy.required_approvals.insert(action, approvals); +} + +public fun set_required_approval( + policy: &mut Policy, + cap: &PolicyCap, + action: String, +) { + policy.set_required_approvals( + cap, + action, + vec_set::singleton(type_name::with_defining_ids()), + ); +} + +/// Remove the action approval for a given action (this will make all requests not resolve). +public fun remove_action_approval(policy: &mut Policy, _: &PolicyCap, action: String) { + policy.versioning.assert_is_valid_version(); + policy.required_approvals.remove(&action); +} + +/// Check if clawback is allowed or not. +/// Aborts early if the management for funds has not been enabled for `T`. +public fun is_fund_clawback_allowed(policy: &Policy): bool { + policy.assert_is_fund_management_enabled(); + policy.versioning.assert_is_valid_version(); + *dynamic_field::borrow(&policy.id, FundsClawbackState()) +} + +/// Allows syncing the versioning of a policy to the namespace's versioning. +/// This is permission-less and can be done +public fun sync_versioning(policy: &mut Policy, namespace: &Namespace) { + policy.versioning = namespace.versioning(); +} + +/// Check if fund management is enabled for a given `T`. +public(package) fun is_fund_management_enabled(policy: &Policy): bool { + dynamic_field::exists_(&policy.id, FundsClawbackState()) +} + +public(package) fun versioning(policy: &Policy): Versioning { policy.versioning } + +public fun assert_is_fund_management_enabled(policy: &Policy) { + assert!(policy.is_fund_management_enabled(), EFundManagementNotEnabled); +} diff --git a/packages/pas/sources/requests/clawback_funds.move b/packages/pas/sources/requests/clawback_funds.move index d4f58af..e391c94 100644 --- a/packages/pas/sources/requests/clawback_funds.move +++ b/packages/pas/sources/requests/clawback_funds.move @@ -1,11 +1,11 @@ module pas::clawback_funds; -use pas::{keys::clawback_funds_action, request::{Self, Request}, rule::Rule}; +use pas::{keys::clawback_funds_action, policy::Policy, request::{Self, Request}}; use sui::balance::Balance; #[error(code = 1)] const EClawbackNotAllowed: vector = - b"Attempted to clawback tokens when clawback is not enabled for this rule."; + b"Attempted to clawback tokens when clawback is not enabled for this policy."; public struct ClawbackFunds { /// `owner` is the wallet OR object address, NOT the chest address @@ -35,13 +35,13 @@ public(package) fun new( } /// Resolve a clawback funds request by: -/// 1. Verify rule is valid -/// 2. Verify rule has clawback enabled -/// 3. Make sure rule has enabled clawback resolution -public fun resolve(request: Request>, rule: &Rule): Balance { - rule.versioning().assert_is_valid_version(); - assert!(rule.is_fund_clawback_allowed(), EClawbackNotAllowed); - let data = request.resolve(rule.required_approvals(clawback_funds_action())); +/// 1. Verify policy is valid +/// 2. Verify policy has clawback enabled +/// 3. Make sure policy has enabled clawback resolution +public fun resolve(request: Request>, policy: &Policy): Balance { + policy.versioning().assert_is_valid_version(); + assert!(policy.is_fund_clawback_allowed(), EClawbackNotAllowed); + let data = request.resolve(policy.required_approvals(clawback_funds_action())); let ClawbackFunds { balance, .. } = data; balance diff --git a/packages/pas/sources/requests/transfer_funds.move b/packages/pas/sources/requests/transfer_funds.move index 9d6fe85..f40bd42 100644 --- a/packages/pas/sources/requests/transfer_funds.move +++ b/packages/pas/sources/requests/transfer_funds.move @@ -1,12 +1,12 @@ module pas::transfer_funds; -use pas::{keys::transfer_funds_action, request::{Self, Request}, rule::Rule}; +use pas::{keys::transfer_funds_action, policy::Policy, request::{Self, Request}}; use sui::balance::{Self, Balance}; /// A transfer request that is generated once a Permissioned Funds Transfer is initiated. /// /// A hot potato that is issued when a transfer is initiated. -/// It can only be resolved by presenting a witness `U` that is the witness of `Rule` +/// It can only be resolved by presenting a witness `U` that is the witness of `Policy` /// /// This enables the `resolve` function of each smart contract to /// be flexible and implement its own mechanisms for validation. @@ -62,10 +62,10 @@ public(package) fun new( } /// resolve a transfer request, if funds management is enabled & there are enough approvals. -public fun resolve(request: Request>, rule: &Rule) { - rule.versioning().assert_is_valid_version(); - rule.assert_is_fund_management_enabled(); - let data = request.resolve(rule.required_approvals(transfer_funds_action())); +public fun resolve(request: Request>, policy: &Policy) { + policy.versioning().assert_is_valid_version(); + policy.assert_is_fund_management_enabled(); + let data = request.resolve(policy.required_approvals(transfer_funds_action())); let TransferFunds { balance, recipient_chest_id, .. } = data; balance::send_funds(balance, recipient_chest_id.to_address()); diff --git a/packages/pas/sources/requests/unlock_funds.move b/packages/pas/sources/requests/unlock_funds.move index 145b026..5feb611 100644 --- a/packages/pas/sources/requests/unlock_funds.move +++ b/packages/pas/sources/requests/unlock_funds.move @@ -1,6 +1,11 @@ module pas::unlock_funds; -use pas::{keys::unlock_funds_action, namespace::Namespace, request::{Self, Request}, rule::Rule}; +use pas::{ + keys::unlock_funds_action, + namespace::Namespace, + policy::Policy, + request::{Self, Request} +}; use sui::{balance::Balance, vec_set}; #[error(code = 0)] @@ -10,8 +15,8 @@ const ECannotResolveManagedAssets: vector = /// An unlock funds request that is generated once a Permissioned Funds Transfer is initiated. /// /// This can be resolved in two ways: -/// 1. If the asset is `permissioned` (there's a `Rule` for that asset), it can only be resolved by the creator -/// by calling `rule::resolve_unlock_funds` +/// 1. If the asset is `permissioned` (there's a `Policy` for that asset), it can only be resolved by the creator +/// by calling `policy::resolve_unlock_funds` /// 2. If the asset is not permissioned, it can be resolved by any address by calling `unlock_funds::resolve_unrestricted` public struct UnlockFunds { /// `from` is the wallet OR object address, NOT the chest address @@ -30,8 +35,8 @@ public fun chest_id(request: &UnlockFunds): ID { request.chest_id } public fun amount(request: &UnlockFunds): u64 { request.amount } -/// This enables unlocking assets that are not managed by a Rule within the system. -/// If a `Rule` exists, they can only be resolved from within the system. +/// This enables unlocking assets that are not managed by a Policy within the system. +/// If a `Policy` exists, they can only be resolved from within the system. /// /// For example, `SUI` will never be a managed asset, so the owner needs to be able /// to withdraw if anyone transfers some to their chest. @@ -39,7 +44,7 @@ public fun resolve_unrestricted( request: Request>, namespace: &Namespace, ): Balance { - assert!(!namespace.rule_exists(), ECannotResolveManagedAssets); + assert!(!namespace.policy_exists(), ECannotResolveManagedAssets); namespace.versioning().assert_is_valid_version(); let data = request.resolve(vec_set::empty()); let UnlockFunds { balance, .. } = data; @@ -61,10 +66,10 @@ public(package) fun new( /// Resolve an unlock funds request as long as funds management is enabled and /// there are enough valid approvals. -public fun resolve(request: Request>, rule: &Rule): Balance { - rule.versioning().assert_is_valid_version(); - rule.assert_is_fund_management_enabled(); - let data = request.resolve(rule.required_approvals(unlock_funds_action())); +public fun resolve(request: Request>, policy: &Policy): Balance { + policy.versioning().assert_is_valid_version(); + policy.assert_is_fund_management_enabled(); + let data = request.resolve(policy.required_approvals(unlock_funds_action())); let UnlockFunds { balance, .. } = data; balance diff --git a/packages/pas/sources/rule.move b/packages/pas/sources/rule.move deleted file mode 100644 index 746b507..0000000 --- a/packages/pas/sources/rule.move +++ /dev/null @@ -1,151 +0,0 @@ -module pas::rule; - -use pas::{keys, namespace::Namespace, versioning::Versioning}; -use std::{string::String, type_name::{Self, TypeName}}; -use sui::{ - coin::TreasuryCap, - derived_object, - dynamic_field, - vec_map::{Self, VecMap}, - vec_set::{Self, VecSet} -}; - -#[error(code = 1)] -const ERuleAlreadyExists: vector = b"A rule for this token type already exists."; -#[error(code = 2)] -const EFundManagementNotEnabled: vector = b"Fund management is not enabled for this rule."; -#[error(code = 3)] -const EFundManagementAlreadyEnabled: vector = - b"Fund management is already enabled for this rule."; -#[error(code = 4)] -const EInvalidAction: vector = b"Invalid action type."; -#[error(code = 5)] -const ENotSupportedAction: vector = - b"The requested action type is not supported by the issuer."; - -/// A rule is set by the owner of `T`, and points to a `TypeName` that needs -/// to be verified by the entity's contract. -/// -/// This is derived from `namespace, TypeName` -public struct Rule has key { - id: UID, - /// The required approvals per request type. - /// The key must be one of the request types (e.g. `transfer_funds`, `unlock_funds` or `clawback_funds`). - /// - /// The value is a vector of approvals that need to be gather to resolve the request. - required_approvals: VecMap>, - /// Block versions to break backwards compatibility -- only used in case of emergency. - versioning: Versioning, -} - -/// Capability for managing a `Rule`. It's 1:1. -public struct RuleCap has key, store { - id: UID, -} - -/// Key that is used to derive the RuleCap ID from `Rule` -public struct RuleCapKey() has copy, drop, store; - -/// A flag saved as to check if claw-backs are enabled -/// for a given asset. -public struct FundsClawbackState() has copy, drop, store; - -/// Create a new `Rule` for `T`. -/// We use `Permit` as the proof of ownership for `T`. -public fun new(namespace: &mut Namespace, _: internal::Permit): (Rule, RuleCap) { - assert!(!namespace.rule_exists(), ERuleAlreadyExists); - - let versioning = namespace.versioning(); - versioning.assert_is_valid_version(); - - let mut rule = Rule { - id: derived_object::claim(namespace.uid_mut(), keys::rule_key()), - required_approvals: vec_map::empty(), - versioning, - }; - - let rule_cap = RuleCap { - id: derived_object::claim(&mut rule.id, RuleCapKey()), - }; - - (rule, rule_cap) -} - -public fun share(rule: Rule) { - transfer::share_object(rule); -} - -/// Enables funds management for a given `T`, adding a DF that tracks the clawback status (true/false). -/// This can only be called once. After calling it, the clawback status can never change! -public fun enable_funds_management( - rule: &mut Rule, - _: &mut TreasuryCap, - clawback_allowed: bool, -) { - assert!(!rule.is_fund_management_enabled(), EFundManagementAlreadyEnabled); - rule.versioning.assert_is_valid_version(); - dynamic_field::add(&mut rule.id, FundsClawbackState(), clawback_allowed); -} - -/// Get the set of required approvals for a given action. -public fun required_approvals(rule: &Rule, action_type: String): VecSet { - assert!(rule.required_approvals.contains(&action_type), ENotSupportedAction); - *rule.required_approvals.get(&action_type) -} - -/// For a set of actions, set the approvals required to conclude the action. -/// -/// Supported actions: ["transfer_funds", "unlock_funds", "clawback_funds"] -public(package) fun set_required_approvals( - rule: &mut Rule, - _: &RuleCap, - action: String, - approvals: VecSet, -) { - rule.versioning.assert_is_valid_version(); - assert!(keys::is_valid_action(action), EInvalidAction); - - if (rule.required_approvals.contains(&action)) { - rule.required_approvals.remove(&action); - }; - rule.required_approvals.insert(action, approvals); -} - -public fun set_required_approval(rule: &mut Rule, cap: &RuleCap, action: String) { - rule.set_required_approvals( - cap, - action, - vec_set::singleton(type_name::with_defining_ids()), - ); -} - -/// Remove the action approval for a given action (this will make all requests not resolve). -public fun remove_action_approval(rule: &mut Rule, _: &RuleCap, action: String) { - rule.versioning.assert_is_valid_version(); - rule.required_approvals.remove(&action); -} - -/// Check if clawback is allowed or not. -/// Aborts early if the management for funds has not been enabled for `T`. -public fun is_fund_clawback_allowed(rule: &Rule): bool { - rule.assert_is_fund_management_enabled(); - rule.versioning.assert_is_valid_version(); - *dynamic_field::borrow(&rule.id, FundsClawbackState()) -} - -/// Allows syncing the versioning of a rule to the namespace's versioning. -/// This is permission-less and can be done -public fun sync_versioning(rule: &mut Rule, namespace: &Namespace) { - rule.versioning = namespace.versioning(); -} - -/// Check if fund management is enabled for a given `T`. -public(package) fun is_fund_management_enabled(rule: &Rule): bool { - dynamic_field::exists_(&rule.id, FundsClawbackState()) -} - -public(package) fun versioning(rule: &Rule): Versioning { rule.versioning } - -public fun assert_is_fund_management_enabled(rule: &Rule) { - assert!(rule.is_fund_management_enabled(), EFundManagementNotEnabled); -} diff --git a/packages/pas/tests/chest_auth_tests.move b/packages/pas/tests/chest_auth_tests.move index 3ae6bed..7abbb04 100644 --- a/packages/pas/tests/chest_auth_tests.move +++ b/packages/pas/tests/chest_auth_tests.move @@ -7,7 +7,7 @@ use sui::test_scenario::return_shared; #[test] fun authenticate_with_uid() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { let namespace_id = object::id(namespace); scenario.next_tx(@0x1); @@ -54,7 +54,7 @@ fun authenticate_with_uid() { #[test, expected_failure(abort_code = ::pas::chest::ENotOwner)] fun try_to_auth_to_another_owners_chest() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); chest::create_and_share(namespace, @0x1); @@ -81,7 +81,7 @@ fun try_to_auth_to_another_owners_chest() { #[test, expected_failure(abort_code = ::pas::chest::ENotOwner)] fun try_to_auth_to_another_uid_chest() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); @@ -101,7 +101,7 @@ fun try_to_auth_to_another_uid_chest() { #[test, expected_failure(abort_code = ::pas::chest::EChestAlreadyExists)] fun try_to_create_chest_with_same_owner() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); chest::create_and_share(namespace, @0x1); chest::create_and_share(namespace, @0x1); diff --git a/packages/pas/tests/clawback_tests.move b/packages/pas/tests/clawback_tests.move index 2194541..32c56b9 100644 --- a/packages/pas/tests/clawback_tests.move +++ b/packages/pas/tests/clawback_tests.move @@ -5,14 +5,14 @@ use pas::{ chest, clawback_funds, e2e::{test_tx, a_witness, A, b_witness, B, AWitness}, - rule::RuleCap + policy::PolicyCap }; use std::{type_name, unit_test::assert_eq}; use sui::balance; #[test] fun clawback_managed_assets() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -27,7 +27,7 @@ fun clawback_managed_assets() { assert_eq!(clawback_request.approvals().length(), 1); assert!(clawback_request.approvals().contains(&type_name::with_defining_ids())); - let balance = clawback_funds::resolve(clawback_request, managed_rule); + let balance = clawback_funds::resolve(clawback_request, managed_policy); assert_eq!(balance.value(), 50); @@ -37,13 +37,13 @@ fun clawback_managed_assets() { }); } -#[test, expected_failure(abort_code = ::pas::rule::ENotSupportedAction)] +#[test, expected_failure(abort_code = ::pas::policy::ENotSupportedAction)] fun try_to_clawback_when_clawback_stamp_is_not_set() { - test_tx!(@0x1, |namespace, managed_rule, _r, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _r, scenario| { scenario.next_tx(@0x1); - let rule_cap = scenario.take_from_sender>(); - managed_rule.remove_action_approval(&rule_cap, "clawback_funds"); + let policy_cap = scenario.take_from_sender>(); + managed_policy.remove_action_approval(&policy_cap, "clawback_funds"); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -51,14 +51,14 @@ fun try_to_clawback_when_clawback_stamp_is_not_set() { let mut clawback_request = chest.clawback_funds(50, scenario.ctx()); clawback_request.approve(a_witness()); - let balance = clawback_funds::resolve(clawback_request, managed_rule); + let balance = clawback_funds::resolve(clawback_request, managed_policy); abort }); } #[test, expected_failure(abort_code = ::pas::clawback_funds::EClawbackNotAllowed)] fun try_to_clawback_unmanaged_assets() { - test_tx!(@0x1, |namespace, _managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, _managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -66,7 +66,7 @@ fun try_to_clawback_unmanaged_assets() { let mut clawback_request = chest.clawback_funds(50, scenario.ctx()); clawback_request.approve(b_witness()); - let _balance = clawback_funds::resolve(clawback_request, unmanaged_rule); + let _balance = clawback_funds::resolve(clawback_request, unmanaged_policy); abort }); diff --git a/packages/pas/tests/e2e.move b/packages/pas/tests/e2e.move index ee89cee..4ae2f6a 100644 --- a/packages/pas/tests/e2e.move +++ b/packages/pas/tests/e2e.move @@ -1,7 +1,7 @@ #[test_only, allow(unused_variable, unused_mut_ref, dead_code)] module pas::e2e; -use pas::{chest::{Self, Chest}, rule::{Self, RuleCap}, transfer_funds, unlock_funds}; +use pas::{chest::{Self, Chest}, policy::{Self, PolicyCap}, transfer_funds, unlock_funds}; use std::{type_name, unit_test::{assert_eq, destroy}}; use sui::{balance::{Self, send_funds}, sui::SUI, test_scenario::return_shared, vec_set}; @@ -14,7 +14,7 @@ public struct BWitness() has drop; #[test] fun e2e() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); let namespace_id = object::id(namespace); @@ -51,7 +51,7 @@ fun e2e() { ); transfer_request.approve(AWitness()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); return_shared(chest); return_shared(another_chest); @@ -60,7 +60,7 @@ fun e2e() { #[test, expected_failure(abort_code = ::pas::request::EInsufficientApprovals)] fun try_to_approve_transfer_with_invalid_witness() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { let namespace_id = object::id(namespace); scenario.next_tx(@0x1); chest::create_and_share(namespace, @0x1); @@ -83,7 +83,7 @@ fun try_to_approve_transfer_with_invalid_witness() { // Add an invalid approval to the request transfer_request.approve(BWitness()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); abort }); @@ -91,7 +91,7 @@ fun try_to_approve_transfer_with_invalid_witness() { #[test] fun test_address_and_derivation_matches() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { let user_one_chest_id = namespace.chest_address(@0x1).to_id(); let user_two_chest_id = namespace.chest_address(@0x2).to_id(); @@ -142,7 +142,7 @@ fun test_address_and_derivation_matches() { #[test] fun unlock_funds_successfully() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -151,7 +151,7 @@ fun unlock_funds_successfully() { let mut unlock_request = chest.unlock_funds(&auth, 50, scenario.ctx()); unlock_request.approve(AWitness()); - let balance = unlock_funds::resolve(unlock_request, managed_rule); + let balance = unlock_funds::resolve(unlock_request, managed_policy); assert_eq!(balance.value(), 50); @@ -162,7 +162,7 @@ fun unlock_funds_successfully() { #[test, expected_failure(abort_code = ::pas::unlock_funds::ECannotResolveManagedAssets)] fun try_to_resolve_unlock_funds_request_for_managed_assets() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -178,7 +178,7 @@ fun try_to_resolve_unlock_funds_request_for_managed_assets() { #[test] fun unlock_non_managed_funds() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -193,26 +193,26 @@ fun unlock_non_managed_funds() { }); } -#[test, expected_failure(abort_code = ::pas::rule::EFundManagementAlreadyEnabled)] +#[test, expected_failure(abort_code = ::pas::policy::EFundManagementAlreadyEnabled)] fun try_to_disable_clawbacks_for_managed_assets() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); // Try to disable clawbacks. let mut cap = sui::coin::create_treasury_cap_for_testing(scenario.ctx()); - managed_rule.enable_funds_management(&mut cap, false); + managed_policy.enable_funds_management(&mut cap, false); abort }); } -#[test, expected_failure(abort_code = ::pas::rule::EFundManagementNotEnabled)] +#[test, expected_failure(abort_code = ::pas::policy::EFundManagementNotEnabled)] fun try_to_transfer_unmanaged_assets() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); - // create a rule but do not enable funds management. - let (rule, cap) = rule::new(namespace, internal::permit()); + // create a policy but do not enable funds management. + let (policy, cap) = policy::new(namespace, internal::permit()); // somehow transfer balance to chest a let mut chest = chest::create(namespace, @0x1); @@ -228,18 +228,18 @@ fun try_to_transfer_unmanaged_assets() { scenario.ctx(), ); - transfer_funds::resolve(transfer_request, &rule); + transfer_funds::resolve(transfer_request, &policy); abort }); } -#[test, expected_failure(abort_code = ::pas::rule::EFundManagementNotEnabled)] +#[test, expected_failure(abort_code = ::pas::policy::EFundManagementNotEnabled)] fun try_to_unlock_unmanaged_assets() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); - // create a rule but do not enable funds management. - let (rule, cap) = rule::new(namespace, internal::permit()); + // create a policy but do not enable funds management. + let (policy, cap) = policy::new(namespace, internal::permit()); // somehow transfer balance to chest a let mut chest = chest::create(namespace, @0x1); @@ -253,7 +253,7 @@ fun try_to_unlock_unmanaged_assets() { scenario.ctx(), ); - let _balance = unlock_funds::resolve(unlock_request, &rule); + let _balance = unlock_funds::resolve(unlock_request, &policy); abort }); @@ -261,12 +261,12 @@ fun try_to_unlock_unmanaged_assets() { #[test] fun derivation_is_consistent() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let chest = chest::create(namespace, @0x1); assert_eq!(namespace.chest_address(@0x1), object::id(&chest).to_address()); - assert_eq!(namespace.rule_address(), object::id(managed_rule).to_address()); + assert_eq!(namespace.policy_address(), object::id(managed_policy).to_address()); chest.share(); }); @@ -274,7 +274,7 @@ fun derivation_is_consistent() { #[test] fun test_unlock_request_getters() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let mut chest = chest::create(namespace, @0x1); chest.deposit_funds(balance::create_for_testing(100)); @@ -292,11 +292,11 @@ fun test_unlock_request_getters() { }); } -#[test, expected_failure(abort_code = ::pas::rule::ERuleAlreadyExists)] -fun try_to_create_duplicate_rule() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { +#[test, expected_failure(abort_code = ::pas::policy::EPolicyAlreadyExists)] +fun try_to_create_duplicate_policy() { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); - let (rule, rule_cap) = rule::new(namespace, internal::permit()); + let (policy, policy_cap) = policy::new(namespace, internal::permit()); abort }); @@ -304,19 +304,19 @@ fun try_to_create_duplicate_rule() { #[test] fun multiple_approvals_required() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); let namespace_id = object::id(namespace); - let rule_cap = scenario.take_from_sender>(); + let policy_cap = scenario.take_from_sender>(); let mut approvals = vec_set::empty(); approvals.insert(type_name::with_defining_ids()); approvals.insert(type_name::with_defining_ids()); - managed_rule.set_required_approvals(&rule_cap, "transfer_funds", approvals); + managed_policy.set_required_approvals(&policy_cap, "transfer_funds", approvals); - scenario.return_to_sender(rule_cap); + scenario.return_to_sender(policy_cap); // create chests of 0x1 and 0x2 let chest = chest::create(namespace, @0x1); @@ -343,7 +343,7 @@ fun multiple_approvals_required() { transfer_request.approve(AWitness()); transfer_request.approve(BWitness()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); return_shared(chest); }); @@ -351,19 +351,19 @@ fun multiple_approvals_required() { #[test, expected_failure(abort_code = ::pas::request::EInsufficientApprovals)] fun multiple_approvals_invalid_order_failure() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); let namespace_id = object::id(namespace); - let rule_cap = scenario.take_from_sender>(); + let policy_cap = scenario.take_from_sender>(); let mut approvals = vec_set::empty(); approvals.insert(type_name::with_defining_ids()); approvals.insert(type_name::with_defining_ids()); - managed_rule.set_required_approvals(&rule_cap, "transfer_funds", approvals); + managed_policy.set_required_approvals(&policy_cap, "transfer_funds", approvals); - scenario.return_to_sender(rule_cap); + scenario.return_to_sender(policy_cap); // create chests of 0x1 and 0x2 let chest = chest::create(namespace, @0x1); @@ -390,14 +390,14 @@ fun multiple_approvals_invalid_order_failure() { transfer_request.approve(BWitness()); transfer_request.approve(AWitness()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); abort }); } #[test, expected_failure(abort_code = ::pas::request::EInvalidNumberOfApprovals)] fun cannot_have_extra_approvals() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); let namespace_id = object::id(namespace); @@ -427,7 +427,7 @@ fun cannot_have_extra_approvals() { transfer_request.approve(BWitness()); transfer_request.approve(AWitness()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); abort }); } @@ -459,8 +459,8 @@ public macro fun test_tx( $admin: address, $f: | &mut pas::namespace::Namespace, - &mut pas::rule::Rule, - &mut pas::rule::Rule, + &mut pas::policy::Policy, + &mut pas::policy::Policy, &mut sui::test_scenario::Scenario, |, ) { @@ -480,45 +480,45 @@ public macro fun test_tx( pas::templates::setup(&mut namespace); - let (mut rule_a, rule_cap_a) = pas::rule::new(&mut namespace, pas::e2e::a_permit()); + let (mut policy_a, policy_cap_a) = pas::policy::new(&mut namespace, pas::e2e::a_permit()); let mut treasury_cap_a = sui::coin::create_treasury_cap_for_testing(scenario.ctx()); - rule_a.enable_funds_management(&mut treasury_cap_a, true); - rule_a.set_required_approval<_, AWitness>(&rule_cap_a, "transfer_funds"); - rule_a.set_required_approval<_, AWitness>(&rule_cap_a, "unlock_funds"); - rule_a.set_required_approval<_, AWitness>(&rule_cap_a, "clawback_funds"); - // rule_a. - sui::transfer::public_transfer(rule_cap_a, $admin); + policy_a.enable_funds_management(&mut treasury_cap_a, true); + policy_a.set_required_approval<_, AWitness>(&policy_cap_a, "transfer_funds"); + policy_a.set_required_approval<_, AWitness>(&policy_cap_a, "unlock_funds"); + policy_a.set_required_approval<_, AWitness>(&policy_cap_a, "clawback_funds"); + // policy_a. + sui::transfer::public_transfer(policy_cap_a, $admin); std::unit_test::destroy(treasury_cap_a); - rule_a.share(); + policy_a.share(); - let (mut rule_b, rule_cap_b) = pas::rule::new(&mut namespace, pas::e2e::b_permit()); + let (mut policy_b, policy_cap_b) = pas::policy::new(&mut namespace, pas::e2e::b_permit()); let mut treasury_cap_b = sui::coin::create_treasury_cap_for_testing(scenario.ctx()); - rule_b.set_required_approval<_, BWitness>(&rule_cap_b, "transfer_funds"); - rule_b.set_required_approval<_, BWitness>(&rule_cap_b, "unlock_funds"); + policy_b.set_required_approval<_, BWitness>(&policy_cap_b, "transfer_funds"); + policy_b.set_required_approval<_, BWitness>(&policy_cap_b, "unlock_funds"); - rule_b.enable_funds_management(&mut treasury_cap_b, false); + policy_b.enable_funds_management(&mut treasury_cap_b, false); std::unit_test::destroy(treasury_cap_b); - std::unit_test::destroy(rule_cap_b); - rule_b.share(); + std::unit_test::destroy(policy_cap_b); + policy_b.share(); scenario.next_tx($admin); - let mut managed_rule = scenario.take_shared>(); - let mut unmanaged_rule = scenario.take_shared>(); + let mut managed_policy = scenario.take_shared>(); + let mut unmanaged_policy = scenario.take_shared>(); $f( &mut namespace, - &mut managed_rule, - &mut unmanaged_rule, + &mut managed_policy, + &mut unmanaged_policy, &mut scenario, ); scenario.next_tx($admin); sui::test_scenario::return_shared(namespace); - sui::test_scenario::return_shared(managed_rule); - sui::test_scenario::return_shared(unmanaged_rule); + sui::test_scenario::return_shared(managed_policy); + sui::test_scenario::return_shared(unmanaged_policy); scenario.end(); } diff --git a/packages/pas/tests/rule_setup_tests.move b/packages/pas/tests/policy_setup_tests.move similarity index 52% rename from packages/pas/tests/rule_setup_tests.move rename to packages/pas/tests/policy_setup_tests.move index 0f31956..78ad582 100644 --- a/packages/pas/tests/rule_setup_tests.move +++ b/packages/pas/tests/policy_setup_tests.move @@ -1,7 +1,7 @@ #[test_only, allow(unused_variable, unused_mut_ref, dead_code)] -module pas::rule_setup_tests; +module pas::policy_setup_tests; -use pas::{chest, e2e::{test_tx, A}, rule::RuleCap, transfer_funds}; +use pas::{chest, e2e::{test_tx, A}, policy::PolicyCap, transfer_funds}; use sui::balance; public struct InvalidActionApproval() has drop; @@ -10,11 +10,11 @@ public struct NewActionApproval() has drop; #[test] fun override_action_approval() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); - let rule_cap = scenario.take_from_sender>(); - managed_rule.set_required_approval<_, NewActionApproval>(&rule_cap, "transfer_funds"); + let policy_cap = scenario.take_from_sender>(); + managed_policy.set_required_approval<_, NewActionApproval>(&policy_cap, "transfer_funds"); // Do a test transfer to verify the override auth works { @@ -30,22 +30,25 @@ fun override_action_approval() { scenario.ctx(), ); transfer_request.approve(NewActionApproval()); - transfer_funds::resolve(transfer_request, managed_rule); + transfer_funds::resolve(transfer_request, managed_policy); chest.share(); }; - scenario.return_to_sender(rule_cap); + scenario.return_to_sender(policy_cap); }); } -#[test, expected_failure(abort_code = ::pas::rule::EInvalidAction)] +#[test, expected_failure(abort_code = ::pas::policy::EInvalidAction)] fun set_invalid_action_approval() { - test_tx!(@0x1, |namespace, managed_rule, unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, unmanaged_policy, scenario| { scenario.next_tx(@0x1); - let rule_cap = scenario.take_from_sender>(); - managed_rule.set_required_approval<_, InvalidActionApproval>(&rule_cap, "invalid_action"); + let policy_cap = scenario.take_from_sender>(); + managed_policy.set_required_approval<_, InvalidActionApproval>( + &policy_cap, + "invalid_action", + ); abort }); diff --git a/packages/pas/tests/versioning_tests.move b/packages/pas/tests/versioning_tests.move index cef8a68..43ea556 100644 --- a/packages/pas/tests/versioning_tests.move +++ b/packages/pas/tests/versioning_tests.move @@ -47,7 +47,7 @@ fun tries_to_setup_namespace_with_invalid_upgrade_cap() { #[test, expected_failure(abort_code = ::pas::namespace::EUpgradeCapPackageMismatch)] fun tries_to_block_version_with_invalid_upgrade_cap() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let upgrade_cap = sui::package::test_publish(package_id(), scenario.ctx()); @@ -59,7 +59,7 @@ fun tries_to_block_version_with_invalid_upgrade_cap() { #[test, expected_failure(abort_code = ::pas::namespace::EUpgradeCapPackageMismatch)] fun tries_to_unblock_version_with_invalid_upgrade_cap() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let upgrade_cap = sui::package::test_publish(package_id(), scenario.ctx()); @@ -70,8 +70,8 @@ fun tries_to_unblock_version_with_invalid_upgrade_cap() { } #[test] -fun block_unblock_versions_and_sync_with_chests_and_rules() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { +fun block_unblock_versions_and_sync_with_chests_and_policies() { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { scenario.next_tx(@0x1); let upgrade_cap = scenario.take_from_sender(); @@ -80,17 +80,17 @@ fun block_unblock_versions_and_sync_with_chests_and_rules() { namespace.block_version(&upgrade_cap, 1); assert!(!namespace.versioning().is_valid_version(1)); chest.sync_versioning(namespace); - managed_rule.sync_versioning(namespace); + managed_policy.sync_versioning(namespace); assert_eq!(chest.versioning(), namespace.versioning()); assert!(!chest.versioning().is_valid_version(1)); - assert!(!managed_rule.versioning().is_valid_version(1)); + assert!(!managed_policy.versioning().is_valid_version(1)); namespace.unblock_version(&upgrade_cap, 1); chest.sync_versioning(namespace); - managed_rule.sync_versioning(namespace); + managed_policy.sync_versioning(namespace); assert!(namespace.versioning().is_valid_version(1)); assert!(chest.versioning().is_valid_version(1)); - assert!(managed_rule.versioning().is_valid_version(1)); + assert!(managed_policy.versioning().is_valid_version(1)); chest.share(); scenario.return_to_sender(upgrade_cap); @@ -99,7 +99,7 @@ fun block_unblock_versions_and_sync_with_chests_and_rules() { #[test, expected_failure(abort_code = ::pas::versioning::EInvalidVersion)] fun try_to_create_chest_with_invalid_version() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { namespace.block_current_version(scenario); let _chest = chest::create(namespace, @0x1); @@ -109,7 +109,7 @@ fun try_to_create_chest_with_invalid_version() { #[test, expected_failure(abort_code = ::pas::versioning::EInvalidVersion)] fun try_unlock_funds_invalid_version_on_chest() { - test_tx!(@0x1, |namespace, managed_rule, _unmanaged_rule, scenario| { + test_tx!(@0x1, |namespace, managed_policy, _unmanaged_policy, scenario| { let mut chest = chest::create(namespace, @0x1); namespace.block_current_version(scenario); diff --git a/packages/ptb/tests/ptb_tests.move b/packages/ptb/tests/ptb_tests.move index 67b8b8a..26756fc 100644 --- a/packages/ptb/tests/ptb_tests.move +++ b/packages/ptb/tests/ptb_tests.move @@ -33,7 +33,7 @@ fun pas_command_with_ext_inputs() { "resolve_transfer", vector[ ptb::ext_input("request"), // TODO: consider namespaces here? - ptb::ext_input("rule_arg"), + ptb::ext_input("policy_arg"), ptb::clock(), ], vector["magic::usdc_app::DEMO_USDC"], diff --git a/packages/testing/demo_usd/sources/demo_usd.move b/packages/testing/demo_usd/sources/demo_usd.move index 20f4266..99bc5d8 100644 --- a/packages/testing/demo_usd/sources/demo_usd.move +++ b/packages/testing/demo_usd/sources/demo_usd.move @@ -4,13 +4,13 @@ /// Demo USD asset for testing the PAS SDK. /// /// This module defines a DEMO_USD witness type that gets registered in the PAS system -/// during package initialization. It sets up a Rule with resolution commands for +/// during package initialization. It sets up a Policy with resolution commands for /// TransferFunds and UnlockFunds actions. module demo_usd::demo_usd; use pas::namespace::Namespace; +use pas::policy::{Self, Policy, PolicyCap}; use pas::request::Request; -use pas::rule::{Self, Rule, RuleCap}; use pas::templates::Templates; use pas::transfer_funds::TransferFunds; use ptb::ptb; @@ -37,7 +37,7 @@ public struct Faucet has key { id: UID, cap: TreasuryCap, metadata: MetadataCap, - rule_cap: Option>, + policy_cap: Option>, } /// Stamp used in PAS for authorizing any admin action. @@ -70,19 +70,19 @@ fun init(otw: DEMO_USD, ctx: &mut TxContext) { id: object::new(ctx), cap, metadata, - rule_cap: option::none(), + policy_cap: option::none(), }); } entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &mut Faucet) { - let (mut rule, cap) = rule::new(namespace, internal::permit()); + let (mut policy, cap) = policy::new(namespace, internal::permit()); // Enable funds management (with clawbacks!) - rule.enable_funds_management(&mut faucet.cap, true); + policy.enable_funds_management(&mut faucet.cap, true); - rule.set_required_approval<_, TransferApproval>(&cap, "transfer_funds"); + policy.set_required_approval<_, TransferApproval>(&cap, "transfer_funds"); - faucet.rule_cap.fill(cap); + faucet.policy_cap.fill(cap); let type_name = type_name::with_defining_ids(); @@ -95,11 +95,11 @@ entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &m ); templates.set_template_command(internal::permit(), cmd); - rule.share(); + policy.share(); } /// starts using v2 approve transfer to test upgradeability. -public fun use_v2(rule: &mut Rule, templates: &mut Templates, faucet: &mut Faucet) { +public fun use_v2(policy: &mut Policy, templates: &mut Templates, faucet: &mut Faucet) { let cmd = ptb::move_call( type_name::with_defining_ids().address_string().to_string(), "demo_usd", @@ -110,7 +110,10 @@ public fun use_v2(rule: &mut Rule, templates: &mut Templates, faucet: templates.set_template_command(internal::permit(), cmd); - rule.set_required_approval<_, TransferApprovalV2>(faucet.rule_cap.borrow(), "transfer_funds"); + policy.set_required_approval<_, TransferApprovalV2>( + faucet.policy_cap.borrow(), + "transfer_funds", + ); } /// Resolver function for transfer requests - simply approves all transfers diff --git a/sdk/pas/src/client.ts b/sdk/pas/src/client.ts index a676f28..4f07387 100644 --- a/sdk/pas/src/client.ts +++ b/sdk/pas/src/client.ts @@ -10,7 +10,7 @@ import { } from './constants.js'; import { deriveChestAddress, - deriveRuleAddress, + derivePolicyAddress, deriveTemplateAddress, deriveTemplateRegistryAddress, } from './derivation.js'; @@ -114,13 +114,13 @@ export class PASClient { } /** - * Derives the rule address for a given asset type T. + * Derives the policy address for a given asset type T. * * @param assetType - The full type of the asset (e.g., "0x2::sui::SUI") - * @returns The derived rule object ID + * @returns The derived policy object ID */ - deriveRuleAddress(assetType: string): string { - return deriveRuleAddress(assetType, this.#packageConfig); + derivePolicyAddress(assetType: string): string { + return derivePolicyAddress(assetType, this.#packageConfig); } /** @@ -151,7 +151,7 @@ export class PASClient { return { /** * Creates a transfer funds intent. At build time, it auto-resolves the issuer's - * approval template commands by reading the Rule and Templates objects on-chain. + * approval template commands by reading the Policy and Templates objects on-chain. * If the recipient chest does not exist, it will be created and shared automatically. * * @param options - Transfer options @@ -178,7 +178,7 @@ export class PASClient { /** * Creates an unlock funds intent for unrestricted (non-managed) assets. - * Use this when no Rule exists for the asset type (e.g., SUI). + * Use this when no Policy exists for the asset type (e.g., SUI). * * @param options - Unlock options * @param options.from - The sender's address (owner of the source chest) diff --git a/sdk/pas/src/contracts/pas/chest.ts b/sdk/pas/src/contracts/pas/chest.ts index 0db6717..1abac72 100644 --- a/sdk/pas/src/contracts/pas/chest.ts +++ b/sdk/pas/src/contracts/pas/chest.ts @@ -123,7 +123,7 @@ export interface UnlockFundsOptions { } /** * Enables a fund unlock flow. This is useful for assets that are not managed by a - * Rule within the system, or if there's a special case where an issuer allows + * Policy within the system, or if there's a special case where an issuer allows * balances to flow out of the system. */ export function unlockFunds(options: UnlockFundsOptions) { @@ -186,7 +186,7 @@ export interface ClawbackFundsOptions { * Initiate a clawback request for an amount of funds. This takes no `Auth`, as * it's an admin action. * - * This can only ever finalize if clawback is enabled in the rule. + * This can only ever finalize if clawback is enabled in the policy. */ export function clawbackFunds(options: ClawbackFundsOptions) { const packageAddress = options.package ?? '@mysten/pas'; diff --git a/sdk/pas/src/contracts/pas/clawback_funds.ts b/sdk/pas/src/contracts/pas/clawback_funds.ts index 0159fcd..152e8d6 100644 --- a/sdk/pas/src/contracts/pas/clawback_funds.ts +++ b/sdk/pas/src/contracts/pas/clawback_funds.ts @@ -84,26 +84,26 @@ export function amount(options: AmountOptions) { } export interface ResolveArguments { request: RawTransactionArgument; - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface ResolveOptions { package?: string; arguments: | ResolveArguments - | [request: RawTransactionArgument, rule: RawTransactionArgument]; + | [request: RawTransactionArgument, policy: RawTransactionArgument]; typeArguments: [string]; } /** * Resolve a clawback funds request by: * - * 1. Verify rule is valid - * 2. Verify rule has clawback enabled - * 3. Make sure rule has enabled clawback resolution + * 1. Verify policy is valid + * 2. Verify policy has clawback enabled + * 3. Make sure policy has enabled clawback resolution */ export function resolve(options: ResolveOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null] satisfies (string | null)[]; - const parameterNames = ['request', 'rule']; + const parameterNames = ['request', 'policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, diff --git a/sdk/pas/src/contracts/pas/keys.ts b/sdk/pas/src/contracts/pas/keys.ts index 3862d8f..764a022 100644 --- a/sdk/pas/src/contracts/pas/keys.ts +++ b/sdk/pas/src/contracts/pas/keys.ts @@ -7,7 +7,7 @@ import { type Transaction } from '@mysten/sui/transactions'; import { MoveTuple, normalizeMoveArguments, type RawTransactionArgument } from '../utils/index.js'; const $moduleName = '@mysten/pas::keys'; -export const RuleKey = new MoveTuple({ name: `${$moduleName}::RuleKey`, fields: [bcs.bool()] }); +export const PolicyKey = new MoveTuple({ name: `${$moduleName}::PolicyKey`, fields: [bcs.bool()] }); export const ChestKey = new MoveTuple({ name: `${$moduleName}::ChestKey`, fields: [bcs.Address] }); export const TemplateKey = new MoveTuple({ name: `${$moduleName}::TemplateKey`, diff --git a/sdk/pas/src/contracts/pas/namespace.ts b/sdk/pas/src/contracts/pas/namespace.ts index f110ec0..c0fb2ac 100644 --- a/sdk/pas/src/contracts/pas/namespace.ts +++ b/sdk/pas/src/contracts/pas/namespace.ts @@ -8,7 +8,7 @@ * Namespace is responsible for creating objects that are easy to query & find: * * 1. Chests - * 2. Rules ... any other module we might add in the future + * 2. Policies ... any other module we might add in the future */ import { bcs } from '@mysten/sui/bcs'; @@ -119,16 +119,16 @@ export function unblockVersion(options: UnblockVersionOptions) { arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), }); } -export interface RuleExistsArguments { +export interface PolicyExistsArguments { namespace: RawTransactionArgument; } -export interface RuleExistsOptions { +export interface PolicyExistsOptions { package?: string; - arguments: RuleExistsArguments | [namespace: RawTransactionArgument]; + arguments: PolicyExistsArguments | [namespace: RawTransactionArgument]; typeArguments: [string]; } -/** Check if `Rule` exists in the namespace */ -export function ruleExists(options: RuleExistsOptions) { +/** Check if `Policy` exists in the namespace */ +export function policyExists(options: PolicyExistsOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null] satisfies (string | null)[]; const parameterNames = ['namespace']; @@ -136,21 +136,21 @@ export function ruleExists(options: RuleExistsOptions) { tx.moveCall({ package: packageAddress, module: 'namespace', - function: 'rule_exists', + function: 'policy_exists', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } -export interface RuleAddressArguments { +export interface PolicyAddressArguments { namespace: RawTransactionArgument; } -export interface RuleAddressOptions { +export interface PolicyAddressOptions { package?: string; - arguments: RuleAddressArguments | [namespace: RawTransactionArgument]; + arguments: PolicyAddressArguments | [namespace: RawTransactionArgument]; typeArguments: [string]; } -/** The derived address for `Rule` */ -export function ruleAddress(options: RuleAddressOptions) { +/** The derived address for `Policy` */ +export function policyAddress(options: PolicyAddressOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null] satisfies (string | null)[]; const parameterNames = ['namespace']; @@ -158,7 +158,7 @@ export function ruleAddress(options: RuleAddressOptions) { tx.moveCall({ package: packageAddress, module: 'namespace', - function: 'rule_address', + function: 'policy_address', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); diff --git a/sdk/pas/src/contracts/pas/rule.ts b/sdk/pas/src/contracts/pas/policy.ts similarity index 81% rename from sdk/pas/src/contracts/pas/rule.ts rename to sdk/pas/src/contracts/pas/policy.ts index e762587..7c5033b 100644 --- a/sdk/pas/src/contracts/pas/rule.ts +++ b/sdk/pas/src/contracts/pas/policy.ts @@ -15,9 +15,9 @@ import * as vec_map from './deps/sui/vec_map.js'; import * as vec_set from './deps/sui/vec_set.js'; import * as versioning from './versioning.js'; -const $moduleName = '@mysten/pas::rule'; -export const Rule = new MoveStruct({ - name: `${$moduleName}::Rule`, +const $moduleName = '@mysten/pas::policy'; +export const Policy = new MoveStruct({ + name: `${$moduleName}::Policy`, fields: { id: bcs.Address, /** @@ -35,14 +35,14 @@ export const Rule = new MoveStruct({ versioning: versioning.Versioning, }, }); -export const RuleCap = new MoveStruct({ - name: `${$moduleName}::RuleCap`, +export const PolicyCap = new MoveStruct({ + name: `${$moduleName}::PolicyCap`, fields: { id: bcs.Address, }, }); -export const RuleCapKey = new MoveTuple({ - name: `${$moduleName}::RuleCapKey`, +export const PolicyCapKey = new MoveTuple({ + name: `${$moduleName}::PolicyCapKey`, fields: [bcs.bool()], }); export const FundsClawbackState = new MoveTuple({ @@ -61,7 +61,7 @@ export interface NewOptions { typeArguments: [string]; } /** - * Create a new `Rule` for `T`. We use `Permit` as the proof of ownership for + * Create a new `Policy` for `T`. We use `Permit` as the proof of ownership for * `T`. */ export function _new(options: NewOptions) { @@ -71,35 +71,35 @@ export function _new(options: NewOptions) { return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'new', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface ShareArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface ShareOptions { package?: string; - arguments: ShareArguments | [rule: RawTransactionArgument]; + arguments: ShareArguments | [policy: RawTransactionArgument]; typeArguments: [string]; } export function share(options: ShareOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null] satisfies (string | null)[]; - const parameterNames = ['rule']; + const parameterNames = ['policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'share', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface EnableFundsManagementArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; _: RawTransactionArgument; clawbackAllowed: RawTransactionArgument; } @@ -108,7 +108,7 @@ export interface EnableFundsManagementOptions { arguments: | EnableFundsManagementArguments | [ - rule: RawTransactionArgument, + policy: RawTransactionArgument, _: RawTransactionArgument, clawbackAllowed: RawTransactionArgument, ]; @@ -122,43 +122,43 @@ export interface EnableFundsManagementOptions { export function enableFundsManagement(options: EnableFundsManagementOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null, 'bool'] satisfies (string | null)[]; - const parameterNames = ['rule', '_', 'clawbackAllowed']; + const parameterNames = ['policy', '_', 'clawbackAllowed']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'enable_funds_management', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface RequiredApprovalsArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; actionType: RawTransactionArgument; } export interface RequiredApprovalsOptions { package?: string; arguments: | RequiredApprovalsArguments - | [rule: RawTransactionArgument, actionType: RawTransactionArgument]; + | [policy: RawTransactionArgument, actionType: RawTransactionArgument]; typeArguments: [string]; } /** Get the set of required approvals for a given action. */ export function requiredApprovals(options: RequiredApprovalsOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, '0x1::string::String'] satisfies (string | null)[]; - const parameterNames = ['rule', 'actionType']; + const parameterNames = ['policy', 'actionType']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'required_approvals', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface SetRequiredApprovalArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; cap: RawTransactionArgument; action: RawTransactionArgument; } @@ -167,7 +167,7 @@ export interface SetRequiredApprovalOptions { arguments: | SetRequiredApprovalArguments | [ - rule: RawTransactionArgument, + policy: RawTransactionArgument, cap: RawTransactionArgument, action: RawTransactionArgument, ]; @@ -176,18 +176,18 @@ export interface SetRequiredApprovalOptions { export function setRequiredApproval(options: SetRequiredApprovalOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null, '0x1::string::String'] satisfies (string | null)[]; - const parameterNames = ['rule', 'cap', 'action']; + const parameterNames = ['policy', 'cap', 'action']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'set_required_approval', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface RemoveActionApprovalArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; _: RawTransactionArgument; action: RawTransactionArgument; } @@ -196,7 +196,7 @@ export interface RemoveActionApprovalOptions { arguments: | RemoveActionApprovalArguments | [ - rule: RawTransactionArgument, + policy: RawTransactionArgument, _: RawTransactionArgument, action: RawTransactionArgument, ]; @@ -209,22 +209,22 @@ export interface RemoveActionApprovalOptions { export function removeActionApproval(options: RemoveActionApprovalOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null, '0x1::string::String'] satisfies (string | null)[]; - const parameterNames = ['rule', '_', 'action']; + const parameterNames = ['policy', '_', 'action']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'remove_action_approval', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface IsFundClawbackAllowedArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface IsFundClawbackAllowedOptions { package?: string; - arguments: IsFundClawbackAllowedArguments | [rule: RawTransactionArgument]; + arguments: IsFundClawbackAllowedArguments | [policy: RawTransactionArgument]; typeArguments: [string]; } /** @@ -234,60 +234,60 @@ export interface IsFundClawbackAllowedOptions { export function isFundClawbackAllowed(options: IsFundClawbackAllowedOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null] satisfies (string | null)[]; - const parameterNames = ['rule']; + const parameterNames = ['policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'is_fund_clawback_allowed', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface SyncVersioningArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; namespace: RawTransactionArgument; } export interface SyncVersioningOptions { package?: string; arguments: | SyncVersioningArguments - | [rule: RawTransactionArgument, namespace: RawTransactionArgument]; + | [policy: RawTransactionArgument, namespace: RawTransactionArgument]; typeArguments: [string]; } /** - * Allows syncing the versioning of a rule to the namespace's versioning. This is + * Allows syncing the versioning of a policy to the namespace's versioning. This is * permission-less and can be done */ export function syncVersioning(options: SyncVersioningOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null] satisfies (string | null)[]; - const parameterNames = ['rule', 'namespace']; + const parameterNames = ['policy', 'namespace']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'sync_versioning', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, }); } export interface AssertIsFundManagementEnabledArguments { - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface AssertIsFundManagementEnabledOptions { package?: string; - arguments: AssertIsFundManagementEnabledArguments | [rule: RawTransactionArgument]; + arguments: AssertIsFundManagementEnabledArguments | [policy: RawTransactionArgument]; typeArguments: [string]; } export function assertIsFundManagementEnabled(options: AssertIsFundManagementEnabledOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null] satisfies (string | null)[]; - const parameterNames = ['rule']; + const parameterNames = ['policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, - module: 'rule', + module: 'policy', function: 'assert_is_fund_management_enabled', arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames), typeArguments: options.typeArguments, diff --git a/sdk/pas/src/contracts/pas/request.ts b/sdk/pas/src/contracts/pas/request.ts index 4daf9e7..712a2bb 100644 --- a/sdk/pas/src/contracts/pas/request.ts +++ b/sdk/pas/src/contracts/pas/request.ts @@ -34,7 +34,7 @@ export interface ApproveOptions> { | [request: RawTransactionArgument, Approval: RawTransactionArgument]; typeArguments: [string, string]; } -/** Adds an approval to a request. Can be called to resolve rules */ +/** Adds an approval to a request. Can be called to resolve policies */ export function approve>(options: ApproveOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, `${options.typeArguments[1]}`] satisfies (string | null)[]; diff --git a/sdk/pas/src/contracts/pas/transfer_funds.ts b/sdk/pas/src/contracts/pas/transfer_funds.ts index 3a1b758..2ce90f6 100644 --- a/sdk/pas/src/contracts/pas/transfer_funds.ts +++ b/sdk/pas/src/contracts/pas/transfer_funds.ts @@ -132,13 +132,13 @@ export function amount(options: AmountOptions) { } export interface ResolveArguments { request: RawTransactionArgument; - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface ResolveOptions { package?: string; arguments: | ResolveArguments - | [request: RawTransactionArgument, rule: RawTransactionArgument]; + | [request: RawTransactionArgument, policy: RawTransactionArgument]; typeArguments: [string]; } /** @@ -148,7 +148,7 @@ export interface ResolveOptions { export function resolve(options: ResolveOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null] satisfies (string | null)[]; - const parameterNames = ['request', 'rule']; + const parameterNames = ['request', 'policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, diff --git a/sdk/pas/src/contracts/pas/unlock_funds.ts b/sdk/pas/src/contracts/pas/unlock_funds.ts index 8daf62e..8e91783 100644 --- a/sdk/pas/src/contracts/pas/unlock_funds.ts +++ b/sdk/pas/src/contracts/pas/unlock_funds.ts @@ -96,8 +96,8 @@ export interface ResolveUnrestrictedOptions { typeArguments: [string]; } /** - * This enables unlocking assets that are not managed by a Rule within the system. - * If a `Rule` exists, they can only be resolved from within the system. + * This enables unlocking assets that are not managed by a Policy within the system. + * If a `Policy` exists, they can only be resolved from within the system. * * For example, `SUI` will never be a managed asset, so the owner needs to be able * to withdraw if anyone transfers some to their chest. @@ -117,13 +117,13 @@ export function resolveUnrestricted(options: ResolveUnrestrictedOptions) { } export interface ResolveArguments { request: RawTransactionArgument; - rule: RawTransactionArgument; + policy: RawTransactionArgument; } export interface ResolveOptions { package?: string; arguments: | ResolveArguments - | [request: RawTransactionArgument, rule: RawTransactionArgument]; + | [request: RawTransactionArgument, policy: RawTransactionArgument]; typeArguments: [string]; } /** @@ -133,7 +133,7 @@ export interface ResolveOptions { export function resolve(options: ResolveOptions) { const packageAddress = options.package ?? '@mysten/pas'; const argumentsTypes = [null, null] satisfies (string | null)[]; - const parameterNames = ['request', 'rule']; + const parameterNames = ['request', 'policy']; return (tx: Transaction) => tx.moveCall({ package: packageAddress, diff --git a/sdk/pas/src/derivation.ts b/sdk/pas/src/derivation.ts index d2f8170..a46324f 100644 --- a/sdk/pas/src/derivation.ts +++ b/sdk/pas/src/derivation.ts @@ -34,25 +34,25 @@ export function deriveChestAddress(owner: string, packageConfig: PASPackageConfi } /** - * Derives the rule address for a given asset type T. + * Derives the policy address for a given asset type T. * - * Rules are derived using the namespace UID and a RuleKey(). - * The key structure in Move is: `RuleKey()` + * Policies are derived using the namespace UID and a PolicyKey(). + * The key structure in Move is: `PolicyKey()` * * @param assetType - The full type of the asset (e.g., "0x2::sui::SUI") * @param packageConfig - PAS package configuration - * @returns The derived rule object ID + * @returns The derived policy object ID */ -export function deriveRuleAddress(assetType: string, packageConfig: PASPackageConfig): string { +export function derivePolicyAddress(assetType: string, packageConfig: PASPackageConfig): string { const { packageId, namespaceId } = packageConfig; - // RuleKey is a phantom type with no fields, so the serialized key is empty + // PolicyKey is a phantom type with no fields, so the serialized key is empty // In BCS, an empty struct is serialized as 0 bytes - const ruleKeyBcs = new Uint8Array([0]); + const policyKeyBcs = new Uint8Array([0]); // The type tag includes the asset type as a generic parameter - const typeTag = `${packageId}::keys::RuleKey<${assetType}>`; - return deriveObjectID(namespaceId, typeTag, ruleKeyBcs); + const typeTag = `${packageId}::keys::PolicyKey<${assetType}>`; + return deriveObjectID(namespaceId, typeTag, policyKeyBcs); } /** diff --git a/sdk/pas/src/error.ts b/sdk/pas/src/error.ts index 9abdf65..e0b6ab0 100644 --- a/sdk/pas/src/error.ts +++ b/sdk/pas/src/error.ts @@ -11,9 +11,9 @@ export class PASClientError extends Error { } } -export class RuleNotFoundError extends PASClientError { +export class PolicyNotFoundError extends PASClientError { constructor(assetType: string, message?: string) { - super(message ?? `Rule not found for asset type ${assetType}.`); - this.name = 'RuleNotFoundError'; + super(message ?? `Policy not found for asset type ${assetType}.`); + this.name = 'PolicyNotFoundError'; } } diff --git a/sdk/pas/src/intents.ts b/sdk/pas/src/intents.ts index b6ad18e..925f05e 100644 --- a/sdk/pas/src/intents.ts +++ b/sdk/pas/src/intents.ts @@ -16,11 +16,11 @@ import { normalizeStructTag } from '@mysten/sui/utils'; import { deriveChestAddress, - deriveRuleAddress, + derivePolicyAddress, deriveTemplateAddress, deriveTemplateRegistryAddress, } from './derivation.js'; -import { PASClientError, RuleNotFoundError } from './error.js'; +import { PASClientError, PolicyNotFoundError } from './error.js'; import { buildMoveCallCommandFromTemplate, getCommandFromTemplate, @@ -201,7 +201,7 @@ class Resolver { readonly objects: Map; /** Pre-fetched template dynamic field objects. */ readonly templates: Map; - /** Pre-parsed template lookup: ruleId:actionType -> approval type names. */ + /** Pre-parsed template lookup: policyId:actionType -> approval type names. */ readonly templateApprovals: Map; /** Chest existence / creation tracking. */ readonly chests: Map; @@ -315,8 +315,8 @@ class Resolver { // -- Template resolution (synchronous, all data pre-fetched) ------------- - resolveTemplateCommands(ruleObjectId: string, actionType: PASActionType) { - const cacheKey = `${ruleObjectId}:${actionType}`; + resolveTemplateCommands(policyObjectId: string, actionType: PASActionType) { + const cacheKey = `${policyObjectId}:${actionType}`; const cached = this.#templateCommandsCache.get(cacheKey); if (cached) return cached; @@ -394,10 +394,10 @@ class Resolver { const fromChestId = deriveChestAddress(from, this.#config); const toChestId = deriveChestAddress(to, this.#config); - const ruleId = deriveRuleAddress(assetType, this.#config); - const ruleObject = this.getObjectOrThrow(ruleId, () => new RuleNotFoundError(assetType)); + const policyId = derivePolicyAddress(assetType, this.#config); + const policyObject = this.getObjectOrThrow(policyId, () => new PolicyNotFoundError(assetType)); const templateCmds = this.resolveTemplateCommands( - ruleObject.objectId, + policyObject.objectId, PASActionType.TransferFunds, ); @@ -409,7 +409,7 @@ class Resolver { ); commands.push(...fromChestCommands); - const ruleArg = this.addObjectInput(ruleId); + const policyArg = this.addObjectInput(policyId); // chest::new_auth const authIdx = baseIdx + commands.length; @@ -446,7 +446,7 @@ class Resolver { addInput: (type, arg) => this.addTemplateInput(type, arg), senderChest: fromChestArg, receiverChest: toChestArg, - rule: ruleArg, + policy: policyArg, request: requestArg, systemType: assetType, }), @@ -460,7 +460,7 @@ class Resolver { package: this.#config.packageId, module: 'transfer_funds', function: 'resolve', - arguments: [requestArg, ruleArg], + arguments: [requestArg, policyArg], typeArguments: [normalizeStructTag(assetType)], }), ); @@ -470,8 +470,8 @@ class Resolver { /** * Builds commands for both restricted and unrestricted unlock flows. - * Restricted: requires a Rule, runs issuer approval templates, then resolve. - * Unrestricted: no Rule needed, calls resolve_unrestricted directly. + * Restricted: requires a Policy, runs issuer approval templates, then resolve. + * Unrestricted: no Policy needed, calls resolve_unrestricted directly. */ buildUnlockFunds( data: UnlockFundsIntentData | UnlockUnrestrictedFundsIntentData, @@ -479,30 +479,30 @@ class Resolver { ): BuildResult { const { from, assetType, amount } = data; const fromChestId = deriveChestAddress(from, this.#config); - const ruleId = deriveRuleAddress(assetType, this.#config); + const policyId = derivePolicyAddress(assetType, this.#config); const isRestricted = data.action === 'unlockFunds'; if (isRestricted) { this.getObjectOrThrow( - ruleId, + policyId, () => new PASClientError( - `Rule does not exist for asset type ${assetType}. ` + + `Policy does not exist for asset type ${assetType}. ` + `That means that the issuer has not yet enabled funds management for this asset. ` + `If this is a non-managed asset, you can use the unrestricted unlock flow by calling unlockUnrestrictedFunds() instead.`, ), ); } else { - if (this.objects.get(ruleId) !== null) { + if (this.objects.get(policyId) !== null) { throw new PASClientError( - `A rule exists for asset type ${assetType}. That means that the issuer has enabled funds management for this asset and you can no longer use the unrestricted unlock flow.`, + `A policy exists for asset type ${assetType}. That means that the issuer has enabled funds management for this asset and you can no longer use the unrestricted unlock flow.`, ); } } const [fromChestArg, commands] = this.resolveChestArg(fromChestId, from, baseIdx); - const ruleArg = isRestricted ? this.addObjectInput(ruleId) : undefined; + const policyArg = isRestricted ? this.addObjectInput(policyId) : undefined; // chest::new_auth const authIdx = baseIdx + commands.length; @@ -533,13 +533,13 @@ class Resolver { if (isRestricted) { // Issuer-defined approval commands from templates - const templateCmds = this.resolveTemplateCommands(ruleId, PASActionType.UnlockFunds); + const templateCmds = this.resolveTemplateCommands(policyId, PASActionType.UnlockFunds); for (const templateCmd of templateCmds) { commands.push( buildMoveCallCommandFromTemplate(templateCmd, { addInput: (type, arg) => this.addTemplateInput(type, arg), senderChest: fromChestArg, - rule: ruleArg, + policy: policyArg, request: requestArg, systemType: assetType, }), @@ -553,7 +553,7 @@ class Resolver { package: this.#config.packageId, module: 'unlock_funds', function: 'resolve', - arguments: [requestArg, ruleArg!], + arguments: [requestArg, policyArg!], typeArguments: [normalizeStructTag(assetType)], }), ); @@ -630,7 +630,7 @@ function collectIntentData(commands: readonly Command[]): IntentDataCollection | const toId = deriveChestAddress(data.to, cfg); objectIds.add(fromId); objectIds.add(toId); - objectIds.add(deriveRuleAddress(data.assetType, cfg)); + objectIds.add(derivePolicyAddress(data.assetType, cfg)); chestRequests.set(fromId, { owner: data.from }); chestRequests.set(toId, { owner: data.to }); break; @@ -639,7 +639,7 @@ function collectIntentData(commands: readonly Command[]): IntentDataCollection | case 'unlockUnrestrictedFunds': { const fromId = deriveChestAddress(data.from, cfg); objectIds.add(fromId); - objectIds.add(deriveRuleAddress(data.assetType, cfg)); + objectIds.add(derivePolicyAddress(data.assetType, cfg)); chestRequests.set(fromId, { owner: data.from }); break; } @@ -707,15 +707,15 @@ async function initializeContext( if (!actionType || !assetType) continue; - const ruleId = deriveRuleAddress(assetType, config); - const key = `${ruleId}:${actionType}`; + const policyId = derivePolicyAddress(assetType, config); + const key = `${policyId}:${actionType}`; if (seen.has(key)) continue; seen.add(key); - const ruleObject = objects.get(ruleId); - if (!ruleObject) continue; + const policyObject = objects.get(policyId); + if (!policyObject) continue; - const approvalTypeNames = getRequiredApprovals(ruleObject, actionType); + const approvalTypeNames = getRequiredApprovals(policyObject, actionType); if (!approvalTypeNames?.length) continue; const templatesId = deriveTemplateRegistryAddress(config); diff --git a/sdk/pas/src/resolution.ts b/sdk/pas/src/resolution.ts index 96d5d37..b78ea16 100644 --- a/sdk/pas/src/resolution.ts +++ b/sdk/pas/src/resolution.ts @@ -8,7 +8,7 @@ import { normalizeStructTag } from '@mysten/sui/utils'; import { Field } from './bcs.js'; import { TypeName } from './contracts/pas/deps/std/type_name.js'; -import { Rule } from './contracts/pas/rule.js'; +import { Policy } from './contracts/pas/policy.js'; import { Command, MoveCall } from './contracts/ptb/ptb.js'; import { PASClientError } from './error.js'; @@ -17,7 +17,7 @@ const OBJECT_BY_TYPE_EXT = 'object_by_type'; const RECEIVING_BY_ID_EXT = 'receiving_by_id'; /** - * Supported PAS action types that can be resolved via Rules. + * Supported PAS action types that can be resolved via Policies. */ export enum PASActionType { /** Transfer funds between chests */ @@ -29,22 +29,22 @@ export enum PASActionType { } /** - * Parses the Rule object to extract the required approval type names for a given action. + * Parses the Policy object to extract the required approval type names for a given action. * - * The Rule's `required_approvals` is a `VecMap>` where: + * The Policy's `required_approvals` is a `VecMap>` where: * - Key is the action name (e.g., "transfer_funds") * - Value is a set of approval TypeNames that must be satisfied * - * @param ruleObject - The Rule object fetched with content + * @param policyObject - The Policy object fetched with content * @returns The list of approval TypeName strings for the given action, or undefined if not found */ export function getRequiredApprovals( - ruleObject: SuiClientTypes.Object<{ content: true }>, + policyObject: SuiClientTypes.Object<{ content: true }>, actionType: PASActionType, ): string[] | undefined { - const rule = Rule.parse(ruleObject.content); + const policy = Policy.parse(policyObject.content); - const entry = rule.required_approvals.contents.find((e) => e.key === actionType); + const entry = policy.required_approvals.contents.find((e) => e.key === actionType); if (!entry) return undefined; @@ -93,8 +93,8 @@ interface RawCommandBuildArgs { senderChest?: Argument; /** The receiver chest argument (already resolved) */ receiverChest?: Argument; - /** The rule argument (already resolved) */ - rule?: Argument; + /** The policy argument (already resolved) */ + policy?: Argument; /** The request argument (already resolved) */ request?: Argument; /** The system type T (e.g., "0x2::sui::SUI") */ @@ -105,7 +105,7 @@ interface RawCommandBuildArgs { * Builds a `Command` (TransactionCommands.MoveCall) from a parsed template command, * suitable for use with `transactionData.replaceCommand()`. * - * Resolves template argument placeholders (pas:request, pas:rule, etc.) into + * Resolves template argument placeholders (pas:request, pas:policy, etc.) into * concrete Argument references, and converts object/pure inputs via the provided * `addInput` callback. * @@ -209,7 +209,7 @@ export function buildMoveCallCommandFromTemplate( if (!command.module_name || !command.function) throw new PASClientError( - 'Module name or function name is missing from the on-chain rule. This means that the issuer has not set up the rule correctly.', + 'Module name or function name is missing from the on-chain policy. This means that the issuer has not set up the policy correctly.', ); return TransactionCommands.MoveCall({ @@ -226,9 +226,9 @@ function resolveRawPasRequest(args: RawCommandBuildArgs, value: string): Argumen case 'pas:request': if (!args.request) throw new PASClientError(`Request is not set in the context.`); return args.request; - case 'pas:rule': - if (!args.rule) throw new PASClientError(`Rule is not set in the context.`); - return args.rule; + case 'pas:policy': + if (!args.policy) throw new PASClientError(`Policy is not set in the context.`); + return args.policy; case 'pas:sender_chest': if (!args.senderChest) throw new PASClientError(`Sender chest is not set in the context.`); return args.senderChest; diff --git a/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move b/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move index 20f4266..99bc5d8 100644 --- a/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move +++ b/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move @@ -4,13 +4,13 @@ /// Demo USD asset for testing the PAS SDK. /// /// This module defines a DEMO_USD witness type that gets registered in the PAS system -/// during package initialization. It sets up a Rule with resolution commands for +/// during package initialization. It sets up a Policy with resolution commands for /// TransferFunds and UnlockFunds actions. module demo_usd::demo_usd; use pas::namespace::Namespace; +use pas::policy::{Self, Policy, PolicyCap}; use pas::request::Request; -use pas::rule::{Self, Rule, RuleCap}; use pas::templates::Templates; use pas::transfer_funds::TransferFunds; use ptb::ptb; @@ -37,7 +37,7 @@ public struct Faucet has key { id: UID, cap: TreasuryCap, metadata: MetadataCap, - rule_cap: Option>, + policy_cap: Option>, } /// Stamp used in PAS for authorizing any admin action. @@ -70,19 +70,19 @@ fun init(otw: DEMO_USD, ctx: &mut TxContext) { id: object::new(ctx), cap, metadata, - rule_cap: option::none(), + policy_cap: option::none(), }); } entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &mut Faucet) { - let (mut rule, cap) = rule::new(namespace, internal::permit()); + let (mut policy, cap) = policy::new(namespace, internal::permit()); // Enable funds management (with clawbacks!) - rule.enable_funds_management(&mut faucet.cap, true); + policy.enable_funds_management(&mut faucet.cap, true); - rule.set_required_approval<_, TransferApproval>(&cap, "transfer_funds"); + policy.set_required_approval<_, TransferApproval>(&cap, "transfer_funds"); - faucet.rule_cap.fill(cap); + faucet.policy_cap.fill(cap); let type_name = type_name::with_defining_ids(); @@ -95,11 +95,11 @@ entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &m ); templates.set_template_command(internal::permit(), cmd); - rule.share(); + policy.share(); } /// starts using v2 approve transfer to test upgradeability. -public fun use_v2(rule: &mut Rule, templates: &mut Templates, faucet: &mut Faucet) { +public fun use_v2(policy: &mut Policy, templates: &mut Templates, faucet: &mut Faucet) { let cmd = ptb::move_call( type_name::with_defining_ids().address_string().to_string(), "demo_usd", @@ -110,7 +110,10 @@ public fun use_v2(rule: &mut Rule, templates: &mut Templates, faucet: templates.set_template_command(internal::permit(), cmd); - rule.set_required_approval<_, TransferApprovalV2>(faucet.rule_cap.borrow(), "transfer_funds"); + policy.set_required_approval<_, TransferApprovalV2>( + faucet.policy_cap.borrow(), + "transfer_funds", + ); } /// Resolver function for transfer requests - simply approves all transfers diff --git a/sdk/pas/test/e2e/demoUsd.ts b/sdk/pas/test/e2e/demoUsd.ts index 3416ba8..97052b6 100644 --- a/sdk/pas/test/e2e/demoUsd.ts +++ b/sdk/pas/test/e2e/demoUsd.ts @@ -14,13 +14,13 @@ export class DemoUsdTestHelpers { get pub() { if (!this.#publicationData) { - throw new Error('Publication data not found. Call `createRule` first.'); + throw new Error('Publication data not found. Call `createPolicy` first.'); } return this.#publicationData; } - // setup the rule - async createRule() { + // setup the policy + async createPolicy() { if (this.#publicationData) { return this.#publicationData; } @@ -76,14 +76,14 @@ export class DemoUsdTestHelpers { } async upgradeToV2() { - const ruleId = this.toolbox.client.pas.deriveRuleAddress(this.demoUsdAssetType); + const policyId = this.toolbox.client.pas.derivePolicyAddress(this.demoUsdAssetType); const templateRegistryId = this.toolbox.client.pas.deriveTemplateRegistryAddress(); const faucetId = this.pub.createdObjects.find((o) => o.type.endsWith('demo_usd::Faucet'))!.id; const tx = new Transaction(); tx.moveCall({ target: `${this.pub.originalId}::demo_usd::use_v2`, - arguments: [tx.object(ruleId), tx.object(templateRegistryId), tx.object(faucetId)], + arguments: [tx.object(policyId), tx.object(templateRegistryId), tx.object(faucetId)], }); await this.toolbox.executeTransaction(tx); } diff --git a/sdk/pas/test/e2e/e2e.isolated.test.ts b/sdk/pas/test/e2e/e2e.isolated.test.ts index 0373e32..6baf826 100644 --- a/sdk/pas/test/e2e/e2e.isolated.test.ts +++ b/sdk/pas/test/e2e/e2e.isolated.test.ts @@ -53,7 +53,7 @@ describe.concurrent( const { balance: chestBalanceAfterTransfer } = await toolbox.getBalance(chestId, suiTypeName); expect(Number(chestBalanceAfterTransfer.balance)).toBe(1_000_000_000); - // try to do an unlock but it should fail because `rule` for Sui does not exist. + // try to do an unlock but it should fail because `policy` for Sui does not exist. const tx = new Transaction(); tx.add( toolbox.client.pas.tx.unlockFunds({ @@ -64,7 +64,7 @@ describe.concurrent( ); // Should fail because SUI is not a managed asset await expect(toolbox.executeTransaction(tx)).rejects.toThrowError( - 'Rule does not exist for asset type ', + 'Policy does not exist for asset type ', ); // Now let's unlock funds properly. @@ -88,10 +88,10 @@ describe.concurrent( expect(Number(chestBalanceAfterUnlock.balance)).toBe(0); }); - it('Should be able to transfer between chests, going through the rule of the issuer;', async () => { + it('Should be able to transfer between chests, going through the policy of the issuer;', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x2'); @@ -136,7 +136,7 @@ describe.concurrent( it('Should be able to create the recipient chest if it does not exist ahead of time', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x2'); @@ -176,7 +176,7 @@ describe.concurrent( it('Should deduplicate chest creation when multiple intents reference the same non-existent chests', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); // Sender is the test keypair (required for Auth), receiver is fresh. const sender = toolbox.address(); @@ -259,7 +259,7 @@ describe.concurrent( it('v1 approval rejects transfers over 10K', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x3'); @@ -289,7 +289,7 @@ describe.concurrent( it('self-transfer is rejected (same chest cannot be borrowed mutably twice)', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const addr = toolbox.address(); const chestId = toolbox.client.pas.deriveChestAddress(addr); @@ -319,7 +319,7 @@ describe.concurrent( it('Should fail to transfer between chests, if there are not enough funds in the source chest', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x2'); @@ -354,7 +354,7 @@ describe.concurrent( it('use_v2 upgrades approval logic and the resolver picks up the new template', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x3'); @@ -388,8 +388,8 @@ describe.concurrent( const toolbox = await setupToolbox(); const asset1 = new DemoUsdTestHelpers(toolbox, 'demo_usd_1'); const asset2 = new DemoUsdTestHelpers(toolbox, 'demo_usd_2'); - await asset1.createRule(); - await asset2.createRule(); + await asset1.createPolicy(); + await asset2.createPolicy(); // Upgrade asset2 to v2 so the two assets use completely different approval code paths. await asset2.upgradeToV2(); @@ -462,7 +462,7 @@ describe.concurrent( it('v2 approval rejects transfers to 0x2', async () => { const toolbox = await setupToolbox(); const demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); const from = toolbox.address(); const to = normalizeSuiAddress('0x2'); diff --git a/sdk/pas/test/e2e/e2e.shared.test.ts b/sdk/pas/test/e2e/e2e.shared.test.ts index 5d545a1..debd61c 100644 --- a/sdk/pas/test/e2e/e2e.shared.test.ts +++ b/sdk/pas/test/e2e/e2e.shared.test.ts @@ -15,7 +15,7 @@ describe('e2e tests with shared PAS package (all tests run in the same PAS packa beforeAll(async () => { toolbox = await setupToolbox(); demoUsd = new DemoUsdTestHelpers(toolbox); - await demoUsd.createRule(); + await demoUsd.createPolicy(); }); it('Should not be able to unlock restricted funds (e.g. DEMO_USD).', async () => { @@ -64,17 +64,17 @@ describe('e2e tests with shared PAS package (all tests run in the same PAS packa ); }); - it('derivations work as expected for rules', async () => { - const ruleObjectId = toolbox.client.pas.deriveRuleAddress(demoUsd.demoUsdAssetType); + it('derivations work as expected for policies', async () => { + const policyObjectId = toolbox.client.pas.derivePolicyAddress(demoUsd.demoUsdAssetType); - const { object: ruleObject } = await toolbox.client.core.getObject({ - objectId: ruleObjectId, + const { object: policyObject } = await toolbox.client.core.getObject({ + objectId: policyObjectId, include: { content: true }, }); - expect(ruleObject).toBeDefined(); - expect(ruleObject.type).toBe( - `${toolbox.client.pas.getPackageConfig().packageId}::rule::Rule<${demoUsd.pub.originalId}::demo_usd::DEMO_USD>`, + expect(policyObject).toBeDefined(); + expect(policyObject.type).toBe( + `${toolbox.client.pas.getPackageConfig().packageId}::policy::Policy<${demoUsd.pub.originalId}::demo_usd::DEMO_USD>`, ); }); }); diff --git a/sdk/pas/test/unit/derivation.test.ts b/sdk/pas/test/unit/derivation.test.ts index 72dc9ca..1251c07 100644 --- a/sdk/pas/test/unit/derivation.test.ts +++ b/sdk/pas/test/unit/derivation.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'; -import { deriveChestAddress, deriveRuleAddress } from '../../src/derivation.js'; +import { deriveChestAddress, derivePolicyAddress } from '../../src/derivation.js'; import type { PASPackageConfig } from '../../src/types.js'; describe('PAS Object Derivation', () => { @@ -59,52 +59,55 @@ describe('PAS Object Derivation', () => { }); }); - describe('deriveRuleAddress', () => { - it('should derive rule address for SUI', () => { - const ruleId = deriveRuleAddress('0x2::sui::SUI', packageConfig); - expect(ruleId).toMatchInlineSnapshot( + describe('derivePolicyAddress', () => { + it('should derive policy address for SUI', () => { + const policyId = derivePolicyAddress('0x2::sui::SUI', packageConfig); + expect(policyId).toMatchInlineSnapshot( `"0xa9b18997ebd455cc62ff1474acbc9c2eeb0b8a0841c4d54844d57a9db0ab9930"`, ); }); - it('should derive rule address for custom token', () => { - const ruleId = deriveRuleAddress('0x123::custom::TOKEN', packageConfig); - expect(ruleId).toMatchInlineSnapshot( + it('should derive policy address for custom token', () => { + const policyId = derivePolicyAddress('0x123::custom::TOKEN', packageConfig); + expect(policyId).toMatchInlineSnapshot( `"0xb458a02e0ac6615ef4101384387fde5f3cc16132a9ce936e53623faa55f51246"`, ); }); - it('should derive rule address for USDC', () => { - const ruleId = deriveRuleAddress( + it('should derive policy address for USDC', () => { + const policyId = derivePolicyAddress( '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC', packageConfig, ); - expect(ruleId).toMatchInlineSnapshot( + expect(policyId).toMatchInlineSnapshot( `"0x148ab4dc1c3e916733cdbd0142f7f9425c8752acea46f35875ff84cf273043a9"`, ); }); - it('should derive rule address for different namespace', () => { + it('should derive policy address for different namespace', () => { const config = { ...packageConfig, namespaceId: '0xdef' }; - const ruleId = deriveRuleAddress('0x2::sui::SUI', config); - expect(ruleId).toMatchInlineSnapshot( + const policyId = derivePolicyAddress('0x2::sui::SUI', config); + expect(policyId).toMatchInlineSnapshot( `"0x3d1cb3fdba6ce2c84bbbd956a6250c10b7aaf18e739e70c147ffa54aa142ac48"`, ); }); it('should handle complex generic types', () => { - const ruleId = deriveRuleAddress('0x2::coin::Coin<0x123::my_token::MY_TOKEN>', packageConfig); - expect(ruleId).toMatchInlineSnapshot( + const policyId = derivePolicyAddress( + '0x2::coin::Coin<0x123::my_token::MY_TOKEN>', + packageConfig, + ); + expect(policyId).toMatchInlineSnapshot( `"0x6e757f34b868c2856c203524b69da114ae0029817b8f40c2a0c2c690f34ce30d"`, ); }); it('should handle nested generics', () => { - const ruleId = deriveRuleAddress( + const policyId = derivePolicyAddress( '0x1::option::Option<0x2::coin::Coin<0x2::sui::SUI>>', packageConfig, ); - expect(ruleId).toMatchInlineSnapshot( + expect(policyId).toMatchInlineSnapshot( `"0xbbb713d47e9ef9630ff158288e422afefb954c62041c4f7b437805397c2ee6f2"`, ); });