Skip to content

feat: nft-contract #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 49 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5ed4ee0
feat: add nft contract copy from aztec packages
daopunk Mar 26, 2025
8705d73
fix: compilation
daopunk Mar 26, 2025
52e38bc
refactor: order funcs base on attributes
daopunk Mar 27, 2025
0ff2081
Merge branch 'dev' into feat/nft-contract
daopunk Mar 29, 2025
9b875ef
chore: update version to dev
daopunk Mar 29, 2025
2d9fb9e
Merge branch 'dev' into feat/nft-contract
daopunk Apr 1, 2025
f00c2ba
Merge branch 'feat/nft-contract' into feat/nft-contract-standardize
daopunk Apr 1, 2025
8bcaf78
refactor: adapt code to wonderland standards
daopunk Apr 1, 2025
f951a7f
refactor: add auth validation logic
daopunk Apr 1, 2025
b324256
Merge branch 'dev' into feat/nft-contract
daopunk Apr 2, 2025
9af2cee
Merge branch 'dev' into feat/nft-contract
daopunk Apr 8, 2025
7cf307f
refactor: nft logic
daopunk Apr 10, 2025
e0fda57
refactor: finish logic for all functions
daopunk Apr 12, 2025
c4eda71
refactor: compiler errors
daopunk Apr 12, 2025
6fd9f2f
fix: compilation errors
daopunk Apr 14, 2025
894cccd
feat: add private minting
daopunk Apr 14, 2025
ed2bf88
chore: natspec for private and auth
daopunk Apr 14, 2025
4fb863d
Merge branch 'dev' into feat/nft-contract
daopunk Apr 17, 2025
61ffc74
chore: token commitment natspec
daopunk Apr 17, 2025
9c3bc44
chore: update aztec version
daopunk Apr 17, 2025
58485af
Merge branch 'feat/nft-contract' into refactor/nft-contract
daopunk Apr 17, 2025
920b865
chore: upgrade to v0.85.0
daopunk Apr 17, 2025
71e1c9d
chore: prune imports and var naming
daopunk Apr 17, 2025
fe9e785
Merge branch 'refactor/nft-contract' into chore/nft-natspec
daopunk Apr 17, 2025
72dddc7
fix: compilation syntax error
daopunk Apr 17, 2025
17fb316
chore: complete natspec
daopunk Apr 17, 2025
2c839cf
chore: format
daopunk Apr 17, 2025
62fa290
fix: merge conflicts
daopunk Apr 17, 2025
e373bd9
refactor: add back logic
daopunk Apr 20, 2025
c01664a
fix: mint logic
daopunk Apr 20, 2025
5ab3ace
chore: add natspec to new funcs
daopunk Apr 20, 2025
8615162
fix: compile error
daopunk Apr 21, 2025
45b70c6
chore: nft natspec (#79)
daopunk Apr 22, 2025
7072bff
Update src/nft_contract/src/main.nr
daopunk Apr 22, 2025
5f82c6a
Update src/nft_contract/src/main.nr
daopunk Apr 22, 2025
d49099b
fix: compilation
daopunk Apr 22, 2025
1811c9d
fix: compilation bugs
wei3erHase Apr 22, 2025
6a4850a
chore: merge feat/nft-contract up branch
wei3erHase Apr 22, 2025
c8824c5
chore: remove old tests
daopunk Apr 23, 2025
2072818
refactor: remove and consolidate redundant logic
daopunk Apr 23, 2025
074d67c
chore: reduce scope
daopunk Apr 23, 2025
2a8fce5
chore: reduce scope
daopunk Apr 23, 2025
a4e1927
refactor: re-add transfer-pri-to-pub-w-commitment func
daopunk Apr 24, 2025
3b632e2
fix: error
daopunk Apr 24, 2025
ef247ab
refactor: update public token owner logic and verification
daopunk Apr 24, 2025
52077fb
fix: compilation
daopunk Apr 24, 2025
ac54033
chore: remove unused param
daopunk Apr 25, 2025
55e1dbe
refactor: nft contract (#76)
daopunk Apr 25, 2025
2545e9b
chore: nft contract tests (#73)
daopunk Apr 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Nargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
members = [
"src/token_contract"
"src/token_contract",
"src/nft_contract"
]
10 changes: 10 additions & 0 deletions src/nft_contract/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "nft_contract"
authors = [""]
compiler_version = ">=1.0.0"
type = "contract"

[dependencies]
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/aztec" }
authwit = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/authwit" }
compressed_string = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.85.0", directory = "noir-projects/aztec-nr/compressed-string" }
565 changes: 565 additions & 0 deletions src/nft_contract/src/main.nr

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions src/nft_contract/src/test.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mod access_control;
mod mint_to_public;
mod mint_to_private;
mod burn_public;
mod burn_private;
mod initialize_transfer_commitment;
mod transfer_private_to_commitment;
mod transfer_private_to_private;
mod transfer_private_to_public_with_commitment;
mod transfer_private_to_public;
mod transfer_public_to_private;
mod transfer_public_to_public;
mod utils;
mod view;
19 changes: 19 additions & 0 deletions src/nft_contract/src/test/access_control.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::NFT;
use crate::test::utils;
use aztec::{
oracle::{
execution::{get_block_number, get_contract_address},
storage::storage_read,
},
prelude::AztecAddress,
test::helpers::{cheatcodes, test_environment::TestEnvironment},
};

#[test]
unconstrained fn nft_minter_is_set() {
let (env, nft_contract_address, owner, minter, recipient) = utils::setup_with_minter(false);
cheatcodes::set_contract_address(nft_contract_address);
let minter_slot = NFT::storage_layout().minter.slot;
let minter_address: AztecAddress = storage_read(nft_contract_address, minter_slot, get_block_number());
assert(minter_address == minter, "minter is not set");
}
142 changes: 142 additions & 0 deletions src/nft_contract/src/test/burn_private.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use crate::NFT;
use crate::test::utils;
use authwit::cheatcodes as authwit_cheatcodes;
use aztec::prelude::AztecAddress;

#[test]
unconstrained fn nft_burn_private_self_success() {
// Setup and mint NFT to owner in private state
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_private(false, token_id);

// Verify initial state
utils::assert_owns_private_nft(nft_contract_address, owner, token_id);
utils::assert_nft_exists(nft_contract_address, token_id);

// Burn the NFT
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_private(owner, token_id, 0)
.call(&mut env.private());
env.advance_block_by(1);

// Verify NFT is burned (no longer in private notes and marked as non-existent)
utils::assert_private_nft_nullified(nft_contract_address, owner, token_id);
utils::assert_nft_does_not_exist(nft_contract_address, token_id);
}

#[test]
unconstrained fn nft_burn_private_authorized_success() {
// Setup with account contracts and mint NFT to owner
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_private(true, token_id);

// Verify initial state
utils::assert_owns_private_nft(nft_contract_address, owner, token_id);
utils::assert_nft_exists(nft_contract_address, token_id);

// Create burn call interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_private(owner, token_id, 1);

// Add authorization witness from owner to recipient
authwit_cheatcodes::add_private_authwit_from_call_interface(
owner,
recipient,
burn_call_interface,
);

// Impersonate recipient to perform the authorized burn
env.impersonate(recipient);
burn_call_interface.call(&mut env.private());
env.advance_block_by(1);

// Verify NFT is burned
utils::assert_private_nft_nullified(nft_contract_address, owner, token_id);
utils::assert_nft_does_not_exist(nft_contract_address, token_id);
}

#[test(should_fail_with = "invalid nonce")]
unconstrained fn nft_burn_private_self_non_zero_nonce_fail() {
// Setup and mint NFT to owner
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_private(false, token_id);

// Attempt to burn with non-zero nonce as owner
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_private(owner, token_id, 1)
.call(&mut env.private());
}

#[test(should_fail_with = "nft not found in private to public")]
unconstrained fn nft_burn_private_non_existent_fail() {
// Setup but don't mint any NFT
let (env, nft_contract_address, owner, minter, recipient) = utils::setup_with_minter(false);
let non_existent_token_id = 12345;

// Attempt to burn non-existent NFT
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_private(owner, non_existent_token_id, 0)
.call(&mut env.private());
}

#[test(should_fail_with = "Unknown auth witness for message hash")]
unconstrained fn nft_burn_private_unauthorized_fail() {
// Setup with account contracts
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_private(true, token_id);

// Create burn interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_private(owner, token_id, 1);

// Impersonate recipient but DON'T add authorization witness
env.impersonate(recipient);
burn_call_interface.call(&mut env.private());
}

#[test(should_fail_with = "Unknown auth witness for message hash")]
unconstrained fn nft_burn_private_wrong_authwit_fail() {
// Setup with account contracts
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_private(true, token_id);

// Create burn interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_private(owner, token_id, 1);

// Add authorization witness but to the wrong address (owner instead of recipient)
authwit_cheatcodes::add_private_authwit_from_call_interface(
owner,
owner, // Wrong address - should be recipient
burn_call_interface,
);

// Impersonate recipient
env.impersonate(recipient);
burn_call_interface.call(&mut env.private());
}

#[test(should_fail_with = "nft not found in private to public")]
unconstrained fn nft_burn_private_public_nft_fail() {
// Setup and mint NFT to owner in public state
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(false, token_id);

// Verify NFT is in public state
utils::assert_owns_public_nft(env, nft_contract_address, owner, token_id);

// Attempt to burn from private state when NFT is actually in public state
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_private(owner, token_id, 0)
.call(&mut env.private());
}
121 changes: 121 additions & 0 deletions src/nft_contract/src/test/burn_public.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::NFT;
use crate::test::utils;
use authwit::cheatcodes as authwit_cheatcodes;
use aztec::prelude::AztecAddress;

#[test]
unconstrained fn nft_burn_public_success() {
// Setup and mint NFT to owner in public state
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(false, token_id);

// Verify initial state
utils::assert_owns_public_nft(env, nft_contract_address, owner, token_id);
utils::assert_nft_exists(nft_contract_address, token_id);

// Burn the NFT
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_public(owner, token_id, 0)
.call(&mut env.public());
env.advance_block_by(1);

// Verify NFT is burned (owner is zero address and NFT doesn't exist)
utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id);
utils::assert_nft_does_not_exist(nft_contract_address, token_id);
}

#[test]
unconstrained fn nft_burn_public_authorized_success() {
// Setup with account contracts and mint NFT to owner
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(true, token_id);

// Create burn call interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_public(owner, token_id, 1);

// Add authorization witness from owner to recipient
authwit_cheatcodes::add_public_authwit_from_call_interface(
owner,
recipient,
burn_call_interface,
);

// Impersonate recipient to perform the authorized burn
env.impersonate(recipient);
burn_call_interface.call(&mut env.public());
env.advance_block_by(1);

// Verify NFT is burned
utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id);
utils::assert_nft_does_not_exist(nft_contract_address, token_id);
}

#[test(should_fail_with = "invalid nonce")]
unconstrained fn nft_burn_public_self_non_zero_nonce_fail() {
// Setup and mint NFT to owner
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(false, token_id);

// Attempt to burn with non-zero nonce as owner
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_public(owner, token_id, 1)
.call(&mut env.public());
}

#[test(should_fail_with = "unauthorized")]
unconstrained fn nft_burn_public_unauthorized_fail() {
// Setup with account contracts
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(true, token_id);

// Create burn interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_public(owner, token_id, 1);

// Impersonate recipient but DON'T add authorization witness
env.impersonate(recipient);
burn_call_interface.call(&mut env.public());
}

#[test(should_fail_with = "unauthorized")]
unconstrained fn nft_burn_public_wrong_authwit_fail() {
// Setup with account contracts
let token_id = 10000;
let (env, nft_contract_address, owner, minter, recipient, token_id) =
utils::setup_and_mint_to_public(true, token_id);

// Create burn interface with non-zero nonce
let burn_call_interface = NFT::at(nft_contract_address)
.burn_public(owner, token_id, 1);

// Add authorization witness but to the wrong address (owner instead of recipient)
authwit_cheatcodes::add_public_authwit_from_call_interface(
owner,
owner, // Wrong address - should be recipient
burn_call_interface,
);

// Impersonate recipient
env.impersonate(recipient);
burn_call_interface.call(&mut env.public());
}

#[test(should_fail_with = "caller is not owner")]
unconstrained fn nft_burn_public_non_existent_fail() {
// Setup but don't mint any NFT
let (env, nft_contract_address, owner, minter, recipient) = utils::setup_with_minter(false);
let non_existent_token_id = 12345;

// Attempt to burn non-existent NFT
env.impersonate(owner);
NFT::at(nft_contract_address)
.burn_public(owner, non_existent_token_id, 0)
.call(&mut env.public());
}
Loading