Skip to content

Commit

Permalink
NFT Library (#42)
Browse files Browse the repository at this point in the history
## Type of change

<!--Delete points that do not apply-->

- New feature

## Changes

The following changes have been made:

- Creates a modular NFT library with the following core attributes:
- `approve()` - Approves a user to transfer the token on the owner's
behalf
    - `approved()` - Returns the approved user for the token
    - `balance_of()` - Returns the number of tokens a user owns
- `is_approved_for_all()` - Returns whether a user is approved to
transfer all tokens on another user's behalf
    - `mint()` - Instantiates a new token
    - `owner_of()` - Returns the owner of a token
- `set_approval_for_all()` - Gives a user the ability to transfer all
tokens on another user's behalf
- `token_minted()` - Returns the number of tokens that have been minted
    - `transfer_to()` - Transfer a token from one owner to another
- The following extensions are available:
    - Administrator
         - `admin()` - Returns the administrator
         - `set_admin()` - Sets an administrator
    - Burnable
         - `burn()` - Deletes the token
    - Meta Data
         - `meta_data()` - Returns the token's stored metadata
         - `set_meta_data()` - Store new metadata for the token
    - Supply
        - `max_supply()` - Returns the maximum set supply
        - `set_max_supply()` - Assigns the maximum supply

## Notes

- More extensions may be available in the future if they are deemed
common additions to NFTs
- This PR bumps to repo from forc v0.30.0 to forc v0.30.1 for the latest
formatter bug fix

## Related Issues

<!--Delete everything after the "#" symbol and replace it with a number.
No spaces between hash and number-->

Closes #19
  • Loading branch information
bitzoic authored Nov 10, 2022
1 parent 701bb9b commit e2ff653
Show file tree
Hide file tree
Showing 51 changed files with 2,266 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
CARGO_TERM_COLOR: always
REGISTRY: ghcr.io
RUST_VERSION: 1.64.0
FORC_VERSION: 0.30.0
FORC_VERSION: 0.30.1
CORE_VERSION: 0.13.2
PATH_TO_SCRIPTS: .github/scripts

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<a href="https://github.com/FuelLabs/sway-libs/actions/workflows/ci.yml" alt="CI">
<img src="https://github.com/FuelLabs/sway-libs/actions/workflows/ci.yml/badge.svg" />
</a>
<a href="https://crates.io/crates/forc/0.30.0" alt="forc">
<img src="https://img.shields.io/badge/forc-v0.30.0-orange" />
<a href="https://crates.io/crates/forc/0.30.1" alt="forc">
<img src="https://img.shields.io/badge/forc-v0.30.1-orange" />
</a>
<a href="./LICENSE" alt="forc">
<img src="https://img.shields.io/github/license/FuelLabs/sway-libs" />
Expand All @@ -32,6 +32,7 @@ These libraries contain helper functions, generalized standards, and other tools
### Libraries

- [Binary Merkle Proof](./sway_libs/src/merkle_proof/) is used to verify Binary Merkle Trees computed off-chain.
- [Non-Fungible Token(NFT)](./sway_libs/src/nft/) is a token library which provides unqiue collectibles, identified and differentiated by token IDs.
- [String](./sway_libs/src/string/) is an interface to implement dynamic length strings that are UTF-8 encoded.

## Using a library
Expand All @@ -55,7 +56,7 @@ sway_libs::binary_merkle_proof::verify_proof;
```

> **Note**
> All projects currently use `forc v0.30.0`, `fuels-rs v0.28.0` and `fuel-core 0.13.2`.
> All projects currently use `forc v0.30.1`, `fuels-rs v0.28.0` and `fuel-core 0.13.2`.
## Contributing

Expand Down
1 change: 1 addition & 0 deletions sway_libs/src/lib.sw
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
library sway_libs;

dep merkle_proof/binary_merkle_proof;
dep nft/nft;
dep string/string;
76 changes: 76 additions & 0 deletions sway_libs/src/nft/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Overview

A non-fungible token (NFT) is a unique token that has an identifier which distinguishes itself from other tokens within the same token contract. Unlike Fuel's Native Assets, these tokens are not fungible with one another and may contain metadata or other traits giving them distinctive characteristics.

Some common applications of an NFT include artwork / collectibles, DeFi short positions, deeds, and more.

For more information please see the [specification](./SPECIFICATION.md).

# Using the Library

## Getting Started

In order to use the `NFT` library, Sway-libs must be added as a dependency to the project's Forc.toml file. To add Sway-libs as a dependency to the Forc.toml file in your project please see the [README.md](../../../README.md). Once this has been done, you may import the `NFT` library using the `use` keyword and explicitly state which functions you will be using. For a list of all functions please see the [specification](./SPECIFICATION.md).

```rust
use sway_libs::nft::{
approve,
mint,
owner_of,
transfer,
};
```

## Basic Functionality

Once imported, a `NFT` can be minted by calling the `mint` function.

```rust
// The user which shall own the newly minted NFT
let new_owner = msg_sender().unwrap();
// The id of the newly minted token
let token_id = 1;

NFTCore::mint(new_owner, token_id);
```

Tokens may be transferred by calling the `transfer` function.

```rust
// The user which the token shall be transferred to
let new_owner = msg_sender().unwrap();
// The id of the token which will be transferred
let token_id = 1;

transfer(new_owner, token_id);
```

You may check the owner of a token by calling the `owner_of` function.

```rust
let token_id: = 1;
let owner = owner_of(token_id).unwrap();
```

Other users may be approved to transfer a token on another's behalf by calling the `approve` function.

```rust
// The user which may transfer the token
let approved_user = msg_sender().unwrap();
// The id of the token which they may transfer
let token_id = 1;

approve(approved_user, token_id);
```

## Extensions

There are a number of different extensions which you may use to further enchance your non-fungible tokens.

These include:
1. [Metadata](./extensions/meta_data/meta_data.sw)
2. [Burning functionality](./extensions/burnable/burnable.sw)
3. [Administrative capabilities](./extensions/administrator/administrator.sw)
4. [Supply limits](./extensions/supply.sw)

For more information please see the [specification](./SPECIFICATION.md).
117 changes: 117 additions & 0 deletions sway_libs/src/nft/SPECIFICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
Table of Contents
- [Overview](#overview)
- [Use Cases](#use-cases)
- [Core Public Functions](#core-public-functions)
- [`approve()`](#approve)
- [`approved()`](#approved)
- [`balance_of()`](#balance_of)
- [`is_approved_for_all()`](#is-approved-for-all)
- [`mint()`](#mint)
- [`owner_of()`](#owner-of)
- [`set_approval_for_all()`](#set_approval-for-all)
- [`tokens_minted()`](#tokens-minted)
- [`transfer()`](#transfer)
- [Extension Public Functions](#extension-public-functions)
- [Administrator](#administrator)
- [`admin()`](#admin)
- [`set_admin()`](#set-admin)
- [Burnable](#burnable)
- [`burn()`](#burn)
- [Metadata](#metadata)
- [`meta_data()`](#meta-data)
- [`set_meta_data()`](#set-meta-data)
- [Supply](#supply)
- [`max_supply()`](#max-supply)
- [`set_max_supply()`](#set-max-supply)

# Overview

This document provides an overview of the NFT library.

It outlines the use cases, i.e. specification, and describes how to implement the library.

# Use Cases

The NFT library can be used anytime individual tokens with distictive characteritics are needed.

Traits can be implemented to provide additional features to the `NFTCore` struct. Some traits are already provided in the extensions portion of this NFT library.

## Core Public Functions

These core functions are basic functionality that should be added to all NFTs, regardless of extensions. It is expected that these functions can be called from any other contract.

### `approve()`

Gives permission to another user to transfer a single, specific token on the owner's behalf.

### `approved()`

Returns the user which is permissioned to transfer the token on the owner's behalf.

### `balance_of()`

Returns the number of tokens owned by a user.

### `is_approved_for_all()`

Returns whether a user is approved to transfer **all** tokens on another user's behalf.

### `mint()`

Creates a new token with an owner and an id.

### `owner_of()`

Returns the owner of a specific token.

### `set_approval_for_all()`

Give permission to a user to transfer **all** tokens owned by another user's behalf.

### `tokens_minted()`

The total number of tokens that have been minted.

### `transfer()`

Transfers ownership of the token from one user to another.

## Extension Public Functions

These extensions are optional and not all NFTs will have these implemented. Whether they should be used is a case-by-case basis.

### Administrator

#### `admin()`

Returns the adiminstrator of the `NFT` library.

#### `set_admin()`

Sets the administrator of the `NFT` library.

### Burnable

#### `burn()`

Deletes the specified token.

### Metadata

#### `meta_data()`

Returns the stored data associated with the specified token.

#### `set_meta_data()`

Stores a struct containing information / data particular to an individual token.

### Supply

#### `max_supply()`

Returns the maximum number of tokens that may be minted.

#### `set_max_supply()`

Sets the maximum number of tokens that may be minted.
12 changes: 12 additions & 0 deletions sway_libs/src/nft/errors.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
library errors;

pub enum AccessError {
OwnerDoesNotExist: (),
SenderNotOwner: (),
SenderNotOwnerOrApproved: (),
}

pub enum InputError {
TokenAlreadyExists: (),
TokenDoesNotExist: (),
}
39 changes: 39 additions & 0 deletions sway_libs/src/nft/events.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
library events;

pub struct ApprovalEvent {
/// The user that has gotten approval to transfer the specified token.
/// If an approval was revoked, the `Option` will be `None`.
approved: Option<Identity>,
/// The user that has given or revoked approval to transfer his/her tokens.
owner: Identity,
/// The unique identifier of the token which the approved may transfer.
token_id: u64,
}

pub struct MintEvent {
/// The owner of the newly minted tokens.
owner: Identity,
/// The token id that has been minted.
token_id: u64,
}

pub struct OperatorEvent {
/// The boolean that signifies whether the `operator` has been approved
approved: bool,
/// The user which may or may not transfer all tokens on the owner's behalf.
operator: Identity,
/// The user which has given or revoked approval to the operator to transfer all of their
/// tokens on their behalf.
owner: Identity,
}

pub struct TransferEvent {
/// The user which previously owned the token that has been transfered.
from: Identity,
// The user that made the transfer. This can be the owner, the approved user, or the operator.
sender: Identity,
/// The user which now owns the token that has been transfered.
to: Identity,
/// The unique identifier of the token which has transfered ownership.
token_id: u64,
}
37 changes: 37 additions & 0 deletions sway_libs/src/nft/extensions/administrator/administrator.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
library administrator;

dep administrator_errors;
dep administrator_events;

use administrator_errors::AdminError;
use administrator_events::AdminEvent;
use ::nft::nft_storage::ADMIN;
use std::{auth::msg_sender, logging::log, storage::{get, store}};

/// Returns the administrator for the library.
#[storage(read)]
pub fn admin() -> Option<Identity> {
get::<Option<Identity>>(ADMIN)
}

/// Changes the library's administrator.
///
/// # Arguments
///
/// * `admin` - The user which is to be set as the new admin.
///
/// # Reverts
///
/// * When the admin is storage is not `None`.
/// * When the sender is not the `admin` in storage.
#[storage(read, write)]
pub fn set_admin(new_admin: Option<Identity>) {
let admin = get::<Option<Identity>>(ADMIN);
require(admin.is_none() || (admin.is_some() && admin.unwrap() == msg_sender().unwrap()), AdminError::SenderNotAdmin);

store(ADMIN, new_admin);

log(AdminEvent {
admin: new_admin,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library administrator_errors;

pub enum AdminError {
SenderNotAdmin: (),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
library administrator_events;

pub struct AdminEvent {
/// The user which is now the admin of this contract.
admin: Option<Identity>,
}
48 changes: 48 additions & 0 deletions sway_libs/src/nft/extensions/burnable/burnable.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
library burnable;

dep burnable_events;

use burnable_events::BurnEvent;
use ::nft::{errors::{AccessError, InputError}, nft_core::NFTCore, nft_storage::{BALANCES, TOKENS}};
use std::{auth::msg_sender, hash::sha256, logging::log, storage::{get, store}};

pub trait Burnable {
/// Deletes this token from storage and decrements the balance of the owner.
///
/// * Reverts
///
/// * When sender is not the owner of the token.
#[storage(read, write)]
fn burn(self);
}

impl Burnable for NFTCore {
#[storage(read, write)]
fn burn(self) {
require(self.owner == msg_sender().unwrap(), AccessError::SenderNotOwner);

store(sha256((BALANCES, self.owner)), get::<u64>(sha256((BALANCES, self.owner))) - 1);
store(sha256((TOKENS, self.token_id)), Option::None::<NFTCore>());

log(BurnEvent {
owner: self.owner,
token_id: self.token_id,
});
}
}

/// Burns the specified token.
///
/// # Arguments
///
/// * `token_id` - The id of the token to burn.
///
/// # Reverts
///
/// * When the `token_id` specified does not map to an existing token.
#[storage(read, write)]
pub fn burn(token_id: u64) {
let nft = get::<Option<NFTCore>>(sha256((TOKENS, token_id)));
require(nft.is_some(), InputError::TokenDoesNotExist);
nft.unwrap().burn();
}
8 changes: 8 additions & 0 deletions sway_libs/src/nft/extensions/burnable/burnable_events.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
library burnable_events;

pub struct BurnEvent {
/// The user that has burned their token.
owner: Identity,
/// The unique identifier of the token which has been burned.
token_id: u64,
}
Loading

0 comments on commit e2ff653

Please sign in to comment.