From 523e24cf91a570b911763d13e47d4441ff013adb Mon Sep 17 00:00:00 2001 From: npty <78221556+npty@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:57:58 +0700 Subject: [PATCH] feat: use hot potato pattern to borrow cap (#96) --- .changeset/swift-socks-think.md | 5 ++ move/operators/sources/operators.move | 114 +++++++++++++------------- 2 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 .changeset/swift-socks-think.md diff --git a/.changeset/swift-socks-think.md b/.changeset/swift-socks-think.md new file mode 100644 index 00000000..56ea5674 --- /dev/null +++ b/.changeset/swift-socks-think.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': minor +--- + +Use hot potato pattern with `sui::borrow` package for loan out a capabilities in operators contract. diff --git a/move/operators/sources/operators.move b/move/operators/sources/operators.move index a68e262d..814927ed 100644 --- a/move/operators/sources/operators.move +++ b/move/operators/sources/operators.move @@ -2,6 +2,7 @@ module operators::operators { use sui::bag::{Self, Bag}; use sui::vec_set::{Self, VecSet}; use sui::event; + use sui::borrow::{Self, Borrow}; use std::ascii::String; use std::type_name; @@ -26,6 +27,8 @@ module operators::operators { operators: VecSet
, // map-like collection of capabilities stored as Sui objects caps: Bag, + // map-like collection of Referents storing loaned capabilities. Referents only get stored for the duration of the tx. + loaned_caps: Bag, } // ------ @@ -74,6 +77,7 @@ module operators::operators { id: object::new(ctx), operators: vec_set::empty(), caps: bag::new(ctx), + loaned_caps: bag::new(ctx), }); let cap = OwnerCap { @@ -121,30 +125,55 @@ module operators::operators { }); } - /// Allows an approved operator to borrow a capability by its ID. - public fun borrow_cap( - self: &Operators, + /// Allows an approved operator to temporarily loan out a capability by its ID. + /// The loaned capability must be restored by the end of the transaction. + public fun loan_cap( + self: &mut Operators, _operator_cap: &OperatorCap, cap_id: ID, ctx: &mut TxContext - ): &T { + ): (T, Borrow) { assert!(self.operators.contains(&ctx.sender()), EOperatorNotFound); assert!(self.caps.contains(cap_id), ECapNotFound); - &self.caps[cap_id] + // Remove the capability from the `Operators` struct to loan it out + let cap = self.caps.remove(cap_id); + + // Create a new `Referent` to store the loaned capability + let mut referent = borrow::new(cap, ctx); + + // Create a `Borrow` hot potato object from the `Referent` that needs to be returned within the same tx + let (loaned_cap, borrow_obj) = borrow::borrow(&mut referent); + + // Store the `Referent` in the `Operators` struct + self.loaned_caps.add(cap_id, referent); + + // Return a tuple of the borrowed capability and the Borrow hot potato object + (loaned_cap, borrow_obj) } - /// Allows an approved operator to borrow a capability by its ID. - public fun borrow_cap_mut( + /// Restores a previously loaned capability back to the `Operators` struct. + /// This function must be called before the end of the transaction to return the loaned capability. + public fun restore_cap( self: &mut Operators, _operator_cap: &OperatorCap, cap_id: ID, - ctx: &mut TxContext - ): &mut T { - assert!(self.operators.contains(&ctx.sender()), EOperatorNotFound); - assert!(self.caps.contains(cap_id), ECapNotFound); + loaned_cap: T, + borrow_obj: Borrow + ) { + assert!(self.loaned_caps.contains(cap_id), ECapNotFound); + + // Remove the `Referent` from the `Operators` struct + let mut referent = self.loaned_caps.remove(cap_id); - &mut self.caps[cap_id] + // Put back the borrowed capability and `T` capability into the `Referent` + borrow::put_back(&mut referent, loaned_cap, borrow_obj); + + // Unpack the `Referent` struct and get the `T` capability + let cap: T = borrow::destroy(referent); + + // Add the capability back to the `Operators` struct + self.caps.add(cap_id, cap); } /// Removes a capability from the `Operators` struct. @@ -167,15 +196,17 @@ module operators::operators { id: object::new(ctx), operators: vec_set::empty(), caps: bag::new(ctx), + loaned_caps: bag::new(ctx), } } #[test_only] fun destroy_operators(operators: Operators) { - let Operators { id, operators, caps } = operators; + let Operators { id, operators, caps, loaned_caps } = operators; id.delete(); caps.destroy_empty(); + loaned_caps.destroy_empty(); let mut keys = operators.into_keys(); @@ -255,14 +286,15 @@ module operators::operators { store_cap(&mut operators, &owner_cap, external_cap); assert!(operators.caps.contains(external_id), 0); - let borrowed_cap = borrow_cap(&operators, &operator_cap, external_id, ctx); - assert!(object::id(borrowed_cap) == external_id, 1); - - let borrowed_mut_cap = borrow_cap_mut(&mut operators, &operator_cap, external_id, ctx); - assert!(object::id(borrowed_mut_cap) == external_id, 1); + let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, external_id, ctx); + assert!(operators.loaned_caps.contains(external_id), 1); + assert!(!operators.caps.contains(external_id), 2); + restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); + assert!(!operators.loaned_caps.contains(external_id), 3); + assert!(operators.caps.contains(external_id), 2); let removed_cap = remove_cap(&mut operators, &owner_cap, external_id); - assert!(!operators.caps.contains(external_id), 2); + assert!(!operators.caps.contains(external_id), 3); destroy_operator_cap(operator_cap); destroy_owner_cap(owner_cap); @@ -297,7 +329,8 @@ module operators::operators { store_cap(&mut operators, &owner_cap, external_cap); remove_operator(&mut operators, &owner_cap, ctx.sender()); - borrow_cap(&operators, &operator_cap, external_id, ctx); + let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, external_id, ctx); + restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); destroy_operator_cap(operator_cap); destroy_owner_cap(owner_cap); @@ -314,45 +347,8 @@ module operators::operators { let operator_id = object::id(&operator_cap); - borrow_cap(&operators, &operator_cap, operator_id, ctx); - - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_operators(operators); - } - - #[test] - #[expected_failure(abort_code = EOperatorNotFound)] - fun test_borrow_cap_mut_not_operator() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); - let external_cap = new_owner_cap(ctx); - - let external_id = object::id(&external_cap); - - store_cap(&mut operators, &owner_cap, external_cap); - remove_operator(&mut operators, &owner_cap, ctx.sender()); - - borrow_cap_mut(&mut operators, &operator_cap, external_id, ctx); - - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_operators(operators); - } - - #[test] - #[expected_failure(abort_code = ECapNotFound)] - fun test_borrow_cap_mut_no_such_cap() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); - - let operator_id = object::id(&operator_cap); - - borrow_cap_mut(&mut operators, &operator_cap, operator_id, ctx); + let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, operator_id, ctx); + restore_cap(&mut operators, &operator_cap, operator_id, cap, loaned_cap); destroy_operator_cap(operator_cap); destroy_owner_cap(owner_cap);