diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..45efd72 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.6.3 +starknet-foundry 0.27.0 diff --git a/Scarb.lock b/Scarb.lock index 211984c..e676ff8 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -36,5 +36,5 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.12.0#06 [[package]] name = "snforge_std" -version = "0.20.1" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.20.1#fea2db8f2b20148cc15ee34b08de12028eb42942" +version = "0.26.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.26.0#50eb589db65e113efe4f09241feb59b574228c7e" diff --git a/Scarb.toml b/Scarb.toml index ed1cf82..6959cf7 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -5,13 +5,13 @@ version = "0.1.0" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest [dependencies] -starknet = "2.6.x" +starknet = "2.6.3" cairo_json = { git = "https://github.com/carbonable-labs/cairo-json.git", tag = "v0.1.0" } alexandria_ascii = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag = "cairo-v2.1.0" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.12.0" } [dev-dependencies] -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.20.1" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.26.0" } [[target.starknet-contract]] sierra = true diff --git a/src/base/types.cairo b/src/base/types.cairo index 8711983..50eee81 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -2,6 +2,7 @@ pub struct ArtistMetadata { name: felt252, bio: felt252, + // wallet_address: ContractAddress, profile_link: felt252, } diff --git a/src/contract.cairo b/src/contract.cairo index 529623a..4acd35e 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -1,34 +1,82 @@ +use starknet::ContractAddress; + +// ************************************************************************* +// OZ IMPORTS +// ************************************************************************* +use openzeppelin::{ + token::erc721::{ERC721Component::{ERC721Metadata, HasComponent}}, + introspection::src5::SRC5Component, +}; + +#[starknet::interface] +trait IERC721Metadata { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; +} + +#[starknet::embeddable] +impl IERC721MetadataImpl< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop +> of IERC721Metadata { + fn name(self: @TContractState) -> ByteArray { + let component = HasComponent::get_component(self); + ERC721Metadata::name(component) + } + + fn symbol(self: @TContractState) -> ByteArray { + let component = HasComponent::get_component(self); + ERC721Metadata::symbol(component) + } +} + #[starknet::contract] -mod SvgPoc { +pub mod SeriesPoc { + use openzeppelin::token::erc721::interface::IERC721Metadata; + use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; + + use core::traits::TryInto; + // use core::num::traits::zero::Zero; + use nicera_svg_poc::interfaces::erc721::IERC721; - use nicera_svg_poc::svg::image::generate_svg; - use nicera_svg_poc::base::types::ArtistMetadata; use nicera_svg_poc::base::types::Series; - use starknet::ContractAddress; - use starknet::get_caller_address; - use zeroable::Zeroable; - use option::OptionTrait; - use array::ArrayTrait; - use traits::Into; + use nicera_svg_poc::base::types::ArtistMetadata; - const IERC721_ID: felt252 = 0x80ac58cd; - const IERC721_METADATA_ID: felt252 = 0x5b5e139f; - const IERC721_RECEIVER_ID: felt252 = 0x150b7a02; + use openzeppelin::{ + token::erc721::{ + ERC721Component, erc721::ERC721Component::InternalTrait as ERC721InternalTrait + }, + introspection::{src5::SRC5Component} + }; + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + + + // allow to check what interface is supported + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721CamelOnly = ERC721Component::ERC721CamelOnlyImpl; + #[abi(embed_v0)] + impl ERC721MetadataCamelOnly = + ERC721Component::ERC721MetadataCamelOnlyImpl; #[storage] struct Storage { - _name: felt252, - _symbol: felt252, - _owners: LegacyMap, - _balances: LegacyMap, - _token_approvals: LegacyMap, - _operator_approvals: LegacyMap<(ContractAddress, ContractAddress), bool>, - _token_uri: LegacyMap, - _owner: ContractAddress, _series_counter: u256, _series_data: LegacyMap, _artists_counter: u256, _artists_data: LegacyMap, + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, } // Events @@ -38,6 +86,10 @@ mod SvgPoc { Transfer: Transfer, Approval: Approval, ApprovalForAll: ApprovalForAll, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, } #[derive(Drop, starknet::Event)] @@ -61,85 +113,70 @@ mod SvgPoc { approved: bool, } + #[constructor] - fn constructor(ref self: ContractState, name_: felt252, symbol_: felt252) { - self._name.write(name_); - self._symbol.write(symbol_); - self._owner.write(get_caller_address()); + fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray,) { + let base_uri = ""; + self.erc721.initializer(name, symbol, base_uri); } - #[abi(embed_v0)] - impl SvgPocImpl of IERC721 { - fn name(self: @ContractState) -> felt252 { - 'nicera_svg_poc' + impl SeriesImpl of IERC721 { + fn name(self: @ContractState) -> ByteArray { + self.erc721.name() } - fn symbol(self: @ContractState) -> felt252 { - 'NSP' + /// @notice returns the collection symbol + fn symbol(self: @ContractState) -> ByteArray { + self.erc721.symbol() } - fn owner(self: @ContractState) -> ContractAddress { - self._owner.read() + fn token_uri(self: @ContractState, token_id: u256) -> ByteArray { + self.erc721.token_uri(token_id) } - - fn token_uri(self: @ContractState, token_id: u256) -> Array { - generate_svg(token_id) - } - fn balance_of(self: @ContractState, owner: ContractAddress) -> u256 { - 1 + self.erc721.balance_of(owner) } - fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { - self._owner.read() + self.erc721.owner_of(token_id) } - fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { - self._owner.read() + self.erc721.get_approved(token_id) } - fn is_approved_for_all( self: @ContractState, owner: ContractAddress, operator: ContractAddress ) -> bool { - false + self.erc721.is_approved_for_all(owner, operator) } - fn transfer_from( - self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 - ) {} + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + self.erc721.transfer_from(from, to, token_id) + } + - // Externals - fn approve(ref self: ContractState, approved: ContractAddress, token_id: u256) {} + fn approve(ref self: ContractState, approved: ContractAddress, token_id: u256) { + self.erc721.approve(approved, token_id) + } fn set_approval_for_all( ref self: ContractState, operator: ContractAddress, approval: bool - ) {} - + ) { + self.erc721.set_approval_for_all(operator, approval) + } fn mint(ref self: ContractState, to: ContractAddress) { assert(!to.is_zero(), 'ERC721: invalid receiver'); - let token_id: felt252 = get_caller_address().into(); - let token_id: u256 = token_id.into(); - assert(!self._exists(token_id), 'ERC721: token already minted'); - - // Update balances - self._balances.write(to, self._balances.read(to) + 1.into()); - - // Update token_id owner + let mut token_id = self.erc721.balanceOf(to) + 1.into(); + self.erc721._mint(to, token_id); - self._owners.write(token_id, to); - - // Emit event + // Emit Event self.emit(Event::Transfer(Transfer { from: Zeroable::zero(), to, token_id })); } - fn get_series(self: @ContractState, series_id: u256) -> Series { self._series_data.read(series_id) } - fn get_artist(self: @ContractState, artist_id: u256) -> ArtistMetadata { self._artists_data.read(artist_id) } - fn create_series( ref self: ContractState, name: felt252, @@ -158,12 +195,4 @@ mod SvgPoc { series_id } } - - /// Helpers (internal functions) - #[generate_trait] - impl HelperImpl of HelperTrait { - fn _exists(self: @ContractState, token_id: u256) -> bool { - !self._owners.read(token_id).is_zero() - } - } } diff --git a/src/interfaces/erc721.cairo b/src/interfaces/erc721.cairo index 5bb0136..c38c1c7 100644 --- a/src/interfaces/erc721.cairo +++ b/src/interfaces/erc721.cairo @@ -5,10 +5,10 @@ use nicera_svg_poc::base::types::Series; #[starknet::interface] trait IERC721 { // Views - fn name(self: @TContractState) -> felt252; - fn symbol(self: @TContractState) -> felt252; - fn owner(self: @TContractState) -> ContractAddress; - fn token_uri(self: @TContractState, token_id: u256) -> Array; + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + // fn owner(self: @TContractState) -> ContractAddress; + fn token_uri(self: @TContractState, token_id: u256) -> ByteArray; fn balance_of(self: @TContractState, owner: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; @@ -16,7 +16,7 @@ trait IERC721 { self: @TContractState, owner: ContractAddress, operator: ContractAddress ) -> bool; fn transfer_from( - self: @TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 ); // Externals