diff --git a/.gitignore b/.gitignore index ec6df06..54672b6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ keystore.json __pycache__ env .env +.env.* test.* # starknet @@ -26,4 +27,5 @@ build .DS_Store tmp -.tmp* \ No newline at end of file +.tmp* +old* \ No newline at end of file diff --git a/Scarb.toml b/Scarb.toml index 3ecf0e8..31f6e84 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -8,7 +8,8 @@ version = "0.1.0" starknet = ">=2.3.1" alexandria_storage = { git = "https://github.com/tekkac/alexandria", branch = "feat/interpolation-fast" } alexandria_numeric = { git = "https://github.com/tekkac/alexandria", branch = "feat/interpolation-fast" } -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" } -token = { git = "https://github.com/dojoengine/origami.git", rev = "60d8eb7875d4c0367d75a337b0ce607ee5992e4b" } +openzeppelin = { git = "https://github.com/cloudvenger/cairo-contracts.git", rev = "fd41fdb9d04a701b2353c4cd0a8bd19f3139e88a" } [[target.starknet-contract]] +sierra = true +casm = false \ No newline at end of file diff --git a/scripts/deploy_project.sh b/scripts/deploy_project.sh index 93fc1f3..5a522b6 100644 --- a/scripts/deploy_project.sh +++ b/scripts/deploy_project.sh @@ -1,15 +1,14 @@ #!/bin/bash source ../.env -SIERRA_FILE=../target/dev/carbon_Project.sierra.json -NAME=0x436172626f6e5f54657374 +SIERRA_FILE=../target/dev/carbon_v3_Project.contract_class.json +NAME="TokCC" SYMBOL="CARBT" DECIMALS=6 -OWNER=0x05bB7458b87FaaA41303A69B771ae26235F28b79aBD5FA1C451C43461DFE1438 -SLOT=1 -PROJECT_VALUE=100000000 -TIMES="2 1688169600 1719792000" -ABSORPTIONS="2 0 2746197000000" +OWNER=0x01e2F67d8132831f210E19c5Ee0197aA134308e16F7f284bBa2c72E28FC464D2 +PROJECT_VALUE=121099000000 +TIMES="21 1674579600 1706115600 1737738000 1769274000 1800810000 1832346000 1863968400 1895504400 1927040400 1958576400 1990198800 2021734800 2053270800 2084806800 2116429200 2147965200 2179501200 2211037200 2242659600 2274195600 2305731600" +ABSORPTIONS="21 0 29609535 47991466 88828605 118438140 370922507 623406874 875891241 1128375608 1380859976 2076175721 2771491466 3466807212 4162122957 4857438703 5552754448 6248070193 6943385939 7638701684 8000000000 8000000000" TON_EQUIVALENT=1000000 # build the solution @@ -45,7 +44,7 @@ deploy() { class_hash=$(declare | tail -n 1) sleep 5 - output=$(starkli deploy $class_hash "$NAME" str:"$SYMBOL" "$DECIMALS" "$OWNER" --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) + output=$(starkli deploy $class_hash str:"$NAME" str:"$SYMBOL" "$OWNER" --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) if [[ $output == *"Error"* ]]; then echo "Error: $output" @@ -57,26 +56,23 @@ deploy() { } setup() { - contract=$(deploy) - sleep 5 - - output=$(starkli invoke $contract set_project_value u256:$SLOT u256:$PROJECT_VALUE --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) - if [[ $output == *"Error"* ]]; then - echo "Error: $output" - exit 1 - fi + #contract=$(deploy) + #sleep 5 + contract="0x0348de44e0109e364e15c85407950a6ed39393a49396ca83c02516c4390f81e8" - output=$(starkli invoke $contract set_certifier u256:$SLOT $OWNER --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) + output=$(starkli invoke $contract set_project_value u256:$PROJECT_VALUE --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) if [[ $output == *"Error"* ]]; then echo "Error: $output" exit 1 fi + echo "Success: $output" - output=$(starkli invoke $contract set_absorptions u256:$SLOT $TIMES $ABSORPTIONS $TON_EQUIVALENT --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) + output=$(starkli invoke $contract set_absorptions $TIMES $ABSORPTIONS $TON_EQUIVALENT --keystore-password $KEYSTORE_PASSWORD --watch 2>&1) if [[ $output == *"Error"* ]]; then echo "Error: $output" exit 1 fi + echo "Success: $output" echo $contract } diff --git a/src/components/absorber.cairo b/src/components/absorber.cairo new file mode 100644 index 0000000..552cd83 --- /dev/null +++ b/src/components/absorber.cairo @@ -0,0 +1,4 @@ +mod module; +mod interface; + +use module::AbsorberComponent; diff --git a/src/components/absorber/interface.cairo b/src/components/absorber/interface.cairo index b0b1a43..00ae488 100644 --- a/src/components/absorber/interface.cairo +++ b/src/components/absorber/interface.cairo @@ -34,10 +34,7 @@ trait IAbsorber { /// Setup the absorption curve parameters. fn set_absorptions( - ref self: TContractState, - times: Span, - absorptions: Span, - ton_equivalent: u64 + ref self: TContractState, times: Span, absorptions: Span, ton_equivalent: u64 ); /// Setup the project value for the given slot. diff --git a/src/components/absorber/module.cairo b/src/components/absorber/module.cairo index 4d1fe47..a46beee 100644 --- a/src/components/absorber/module.cairo +++ b/src/components/absorber/module.cairo @@ -1,31 +1,27 @@ -#[starknet::contract] -mod Absorber { +#[starknet::component] +mod AbsorberComponent { // Core imports - use traits::Into; use array::{ArrayTrait, SpanTrait}; // Starknet imports - use starknet::{get_block_timestamp, get_caller_address, ContractAddress}; // External imports - use alexandria_numeric::interpolate::{ interpolate_fast as interpolate, Interpolation, Extrapolation }; use alexandria_storage::list::{List, ListTrait}; // Internal imports - use carbon_v3::components::absorber::interface::IAbsorber; #[storage] struct Storage { - absorber_ton_equivalent: u64, - absorber_project_value: u256, - absorber_times: List, - absorber_absorptions: List, + Absorber_ton_equivalent: u64, + Absorber_project_value: u256, + Absorber_times: List, + Absorber_absorptions: List, } #[event] @@ -37,43 +33,48 @@ mod Absorber { #[derive(Drop, starknet::Event)] struct AbsorptionUpdate { + #[key] time: u64, } #[derive(Drop, starknet::Event)] struct ProjectValueUpdate { + #[key] value: u256 } - impl AbsorberImpl of IAbsorber { + #[embeddable_as(AbsorberImpl)] + impl Absorber< + TContractState, +HasComponent, +Drop + > of IAbsorber> { // Absorption - fn get_start_time(self: @ContractState) -> u64 { - let times = self.absorber_times.read(); + fn get_start_time(self: @ComponentState) -> u64 { + let times = self.Absorber_times.read(); if times.len() == 0 { return 0; } times[0] } - fn get_final_time(self: @ContractState) -> u64 { - let times = self.absorber_times.read(); + fn get_final_time(self: @ComponentState) -> u64 { + let times = self.Absorber_times.read(); if times.len() == 0 { return 0; } times[times.len() - 1] } - fn get_times(self: @ContractState) -> Span { - self.absorber_times.read().array().span() + fn get_times(self: @ComponentState) -> Span { + self.Absorber_times.read().array().span() } - fn get_absorptions(self: @ContractState) -> Span { - self.absorber_absorptions.read().array().span() + fn get_absorptions(self: @ComponentState) -> Span { + self.Absorber_absorptions.read().array().span() } - fn get_absorption(self: @ContractState, time: u64) -> u64 { - let times = self.absorber_times.read(); + fn get_absorption(self: @ComponentState, time: u64) -> u64 { + let times = self.Absorber_times.read(); if times.len() == 0 { return 0; } - let absorptions = self.absorber_absorptions.read(); + let absorptions = self.Absorber_absorptions.read(); if absorptions.len() == 0 { return 0; } @@ -92,30 +93,30 @@ mod Absorber { absorption.try_into().expect('Absorber: Absorption overflow') } - fn get_current_absorption(self: @ContractState) -> u64 { + fn get_current_absorption(self: @ComponentState) -> u64 { self.get_absorption(get_block_timestamp()) } - fn get_final_absorption(self: @ContractState) -> u64 { - let absorptions = self.absorber_absorptions.read(); + fn get_final_absorption(self: @ComponentState) -> u64 { + let absorptions = self.Absorber_absorptions.read(); if absorptions.len() == 0 { return 0; } absorptions[absorptions.len() - 1] } - fn get_project_value(self: @ContractState) -> u256 { - self.absorber_project_value.read() + fn get_project_value(self: @ComponentState) -> u256 { + self.Absorber_project_value.read() } - fn get_ton_equivalent(self: @ContractState) -> u64 { - self.absorber_ton_equivalent.read() + fn get_ton_equivalent(self: @ComponentState) -> u64 { + self.Absorber_ton_equivalent.read() } - fn is_setup(self: @ContractState) -> bool { - self.absorber_project_value.read() - * self.absorber_times.read().len().into() - * self.absorber_absorptions.read().len().into() - * self.absorber_ton_equivalent.read().into() != 0 + fn is_setup(self: @ComponentState) -> bool { + self.Absorber_project_value.read() + * self.Absorber_times.read().len().into() + * self.Absorber_absorptions.read().len().into() + * self.Absorber_ton_equivalent.read().into() != 0 } fn set_absorptions( - ref self: ContractState, + ref self: ComponentState, times: Span, absorptions: Span, ton_equivalent: u64 @@ -126,9 +127,9 @@ mod Absorber { assert(ton_equivalent > 0, 'Ton equivalent must be positive'); // [Effect] Clean times and absorptions - let mut stored_times = self.absorber_times.read(); + let mut stored_times: List = self.Absorber_times.read(); stored_times.len = 0; - let mut stored_absorptions = self.absorber_absorptions.read(); + let mut stored_absorptions: List = self.Absorber_absorptions.read(); stored_absorptions.len = 0; // [Effect] Store new times and absorptions @@ -150,29 +151,28 @@ mod Absorber { }; // [Effect] Store new ton equivalent - self.absorber_ton_equivalent.write(ton_equivalent); + self.Absorber_ton_equivalent.write(ton_equivalent); // [Event] Emit event let current_time = get_block_timestamp(); self.emit(AbsorptionUpdate { time: current_time }); } - fn set_project_value(ref self: ContractState, project_value: u256) { + fn set_project_value(ref self: ComponentState, project_value: u256) { // [Event] Update storage - self.absorber_project_value.write(project_value); + self.Absorber_project_value.write(project_value); // [Event] Emit event - self - .emit( - Event::ProjectValueUpdate( - ProjectValueUpdate { value: project_value } - ) - ); + self.emit(Event::ProjectValueUpdate(ProjectValueUpdate { value: project_value })); } } #[generate_trait] - impl PrivateImpl of PrivateTrait { - fn __list_u64_into_u256(self: @ContractState, list: @List) -> Span { + impl InternalImpl< + TContractState, +HasComponent, +Drop + > of InternalTrait { + fn __list_u64_into_u256( + self: @ComponentState, list: @List + ) -> Span { let mut array = ArrayTrait::::new(); let mut index = 0; loop { @@ -187,315 +187,3 @@ mod Absorber { } } - -#[cfg(test)] -mod Test { - use starknet::testing::set_block_timestamp; - use super::Absorber; - - use alexandria_storage::list::{List, ListTrait}; - - fn STATE() -> Absorber::ContractState { - Absorber::contract_state_for_testing() - } - - #[test] - #[available_gas(20_000_000)] - fn test_setup() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - let project_value = 100; - Absorber::AbsorberImpl::set_project_value(ref state, project_value); - // [Assert] Absorber is setup - let is_setup = Absorber::AbsorberImpl::is_setup(@state); - assert(is_setup, 'Absorber is not setup'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_not_setup_missing_project_value() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Absorber is setup - let is_setup = Absorber::AbsorberImpl::is_setup(@state); - assert(!is_setup, 'Absorber is not setup'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_not_setup_missing_absorptions() { - // [Setup] - let mut state = STATE(); - let project_value = 100; - Absorber::AbsorberImpl::set_project_value(ref state, project_value); - // [Assert] Absorber is setup - let is_setup = Absorber::AbsorberImpl::is_setup(@state); - assert(!is_setup, 'Absorber is not setup'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_project_value() { - // [Setup] - let mut state = STATE(); - // [Assert] Project value is 0 by default - let project_value = Absorber::AbsorberImpl::get_project_value(@state); - assert(project_value == 0, 'Wrong project value'); - // [Assert] Project value is set correctly - Absorber::AbsorberImpl::set_project_value(ref state, 100); - let project_value = Absorber::AbsorberImpl::get_project_value(@state); - assert(project_value == 100, 'Wrong project value'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_absorptions() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Times are set correctly - let stored_times = Absorber::AbsorberImpl::get_times(@state); - assert(stored_times == times, 'Wrong times'); - // [Assert] Absorptions are set correctly - let stored_absorptions = Absorber::AbsorberImpl::get_absorptions(@state); - assert(stored_absorptions == absorptions, 'Wrong absorptions'); - // [Assert] Ton equivalent is set correctly - let stored_ton_equivalent = Absorber::AbsorberImpl::get_ton_equivalent(@state); - assert(stored_ton_equivalent == ton_equivalent, 'Wrong ton equivalent'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_current_absorption() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![ - 0, 1179750000000, 2359500000000, 3539250000000, 4719000000000 - ] - .span(); - let ton_equivalent = 1000000; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Before start, absorption = absorptions[0] - set_block_timestamp(*times.at(0) - 86000); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption == *absorptions.at(0), 'Wrong absorption'); - // [Assert] At start, absorption = absorptions[0] - set_block_timestamp(*times.at(0)); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption == *absorptions.at(0), 'Wrong absorption'); - // [Assert] After start, absorptions[0] < absorption < absorptions[1] - set_block_timestamp(*times.at(0) + 86000); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption > *absorptions.at(0), 'Wrong absorption'); - assert(absorption < *absorptions.at(1), 'Wrong absorption'); - // [Assert] Before end, absorptions[-2] < absorption < absorptions[-1] - set_block_timestamp(*times.at(times.len() - 1) - 86000); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption > *absorptions.at(absorptions.len() - 2), 'Wrong absorption'); - assert(absorption < *absorptions.at(absorptions.len() - 1), 'Wrong absorption'); - // [Assert] At end, absorption = absorptions[-1] - set_block_timestamp(*times.at(times.len() - 1)); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption == *absorptions.at(absorptions.len() - 1), 'Wrong absorption'); - // [Assert] After end, absorption = absorptions[-1] - set_block_timestamp(*times.at(times.len() - 1) + 86000); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption == *absorptions.at(absorptions.len() - 1), 'Wrong absorption'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_current_absorption_zero_not_set() { - // [Setup] - let mut state = STATE(); - set_block_timestamp(86000); - let absorption = Absorber::AbsorberImpl::get_current_absorption(@state); - assert(absorption == 0, 'Wrong absorption'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_start_time() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1000000; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Start time = times[0] - let time = Absorber::AbsorberImpl::get_start_time(@state); - assert(time == *times.at(0), 'Wrong time'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_start_time_zero_not_set() { - // [Setup] - let mut state = STATE(); - // [Assert] Start time = 0 - let time = Absorber::AbsorberImpl::get_start_time(@state); - assert(time == 0, 'Wrong time'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_final_time() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1000000; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Final time = times[-1] - let time = Absorber::AbsorberImpl::get_final_time(@state); - assert(time == *times.at(times.len() - 1), 'Wrong time'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_final_time_zero_not_set() { - // [Setup] - let mut state = STATE(); - // [Assert] Final time = times[-1] - let time = Absorber::AbsorberImpl::get_final_time(@state); - assert(time == 0, 'Wrong time'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_final_absorption() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 1000000; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - // [Assert] Final absorption = absorptions[-1] - let absorption = Absorber::AbsorberImpl::get_final_absorption(@state); - assert(absorption == *absorptions.at(absorptions.len() - 1), 'Wrong absorption'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_final_absorption_zero_not_set() { - // [Setup] - let mut state = STATE(); - // [Assert] Final absorption = absorptions[-1] - let absorption = Absorber::AbsorberImpl::get_final_absorption(@state); - assert(absorption == 0, 'Wrong absorption'); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Times and absorptions mismatch',))] - fn test_set_absorptions_revert_mismatch() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![].span(); - let ton_equivalent = 0; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Inputs cannot be empty',))] - fn test_set_absorptions_revert_empty() { - // [Setup] - let mut state = STATE(); - let times: Span = array![].span(); - let absorptions: Span = array![].span(); - let ton_equivalent = 0; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Ton equivalent must be positive',))] - fn test_set_absorptions_revert_not_positive() { - // [Setup] - let mut state = STATE(); - let times: Span = array![1651363200, 1659312000, 1667260800, 1675209600, 1682899200] - .span(); - let absorptions: Span = array![0, 1179750, 2359500, 3539250, 4719000].span(); - let ton_equivalent = 0; - Absorber::AbsorberImpl::set_absorptions(ref state, times, absorptions, ton_equivalent); - } - - #[starknet::interface] - trait ITestList { - fn set_list(ref self: T, length: u64, mul: u64); - fn do_list_into(ref self: T); - } - - #[starknet::contract] - mod TestList { - use alexandria_storage::list::{List, ListTrait}; - #[storage] - struct Storage { - _test_list64: List, - _test_list256: List, - } - - impl ITestImpl of super::ITestList { - fn set_list(ref self: ContractState, length: u64, mul: u64) { - let mut list = self._test_list64.read(); - let mut i = 1; - loop { - if i > length { - break; - } - list.append(i * mul); - i += 1; - }; - } - fn do_list_into(ref self: ContractState) { - let mut array = ArrayTrait::::new(); - let mut index = 0; - let list = self._test_list64.read(); - loop { - if index == list.len() { - break (); - } - array.append(list[index].into()); - index += 1; - }; - array.span(); - } - } - } - - fn TEST_LIST_STATE() -> TestList::ContractState { - TestList::contract_state_for_testing() - } - - #[test] - #[available_gas(999_350_000)] - fn test_list_into_array() { - // [Setup] - let mut state = TEST_LIST_STATE(); - let n = 50; - TestList::ITestImpl::set_list(ref state, n, 2); - TestList::ITestImpl::do_list_into(ref state); - } -} diff --git a/src/components/access/interface.cairo b/src/components/access/interface.cairo deleted file mode 100644 index a221564..0000000 --- a/src/components/access/interface.cairo +++ /dev/null @@ -1,20 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IMinter { - fn get_minters(self: @TContractState) -> Span; - fn add_minter(ref self: TContractState, user: ContractAddress); - fn revoke_minter(ref self: TContractState, user: ContractAddress); -} - -#[starknet::interface] -trait ICertifier { - fn get_certifier(self: @TContractState) -> ContractAddress; - fn set_certifier(ref self: TContractState, user: ContractAddress); -} - -#[starknet::interface] -trait IWithdrawer { - fn get_withdrawer(self: @TContractState) -> ContractAddress; - fn set_withdrawer(ref self: TContractState, user: ContractAddress); -} diff --git a/src/components/access/module.cairo b/src/components/access/module.cairo deleted file mode 100644 index fea4293..0000000 --- a/src/components/access/module.cairo +++ /dev/null @@ -1,342 +0,0 @@ -#[starknet::contract] -mod Access { - // Core imports - - use poseidon::PoseidonTrait; - use hash::HashStateTrait; - use array::ArrayTrait; - use traits::Into; - - // Starknet imports - - use starknet::ContractAddress; - - // External imports - - use openzeppelin::access::accesscontrol::accesscontrol::AccessControl; - use openzeppelin::introspection::interface::{ISRC5, ISRC5Camel}; - use openzeppelin::introspection::src5::SRC5; - - // Internal imports - - use carbon_v3::components::access::interface::{IMinter, ICertifier, IWithdrawer}; - - // Constants - - const MINTER_ROLE: felt252 = 'MINTER'; - const CERTIFIER_ROLE: felt252 = 'CERTIFIER'; - const WITHDRAWER_ROLE: felt252 = 'WITHDRAWER'; - - #[storage] - struct Storage { - _access_role_members: LegacyMap<(felt252, u32), ContractAddress>, - _access_role_members_index: LegacyMap<(felt252, ContractAddress), u32>, - _access_role_members_len: LegacyMap, - } - - impl MinterImpl of IMinter { - fn get_minters(self: @ContractState) -> Span { - let role = self._hash(MINTER_ROLE); - self._get_role_members(role) - } - - fn add_minter(ref self: ContractState, user: ContractAddress) { - let role = self._hash(MINTER_ROLE); - self._set_role(role, user); - } - - fn revoke_minter(ref self: ContractState, user: ContractAddress) { - let role = self._hash(MINTER_ROLE); - self._revoke_role(role, user); - } - } - - impl CertifierImpl of ICertifier { - fn get_certifier(self: @ContractState) -> ContractAddress { - let role = self._hash(CERTIFIER_ROLE); - self._access_role_members.read((role, 0)) - } - - fn set_certifier(ref self: ContractState, user: ContractAddress) { - // [Effect] Revoke current certifier - let role = self._hash(CERTIFIER_ROLE); - let certifier = self._access_role_members.read((role, 0)); - let mut unsafe_state = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::_revoke_role(ref unsafe_state, role, certifier); - - // [Effect] Set new certifier - AccessControl::InternalImpl::_grant_role(ref unsafe_state, role, user); - self._access_role_members.write((role, 0), user); - } - } - - impl WithdrawerImpl of IWithdrawer { - fn get_withdrawer(self: @ContractState) -> ContractAddress { - self._access_role_members.read((WITHDRAWER_ROLE, 0)) - } - - fn set_withdrawer(ref self: ContractState, user: ContractAddress) { - // [Effect] Revoke current withdrawer - let withdrawer = self._access_role_members.read((WITHDRAWER_ROLE, 0)); - let mut unsafe_state = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::_revoke_role( - ref unsafe_state, WITHDRAWER_ROLE, withdrawer - ); - - // [Effect] Set new withdrawer - AccessControl::InternalImpl::_grant_role(ref unsafe_state, WITHDRAWER_ROLE, user); - self._access_role_members.write((WITHDRAWER_ROLE, 0), user); - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer(ref self: ContractState) { - let mut unsafe_sate = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::initializer(ref unsafe_sate); - } - - fn assert_only_minter(self: @ContractState) { - let role = self._hash(MINTER_ROLE); - let unsafe_state = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::assert_only_role(@unsafe_state, role); - } - - fn assert_only_certifier(self: @ContractState) { - let role = self._hash(CERTIFIER_ROLE); - let unsafe_state = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::assert_only_role(@unsafe_state, role); - } - - fn assert_only_withdrawer(self: @ContractState) { - let unsafe_state = AccessControl::unsafe_new_contract_state(); - AccessControl::InternalImpl::assert_only_role(@unsafe_state, WITHDRAWER_ROLE); - } - - fn _hash(self: @ContractState, role: felt252) -> felt252 { - let mut state = PoseidonTrait::new(); - state = state.update(role); - state.finalize() - } - - fn _get_role_members(self: @ContractState, role: felt252) -> Span { - let len = self._access_role_members_len.read(role); - let mut members: Array = ArrayTrait::new(); - let mut index = 0; - loop { - if index == len { - break; - } - members.append(self._access_role_members.read((role, index))); - index += 1; - }; - members.span() - } - - fn _set_role(ref self: ContractState, role: felt252, user: ContractAddress) { - // [Check] User already has role - let mut unsafe_sate = AccessControl::unsafe_new_contract_state(); - if AccessControl::AccessControlImpl::has_role(@unsafe_sate, role, user) { - return; - } - - // [Effect] Add user role - AccessControl::InternalImpl::_grant_role(ref unsafe_sate, role, user); - let index = self._access_role_members_len.read(role); - self._access_role_members.write((role, index), user); - self._access_role_members_index.write((role, user), index); - self._access_role_members_len.write(role, index + 1); - } - - fn _revoke_role(ref self: ContractState, role: felt252, user: ContractAddress) { - // [Check] User already has role - let mut unsafe_sate = AccessControl::unsafe_new_contract_state(); - if !AccessControl::AccessControlImpl::has_role(@unsafe_sate, role, user) { - return; - } - - // [Effect] Remove user role - let len = self._access_role_members_len.read(role); - let user_index = self._access_role_members_index.read((role, user)); - let last_user = self._access_role_members.read((role, len - 1)); - self._access_role_members_len.write(role, len - 1); - self._access_role_members.write((role, user_index), last_user); - self._access_role_members_index.write((role, last_user), user_index); - AccessControl::InternalImpl::_revoke_role(ref unsafe_sate, role, user); - } - } -} - -#[cfg(test)] -mod Test { - use starknet::testing::set_caller_address; - use super::Access; - - fn STATE() -> Access::ContractState { - Access::unsafe_new_contract_state() - } - - fn MINTER() -> starknet::ContractAddress { - starknet::contract_address_const::<'MINTER'>() - } - - fn CERTIFIER() -> starknet::ContractAddress { - starknet::contract_address_const::<'CERTIFIER'>() - } - - fn WITHDRAWER() -> starknet::ContractAddress { - starknet::contract_address_const::<'WITHDRAWER'>() - } - - fn PROVISIONER() -> starknet::ContractAddress { - starknet::contract_address_const::<'PROVISIONER'>() - } - - fn ANYONE() -> starknet::ContractAddress { - starknet::contract_address_const::<'ANYONE'>() - } - - fn ZERO() -> starknet::ContractAddress { - starknet::contract_address_const::<0>() - } - - #[test] - #[available_gas(20_000_000)] - fn test_initialization() { - // [Setup] - let mut state = STATE(); - // [Assert] Initialization pass - Access::InternalImpl::initializer(ref state); - } - - #[test] - #[available_gas(20_000_000)] - fn test_minter() { - // [Setup] - let mut state = STATE(); - // [Assert] Minters are null - let minters = Access::MinterImpl::get_minters(@state); - assert(minters == array![].span(), 'Wrong minters'); - // [Assert] Add minters - Access::MinterImpl::add_minter(ref state, CERTIFIER()); - Access::MinterImpl::add_minter(ref state, ANYONE()); - Access::MinterImpl::add_minter(ref state, MINTER()); - let minters = Access::MinterImpl::get_minters(@state); - assert(minters == array![CERTIFIER(), ANYONE(), MINTER()].span(), 'Wrong minters'); - // [Assert] Remove 2nd minter - Access::MinterImpl::revoke_minter(ref state, ANYONE()); - let minters = Access::MinterImpl::get_minters(@state); - assert(minters == array![CERTIFIER(), MINTER()].span(), 'Wrong minters'); - // [Assert] Remove 1st minter - Access::MinterImpl::revoke_minter(ref state, CERTIFIER()); - let minters = Access::MinterImpl::get_minters(@state); - assert(minters == array![MINTER()].span(), 'Wrong minters'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_assert_minter() { - // [Setup] - let mut state = STATE(); - Access::MinterImpl::add_minter(ref state, MINTER()); - // [Assert] Minter - set_caller_address(MINTER()); - Access::InternalImpl::assert_only_minter(@state); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Caller is missing role',))] - fn test_assert_minter_revert_not_minter() { - // [Setup] - let mut state = STATE(); - Access::MinterImpl::add_minter(ref state, MINTER()); - // [Assert] Minter - set_caller_address(ANYONE()); - Access::InternalImpl::assert_only_minter(@state); - } - - #[test] - #[available_gas(20_000_000)] - fn test_certifier() { - // [Setup] - let mut state = STATE(); - // [Assert] Certifier is null - let certifier = Access::CertifierImpl::get_certifier(@state); - assert(certifier == ZERO(), 'Wrong certifier'); - // [Assert] Certifier is set correctly - Access::CertifierImpl::set_certifier(ref state, CERTIFIER()); - let certifier = Access::CertifierImpl::get_certifier(@state); - assert(certifier == CERTIFIER(), 'Wrong certifier'); - // [Assert] Certifier is changed correctly - Access::CertifierImpl::set_certifier(ref state, ZERO()); - let certifier = Access::CertifierImpl::get_certifier(@state); - assert(certifier != CERTIFIER(), 'Wrong certifier'); - assert(certifier == ZERO(), 'Wrong certifier'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_assert_certifier() { - // [Setup] - let mut state = STATE(); - Access::CertifierImpl::set_certifier(ref state, CERTIFIER()); - // [Assert] Certifier - set_caller_address(CERTIFIER()); - Access::InternalImpl::assert_only_certifier(@state); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Caller is missing role',))] - fn test_assert_certifier_revert_not_certifier() { - // [Setup] - let mut state = STATE(); - Access::CertifierImpl::set_certifier(ref state, CERTIFIER()); - // [Assert] Certifier - set_caller_address(ANYONE()); - Access::InternalImpl::assert_only_certifier(@state); - } - - #[test] - #[available_gas(20_000_000)] - fn test_withdrawer() { - // [Setup] - let mut state = STATE(); - // [Assert] Withdrawer is null - let withdrawer = Access::WithdrawerImpl::get_withdrawer(@state); - assert(withdrawer == ZERO(), 'Wrong withdrawer'); - // [Assert] Withdrawer is set correctly - Access::WithdrawerImpl::set_withdrawer(ref state, WITHDRAWER()); - let withdrawer = Access::WithdrawerImpl::get_withdrawer(@state); - assert(withdrawer == WITHDRAWER(), 'Wrong withdrawer'); - // [Assert] Withdrawer is changed correctly - Access::WithdrawerImpl::set_withdrawer(ref state, ZERO()); - let withdrawer = Access::WithdrawerImpl::get_withdrawer(@state); - assert(withdrawer != WITHDRAWER(), 'Wrong withdrawer'); - assert(withdrawer == ZERO(), 'Wrong withdrawer'); - } - - #[test] - #[available_gas(20_000_000)] - fn test_assert_withdrawer() { - // [Setup] - let mut state = STATE(); - Access::WithdrawerImpl::set_withdrawer(ref state, WITHDRAWER()); - // [Assert] Withdrawer - set_caller_address(WITHDRAWER()); - Access::InternalImpl::assert_only_withdrawer(@state); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Caller is missing role',))] - fn test_assert_withdrawer_revert_not_withdrawer() { - // [Setup] - let mut state = STATE(); - Access::WithdrawerImpl::set_withdrawer(ref state, WITHDRAWER()); - // [Assert] Withdrawer - set_caller_address(ANYONE()); - Access::InternalImpl::assert_only_withdrawer(@state); - } -} diff --git a/src/components/mint.cairo b/src/components/mint.cairo new file mode 100644 index 0000000..b717ea0 --- /dev/null +++ b/src/components/mint.cairo @@ -0,0 +1,5 @@ +mod module; +mod booking; +mod interface; + +use module::MintComponent; diff --git a/src/components/mint/module.cairo b/src/components/mint/module.cairo index ee49f8f..2239bac 100644 --- a/src/components/mint/module.cairo +++ b/src/components/mint/module.cairo @@ -1,5 +1,5 @@ -#[starknet::contract] -mod Mint { +#[starknet::component] +mod MintComponent { // Core imports use zeroable::Zeroable; @@ -16,7 +16,7 @@ mod Mint { // External imports use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; - use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait}; + use openzeppelin::token::erc1155::interface::{IERC1155Dispatcher, IERC1155DispatcherTrait}; // Internal imports @@ -30,17 +30,17 @@ mod Mint { #[storage] struct Storage { - mint_carbonable_project_address: ContractAddress, - mint_carbonable_project_slot: u256, - mint_payment_token_address: ContractAddress, - mint_public_sale_open: bool, - mint_max_value: u256, - mint_unit_price: u256, - mint_claimed_value: LegacyMap, - mint_remaining_value: u256, - mint_count: LegacyMap::, - mint_booked_values: LegacyMap::<(ContractAddress, u32), Booking>, - mint_cancel: bool, + Mint_carbonable_project_address: ContractAddress, + Mint_carbonable_project_slot: u256, + Mint_payment_token_address: ContractAddress, + Mint_public_sale_open: bool, + Mint_max_value: u256, + Mint_unit_price: u256, + Mint_claimed_value: LegacyMap, + Mint_remaining_value: u256, + Mint_count: LegacyMap::, + Mint_booked_values: LegacyMap::<(ContractAddress, u32), Booking>, + Mint_cancel: bool, } #[event] @@ -96,42 +96,49 @@ mod Mint { time: u64 } - impl MintImpl of IMint { - fn get_carbonable_project_address(self: @ContractState) -> ContractAddress { - self.mint_carbonable_project_address.read() + #[embeddable_as(MintImpl)] + impl Mint< + TContractState, +HasComponent, +Drop + > of IMint> { + fn get_carbonable_project_address( + self: @ComponentState + ) -> ContractAddress { + self.Mint_carbonable_project_address.read() } - fn get_payment_token_address(self: @ContractState) -> ContractAddress { - self.mint_payment_token_address.read() + fn get_payment_token_address(self: @ComponentState) -> ContractAddress { + self.Mint_payment_token_address.read() } - fn is_public_sale_open(self: @ContractState) -> bool { - self.mint_public_sale_open.read() + fn is_public_sale_open(self: @ComponentState) -> bool { + self.Mint_public_sale_open.read() } - fn get_unit_price(self: @ContractState) -> u256 { - self.mint_unit_price.read() + fn get_unit_price(self: @ComponentState) -> u256 { + self.Mint_unit_price.read() } - fn get_available_value(self: @ContractState) -> u256 { - self.mint_remaining_value.read() + fn get_available_value(self: @ComponentState) -> u256 { + self.Mint_remaining_value.read() } - fn get_claimed_value(self: @ContractState, account: ContractAddress) -> u256 { - self.mint_claimed_value.read(account) + fn get_claimed_value( + self: @ComponentState, account: ContractAddress + ) -> u256 { + self.Mint_claimed_value.read(account) } - fn is_sold_out(self: @ContractState) -> bool { + fn is_sold_out(self: @ComponentState) -> bool { self.get_available_value() == 0 } - fn is_canceled(self: @ContractState) -> bool { - self.mint_cancel.read() + fn is_canceled(self: @ComponentState) -> bool { + self.Mint_cancel.read() } - fn set_public_sale_open(ref self: ContractState, public_sale_open: bool) { + fn set_public_sale_open(ref self: ComponentState, public_sale_open: bool) { // [Effect] Update storage - self.mint_public_sale_open.write(public_sale_open); + self.Mint_public_sale_open.write(public_sale_open); // [Event] Emit event let current_time = get_block_timestamp(); @@ -142,16 +149,16 @@ mod Mint { }; } - fn set_unit_price(ref self: ContractState, unit_price: u256) { + fn set_unit_price(ref self: ComponentState, unit_price: u256) { // [Check] Value not null assert(unit_price > 0, 'Invalid unit price'); // [Effect] Store value - self.mint_unit_price.write(unit_price); + self.Mint_unit_price.write(unit_price); } - fn withdraw(ref self: ContractState) { + fn withdraw(ref self: ComponentState) { // [Compute] Balance to withdraw - let token_address = self.mint_payment_token_address.read(); + let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20CamelDispatcher { contract_address: token_address }; let contract_address = get_contract_address(); let balance = erc20.balanceOf(contract_address); @@ -163,7 +170,7 @@ mod Mint { } fn transfer( - ref self: ContractState, + ref self: ComponentState, token_address: ContractAddress, recipient: ContractAddress, amount: u256 @@ -173,16 +180,16 @@ mod Mint { assert(success, 'Transfer failed'); } - fn book(ref self: ContractState, value: u256, force: bool) { + fn book(ref self: ComponentState, value: u256, force: bool) { // [Check] Public sale is open - let public_sale_open = self.mint_public_sale_open.read(); + let public_sale_open = self.Mint_public_sale_open.read(); assert(public_sale_open, 'Sale is closed'); // [Interaction] Buy self._safe_book(value, force); } - fn claim(ref self: ContractState, user_address: ContractAddress, id: u32) { + fn claim(ref self: ComponentState, user_address: ContractAddress, id: u32) { // [Check] Project is sold out assert(self.is_sold_out(), 'Mint not sold out'); @@ -190,16 +197,16 @@ mod Mint { assert(!self.is_canceled(), 'Mint canceled'); // [Check] Booking - let mut booking = self.mint_booked_values.read((user_address, id)); + let mut booking = self.Mint_booked_values.read((user_address, id)); assert(booking.is_status(BookingStatus::Booked), 'Booking not found'); // [Effect] Update Booking status booking.set_status(BookingStatus::Minted); - self.mint_booked_values.write((user_address.into(), id), booking); + self.Mint_booked_values.write((user_address.into(), id), booking); // [Interaction] Mint // TODO : define the vintage - let projects_contract = self.mint_carbonable_project_address.read(); + let projects_contract = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: projects_contract }; project.mint(user_address.into(), 1, booking.value); @@ -207,9 +214,11 @@ mod Mint { self.emit(BookingClaimed { address: user_address, id, value: booking.value, }); } - fn refund(ref self: ContractState, user_address: ContractAddress, id: u32) { + fn refund( + ref self: ComponentState, user_address: ContractAddress, id: u32 + ) { // [Check] Booking - let mut booking = self.mint_booked_values.read((user_address, id)); + let mut booking = self.Mint_booked_values.read((user_address, id)); assert( booking.is_status(BookingStatus::Failed) || self.is_canceled(), 'Booking not refundable' @@ -217,10 +226,10 @@ mod Mint { // [Effect] Update Booking status booking.set_status(BookingStatus::Refunded); - self.mint_booked_values.write((user_address.into(), id), booking); + self.Mint_booked_values.write((user_address.into(), id), booking); // [Interaction] Refund - let token_address = self.mint_payment_token_address.read(); + let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20CamelDispatcher { contract_address: token_address }; let contract_address = get_contract_address(); let success = erc20.transfer(user_address, booking.value); @@ -231,13 +240,16 @@ mod Mint { } fn refund_to( - ref self: ContractState, to: ContractAddress, user_address: ContractAddress, id: u32 + ref self: ComponentState, + to: ContractAddress, + user_address: ContractAddress, + id: u32 ) { // [Check] To address connot be zero assert(!to.is_zero(), 'Invalid to address'); // [Check] Booking - let mut booking = self.mint_booked_values.read((user_address, id)); + let mut booking = self.Mint_booked_values.read((user_address, id)); assert( booking.is_status(BookingStatus::Failed) || self.is_canceled(), 'Booking not refundable' @@ -245,10 +257,10 @@ mod Mint { // [Effect] Update Booking status booking.set_status(BookingStatus::Refunded); - self.mint_booked_values.write((user_address.into(), id), booking); + self.Mint_booked_values.write((user_address.into(), id), booking); // [Interaction] Refund - let token_address = self.mint_payment_token_address.read(); + let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20CamelDispatcher { contract_address: token_address }; let contract_address = get_contract_address(); let success = erc20.transfer(to, booking.value); @@ -258,12 +270,12 @@ mod Mint { self.emit(BookingRefunded { address: user_address, id, value: booking.value, }); } - fn cancel(ref self: ContractState) { + fn cancel(ref self: ComponentState) { // [Check] Mint is not already canceled assert(!self.is_canceled(), 'Mint already canceled'); // [Effect] Update storage - self.mint_cancel.write(true); + self.Mint_cancel.write(true); // [Event] Emit let current_time = get_block_timestamp(); @@ -272,9 +284,11 @@ mod Mint { } #[generate_trait] - impl InternalImpl of InternalTrait { + impl InternalImpl< + TContractState, +HasComponent, +Drop + > of InternalTrait { fn initializer( - ref self: ContractState, + ref self: ComponentState, carbonable_project_address: ContractAddress, payment_token_address: ContractAddress, public_sale_open: bool, @@ -285,18 +299,18 @@ mod Mint { assert(unit_price > 0, 'Invalid unit price'); // [Effect] Update storage - self.mint_carbonable_project_address.write(carbonable_project_address); + self.Mint_carbonable_project_address.write(carbonable_project_address); // [Effect] Update storage - self.mint_payment_token_address.write(payment_token_address); - self.mint_unit_price.write(unit_price); - self.mint_remaining_value.write(max_value); + self.Mint_payment_token_address.write(payment_token_address); + self.Mint_unit_price.write(unit_price); + self.Mint_remaining_value.write(max_value); // [Effect] Use dedicated function to emit corresponding events self.set_public_sale_open(public_sale_open); } - fn _safe_book(ref self: ContractState, value: u256, force: bool) -> u256 { + fn _safe_book(ref self: ComponentState, value: u256, force: bool) -> u256 { // [Check] Project is not canceled assert(!self.is_canceled(), 'Mint canceled'); @@ -320,9 +334,9 @@ mod Mint { assert(value <= available_value, 'Not enough available value'); // [Interaction] Pay - let unit_price = self.mint_unit_price.read(); + let unit_price = self.Mint_unit_price.read(); let amount = value * unit_price; - let token_address = self.mint_payment_token_address.read(); + let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20CamelDispatcher { contract_address: token_address }; let contract_address = get_contract_address(); let success = erc20.transferFrom(caller_address, contract_address, amount); @@ -338,27 +352,27 @@ mod Mint { } fn _book( - ref self: ContractState, + ref self: ComponentState, user_address: ContractAddress, amount: u256, value: u256, status: BookingStatus ) { // [Effect] Compute and update user mint count - let mint_id = self.mint_count.read(user_address) + 1_u32; - self.mint_count.write(user_address, mint_id); + let Mint_id = self.Mint_count.read(user_address) + 1_u32; + self.Mint_count.write(user_address, Mint_id); // [Effect] Update remaining value if booked let mut booking = BookingTrait::new(value, amount, status); if booking.is_status(BookingStatus::Booked) { - self.mint_remaining_value.write(self.mint_remaining_value.read() - value); + self.Mint_remaining_value.write(self.Mint_remaining_value.read() - value); }; // [Effect] Store booking - self.mint_booked_values.write((user_address, mint_id), booking); + self.Mint_booked_values.write((user_address, Mint_id), booking); // [Event] Emit event - self.emit(BookingHandled { address: user_address, id: mint_id, value }); + self.emit(BookingHandled { address: user_address, id: Mint_id, value }); // [Effect] Close the sale if sold out if self.is_sold_out() { @@ -370,338 +384,11 @@ mod Mint { }; } - fn _available_public_value(self: @ContractState) -> u256 { + fn _available_public_value(self: @ComponentState) -> u256 { // [Compute] Available value - let remaining_value = self.mint_remaining_value.read(); + let remaining_value = self.Mint_remaining_value.read(); remaining_value } } } - -#[cfg(test)] -mod Test { - // Core imports - - use array::{ArrayTrait, SpanTrait}; - use traits::TryInto; - use poseidon::PoseidonTrait; - use hash::HashStateTrait; - use debug::PrintTrait; - - // Starknet imports - - use starknet::{ContractAddress, get_contract_address, get_block_timestamp}; - use starknet::testing::{set_block_timestamp, set_caller_address, set_contract_address}; - - // External imports - - use alexandria_data_structures::merkle_tree::{ - Hasher, MerkleTree, poseidon::PoseidonHasherImpl, MerkleTreeTrait, - }; - - // Internal imports - - use super::Mint; - use super::Mint::mint_carbonable_project_address::InternalContractMemberStateTrait as MintProjectAddressTrait; - use super::Mint::mint_remaining_value::InternalContractMemberStateTrait as MintRemainingValueTrait; - use super::Mint::mint_payment_token_address::InternalContractMemberStateTrait as MintPaymentTokenAddressTrait; - - // Constants - - const UNIT_PRICE: u256 = 10; - const ALLOCATION: felt252 = 5; - const PROOF: felt252 = 0x58f605c335d6edee10b834aedf74f8ed903311799ecde69461308439a4537c7; - const BILION: u256 = 1000000000; - - fn STATE() -> Mint::ContractState { - Mint::contract_state_for_testing() - } - - fn ACCOUNT() -> ContractAddress { - starknet::contract_address_const::<1001>() - } - - fn ZERO() -> ContractAddress { - starknet::contract_address_const::<0>() - } - - // Mocks - - #[starknet::contract] - mod ProjectMock { - use starknet::ContractAddress; - - #[storage] - struct Storage {} - - #[generate_trait] - #[external(v0)] - impl MockImpl of MockTrait { - fn get_project_value(self: @ContractState, slot: u256) -> u256 { - super::BILION - } - fn total_value(self: @ContractState, slot: u256) -> u256 { - 0 - } - fn mint(ref self: ContractState, to: ContractAddress, slot: u256, value: u256) -> u256 { - 0 - } - } - } - - #[starknet::contract] - mod ERC20Mock { - use starknet::ContractAddress; - - #[storage] - struct Storage {} - - #[generate_trait] - #[external(v0)] - impl ERC20Impl of ERC20Trait { - fn balanceOf(self: @ContractState, owner: ContractAddress) -> u256 { - 100 - } - fn transferFrom( - ref self: ContractState, from: ContractAddress, to: ContractAddress, value: u256 - ) -> bool { - true - } - fn transfer(ref self: ContractState, to: ContractAddress, value: u256) -> bool { - true - } - } - } - - fn project_mock() -> ContractAddress { - // [Deploy] - let class_hash = ProjectMock::TEST_CLASS_HASH.try_into().unwrap(); - let (address, _) = starknet::deploy_syscall(class_hash, 0, array![].span(), false) - .expect('Project deploy failed'); - address - } - - fn erc20_mock() -> ContractAddress { - // [Deploy] - let class_hash = ERC20Mock::TEST_CLASS_HASH.try_into().unwrap(); - let (address, _) = starknet::deploy_syscall(class_hash, 0, array![].span(), false) - .expect('ERC20 deploy failed'); - address - } - - #[test] - #[available_gas(20_000_000)] - fn testmint_public_sale() { - // [Setup] - let mut state = STATE(); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Storage - let public_sale_open = Mint::MintImpl::is_public_sale_open(@state); - assert(public_sale_open, 'Public sale is not open'); - } - - #[test] - #[available_gas(20_000_000)] - fn testmint_unit_price() { - // [Setup] - let mut state = STATE(); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - // [Assert] Storage - let unit_price = Mint::MintImpl::get_unit_price(@state); - assert(unit_price == UNIT_PRICE, 'Invalid unit price'); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Sale is closed',))] - fn testmint_book_revert_sale_closed() { - // [Setup] - let mut state = STATE(); - // [Assert] Book - Mint::MintImpl::book(ref state, 10, false); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Invalid caller',))] - fn testmint_book_revert_invalid_caller() { - // [Setup] - let mut state = STATE(); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - Mint::MintImpl::book(ref state, 10, false); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Value too high',))] - fn testmint_book_value_too_high() { - // [Setup] - let mut state = STATE(); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - set_caller_address(ACCOUNT()); - Mint::MintImpl::book(ref state, 10, false); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Mint canceled',))] - fn testmint_book_revert_canceled() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Cancel mint - Mint::MintImpl::cancel(ref state); - // [Assert] Book - set_caller_address(ACCOUNT()); - let value: u256 = 10; - Mint::MintImpl::book(ref state, value, false); - } - - #[test] - #[available_gas(20_000_000)] - fn testmint_book() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - set_caller_address(ACCOUNT()); - let value: u256 = 10; - Mint::MintImpl::book(ref state, value, false); - // [Assert] Cancel mint - Mint::MintImpl::cancel(ref state); - // [Assert] Not sold out - assert(!Mint::MintImpl::is_sold_out(@state), 'Mint sold out'); - // [Assert] Events - let contract = get_contract_address(); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.address == ACCOUNT(), 'Wrong event address'); - assert(event.id == 1, 'Wrong event id'); - assert(event.value == value, 'Wrong event value'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - } - - #[test] - #[available_gas(20_000_000)] - fn testmint_refund_canceled() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - set_caller_address(ACCOUNT()); - let value: u256 = 10; - Mint::MintImpl::book(ref state, value, false); - // [Assert] Cancel mint - Mint::MintImpl::cancel(ref state); - // [Assert] Not sold out - assert(!Mint::MintImpl::is_sold_out(@state), 'Mint sold out'); - // [Assert] Refund - Mint::MintImpl::refund(ref state, ACCOUNT(), 1); - // [Assert] Events - let contract = get_contract_address(); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.address == ACCOUNT(), 'Wrong event address'); - assert(event.id == 1, 'Wrong event id'); - assert(event.value == value, 'Wrong event value'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.address == ACCOUNT(), 'Wrong event address'); - assert(event.id == 1, 'Wrong event id'); - assert(event.value == value, 'Wrong event value'); - } - - #[test] - #[available_gas(20_000_000)] - fn testmint_claim() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - set_caller_address(ACCOUNT()); - let value: u256 = 1000; - Mint::MintImpl::book(ref state, value, true); - // [Assert] Sold out - assert(Mint::MintImpl::is_sold_out(@state), 'Contract not sold out'); - // [Assert] Claim - Mint::MintImpl::claim(ref state, ACCOUNT(), 1); - // [Assert] Events - let contract = get_contract_address(); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.address == ACCOUNT(), 'Wrong event address'); - assert(event.id == 1, 'Wrong event id'); - assert(event.value == value, 'Wrong event value'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.time == get_block_timestamp(), 'Wrong event timestamp'); - let event = starknet::testing::pop_log::(contract).unwrap(); - assert(event.address == ACCOUNT(), 'Wrong event address'); - assert(event.id == 1, 'Wrong event id'); - assert(event.value == value, 'Wrong event value'); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Booking not found',))] - fn testmint_claim_twice_revert_not_found() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - // [Assert] Book - set_caller_address(ACCOUNT()); - Mint::MintImpl::book(ref state, 1000, true); - // [Assert] Sold out - assert(Mint::MintImpl::is_sold_out(@state), 'Contract not sold out'); - // [Assert] Claim - Mint::MintImpl::claim(ref state, ACCOUNT(), 1); - Mint::MintImpl::claim(ref state, ACCOUNT(), 1); - } - - #[test] - #[available_gas(20_000_000)] - #[should_panic(expected: ('Mint canceled',))] - fn testmint_claim_revert_canceled() { - // [Setup] - let mut state = STATE(); - state.mint_carbonable_project_address.write(project_mock()); - state.mint_payment_token_address.write(erc20_mock()); - state.mint_remaining_value.write(1000); - Mint::MintImpl::set_unit_price(ref state, UNIT_PRICE); - Mint::MintImpl::set_public_sale_open(ref state, true); - Mint::MintImpl::cancel(ref state); - // [Assert] Book - set_caller_address(ACCOUNT()); - Mint::MintImpl::book(ref state, 1000, true); - } -} diff --git a/src/contracts/minter.cairo b/src/contracts/minter.cairo index 1841b4b..0bf4fa2 100644 --- a/src/contracts/minter.cairo +++ b/src/contracts/minter.cairo @@ -5,19 +5,50 @@ mod Minter { use starknet::{get_caller_address, ContractAddress, ClassHash}; // Ownable - use openzeppelin::access::ownable::interface::IOwnable; - use openzeppelin::access::ownable::ownable::Ownable; - + use openzeppelin::access::ownable::OwnableComponent; // Upgradable - use openzeppelin::upgrades::interface::IUpgradeable; - use openzeppelin::upgrades::upgradeable::Upgradeable; - + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; // Mint - use carbon_v3::components::mint::interface::{IMint}; - use carbon_v3::components::mint::module::Mint; + use carbon_v3::components::mint::module::MintComponent; + + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: MintComponent, storage: mint, event: MintEvent); + + // ABI + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl OwnableCamelOnlyImpl = + OwnableComponent::OwnableCamelOnlyImpl; + #[abi(embed_v0)] + impl MintImpl = MintComponent::MintImpl; + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl MintInternalImpl = MintComponent::InternalImpl; #[storage] - struct Storage {} + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + mint: MintComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + MintEvent: MintComponent::Event + } #[constructor] fn constructor( @@ -33,207 +64,15 @@ mod Minter { reserved_value: u256, owner: ContractAddress ) { + self.ownable.initializer(owner); self + .mint .initializer( - carbonable_project_address, - carbonable_project_slot, - payment_token_address, - public_sale_open, - max_value_per_tx, - min_value_per_tx, - max_value, - unit_price, - reserved_value, - owner - ); - } - - // Upgradable - - #[external(v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Upgrade - let mut unsafe_state = Upgradeable::unsafe_new_contract_state(); - Upgradeable::InternalImpl::_upgrade(ref unsafe_state, new_class_hash) - } - } - - // Access control - - #[external(v0)] - impl OwnableImpl of IOwnable { - fn owner(self: @ContractState) -> ContractAddress { - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::owner(@unsafe_state) - } - - fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::transfer_ownership(ref unsafe_state, new_owner) - } - - fn renounce_ownership(ref self: ContractState) { - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::renounce_ownership(ref unsafe_state) - } - } - - // Externals - - #[external(v0)] - impl MintImpl of IMint { - fn get_carbonable_project_address(self: @ContractState) -> ContractAddress { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::get_carbonable_project_address(@unsafe_state) - } - - - fn get_payment_token_address(self: @ContractState) -> ContractAddress { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::get_payment_token_address(@unsafe_state) - } - - - fn is_public_sale_open(self: @ContractState) -> bool { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::is_public_sale_open(@unsafe_state) - } - - fn get_unit_price(self: @ContractState) -> u256 { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::get_unit_price(@unsafe_state) - } - - fn get_available_value(self: @ContractState) -> u256 { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::get_available_value(@unsafe_state) - } - - fn get_claimed_value(self: @ContractState, account: ContractAddress) -> u256 { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::get_claimed_value(@unsafe_state, account) - } - - fn is_sold_out(self: @ContractState) -> bool { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::is_sold_out(@unsafe_state) - } - - fn is_canceled(self: @ContractState) -> bool { - let unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::is_canceled(@unsafe_state) - } - - fn set_public_sale_open(ref self: ContractState, public_sale_open: bool) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Set public sale open - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::set_public_sale_open(ref unsafe_state, public_sale_open) - } - - fn set_unit_price(ref self: ContractState, unit_price: u256) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Set unit price - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::set_unit_price(ref unsafe_state, unit_price) - } - - fn withdraw(ref self: ContractState) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Withdraw - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::withdraw(ref unsafe_state) - } - - fn transfer( - ref self: ContractState, - token_address: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Transfer - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::transfer(ref unsafe_state, token_address, recipient, amount) - } - - fn book(ref self: ContractState, value: u256, force: bool) { - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::book(ref unsafe_state, value, force) - } - - fn claim(ref self: ContractState, user_address: ContractAddress, id: u32) { - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::claim(ref unsafe_state, user_address, id) - } - - fn refund(ref self: ContractState, user_address: ContractAddress, id: u32) { - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::refund(ref unsafe_state, user_address, id) - } - - fn refund_to( - ref self: ContractState, to: ContractAddress, user_address: ContractAddress, id: u32 - ) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Refund to address - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::refund_to(ref unsafe_state, to, user_address, id) - } - - fn cancel(ref self: ContractState) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Cancel the mint - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::MintImpl::cancel(ref unsafe_state) - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer( - ref self: ContractState, - carbonable_project_address: ContractAddress, - carbonable_project_slot: u256, - payment_token_address: ContractAddress, - public_sale_open: bool, - max_value_per_tx: u256, - min_value_per_tx: u256, - max_value: u256, - unit_price: u256, - reserved_value: u256, - owner: ContractAddress - ) { - // Access control - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::initializer(ref unsafe_state, owner); - - // Mint - let mut unsafe_state = Mint::unsafe_new_contract_state(); - Mint::InternalImpl::initializer( - ref unsafe_state, carbonable_project_address, payment_token_address, public_sale_open, max_value, unit_price ); - } } } diff --git a/src/contracts/project.cairo b/src/contracts/project.cairo index 8d52f54..2626120 100644 --- a/src/contracts/project.cairo +++ b/src/contracts/project.cairo @@ -4,437 +4,150 @@ use starknet::ContractAddress; trait IExternal { fn mint(ref self: ContractState, to: ContractAddress, token_id: u256, value: u256); fn burn(ref self: ContractState, token_id: u256, value: u256); + fn batch_mint( + ref self: ContractState, to: ContractAddress, token_ids: Span, values: Span + ); + fn batch_burn(ref self: ContractState, token_ids: Span, values: Span); + fn set_uri(ref self: ContractState, token_id: u256, uri: felt252); + fn set_list_uri(ref self: ContractState, token_ids: Span, uris: Span); } #[starknet::contract] mod Project { + use openzeppelin::token::erc1155::erc1155::ERC1155Component::InternalTrait; use core::traits::Into; use starknet::{get_caller_address, ContractAddress, ClassHash}; // Ownable - use openzeppelin::access::ownable::interface::IOwnable; - use openzeppelin::access::ownable::ownable::Ownable; - + use openzeppelin::access::ownable::OwnableComponent; // Upgradable - use openzeppelin::upgrades::interface::IUpgradeable; - use openzeppelin::upgrades::upgradeable::Upgradeable; - + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; //SRC5 - use openzeppelin::introspection::interface::{ISRC5, ISRC5Camel}; - use openzeppelin::introspection::src5::SRC5; - - // ERC721 - use openzeppelin::token::erc721::interface::{ - IERC721, IERC721Metadata, IERC721CamelOnly, IERC721MetadataCamelOnly - }; - + use openzeppelin::introspection::src5::SRC5Component; // ERC1155 - use token::erc1155::interface::{IERC1155, IERC1155Metadata}; - use token::erc1155::erc1155::ERC1155; - - // Access control - use carbon_v3::components::access::interface::{IMinter, ICertifier}; - use carbon_v3::components::access::module::Access; - + use openzeppelin::token::erc1155::ERC1155Component; // Absorber - use carbon_v3::components::absorber::interface::IAbsorber; - use carbon_v3::components::absorber::module::Absorber; + use carbon_v3::components::absorber::module::AbsorberComponent; - const IERC165_BACKWARD_COMPATIBLE_ID: u32 = 0x80ac58cd_u32; - // Storage + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: AbsorberComponent, storage: absorber, event: AbsorberEvent); - #[storage] - struct Storage {} + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Impl = ERC1155Component::ERC1155Impl; + #[abi(embed_v0)] + impl ERC1155MetadataImpl = ERC1155Component::ERC1155MetadataImpl; + #[abi(embed_v0)] + impl ERC1155CamelOnly = ERC1155Component::ERC1155CamelOnlyImpl; + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl OwnableCamelOnlyImpl = + OwnableComponent::OwnableCamelOnlyImpl; + #[abi(embed_v0)] + impl AbsorberImpl = AbsorberComponent::AbsorberImpl; + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + // Constants + const IERC165_BACKWARD_COMPATIBLE_ID: felt252 = 0x80ac58cd; + const OLD_IERC1155_ID: felt252 = 0xd9b67a26; - // Events + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + absorber: AbsorberComponent::Storage, + } #[event] #[derive(Drop, starknet::Event)] enum Event { - OwnershipTransferred: OwnershipTransferred, - Upgraded: Upgraded, - Transfer: Transfer, - Approval: Approval, - ApprovalForAll: ApprovalForAll, - ApprovalForSlot: ApprovalForSlot, - TransferValue: TransferValue, - ApprovalValue: ApprovalValue, - SlotChanged: SlotChanged, - } - - #[derive(Drop, starknet::Event)] - struct OwnershipTransferred { - previous_owner: ContractAddress, - new_owner: ContractAddress, - } - - #[derive(Drop, starknet::Event)] - struct Upgraded { - class_hash: ClassHash - } - - #[derive(Drop, starknet::Event)] - struct Transfer { - #[key] - from: ContractAddress, - #[key] - to: ContractAddress, - #[key] - token_id: u256 - } - - #[derive(Drop, starknet::Event)] - struct Approval { - #[key] - owner: ContractAddress, - #[key] - approved: ContractAddress, - #[key] - token_id: u256 + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + AbsorberEvent: AbsorberComponent::Event } - #[derive(Drop, starknet::Event)] - struct ApprovalForAll { - #[key] - owner: ContractAddress, - #[key] - operator: ContractAddress, - approved: bool - } - - #[derive(Drop, starknet::Event)] - struct ApprovalForSlot { - owner: ContractAddress, - slot: u256, - operator: ContractAddress, - approved: bool, - } - - #[derive(Drop, starknet::Event)] - struct TransferValue { - from_token_id: u256, - to_token_id: u256, - value: u256, - } - - #[derive(Drop, starknet::Event)] - struct ApprovalValue { - token_id: u256, - operator: ContractAddress, - value: u256 - } - - #[derive(Drop, starknet::Event)] - struct SlotChanged { - token_id: u256, - old_slot: u256, - new_slot: u256, + mod Errors { + const UNEQUAL_ARRAYS_URI: felt252 = 'URI Array len do not match'; } // Constructor - #[constructor] fn constructor( - ref self: ContractState, - name: felt252, - symbol: felt252, - uri: felt252, - value_decimals: u8, - owner: ContractAddress + ref self: ContractState, name: felt252, symbol: felt252, owner: ContractAddress ) { - self.initializer(name, symbol, uri, value_decimals, owner); - } - - // Upgradable + self.erc1155.initializer(name, symbol); + self.ownable.initializer(owner); - #[external(v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Upgrade - let mut unsafe_state = Upgradeable::unsafe_new_contract_state(); - Upgradeable::InternalImpl::_upgrade(ref unsafe_state, new_class_hash) - } + self.src5.register_interface(OLD_IERC1155_ID); + self.src5.register_interface(IERC165_BACKWARD_COMPATIBLE_ID); } - // Access control - - #[external(v0)] - impl OwnableImpl of IOwnable { - fn owner(self: @ContractState) -> ContractAddress { - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::owner(@unsafe_state) - } - - fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::transfer_ownership(ref unsafe_state, new_owner) - } - - fn renounce_ownership(ref self: ContractState) { - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::OwnableImpl::renounce_ownership(ref unsafe_state) - } - } - - #[external(v0)] - impl MinterImpl of IMinter { - fn get_minters(self: @ContractState) -> Span { - let unsafe_state = Access::unsafe_new_contract_state(); - Access::MinterImpl::get_minters(@unsafe_state) - } - - fn add_minter(ref self: ContractState, user: ContractAddress) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Add minter - let mut unsafe_state = Access::unsafe_new_contract_state(); - Access::MinterImpl::add_minter(ref unsafe_state, user) - } - - fn revoke_minter(ref self: ContractState, user: ContractAddress) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Revoke minter - let mut unsafe_state = Access::unsafe_new_contract_state(); - Access::MinterImpl::revoke_minter(ref unsafe_state, user) - } - } - - #[external(v0)] - impl CertifierImpl of ICertifier { - fn get_certifier(self: @ContractState) -> ContractAddress { - let unsafe_state = Access::unsafe_new_contract_state(); - Access::CertifierImpl::get_certifier(@unsafe_state) - } - - fn set_certifier(ref self: ContractState, user: ContractAddress) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Set certifier - let mut unsafe_state = Access::unsafe_new_contract_state(); - Access::CertifierImpl::set_certifier(ref unsafe_state, user) - } - } - - // SRC5 - - #[external(v0)] - impl SRC5Impl of ISRC5 { - fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - if interface_id == IERC165_BACKWARD_COMPATIBLE_ID.into() { - return true; - } - let unsafe_state = SRC5::unsafe_new_contract_state(); - SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) - } - } - - #[external(v0)] - impl SRC5CamelImpl of ISRC5Camel { - fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - self.supports_interface(interfaceId) - } - } - - // ERC1155 - + // Externals #[external(v0)] - impl ERC1155Impl of IERC1155 { - fn balance_of(self: @ContractState, account: ContractAddress, id: u256) -> u256 { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::balance_of(@unsafe_state, account, id) - } - - fn balance_of_batch( - self: @ContractState, accounts: Array, ids: Array - ) -> Array { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::balance_of_batch(@unsafe_state, accounts, ids) - } - - fn set_approval_for_all( - ref self: ContractState, operator: ContractAddress, approved: bool - ) { - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::set_approval_for_all(ref unsafe_state, operator, approved) - } - - fn is_approved_for_all( - self: @ContractState, account: ContractAddress, operator: ContractAddress - ) -> bool { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::is_approved_for_all(@unsafe_state, account, operator) + impl ExternalImpl of super::IExternal { + fn mint(ref self: ContractState, to: ContractAddress, token_id: u256, value: u256) { + self.erc1155._mint(to, token_id, value); } - fn safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ) { - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::safe_transfer_from( - ref unsafe_state, from, to, id, amount, data - ) + fn burn(ref self: ContractState, token_id: u256, value: u256) { + self.erc1155._burn(get_caller_address(), token_id, value); } - fn safe_batch_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array + fn batch_mint( + ref self: ContractState, to: ContractAddress, token_ids: Span, values: Span ) { - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155Impl::safe_batch_transfer_from( - ref unsafe_state, from, to, ids, amounts, data - ) - } - } - - // Absorber - - #[external(v0)] - impl AbsorberImpl of IAbsorber { - fn get_start_time(self: @ContractState) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_start_time(@unsafe_state) - } - - fn get_final_time(self: @ContractState) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_final_time(@unsafe_state) - } - - fn get_times(self: @ContractState) -> Span { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_times(@unsafe_state) - } - - fn get_absorptions(self: @ContractState) -> Span { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_absorptions(@unsafe_state) + self.erc1155._batch_mint(to, token_ids, values); } - fn get_absorption(self: @ContractState, time: u64) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_absorption(@unsafe_state, time) + fn batch_burn(ref self: ContractState, token_ids: Span, values: Span) { + self.erc1155._batch_burn(get_caller_address(), token_ids, values); } - fn get_current_absorption(self: @ContractState) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_current_absorption(@unsafe_state) + fn set_uri(ref self: ContractState, token_id: u256, uri: felt252) { + self.erc1155._set_uri(token_id, uri); } - fn get_final_absorption(self: @ContractState) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_final_absorption(@unsafe_state) - } - - fn get_project_value(self: @ContractState) -> u256 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_project_value(@unsafe_state) - } - - fn get_ton_equivalent(self: @ContractState) -> u64 { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::get_ton_equivalent(@unsafe_state) - } - - fn is_setup(self: @ContractState) -> bool { - let unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::is_setup(@unsafe_state) - } - - fn set_absorptions( - ref self: ContractState, - times: Span, - absorptions: Span, - ton_equivalent: u64 + fn set_list_uri( + ref self: ContractState, mut token_ids: Span, mut uris: Span ) { - // [Check] Only certifier - let unsafe_state = Access::unsafe_new_contract_state(); - let certifier = Access::InternalImpl::assert_only_certifier(@unsafe_state); - // [Effect] Set absorptions - let mut unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::set_absorptions( - ref unsafe_state, times, absorptions, ton_equivalent - ) - } + assert(token_ids.len() == uris.len(), Errors::UNEQUAL_ARRAYS_URI); - fn set_project_value(ref self: ContractState, project_value: u256) { - // [Check] Only owner - let unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::assert_only_owner(@unsafe_state); - // [Effect] Set project value - let mut unsafe_state = Absorber::unsafe_new_contract_state(); - Absorber::AbsorberImpl::set_project_value(ref unsafe_state, project_value) - } - } - - #[external(v0)] - impl ERC1155MetadataImpl of IERC1155Metadata { - fn name(self: @ContractState) -> felt252 { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155MetadataImpl::name(@unsafe_state) - } - - fn symbol(self: @ContractState) -> felt252 { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155MetadataImpl::symbol(@unsafe_state) - } + loop { + if token_ids.len() == 0 { + break; + } + let id = *token_ids.pop_front().unwrap(); + let uri = *uris.pop_front().unwrap(); - fn uri(self: @ContractState, token_id: u256) -> felt252 { - let unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::ERC1155MetadataImpl::uri(@unsafe_state, token_id) - } - } - - // Externals - - #[external(v0)] - impl ExternalImpl of super::IExternal { - - fn mint(ref self: ContractState, to: ContractAddress, token_id: u256, value: u256) { - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::InternalImpl::_mint(ref unsafe_state, to, token_id, value) - } - - fn burn(ref self: ContractState, token_id: u256, value: u256) { - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::InternalImpl::_burn(ref unsafe_state, token_id, value) - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn initializer( - ref self: ContractState, - name: felt252, - symbol: felt252, - uri: felt252, - value_decimals: u8, - owner: ContractAddress - ) { - // ERC721 & ERC3525 - let mut unsafe_state = ERC1155::unsafe_new_contract_state(); - ERC1155::InternalImpl::initializer(ref unsafe_state, name, symbol, uri); - // Access control - let mut unsafe_state = Ownable::unsafe_new_contract_state(); - Ownable::InternalImpl::initializer(ref unsafe_state, owner); - let mut unsafe_state = Access::unsafe_new_contract_state(); - Access::InternalImpl::initializer(ref unsafe_state); + self.erc1155._set_uri(id, uri); + } } } } diff --git a/src/lib.cairo b/src/lib.cairo index 2ce7475..08c5f61 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,18 +1,6 @@ mod components { - mod absorber { - mod interface; - mod module; - } - mod access { - mod interface; - mod module; - } - - mod mint { - mod booking; - mod interface; - mod module; - } + mod absorber; + mod mint; } mod contracts {