Skip to content

Commit

Permalink
Merge pull request #93 from onflow/add-hybrid-custody
Browse files Browse the repository at this point in the history
Add HybridCustody dependencies & cross-account listing txn
  • Loading branch information
sisyphusSmiling authored Dec 19, 2023
2 parents 5d0c203 + 12760b4 commit abe3fbb
Show file tree
Hide file tree
Showing 23 changed files with 2,753 additions and 28 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
lib/js/test/node_modules/*
lib/js/test/node_modules/*
.idea
*.pkey
*.pub
.env
174 changes: 174 additions & 0 deletions contracts/hybrid-custody/CapabilityDelegator.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/// CapabilityDelegator is a contract used to share Capabiltities to other accounts. It is used by the
/// HybridCustody contract to allow more flexible sharing of Capabilities when an app wants to share things
/// that aren't the NFT-standard interface types.
///
/// Inside of CapabilityDelegator is a resource called `Delegator` which maintains a mapping of public and private
/// Capabilities. They cannot and should not be mixed. A public `Delegator` is able to be borrowed by anyone, whereas a
/// private `Delegator` can only be borrowed from the child account when you have access to the full `ChildAccount`
/// resource.
///
pub contract CapabilityDelegator {

/* --- Canonical Paths --- */
//
pub let StoragePath: StoragePath
pub let PrivatePath: PrivatePath
pub let PublicPath: PublicPath

/* --- Events --- */
//
pub event DelegatorCreated(id: UInt64)
pub event DelegatorUpdated(id: UInt64, capabilityType: Type, isPublic: Bool, active: Bool)

/// Private interface for Capability retrieval
///
pub resource interface GetterPrivate {
pub fun getPrivateCapability(_ type: Type): Capability? {
post {
result == nil || type.isSubtype(of: result.getType()): "incorrect returned capability type"
}
}
pub fun findFirstPrivateType(_ type: Type): Type?
pub fun getAllPrivate(): [Capability]
}

/// Exposes public Capability retrieval
///
pub resource interface GetterPublic {
pub fun getPublicCapability(_ type: Type): Capability? {
post {
result == nil || type.isSubtype(of: result.getType()): "incorrect returned capability type "
}
}

pub fun findFirstPublicType(_ type: Type): Type?
pub fun getAllPublic(): [Capability]
}

/// This Delegator is used to store Capabilities, partitioned by public and private access with corresponding
/// GetterPublic and GetterPrivate conformances.AccountCapabilityController
///
pub resource Delegator: GetterPublic, GetterPrivate {
access(self) let privateCapabilities: {Type: Capability}
access(self) let publicCapabilities: {Type: Capability}

// ------ Begin Getter methods
//
/// Returns the public Capability of the given Type if it exists
///
pub fun getPublicCapability(_ type: Type): Capability? {
return self.publicCapabilities[type]
}

/// Returns the private Capability of the given Type if it exists
///
///
/// @param type: Type of the Capability to retrieve
/// @return Capability of the given Type if it exists, nil otherwise
///
pub fun getPrivateCapability(_ type: Type): Capability? {
return self.privateCapabilities[type]
}

/// Returns all public Capabilities
///
/// @return List of all public Capabilities
///
pub fun getAllPublic(): [Capability] {
return self.publicCapabilities.values
}

/// Returns all private Capabilities
///
/// @return List of all private Capabilities
///
pub fun getAllPrivate(): [Capability] {
return self.privateCapabilities.values
}

/// Returns the first public Type that is a subtype of the given Type
///
/// @param type: Type to check for subtypes
/// @return First public Type that is a subtype of the given Type, nil otherwise
///
pub fun findFirstPublicType(_ type: Type): Type? {
for t in self.publicCapabilities.keys {
if t.isSubtype(of: type) {
return t
}
}

return nil
}

/// Returns the first private Type that is a subtype of the given Type
///
/// @param type: Type to check for subtypes
/// @return First private Type that is a subtype of the given Type, nil otherwise
///
pub fun findFirstPrivateType(_ type: Type): Type? {
for t in self.privateCapabilities.keys {
if t.isSubtype(of: type) {
return t
}
}

return nil
}
// ------- End Getter methods
/// Adds a Capability to the Delegator
///
/// @param cap: Capability to add
/// @param isPublic: Whether the Capability should be public or private
///
pub fun addCapability(cap: Capability, isPublic: Bool) {
pre {
cap.check<&AnyResource>(): "Invalid Capability provided"
}
if isPublic {
self.publicCapabilities.insert(key: cap.getType(), cap)
} else {
self.privateCapabilities.insert(key: cap.getType(), cap)
}
emit DelegatorUpdated(id: self.uuid, capabilityType: cap.getType(), isPublic: isPublic, active: true)
}

/// Removes a Capability from the Delegator
///
/// @param cap: Capability to remove
///
pub fun removeCapability(cap: Capability) {
if let removedPublic = self.publicCapabilities.remove(key: cap.getType()) {
emit DelegatorUpdated(id: self.uuid, capabilityType: cap.getType(), isPublic: true, active: false)
}

if let removedPrivate = self.privateCapabilities.remove(key: cap.getType()) {
emit DelegatorUpdated(id: self.uuid, capabilityType: cap.getType(), isPublic: false, active: false)
}
}

init() {
self.privateCapabilities = {}
self.publicCapabilities = {}
}
}

/// Creates a new Delegator and returns it
///
/// @return Newly created Delegator
///
pub fun createDelegator(): @Delegator {
let delegator <- create Delegator()
emit DelegatorCreated(id: delegator.uuid)
return <- delegator
}

init() {
let identifier = "CapabilityDelegator_".concat(self.account.address.toString())
self.StoragePath = StoragePath(identifier: identifier)!
self.PrivatePath = PrivatePath(identifier: identifier)!
self.PublicPath = PublicPath(identifier: identifier)!
}
}

105 changes: 105 additions & 0 deletions contracts/hybrid-custody/CapabilityFactory.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/// # Capability Factory
///
/// This contract defines a Factory interface and a Manager resource to contain Factory implementations, as well as a
/// Getter interface for retrieval of contained Factories.
///
/// A Factory is defines a method getCapability() which defines the retrieval pattern of a Capability from a given
/// account at the specified path. This pattern arose out of a need to retrieve arbitrary & castable Capabilities from
/// an account under the static typing constraints inherent to Cadence.
///
/// The Manager resource is a container for Factories, and implements the Getter interface.
///
/// **Note:** It's generally an anti-pattern to pass around AuthAccount references; however, the need for castable
/// Capabilities is critical to the use case of Hybrid Custody. It's advised to use Factories sparingly and only for
/// cases where Capabilities must be castable by the caller.
///
pub contract CapabilityFactory {

pub let StoragePath: StoragePath
pub let PrivatePath: PrivatePath
pub let PublicPath: PublicPath

/// Factory structures a common interface for Capability retrieval from a given account at a specified path
///
pub struct interface Factory {
pub fun getCapability(acct: &AuthAccount, path: CapabilityPath): Capability
}

/// Getter defines an interface for retrieval of a Factory if contained within the implementing resource
///
pub resource interface Getter {
pub fun getSupportedTypes(): [Type]
pub fun getFactory(_ t: Type): {CapabilityFactory.Factory}?
}

/// Manager is a resource that contains Factories and implements the Getter interface for retrieval of contained
/// Factories
///
pub resource Manager: Getter {
/// Mapping of Factories indexed on Type of Capability they retrieve
pub let factories: {Type: {CapabilityFactory.Factory}}

/// Retrieves a list of Types supported by contained Factories
///
/// @return List of Types supported by the Manager
///
pub fun getSupportedTypes(): [Type] {
return self.factories.keys
}

/// Retrieves a Factory from the Manager, returning it or nil if it doesn't exist
///
/// @param t: Type the Factory is indexed on
///
pub fun getFactory(_ t: Type): {CapabilityFactory.Factory}? {
return self.factories[t]
}

/// Adds a Factory to the Manager, conditioned on the Factory not already existing
///
/// @param t: Type of Capability the Factory retrieves
/// @param f: Factory to add
///
pub fun addFactory(_ t: Type, _ f: {CapabilityFactory.Factory}) {
pre {
!self.factories.containsKey(t): "Factory of given type already exists"
}
self.factories[t] = f
}

/// Updates a Factory in the Manager, adding if it didn't already exist
///
/// @param t: Type of Capability the Factory retrieves
/// @param f: Factory to replace existing Factory
///
pub fun updateFactory(_ t: Type, _ f: {CapabilityFactory.Factory}) {
self.factories[t] = f
}

/// Removes a Factory from the Manager, returning it or nil if it didn't exist
///
/// @param t: Type the Factory is indexed on
///
pub fun removeFactory(_ t: Type): {CapabilityFactory.Factory}? {
return self.factories.remove(key: t)
}

init () {
self.factories = {}
}
}

/// Creates a Manager resource
///
/// @return Manager resource
pub fun createFactoryManager(): @Manager {
return <- create Manager()
}

init() {
let identifier = "CapabilityFactory_".concat(self.account.address.toString())
self.StoragePath = StoragePath(identifier: identifier)!
self.PrivatePath = PrivatePath(identifier: identifier)!
self.PublicPath = PublicPath(identifier: identifier)!
}
}
Loading

0 comments on commit abe3fbb

Please sign in to comment.