Skip to content

Commit

Permalink
NFTs module, minting from the board, and color based pixel quests
Browse files Browse the repository at this point in the history
  • Loading branch information
b-j-roberts committed Apr 12, 2024
1 parent 5a75e34 commit 6208780
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 40 deletions.
6 changes: 6 additions & 0 deletions onchain/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ version = 1
name = "art_peace"
version = "0.1.0"
dependencies = [
"openzeppelin",
"snforge_std",
]

[[package]]
name = "openzeppelin"
version = "0.11.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a83f36b23f1af6e160288962be4a2701c3ecbcda"

[[package]]
name = "snforge_std"
version = "0.20.0"
Expand Down
1 change: 1 addition & 0 deletions onchain/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version = "0.1.0"

[dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.20.0" }
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.11.0" }
starknet = "2.6.3"

[scripts]
Expand Down
42 changes: 42 additions & 0 deletions onchain/src/art_peace.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod ArtPeace {
use starknet::ContractAddress;
use art_peace::{IArtPeace, Pixel};
use art_peace::quests::{IQuestDispatcher, IQuestDispatcherTrait};
use art_peace::nfts::{IArtPeaceNFTMinter, NFTMetadata, NFTMintParams, ICanvasNFTAdditionalDispatcher, ICanvasNFTAdditionalDispatcherTrait};
use art_peace::templates::component::TemplateStoreComponent;
use art_peace::templates::{ITemplateVerifier, TemplateMetadata};

Expand Down Expand Up @@ -34,6 +35,7 @@ pub mod ArtPeace {
main_quests_count: u32,
// Map: quest index -> quest contract address
main_quests: LegacyMap::<u32, ContractAddress>,
nft_contract: ContractAddress,
// Map: (day_index, user's address, color index) -> amount of pixels placed
user_pixels_placed: LegacyMap::<(u32, ContractAddress, u8), u32>,
#[substorage(v0)]
Expand Down Expand Up @@ -69,6 +71,7 @@ pub mod ArtPeace {
pub end_time: u64,
pub daily_quests: Span<ContractAddress>,
pub main_quests: Span<ContractAddress>,
pub nft_contract: ContractAddress,
}

#[constructor]
Expand Down Expand Up @@ -110,6 +113,8 @@ pub mod ArtPeace {
self.main_quests.write(i, *init_params.main_quests.at(i));
i += 1;
};

self.nft_contract.write(init_params.nft_contract);
}

#[abi(embed_v0)]
Expand Down Expand Up @@ -346,6 +351,10 @@ pub mod ArtPeace {
}
}

fn get_nft_contract(self: @ContractState) -> ContractAddress {
self.nft_contract.read()
}

fn get_user_pixels_placed(self: @ContractState, user: ContractAddress) -> u32 {
let mut i = 0;
let mut total = 0;
Expand Down Expand Up @@ -375,13 +384,45 @@ pub mod ArtPeace {
total
}

fn get_user_pixels_placed_color(
self: @ContractState, user: ContractAddress, color: u8
) -> u32 {
let mut total = 0;
let last_day = self.day_index.read() + 1;
let mut i = 0;
while i < last_day {
total += self.user_pixels_placed.read((i, user, color));
i += 1;
};
total
}

fn get_user_pixels_placed_day_color(
self: @ContractState, user: ContractAddress, day: u32, color: u8
) -> u32 {
self.user_pixels_placed.read((day, user, color))
}
}

#[abi(embed_v0)]
impl ArtPeaceNFTMinter of IArtPeaceNFTMinter<ContractState> {
fn mint_nft(
self: @ContractState, mint_params: NFTMintParams
) {
let metadata = NFTMetadata {
position: mint_params.position,
width: mint_params.width,
height: mint_params.height,
image_hash: 0, // TODO
block_number: starknet::get_block_number(),
minter: starknet::get_caller_address(),
};
ICanvasNFTAdditionalDispatcher {
contract_address: self.nft_contract.read(),
}.mint(metadata, starknet::get_caller_address());
}
}

#[abi(embed_v0)]
impl ArtPeaceTemplateVerifier of ITemplateVerifier<ContractState> {
// TODO: Check template function
Expand All @@ -408,6 +449,7 @@ pub mod ArtPeace {
let pos = template_pos_x + x + (template_pos_y + y) * canvas_width;
let color = *template_image
.at((x + y * template_metadata.width).try_into().unwrap());
// TODO: Check if the color is transparent
if color == self.canvas.read(pos).color {
matches += 1;
}
Expand Down
6 changes: 6 additions & 0 deletions onchain/src/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,17 @@ pub trait IArtPeace<TContractState> {
fn claim_today_quest(ref self: TContractState, quest_id: u32);
fn claim_main_quest(ref self: TContractState, quest_id: u32);

// NFT info
fn get_nft_contract(self: @TContractState) -> starknet::ContractAddress;

// Stats
fn get_user_pixels_placed(self: @TContractState, user: starknet::ContractAddress) -> u32;
fn get_user_pixels_placed_day(
self: @TContractState, user: starknet::ContractAddress, day: u32
) -> u32;
fn get_user_pixels_placed_color(
self: @TContractState, user: starknet::ContractAddress, color: u8
) -> u32;
fn get_user_pixels_placed_day_color(
self: @TContractState, user: starknet::ContractAddress, day: u32, color: u8
) -> u32;
Expand Down
10 changes: 10 additions & 0 deletions onchain/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ mod templates {
};
}

mod nfts {
pub mod interface;
mod component;
mod canvas_nft;

use interface::{
NFTMintParams, NFTMetadata, IArtPeaceNFTMinter, ICanvasNFTStoreDispatcher, ICanvasNFTStoreDispatcherTrait, IArtPeaceNFTMinterDispatcher, IArtPeaceNFTMinterDispatcherTrait, ICanvasNFTAdditional, ICanvasNFTAdditionalDispatcher, ICanvasNFTAdditionalDispatcherTrait
};
}

#[cfg(test)]
mod tests {
mod art_peace;
Expand Down
64 changes: 64 additions & 0 deletions onchain/src/nfts/canvas_nft.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#[starknet::contract]
mod CanvasNFT {
use openzeppelin::token::erc721::ERC721Component;
use openzeppelin::introspection::src5::SRC5Component;
use starknet::ContractAddress;
use art_peace::nfts::component::CanvasNFTStoreComponent;
use art_peace::nfts::{ICanvasNFTAdditional, NFTMetadata};

component!(path: ERC721Component, storage: erc721, event: ERC721Event);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
component!(path: CanvasNFTStoreComponent, storage: nfts, event: NFTEvent);

#[abi(embed_v0)]
impl ERC721Impl = ERC721Component::ERC721Impl<ContractState>;
#[abi(embed_v0)]
impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl<ContractState>;
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
#[abi(embed_v0)]
impl CanvasNFTStoreImpl = CanvasNFTStoreComponent::CanvasNFTStoreImpl<ContractState>;

impl InternalImpl = ERC721Component::InternalImpl<ContractState>;

#[storage]
struct Storage {
art_peace: ContractAddress,
#[substorage(v0)]
erc721: ERC721Component::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage,
#[substorage(v0)]
nfts: CanvasNFTStoreComponent::Storage,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC721Event: ERC721Component::Event,
#[flat]
SRC5Event: SRC5Component::Event,
#[flat]
NFTEvent: CanvasNFTStoreComponent::Event,
}

#[constructor]
fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray, art_peace_addr: ContractAddress) {
self.art_peace.write(art_peace_addr);
let base_uri = format!("{:?}", art_peace_addr);
self.erc721.initializer(name, symbol, base_uri);
}

#[abi(embed_v0)]
impl CanvasNFTAdditional of ICanvasNFTAdditional<ContractState> {
fn mint(ref self: ContractState, metadata: NFTMetadata, receiver: ContractAddress) {
assert(self.art_peace.read() == starknet::get_caller_address(), 'Only ArtPeace contract can mint');
let token_id = self.nfts.get_nfts_count();
self.nfts.nfts_data.write(token_id, metadata);
self.erc721._mint(receiver, token_id);
self.nfts.nfts_count.write(token_id + 1);
// TODO: self.emit(Event::NFTEvent::CanvasNFTMinted { token_id, metadata });
}
}
}
47 changes: 47 additions & 0 deletions onchain/src/nfts/component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[starknet::component]
mod CanvasNFTStoreComponent {
use art_peace::nfts::interface::{ICanvasNFTStore, NFTMetadata};

#[storage]
struct Storage {
nfts_count: u256,
// Map: nft's token_id -> nft's metadata
nfts_data: LegacyMap::<u256, NFTMetadata>,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
CanvasNFTMinted: CanvasNFTMinted,
}

#[derive(Drop, starknet::Event)]
struct CanvasNFTMinted {
#[key]
token_id: u256,
metadata: NFTMetadata,
}

#[embeddable_as(CanvasNFTStoreImpl)]
impl CanvasNFTStore<
TContractState, +HasComponent<TContractState>
> of ICanvasNFTStore<ComponentState<TContractState>> {
fn get_nfts_count(self: @ComponentState<TContractState>) -> u256 {
return self.nfts_count.read();
}

fn get_nft_metadata(self: @ComponentState<TContractState>, token_id: u256) -> NFTMetadata {
return self.nfts_data.read(token_id);
}

fn get_nft_minter(self: @ComponentState<TContractState>, token_id: u256) -> starknet::ContractAddress {
let metadata: NFTMetadata = self.nfts_data.read(token_id);
return metadata.minter;
}

fn get_nft_image_hash(self: @ComponentState<TContractState>, token_id: u256) -> felt252 {
let metadata: NFTMetadata = self.nfts_data.read(token_id);
return metadata.image_hash;
}
}
}
39 changes: 39 additions & 0 deletions onchain/src/nfts/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#[derive(Drop, Serde)]
pub struct NFTMintParams {
position: u128,
width: u128,
height: u128,
}

#[derive(Drop, Serde, PartialEq, starknet::Store)]
pub struct NFTMetadata {
position: u128,
width: u128,
height: u128,
image_hash: felt252,
block_number: u64,
minter: starknet::ContractAddress,
}

#[starknet::interface]
pub trait ICanvasNFTStore<TContractState> {
// Returns the on-chain metadata of the NFT.
fn get_nft_metadata(self: @TContractState, token_id: u256) -> NFTMetadata;
fn get_nft_minter(self: @TContractState, token_id: u256) -> starknet::ContractAddress;
fn get_nft_image_hash(self: @TContractState, token_id: u256) -> felt252;

// Returns the number of NFTs stored in the contract state.
fn get_nfts_count(self: @TContractState) -> u256;
}

#[starknet::interface]
pub trait ICanvasNFTAdditional<TContractState> {
// Mint a new NFT called by the ArtPeaceNFTMinter contract.
fn mint(ref self: TContractState, metadata: NFTMetadata, receiver: starknet::ContractAddress);
}

#[starknet::interface]
pub trait IArtPeaceNFTMinter<TContractState> {
// Mints a new NFT from the canvas using init params, and returns the token ID.
fn mint_nft(self: @TContractState, mint_params: NFTMintParams);
}
Loading

0 comments on commit 6208780

Please sign in to comment.