-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Dan Sover <dan.sover@avalabs.org>
- Loading branch information
Showing
9 changed files
with
464 additions
and
0 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 |
---|---|---|
|
@@ -68,3 +68,7 @@ osxcross/ | |
target/ | ||
Cargo.lock | ||
**/*.rs.bk | ||
|
||
# simulator | ||
simulator | ||
!simulator/ |
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
Binary file not shown.
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,20 @@ | ||
[package] | ||
name = "nft" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
wasmlanche_sdk = { version = "0.1.0", path = "../../wasmlanche_sdk", features = ["simulator"]} | ||
serde = "1.0.188" | ||
|
||
[dev-dependencies] | ||
serde_json = "1.0.68" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] # set the crate(needed for cargo build to work properly) | ||
|
||
[[integration]] | ||
name = "nft" | ||
path = "tests/nft.rs" |
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,18 @@ | ||
# nft | ||
|
||
The `nft` HyperSDK Program is an example of an NFT (non-fungible token). | ||
|
||
For more information on NFTs, see the [resources at nft | ||
school](https://nftschool.dev/concepts/non-fungible-tokens/#a-bit-of-history). | ||
|
||
## Usage | ||
|
||
The program exposes `mint` and `burn` methods which are publicly accessible. | ||
Building a `Plan` with `Steps` to invoke these methods is the standard way to | ||
interact with `nft`. See [the integration test](./tests/nft.rs) for a simple | ||
invocation of `nft` via the WASM runtime. | ||
|
||
## Testing | ||
|
||
Use the [simulator](../../wasmlanche_sdk/src/simulator.rs) provided to run a | ||
custom program. Detailed documentation on the simulator will soon be available. |
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,32 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -euo pipefail | ||
|
||
if ! [[ "$0" =~ scripts/build_and_test.sh ]]; then | ||
echo "must be run from crate root" | ||
exit 255 | ||
fi | ||
|
||
root="$(pwd)" | ||
|
||
simulator_path="${PWD}"/../../../cmd/simulator | ||
simulator_bin="${simulator_path}"/bin/simulator | ||
|
||
echo "Downloading dependencies..." | ||
cd "${simulator_path}" | ||
go mod download | ||
|
||
echo "Building Simulator..." | ||
go build -o "${simulator_bin}" "${simulator_path}"/simulator.go | ||
|
||
# Set environment variables for the test | ||
|
||
# The path to the simulator binary | ||
export SIMULATOR_PATH="${simulator_bin}" | ||
|
||
# The path to the compiled Wasm program to be tested | ||
export PROGRAM_PATH="${root}"/../examples/testdata/nft.wasm | ||
|
||
echo "Running Simulator Tests..." | ||
|
||
cargo test -p nft -- --include-ignored |
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,22 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -euo pipefail | ||
|
||
if ! [[ "$0" =~ scripts/copy_wasm.sh ]]; then | ||
echo "must be run from crate root" | ||
exit 255 | ||
fi | ||
|
||
root="$(pwd)" | ||
|
||
# Build the program | ||
build_script_path="${root}"/../../scripts/build.sh | ||
sh "${build_script_path}" out | ||
|
||
# Copy wasm file over | ||
cp out/nft.wasm "${root}"/../examples/testdata/nft.wasm | ||
|
||
# Delete build artifacts | ||
rm -rf "${root}"/out | ||
|
||
echo 'Successfully copied nft.wasm to examples/testdata' |
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,164 @@ | ||
//! A basic NFT contract. | ||
//! The program serves as a non-fungible token with the ability to mint and burn. | ||
//! Only supports whole units with no decimal places. | ||
//! | ||
//! The NFT must support the common NFT metadata format. | ||
//! This includes the name, symbol, and URI of the NFT. | ||
use wasmlanche_sdk::{ | ||
memory::{Memory, Pointer}, | ||
program::Program, | ||
public, state_keys, | ||
types::Address, | ||
}; | ||
|
||
/// The program storage keys. | ||
#[state_keys] | ||
enum StateKey { | ||
/// The total supply of the token. Key prefix 0x0. | ||
MaxSupply, | ||
/// The name of the token. Key prefix 0x1. | ||
Name, | ||
/// The symbol of the token. Key prefix 0x2. | ||
Symbol, | ||
/// Metadata of the token. Key prefix 0x3. | ||
Uri, | ||
/// Balances of the NFT token by address. Key prefix 0x4(address). | ||
Balances(Address), | ||
/// Counter -- used to keep track of total NFTs minted. Key prefix 0x5. | ||
Counter, | ||
} | ||
|
||
/// Initializes the NFT with all required metadata. | ||
/// This includes the name, symbol, image URI, owner, and total supply. | ||
/// Returns true if the initialization was successful. | ||
#[public] | ||
// TODO: update the macro to enable String arguments | ||
#[allow(clippy::too_many_arguments)] | ||
pub fn init( | ||
program: Program, | ||
nft_name_ptr: i64, | ||
nft_name_length: i64, | ||
nft_symbol_ptr: i64, | ||
nft_symbol_length: i64, | ||
nft_uri_ptr: i64, | ||
nft_uri_length: i64, | ||
nft_max_supply: i64, | ||
) -> bool { | ||
let name_ptr = Memory::new(Pointer::from(nft_name_ptr)); | ||
let nft_name = unsafe { name_ptr.range(nft_name_length as usize) }; | ||
|
||
let nft_symbol_ptr = Memory::new(Pointer::from(nft_symbol_ptr)); | ||
let nft_symbol = unsafe { nft_symbol_ptr.range(nft_symbol_length as usize) }; | ||
|
||
let nft_uri_ptr = Memory::new(Pointer::from(nft_uri_ptr)); | ||
let nft_uri = unsafe { nft_uri_ptr.range(nft_uri_length as usize) }; | ||
|
||
let counter = program | ||
.state() | ||
.get::<i64, _>(StateKey::Counter.to_vec()) | ||
.unwrap_or_default(); | ||
|
||
assert_eq!(counter, 0, "init already called"); | ||
|
||
// Set token name | ||
program | ||
.state() | ||
.store(StateKey::Name.to_vec(), &nft_name) | ||
.expect("failed to store nft name"); | ||
|
||
// Set token symbol | ||
program | ||
.state() | ||
.store(StateKey::Symbol.to_vec(), &nft_symbol) | ||
.expect("failed to store nft symbol"); | ||
|
||
// Set token URI | ||
program | ||
.state() | ||
.store(StateKey::Uri.to_vec(), &nft_uri) | ||
.expect("failed to store nft uri"); | ||
|
||
// Set total supply | ||
program | ||
.state() | ||
.store(StateKey::MaxSupply.to_vec(), &nft_max_supply) | ||
.expect("failed to store total supply"); | ||
|
||
// Initialize counter | ||
program | ||
.state() | ||
.store(StateKey::Counter.to_vec(), &0_i64) | ||
.expect("failed to store counter"); | ||
|
||
true | ||
} | ||
|
||
/// Mints NFT tokens and sends them to the recipient. | ||
#[public] | ||
pub fn mint(program: Program, recipient: Address, amount: i64) -> bool { | ||
let counter = program | ||
.state() | ||
.get::<i64, _>(StateKey::Counter.to_vec()) | ||
.unwrap_or_default(); | ||
|
||
let max_supply = program | ||
.state() | ||
.get::<i64, _>(StateKey::MaxSupply.to_vec()) | ||
.unwrap_or_default(); | ||
|
||
assert!( | ||
counter + amount <= max_supply, | ||
"max supply for nft exceeded" | ||
); | ||
|
||
let balance = program | ||
.state() | ||
.get::<i64, _>(StateKey::Balances(recipient).to_vec()) | ||
.unwrap_or_default(); | ||
|
||
// TODO: check for overflow | ||
program | ||
.state() | ||
.store(StateKey::Balances(recipient).to_vec(), &(balance + amount)) | ||
.expect("failed to store balance"); | ||
|
||
// TODO: check for overflow | ||
program | ||
.state() | ||
.store(StateKey::Counter.to_vec(), &(counter + amount)) | ||
.is_ok() | ||
} | ||
|
||
#[public] | ||
pub fn burn(program: Program, from: Address, amount: i64) -> bool { | ||
let balance = program | ||
.state() | ||
.get::<i64, _>(StateKey::Balances(from).to_vec()) | ||
.unwrap_or_default(); | ||
|
||
assert!( | ||
balance >= amount, | ||
"balance must be greater than or equal to amount burned" | ||
); | ||
|
||
let counter = program | ||
.state() | ||
.get::<i64, _>(StateKey::Counter.to_vec()) | ||
.unwrap_or_default(); | ||
|
||
assert!(counter >= amount, "cannot burn more nfts"); | ||
|
||
// TODO: check for underflow | ||
program | ||
.state() | ||
.store(StateKey::Balances(from).to_vec(), &(balance - amount)) | ||
.is_ok() | ||
} | ||
|
||
#[public] | ||
fn balance(program: Program, owner: Address) -> i64 { | ||
program | ||
.state() | ||
.get::<i64, _>(StateKey::Balances(owner).to_vec()) | ||
.unwrap_or_default() | ||
} |
Oops, something went wrong.