Implementing the ID Pointer Pattern
In this tutorial, we will demonstrate how to implement the ID Pointer pattern in the Sui ecosystem. The ID Pointer pattern separates an object's main data and its accessors/capabilities by linking the latter to the original. This pattern can be used in various applications, such as issuing transferable capabilities for shared objects, splitting dynamic data and static data, and avoiding unnecessary type linking in generic applications.
For this example, we will implement a simple Lock and Key mechanics on Sui, where a Lock<T> is a shared object that can contain any object, and a Key is an owned object required to access the contents of the lock.
Follow these steps to implement the ID Pointer pattern:
- Create the lock_and_key module: Define a new module
examples::lock_and_keythat will implement the ID Pointer pattern.
module examples::lock_and_key {
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
use std::option::{Self, Option};
}
- Define constants and errors: Define error codes for the lock being empty, the key not matching the lock, and the lock being full.
const ELockIsEmpty: u64 = 0;
const EKeyMismatch: u64 = 1;
const ELockIsFull: u64 = 2;
- Define the Lock and Key structs: Create a
Lockstruct that stores any content inside it and aKeystruct that is linked to itsLockusing anIDfield.
struct Lock<T: store + key> has key {
id: UID,
locked: Option<T>
}
struct Key<phantom T: store + key> has key, store {
id: UID,
for: ID,
}
- Create the key_for function: Define a public function
key_forthat takes a reference to aKey<T>and returns the ID of the associatedLock.
public fun key_for<T: store + key>(key: &Key<T>): ID {
key.for
}
- Create the create function: Define an entry function
createthat takes an object of typeTand a mutable transaction context as arguments. It creates a newLockwith the given object and aKeylinked to the lock, then transfers the key to the transaction sender.
public entry fun create<T: store + key>(obj: T, ctx: &mut TxContext) {
let id = object::new(ctx);
let for = object::uid_to_inner(&id);
transfer::share_object(Lock<T> {
id,
locked: option::some(obj),
});
transfer::transfer(Key<T> {
for,
id: object::new(ctx)
}, tx_context::sender(ctx));
}
- Create the lock and unlock functions: Define entry functions
lockandunlockthat take an object of typeT, a mutable reference to aLock<T>, and a reference to aKey<T>as arguments. Thelockfunction locks the object inside the shared lock if it's empty and the key matches the lock. Theunlockfunction unlocks the lock and returns its contents if the key matches the lock and it's not empty.
public entry fun lock<T: store + key>(
obj: T,
lock: &mut Lock<T>,
key: &Key<T>,
) {
assert