Skip to content
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

Feat: Metadata handler #119

Merged
merged 6 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 27 additions & 12 deletions src/components/metadata.cairo
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
use starknet::ClassHash;
use starknet::{ClassHash, ContractAddress};

#[starknet::interface]
trait IMetadataHandler<TContractState> {
fn uri(self: @TContractState, token_id: u256) -> ByteArray;
fn uri(self: @TContractState, token_id: u256) -> Span<felt252>;
fn get_provider(self: @TContractState) -> ContractAddress;
fn set_provider(ref self: TContractState, provider: ContractAddress);
fn get_uri(self: @TContractState) -> ClassHash;
fn set_uri(ref self: TContractState, class_hash: ClassHash);
}

#[starknet::interface]
trait IMetadataDescriptor<TContractState> {
fn construct_uri(self: @TContractState, token_id: u256) -> ByteArray;
fn construct_uri(self: @TContractState, token_id: u256) -> Span<felt252>;
}

#[starknet::component]
mod MetadataComponent {
use starknet::ClassHash;
use starknet::{ClassHash, ContractAddress};
use super::{IMetadataDescriptorLibraryDispatcher, IMetadataDescriptorDispatcherTrait};

#[storage]
struct Storage {
uri_implementation: ClassHash
MetadataHandler_uri_implementation: ClassHash,
MetadataHandler_provider: ContractAddress,
}

#[derive(Drop, starknet::Event)]
Expand All @@ -37,17 +41,29 @@ mod MetadataComponent {
impl CarbonV3Metadata<
TContractState, +HasComponent<TContractState>
> of super::IMetadataHandler<ComponentState<TContractState>> {
fn uri(self: @ComponentState<TContractState>, token_id: u256) -> ByteArray {
let class_hash = self.uri_implementation.read();
fn uri(self: @ComponentState<TContractState>, token_id: u256) -> Span<felt252> {
let class_hash = self.MetadataHandler_uri_implementation.read();
let uri_lib = IMetadataDescriptorLibraryDispatcher { class_hash };
uri_lib.construct_uri(token_id)
}

fn set_uri(ref self: ComponentState<TContractState>, class_hash: ClassHash) {
fn get_provider(self: @ComponentState<TContractState>) -> ContractAddress {
self.MetadataHandler_provider.read()
}

fn set_provider(ref self: ComponentState<TContractState>, provider: ContractAddress) {
self.MetadataHandler_provider.write(provider);
}

fn get_uri(self: @ComponentState<TContractState>,) -> ClassHash {
self.MetadataHandler_uri_implementation.read()
}

fn set_uri(ref self: ComponentState<TContractState>, class_hash: ClassHash,) {
assert(!class_hash.is_zero(), 'URI class hash cannot be zero');
let old_class_hash = self.uri_implementation.read();
let old_class_hash = self.MetadataHandler_uri_implementation.read();
self.MetadataHandler_uri_implementation.write(class_hash);
self.emit(MetadataUpgraded { old_class_hash, class_hash });
self.uri_implementation.write(class_hash);
}
}
}
Expand All @@ -69,7 +85,6 @@ mod TestMetadataComponent {
let dispatcher = IMetadataHandlerDispatcher { contract_address };

dispatcher.set_uri(metadata_class.class_hash);
let uri = dispatcher.uri(1);
assert_eq!(uri, "bla bla bla");
assert_eq!(dispatcher.get_uri(), metadata_class.class_hash);
}
}
98 changes: 60 additions & 38 deletions src/contracts/project.cairo
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
use starknet::ContractAddress;
use starknet::{ContractAddress, ClassHash};

#[starknet::interface]
trait IExternal<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, token_id: u256, value: u256);
fn offset(ref self: ContractState, from: ContractAddress, token_id: u256, value: u256);
trait IExternal<TContractState> {
fn mint(ref self: TContractState, to: ContractAddress, token_id: u256, value: u256);
fn offset(ref self: TContractState, from: ContractAddress, token_id: u256, value: u256);
fn batch_mint(
ref self: ContractState, to: ContractAddress, token_ids: Span<u256>, values: Span<u256>
ref self: TContractState, to: ContractAddress, token_ids: Span<u256>, values: Span<u256>
);
fn batch_offset(
ref self: ContractState, from: ContractAddress, token_ids: Span<u256>, values: Span<u256>
ref self: TContractState, from: ContractAddress, token_ids: Span<u256>, values: Span<u256>
);
fn set_uri(ref self: ContractState, uri: ByteArray);
fn get_uri(self: @ContractState, token_id: u256) -> ByteArray;
fn decimals(self: @ContractState) -> u8;
fn only_owner(self: @ContractState, caller_address: ContractAddress) -> bool;
fn grant_minter_role(ref self: ContractState, minter: ContractAddress);
fn revoke_minter_role(ref self: ContractState, account: ContractAddress);
fn grant_offsetter_role(ref self: ContractState, offsetter: ContractAddress);
fn revoke_offsetter_role(ref self: ContractState, account: ContractAddress);
fn balance_of(self: @ContractState, account: ContractAddress, token_id: u256) -> u256;
fn uri(self: @TContractState, token_id: u256) -> Span<felt252>;
fn get_provider(self: @TContractState) -> ContractAddress;
fn set_provider(ref self: TContractState, provider: ContractAddress);
fn get_uri(self: @TContractState) -> ClassHash;
fn set_uri(ref self: TContractState, class_hash: ClassHash);
fn decimals(self: @TContractState) -> u8;
fn only_owner(self: @TContractState, caller_address: ContractAddress) -> bool;
fn grant_minter_role(ref self: TContractState, minter: ContractAddress);
fn revoke_minter_role(ref self: TContractState, account: ContractAddress);
fn grant_offsetter_role(ref self: TContractState, offsetter: ContractAddress);
fn revoke_offsetter_role(ref self: TContractState, account: ContractAddress);
fn balance_of(self: @TContractState, account: ContractAddress, token_id: u256) -> u256;
fn balance_of_batch(
self: @ContractState, accounts: Span<ContractAddress>, token_ids: Span<u256>
self: @TContractState, accounts: Span<ContractAddress>, token_ids: Span<u256>
) -> Span<u256>;
fn shares_of(self: @ContractState, account: ContractAddress, token_id: u256) -> u256;
fn shares_of(self: @TContractState, account: ContractAddress, token_id: u256) -> u256;
fn safe_transfer_from(
ref self: ContractState,
ref self: TContractState,
from: ContractAddress,
to: ContractAddress,
token_id: u256,
value: u256,
data: Span<felt252>
);
fn safe_batch_transfer_from(
ref self: ContractState,
ref self: TContractState,
from: ContractAddress,
to: ContractAddress,
token_ids: Span<u256>,
values: Span<u256>,
data: Span<felt252>
);
fn is_approved_for_all(
self: @ContractState, owner: ContractAddress, operator: ContractAddress
self: @TContractState, owner: ContractAddress, operator: ContractAddress
) -> bool;
fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool);
fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool);
}


Expand All @@ -59,8 +62,10 @@ mod Project {
use openzeppelin::introspection::src5::SRC5Component;
// ERC1155
use carbon_v3::components::erc1155::ERC1155Component;
// Absorber
// Vintage
use carbon_v3::components::vintage::VintageComponent;
// Metadata
use carbon_v3::components::metadata::MetadataComponent;
// Access Control - RBAC
use openzeppelin::access::accesscontrol::AccessControlComponent;
// ERC4906
Expand All @@ -73,12 +78,13 @@ mod Project {
component!(path: VintageComponent, storage: vintage, event: VintageEvent);
component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);
component!(path: ERC4906Component, storage: erc4906, event: ERC4906Event);
component!(path: MetadataComponent, storage: metadata, event: MetadataEvent);

// ERC1155
impl ERC1155Impl = ERC1155Component::ERC1155Impl<ContractState>;
#[abi(embed_v0)]
impl ERC1155MetadataURIImpl =
ERC1155Component::ERC1155MetadataURIImpl<ContractState>;
// #[abi(embed_v0)]
// impl ERC1155MetadataURIImpl =
// ERC1155Component::ERC1155MetadataURIImpl<ContractState>;
#[abi(embed_v0)]
impl ERC1155Camel = ERC1155Component::ERC1155CamelImpl<ContractState>;
#[abi(embed_v0)]
Expand All @@ -94,6 +100,8 @@ mod Project {
#[abi(embed_v0)]
impl AccessControlImpl =
AccessControlComponent::AccessControlImpl<ContractState>;
// Metadata
impl CarbonV3MetadataImpl = MetadataComponent::CarbonV3MetadataImpl<ContractState>;

impl ERC1155InternalImpl = ERC1155Component::InternalImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
Expand Down Expand Up @@ -127,6 +135,8 @@ mod Project {
accesscontrol: AccessControlComponent::Storage,
#[substorage(v0)]
erc4906: ERC4906Component::Storage,
#[substorage(v0)]
metadata: MetadataComponent::Storage,
}

#[event]
Expand All @@ -146,6 +156,8 @@ mod Project {
AccessControlEvent: AccessControlComponent::Event,
#[flat]
ERC4906Event: ERC4906Component::Event,
#[flat]
MetadataEvent: MetadataComponent::Event,
}

mod Errors {
Expand All @@ -156,19 +168,14 @@ mod Project {
// Constructor
#[constructor]
fn constructor(
ref self: ContractState,
base_uri: felt252,
owner: ContractAddress,
starting_year: u32,
number_of_years: u32
ref self: ContractState, owner: ContractAddress, starting_year: u32, number_of_years: u32
) {
self.accesscontrol.initializer();
self.accesscontrol._grant_role(OWNER_ROLE, owner);
self.accesscontrol._set_role_admin(MINTER_ROLE, OWNER_ROLE);
self.accesscontrol._set_role_admin(OFFSETTER_ROLE, OWNER_ROLE);
self.accesscontrol._set_role_admin(OWNER_ROLE, OWNER_ROLE);
let base_uri_bytearray: ByteArray = format!("{}", base_uri);
self.erc1155.initializer(base_uri_bytearray);
self.erc1155.initializer("");
self.ownable.initializer(owner);
self.vintage.initializer(starting_year, number_of_years);

Expand Down Expand Up @@ -217,9 +224,25 @@ mod Project {
self.erc1155.batch_burn(from, token_ids, values);
}

fn set_uri(ref self: ContractState, uri: ByteArray) {
// TODO: use own Metadata impl
self.erc1155.set_base_uri(uri);
fn uri(self: @ContractState, token_id: u256) -> Span<felt252> {
self.metadata.uri(token_id)
}

fn get_provider(self: @ContractState) -> ContractAddress {
self.metadata.get_provider()
}

fn set_provider(ref self: ContractState, provider: ContractAddress) {
let isOwner = self.accesscontrol.has_role(OWNER_ROLE, get_caller_address());
assert!(isOwner, "Caller is not owner");
self.metadata.set_provider(provider);
}

fn set_uri(ref self: ContractState, class_hash: ClassHash) {
let isOwner = self.accesscontrol.has_role(OWNER_ROLE, get_caller_address());
assert!(isOwner, "Caller is not owner");

self.metadata.set_uri(class_hash);

let num_vintages = self.vintage.get_num_vintages();

Expand All @@ -229,9 +252,8 @@ mod Project {
._emit_batch_metadata_update(fromTokenId: 0, toTokenId: num_vintages.into());
}

fn get_uri(self: @ContractState, token_id: u256) -> ByteArray {
let uri_result: ByteArray = self.erc1155.uri(token_id);
uri_result
fn get_uri(self: @ContractState) -> ClassHash {
self.metadata.get_uri()
}

fn decimals(self: @ContractState) -> u8 {
Expand Down
4 changes: 2 additions & 2 deletions src/mock/metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ mod TestMetadata {

#[abi(embed_v0)]
impl MetadataProviderImpl of IMetadataDescriptor<ContractState> {
fn construct_uri(self: @ContractState, token_id: u256) -> ByteArray {
"bla bla bla"
fn construct_uri(self: @ContractState, token_id: u256) -> Span<felt252> {
array!['http://imgur.com/', 'o7a3j', '.png'].span()
}
}
}
1 change: 0 additions & 1 deletion tests/test_mint.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use snforge_std::{
ContractClassTrait, test_address, spy_events, EventSpy, CheatSpan, start_cheat_caller_address,
stop_cheat_caller_address,
};
use alexandria_storage::list::{List, ListTrait};

// Components

Expand Down
2 changes: 0 additions & 2 deletions tests/test_offsetter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ use snforge_std::{
ContractClassTrait, test_address, spy_events, EventSpy, start_cheat_caller_address,
stop_cheat_caller_address
};
use alexandria_storage::list::{List, ListTrait};

// Components

use carbon_v3::components::vintage::interface::{IVintageDispatcher, IVintageDispatcherTrait};
Expand Down
28 changes: 11 additions & 17 deletions tests/test_project.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// TODO: use token_ids instead of years as vintage
// Starknet deps

use starknet::{ContractAddress, contract_address_const, get_caller_address};
use starknet::{ContractAddress, contract_address_const, get_caller_address, ClassHash};

// External deps

Expand All @@ -10,7 +10,6 @@ use snforge_std as snf;
use snforge_std::{
ContractClassTrait, EventSpy, start_cheat_caller_address, stop_cheat_caller_address, spy_events
};
use alexandria_storage::list::{List, ListTrait};

// Models

Expand All @@ -23,9 +22,7 @@ use carbon_v3::components::vintage::interface::{
IVintage, IVintageDispatcher, IVintageDispatcherTrait
};
use carbon_v3::components::minter::interface::{IMint, IMintDispatcher, IMintDispatcherTrait};
use carbon_v3::components::erc1155::interface::{
IERC1155MetadataURI, IERC1155MetadataURIDispatcher, IERC1155MetadataURIDispatcherTrait
};
use carbon_v3::components::metadata::{IMetadataHandlerDispatcher, IMetadataHandlerDispatcherTrait};
use erc4906::erc4906_component::ERC4906Component::{Event, MetadataUpdate, BatchMetadataUpdate};

// Contracts
Expand Down Expand Up @@ -645,20 +642,17 @@ fn test_project_metadata_update() {
let owner_address: ContractAddress = contract_address_const::<'OWNER'>();
let (project_address, _) = default_setup_and_deploy();
let project_contract = IProjectDispatcher { contract_address: project_address };
let erc1155_meta = IERC1155MetadataURIDispatcher { contract_address: project_address };
let base_uri: ByteArray = format!("{}", 'uri');
let mut new_uri: ByteArray = format!("{}", 'new/uri');
let metadata = IMetadataHandlerDispatcher { contract_address: project_address };
let base_uri: ClassHash = 0.try_into().unwrap();
let mut new_uri: ClassHash = 'new/uri'.try_into().unwrap();

start_cheat_caller_address(project_address, owner_address);

// let num_vintages = vintages.get_num_vintages();
let vintage = 1;

assert(erc1155_meta.uri(vintage) == base_uri, 'Wrong base token URI');
assert(metadata.get_uri() == base_uri, 'Wrong base token URI');

project_contract.set_uri(new_uri.clone());
project_contract.set_uri(new_uri);

assert(erc1155_meta.uri(vintage) == new_uri.clone(), 'Wrong updated token URI');
assert(metadata.get_uri() == new_uri, 'Wrong updated token URI');
//check event emitted
// let expected_batch_metadata_update = BatchMetadataUpdate {
// from_token_id: 0, to_token_id: num_vintages.into()
Expand All @@ -673,9 +667,9 @@ fn test_set_uri() {
let project_contract = IProjectDispatcher { contract_address: project_address };

start_cheat_caller_address(project_address, owner_address);
project_contract.set_uri("test_uri");
let uri = project_contract.get_uri(1);
assert_eq!(uri, "test_uri");
project_contract.set_uri('test_uri'.try_into().unwrap());
let uri = project_contract.get_uri();
assert_eq!(uri, 'test_uri'.try_into().unwrap());
}

#[test]
Expand Down
7 changes: 1 addition & 6 deletions tests/tests_lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use snforge_std::{
ContractClassTrait, EventSpy, spy_events, EventSpyTrait, EventSpyAssertionsTrait,
start_cheat_caller_address, stop_cheat_caller_address
};
use alexandria_storage::list::{List, ListTrait};

// Models

Expand Down Expand Up @@ -102,14 +101,10 @@ fn share_to_buy_amount(minter_address: ContractAddress, share: u256) -> u256 {

fn deploy_project() -> (ContractAddress, EventSpy) {
let contract = snf::declare("Project").expect('Declaration failed');
let uri = 'uri';
let starting_year: u64 = 2024;
let number_of_years: u64 = 20;
let mut calldata: Array<felt252> = array![
uri,
contract_address_const::<'OWNER'>().into(),
starting_year.into(),
number_of_years.into()
contract_address_const::<'OWNER'>().into(), starting_year.into(), number_of_years.into()
];
let (contract_address, _) = contract.deploy(@calldata).expect('Deployment failed');
let mut spy = spy_events();
Expand Down
Loading