contract-of

Retrieving the principal of a contract implementing a trait in Clarity smart contracts.

Function Signature

(contract-of <trait-reference>)
  • Input: A trait reference
  • Output: The principal of the contract implementing the trait

Why it matters

The contract-of function is crucial for:

  1. Retrieving the principal (address) of a contract implementing a specific trait.
  2. Enabling dynamic interactions with contracts based on traits.
  3. Implementing contract-agnostic functions that work with any contract adhering to a specific interface.
  4. Enhancing interoperability between contracts in a composable ecosystem.

When to use it

Use the contract-of function when you need to:

  • Get the actual contract address from a trait reference.
  • Perform operations that require the contract's principal, such as authorization checks.
  • Implement functions that can work with multiple contracts implementing the same trait.
  • Debug or log information about which contract is being interacted with.
  • Manage routing logic between different versions of contracts for upgradeable smart contracts.
  • Implement access control mechanisms to restrict function calls to designated contracts.

Best Practices

  • Use contract-of in combination with traits to create more flexible and composable smart contracts.
  • Remember that contract-of returns a principal, which can be used in other Clarity functions expecting a contract address.
  • Consider using contract-of when implementing proxy or router contracts that work with multiple similar contracts.
  • Be aware that contract-of can only be used with trait references, not with direct contract references.

Practical Example: Modular Approach to Extension Contracts

Let's implement a system where specific functions can only be called by designated extension contracts:

Define the Extension Trait

First, define a trait for the extension contract:

(define-trait extension-trait
	(
		(callback (principal (buff 34)) (response bool uint))
	)
)

Implement the Main Contract

Next, implement the main contract with the request-extension-callback function:

(use-trait extensionTrait .extension-trait.extension-trait)
(define-map Extensions principal bool)

(define-public (set-extension (extension principal) (enabled bool))
  (ok (map-set Extensions extension enabled))
)

(define-public (request-extension-callback (extension <extensionTrait>) (memo (buff 34)))
  (let
    (
      (sender tx-sender)
    )
    (asserts! (and (is-extension contract-caller) (is-eq contract-caller (contract-of extension))) (err u1))
    (as-contract (contract-call? extension callback sender memo))
  )
)

(define-read-only (is-extension (extension principal))
  (default-to false (map-get? Extensions extension))
)

Explanation

  1. Define the Extension Trait: The extensionTrait defines a callback function that the extension contract must implement.
  2. Data Map for Valid Extensions: Extensions is a map that stores the status (enabled/disabled) of extension contracts.
  3. Public Function set-extension: This function allows adding or removing an extension contract from the Extensions map.
  4. Public Function request-extension-callback: This function:
    • Retrieves the principal of the extension contract using contract-of.
    • Asserts that the caller is a valid extension and matches the extension principal.
    • Calls the callback function on the extension contract using contract-call?.
  5. Read-Only Function is-extension: This function checks if a given contract principal is in the Extensions map and is enabled.

Usage

  1. Set an Extension:

    (set-extension 'SP2J4ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6ZQ6 true)
  2. Request Extension Callback:

    (request-extension-callback .extension-contract (buff 34 "example memo"))

This example demonstrates:

  1. Using contract-of to get the principal of the extension contract implementing the extension trait.
  2. Implementing a read-only function to check if the caller is a valid extension.
  3. Restricting access to certain functions based on the authorized extension contracts.
  4. Allowing the authorized extension contracts to call specific functions.

Common Pitfalls

  1. Attempting to use contract-of with a direct contract reference instead of a trait reference.
  2. Forgetting that contract-of returns a principal, not a contract reference itself.
  3. Not handling potential errors when working with trait references that might not be properly initialized.
  • use-trait: Used to define trait references that can be used with contract-of.
  • contract-call?: Often used in combination with contract-of to call functions on the retrieved contract.
  • is-eq: Can be used to compare the returned principal with known contract addresses.

Conclusion

The contract-of function is a powerful tool for creating flexible and interoperable smart contracts in Clarity. By allowing contracts to dynamically retrieve the principal of trait-implementing contracts, it enables the creation of more generic and reusable code. When used effectively, contract-of can significantly enhance the composability and modularity of your smart contract ecosystem, especially in scenarios like access control management where restricting function calls to designated contracts is essential.