-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from MystenLabs/ml/managed-names
Managed names (Enoki name delegation)
- Loading branch information
Showing
5 changed files
with
616 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# @generated by Move, please check-in and do not edit manually. | ||
|
||
[move] | ||
version = 0 | ||
manifest_digest = "7368941C2FB0FAED89AAC0463B63A3D6455F373C692F517FF0465426E66A9B90" | ||
deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" | ||
|
||
dependencies = [ | ||
{ name = "Sui" }, | ||
{ name = "suins" }, | ||
] | ||
|
||
[[move.package]] | ||
name = "MoveStdlib" | ||
source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/mainnet", subdir = "crates/sui-framework/packages/move-stdlib" } | ||
|
||
[[move.package]] | ||
name = "Sui" | ||
source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/mainnet", subdir = "crates/sui-framework/packages/sui-framework" } | ||
|
||
dependencies = [ | ||
{ name = "MoveStdlib" }, | ||
] | ||
|
||
[[move.package]] | ||
name = "suins" | ||
source = { local = "../suins" } | ||
|
||
dependencies = [ | ||
{ name = "Sui" }, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "managed_names" | ||
version = "0.0.1" | ||
|
||
[dependencies] | ||
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/mainnet" } | ||
suins = { local = "../suins" } | ||
|
||
[addresses] | ||
managed_names = "0x0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
|
||
/// A simple wrapper to allow having a `third-party` managed `SuinsRegistration` object | ||
/// without the danger of losing it. | ||
/// | ||
/// It's particularly useful for `Enoki<->SuiNS`, where the `SuinsRegistration` object | ||
/// will be owned by a multi-sig address (for any company), and Enoki addresses will be able to | ||
/// borrow it for creating subdomains. | ||
/// | ||
/// Also, instead of going the `cap` way, we go with the `address` way to remove any chance of | ||
/// equivocation, both for Enoki Backends + owner actions on the domain. | ||
/// | ||
/// Since `SuiNS` is required as a parameter to create subdomains anyways, | ||
/// we're also using it to store the managed names (to avoid using separate shared objects). | ||
/// | ||
module managed_names::managed { | ||
use std::vector; | ||
use std::string::{String}; | ||
use std::option::{Self, Option}; | ||
|
||
use sui::object::{Self, ID}; | ||
use sui::table::{Self, Table}; | ||
use sui::tx_context::{TxContext, sender}; | ||
use sui::clock::Clock; | ||
use sui::transfer; | ||
|
||
use suins::domain::{Self, Domain}; | ||
use suins::suins_registration::{Self, SuinsRegistration}; | ||
use suins::suins::{Self, SuiNS, AdminCap}; | ||
|
||
/// Tries to add an NFT that has expired. | ||
const EExpiredNFT: u64 = 1; | ||
/// Tries to add a name that already exists (that's impossible but protecting anyways). | ||
const EAlreadyExists: u64 = 2; | ||
/// Tries to borrow a name that doesn't exist in the managed registry | ||
const ENameNotExists: u64 = 3; | ||
/// Tries to do an unauthorized action on a name. | ||
const ENotAuthorized: u64 = 4; | ||
/// Tries to return an NFT that doesn't match the promise. | ||
const EInvalidReturnedNFT: u64 = 5; | ||
|
||
/// Authorizes the `ManagedNames` to add a `registry` under the main SuiNS object. | ||
struct ManagedNamesApp has drop {} | ||
|
||
|
||
/// The `registry` that holds the managed names per domain. | ||
/// To simplify, we can only hold a single managed name per domain. | ||
/// If a valid NFT is passed, the previous name is returned to the owner (who can burn it, as it's an expired one). | ||
struct ManagedNames has store { | ||
names: Table<Domain, ManagedName> | ||
} | ||
|
||
/// A managed name. | ||
/// `owner`: the only address that can get the `NFT` back | ||
/// `allowlist`: A list of allowed addresses (that can borrow + return the `NFT`) | ||
/// `nft`: The `SuinsRegistration` object that can be borrowed. | ||
struct ManagedName has store { | ||
owner: address, | ||
allowed_addresses: vector<address>, | ||
nft: Option<SuinsRegistration> | ||
} | ||
|
||
/// A hot-potato promise that the NFT will be returned upon borrowing. | ||
struct ReturnPromise { | ||
id: ID | ||
} | ||
|
||
// Create the store that will hold the managed names as an admin. | ||
public fun setup(self: &mut SuiNS, cap: &AdminCap, ctx: &mut TxContext) { | ||
suins::add_registry(cap, self, ManagedNames { | ||
names: table::new(ctx) | ||
}); | ||
} | ||
|
||
|
||
/// Attaches a `SuinsRegistration` object for usability from third-party addresses. | ||
public fun attach_managed_name( | ||
suins: &mut SuiNS, | ||
nft: SuinsRegistration, | ||
clock: &Clock, | ||
allowed_addresses: vector<address>, | ||
ctx: &mut TxContext | ||
) { | ||
assert!(!suins_registration::has_expired(&nft, clock), EExpiredNFT); | ||
|
||
let managed_names = managed_names_mut(suins); | ||
|
||
let domain = suins_registration::domain(&nft); | ||
|
||
// if the name exists. We check if it's expired, and return it to the owner. | ||
if(table::contains(&managed_names.names, domain)) { | ||
let existing = table::remove(&mut managed_names.names, domain); | ||
|
||
let ManagedName { nft, allowed_addresses: _, owner } = existing; | ||
|
||
let existing_nft = option::destroy_some(nft); | ||
|
||
assert!(suins_registration::has_expired(&existing_nft, clock), EAlreadyExists); | ||
// transfer it back to the owner. | ||
transfer::public_transfer(existing_nft, owner); | ||
}; | ||
|
||
// add the name to the managed names list. | ||
table::add(&mut managed_names.names, domain, ManagedName { | ||
owner: sender(ctx), | ||
allowed_addresses, | ||
nft: option::some(nft) | ||
}); | ||
} | ||
|
||
/// Allows the `owner` to remove a name from the managed system. | ||
public fun remove_attached_name( | ||
suins: &mut SuiNS, | ||
name: String, | ||
ctx: &mut TxContext | ||
): SuinsRegistration { | ||
let managed_names = managed_names_mut(suins); | ||
let domain = domain::new(name); | ||
|
||
assert!(table::contains(&managed_names.names, domain), ENameNotExists); | ||
let existing = table::remove(&mut managed_names.names, domain); | ||
|
||
assert!(is_owner(&existing, sender(ctx)), ENotAuthorized); | ||
|
||
let ManagedName { nft, allowed_addresses: _, owner: _ } = existing; | ||
|
||
option::destroy_some(nft) | ||
} | ||
|
||
|
||
/// Allow a list of addresses to borrow the `SuinsRegistration` object. | ||
public fun allow_addresses( | ||
suins: &mut SuiNS, | ||
name: String, | ||
addresses: vector<address>, | ||
ctx: &mut TxContext | ||
) { | ||
let existing = internal_get_managed_name(managed_names_mut(suins), domain::new(name)); | ||
assert!(is_owner(existing, sender(ctx)), ENotAuthorized); | ||
|
||
while(vector::length(&addresses) > 0) { | ||
let addr = vector::pop_back(&mut addresses); | ||
|
||
if(!vector::contains(&existing.allowed_addresses, &addr)) { | ||
vector::push_back(&mut existing.allowed_addresses, addr); | ||
} | ||
} | ||
} | ||
|
||
// Removes a list of addresses from the allow-list. | ||
public fun revoke_addresses( | ||
suins: &mut SuiNS, | ||
name: String, | ||
addresses: vector<address>, | ||
ctx: &mut TxContext | ||
) { | ||
let existing = internal_get_managed_name(managed_names_mut(suins), domain::new(name)); | ||
assert!(is_owner(existing, sender(ctx)), ENotAuthorized); | ||
|
||
while(vector::length(&addresses) > 0) { | ||
let addr = vector::pop_back(&mut addresses); | ||
|
||
let (has_address, index) = vector::index_of(&existing.allowed_addresses, &addr); | ||
|
||
if (has_address) { | ||
vector::remove(&mut existing.allowed_addresses, index); | ||
} | ||
} | ||
} | ||
|
||
/// Borrows the `SuinsRegistration` object. | ||
public fun borrow_val( | ||
suins: &mut SuiNS, | ||
name: String, | ||
ctx: &mut TxContext | ||
): (SuinsRegistration, ReturnPromise) { | ||
let existing = internal_get_managed_name(managed_names_mut(suins), domain::new(name)); | ||
|
||
assert!(is_authorized_address(existing, sender(ctx)), ENotAuthorized); | ||
|
||
let nft = option::extract(&mut existing.nft); | ||
let id = object::id(&nft); | ||
|
||
(nft, ReturnPromise { | ||
id | ||
}) | ||
} | ||
|
||
/// Returns the `SuinsRegistration` object back with the promise. | ||
public fun return_val( | ||
suins: &mut SuiNS, | ||
nft: SuinsRegistration, | ||
promise: ReturnPromise | ||
) { | ||
let ReturnPromise { id } = promise; | ||
assert!(object::id(&nft) == id, EInvalidReturnedNFT); | ||
|
||
let existing = internal_get_managed_name(managed_names_mut(suins), suins_registration::domain(&nft)); | ||
|
||
// return the NFT back. | ||
option::fill(&mut existing.nft, nft) | ||
} | ||
|
||
|
||
fun internal_get_managed_name(managed_names: &mut ManagedNames, domain: Domain): &mut ManagedName { | ||
assert!(table::contains(&managed_names.names, domain), ENameNotExists); | ||
|
||
table::borrow_mut(&mut managed_names.names, domain) | ||
} | ||
|
||
|
||
fun is_owner(self: &ManagedName, addr: address): bool { | ||
self.owner == addr | ||
} | ||
/// Check if an address is authorized for borrowing. | ||
fun is_authorized_address(self: &ManagedName, addr: address): bool { | ||
self.owner == addr || vector::contains(&self.allowed_addresses, &addr) | ||
} | ||
|
||
|
||
/// an immutable reference to the registry. | ||
fun managed_names(self: &SuiNS): &ManagedNames { | ||
suins::registry<ManagedNames>(self) | ||
} | ||
|
||
/// a mutable reference to the registry | ||
fun managed_names_mut(self: &mut SuiNS): &mut ManagedNames { | ||
suins::app_registry_mut<ManagedNamesApp, ManagedNames>(ManagedNamesApp {}, self) | ||
} | ||
} |
Oops, something went wrong.