Skip to content

Commit

Permalink
More reorganization and cleanup, add comments and basic README
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeHartnell committed Jun 21, 2023
1 parent 1d5d038 commit 1411d27
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 221 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ incremental = false
codegen-units = 1
incremental = false

[profile.release.package.cw721-sylvia-base]
codegen-units = 1
incremental = false

[profile.release]
rpath = false
lto = true
Expand Down
6 changes: 0 additions & 6 deletions contracts/cw721-base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,3 @@ This allows you to use custom `ExecuteMsg` and `QueryMsg` with your additional
calls, but then use the underlying implementation for the standard cw721
messages you want to support. The same with `QueryMsg`. You will most
likely want to write a custom, domain-specific `instantiate`.

**TODO: add example when written**

For now, you can look at [`cw721-staking`](../cw721-staking/README.md)
for an example of how to "inherit" cw721 functionality and combine it with custom logic.
The process is similar for cw721.
15 changes: 10 additions & 5 deletions contracts/cw721-sylvia-base/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
[package]
name = "cw721-sylvia-base"
version = "0.1.0"
edition = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
name = "cw721-sylvia-base"
description = "Basic implementation of cw721 NFTs using the Sylvia framework."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
rust-version = { workspace = true }


[features]
library = []
Expand Down
8 changes: 8 additions & 0 deletions contracts/cw721-sylvia-base/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# cw721-sylvia-base

This is a basic implementation of a cw721 NFT contract. It implements
the [CW721 spec](../../packages/cw721/README.md) and is designed to
be deployed as is, or imported into other contracts to easily build
cw721-compatible NFTs with custom logic. It uses the [Sylvia](https://github.com/cosmwasm/sylvia) smart contract framework for easier extension.

Use this as a base to build your own custom NFT contract.
3 changes: 2 additions & 1 deletion contracts/cw721-sylvia-base/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use cw_utils::maybe_addr;
use sylvia::contract;
use sylvia::types::{ExecCtx, QueryCtx};

use crate::contract::{Approval, Cw721Contract, TokenInfo, DEFAULT_LIMIT, MAX_LIMIT};
use crate::contract::{Cw721Contract, DEFAULT_LIMIT, MAX_LIMIT};
use crate::state::{Approval, TokenInfo};
use crate::ContractError;

#[contract(module=crate::contract)]
Expand Down
88 changes: 24 additions & 64 deletions contracts/cw721-sylvia-base/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,25 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, BlockInfo, Empty, Response, StdResult};
use cosmwasm_std::{Addr, Empty, Response, StdResult};
use cw721::{cw721_interface, ContractInfoResponse, Expiration};
use cw_ownable::Ownership;
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex};
use cw_storage_plus::{IndexedMap, Item, Map, MultiIndex};
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};

use crate::responses::MinterResponse;
use crate::state::{token_owner_idx, TokenIndexes, TokenInfo};
use crate::ContractError;

// Version info for migration
pub const CONTRACT_NAME: &str = "crates.io:cw721-sylvia-base";
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

/// Default limit for query pagination
pub const DEFAULT_LIMIT: u32 = 10;
/// Maximum limit for query pagination
pub const MAX_LIMIT: u32 = 100;

// TODO should this just be in cw721_interface along with the other responses?
#[cw_serde]
pub struct Approval {
/// Account that can transfer/send the token
pub spender: Addr,
/// When the Approval expires (maybe Expiration::never)
pub expires: Expiration,
}

impl Approval {
pub fn is_expired(&self, block: &BlockInfo) -> bool {
self.expires.is_expired(block)
}
}

/// TODO move to a responses file?
/// Shows who can mint these tokens
#[cw_serde]
pub struct MinterResponse {
pub minter: Option<String>,
}

// TODO what do we want to do with extensions? They seem to be unnessary now?
// TODO kill extensions
#[cw_serde]
pub struct TokenInfo {
/// The owner of the newly minted NFT
pub owner: Addr,
/// Approvals are stored here, as we clear them all upon transfer and cannot accumulate much
pub approvals: Vec<Approval>,

/// Universal resource identifier for this NFT
/// Should point to a JSON file that conforms to the ERC721
/// Metadata JSON Schema
pub token_uri: Option<String>,

pub extension: Empty,
}

pub fn token_owner_idx(_pk: &[u8], d: &TokenInfo) -> Addr {
d.owner.clone()
}

/// Indexed map for NFT tokens by owner
pub struct TokenIndexes<'a> {
pub owner: MultiIndex<'a, Addr, TokenInfo, String>,
}
impl<'a> IndexList<TokenInfo> for TokenIndexes<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ (dyn Index<TokenInfo> + '_)> + '_> {
let v: Vec<&dyn Index<TokenInfo>> = vec![&self.owner];
Box::new(v.into_iter())
}
}

pub struct Cw721Contract<'a> {
pub contract_info: Item<'a, ContractInfoResponse>,
pub token_count: Item<'a, u64>,
/// Stored as (granter, operator) giving operator full control over granter's account
pub operators: Map<'a, (&'a Addr, &'a Addr), Expiration>,
pub tokens: IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a>>,
}

/// The instantiation message data for this contract, used to set initial state
#[cw_serde]
pub struct InstantiateMsgData {
/// Name of the NFT contract
Expand All @@ -90,6 +33,17 @@ pub struct InstantiateMsgData {
pub minter: String,
}

/// The struct representing this contract, holds contract state.
/// See Sylvia docmentation for more info about customizing this.
pub struct Cw721Contract<'a> {
pub contract_info: Item<'a, ContractInfoResponse>,
pub token_count: Item<'a, u64>,
/// Stored as (granter, operator) giving operator full control over granter's account
pub operators: Map<'a, (&'a Addr, &'a Addr), Expiration>,
pub tokens: IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a>>,
}

/// The actual contract implementation, base cw721 logic is implemented in base.rs
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
#[error(ContractError)]
Expand Down Expand Up @@ -183,3 +137,9 @@ impl Cw721Contract<'_> {
cw_ownable::get_ownership(ctx.deps.storage)
}
}

impl Default for Cw721Contract<'_> {
fn default() -> Self {
Self::new()
}
}
6 changes: 5 additions & 1 deletion contracts/cw721-sylvia-base/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
mod base;
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]

pub mod base;
pub mod contract;
mod error;
pub mod responses;
pub mod state;

#[cfg(test)]
mod multitest;
Expand Down
3 changes: 2 additions & 1 deletion contracts/cw721-sylvia-base/src/multitest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
contract::{multitest_utils::Cw721ContractProxy, InstantiateMsgData, MinterResponse},
contract::{multitest_utils::Cw721ContractProxy, InstantiateMsgData},
ContractError,
};
use cosmwasm_std::Empty;
Expand All @@ -11,6 +11,7 @@ use sylvia::multitest::App;

use crate::base::test_utils::Cw721Interface;
use crate::contract::multitest_utils::CodeId;
use crate::responses::MinterResponse;

const CREATOR: &str = "creator";
const RANDOM: &str = "random";
Expand Down
7 changes: 7 additions & 0 deletions contracts/cw721-sylvia-base/src/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use cosmwasm_schema::cw_serde;

/// Shows who can mint these tokens
#[cw_serde]
pub struct MinterResponse {
pub minter: Option<String>,
}
48 changes: 48 additions & 0 deletions contracts/cw721-sylvia-base/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, BlockInfo, Empty};
use cw_ownable::Expiration;
use cw_storage_plus::{Index, IndexList, MultiIndex};

#[cw_serde]
pub struct Approval {
/// Account that can transfer/send the token
pub spender: Addr,
/// When the Approval expires (maybe Expiration::never)
pub expires: Expiration,
}

impl Approval {
pub fn is_expired(&self, block: &BlockInfo) -> bool {
self.expires.is_expired(block)
}
}

#[cw_serde]
pub struct TokenInfo {
/// The owner of the newly minted NFT
pub owner: Addr,
/// Approvals are stored here, as we clear them all upon transfer and cannot accumulate much
pub approvals: Vec<Approval>,

/// Universal resource identifier for this NFT
/// Should point to a JSON file that conforms to the ERC721
/// Metadata JSON Schema
pub token_uri: Option<String>,

pub extension: Empty,
}

pub fn token_owner_idx(_pk: &[u8], d: &TokenInfo) -> Addr {
d.owner.clone()
}

/// Indexed map for NFT tokens by owner
pub struct TokenIndexes<'a> {
pub owner: MultiIndex<'a, Addr, TokenInfo, String>,
}
impl<'a> IndexList<TokenInfo> for TokenIndexes<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ (dyn Index<TokenInfo> + '_)> + '_> {
let v: Vec<&dyn Index<TokenInfo>> = vec![&self.owner];
Box::new(v.into_iter())
}
}
Loading

0 comments on commit 1411d27

Please sign in to comment.