diff --git a/.all-contributorsrc b/.all-contributorsrc index 51c74d2d..62934edc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -81,6 +81,42 @@ "contributions": [ "code" ] + }, + { + "login": "fishonamos", + "name": "Fishon Amos", + "avatar_url": "https://avatars.githubusercontent.com/u/43862685?v=4", + "profile": "https://fishonsnote.medium.com/", + "contributions": [ + "code" + ] + }, + { + "login": "Xaxxoo", + "name": "Xaxxoo", + "avatar_url": "https://avatars.githubusercontent.com/u/51526246?v=4", + "profile": "https://github.com/Xaxxoo", + "contributions": [ + "code" + ] + }, + { + "login": "manoahLinks", + "name": "Mano.dev", + "avatar_url": "https://avatars.githubusercontent.com/u/100848212?v=4", + "profile": "https://github.com/manoahLinks", + "contributions": [ + "code" + ] + }, + { + "login": "Otaiki1", + "name": "Abdulsamad sadiq", + "avatar_url": "https://avatars.githubusercontent.com/u/38711713?v=4", + "profile": "https://github.com/Otaiki1", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 727a898e..a5519478 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ Thanks goes to these wonderful people. Follow the [contributors guide](https://g 0xK2
0xK2

💻 + Fishon Amos
Fishon Amos

💻 + Xaxxoo
Xaxxoo

💻 + Mano.dev
Mano.dev

💻 + Abdulsamad sadiq
Abdulsamad sadiq

💻 diff --git a/configs/canvas.config.json b/configs/canvas.config.json index 20b71057..a8c24d4a 100644 --- a/configs/canvas.config.json +++ b/configs/canvas.config.json @@ -1,7 +1,7 @@ { "canvas": { - "width": 35, - "height": 24 + "width": 256, + "height": 256 }, "colors": [ "010001", diff --git a/docker-compose.yml b/docker-compose.yml index 4e9e2252..cdcce894 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,11 +107,11 @@ services: - backend - devnet volumes: - - configs:/configs - ./frontend/package.json:/app/package.json - ./frontend/package-lock.json:/app/package-lock.json - ./frontend/public/:/app/public - ./frontend/src:/app/src + - configs:/app/src/configs volumes: redis: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ef89246e..8651eba2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -6,7 +6,8 @@ WORKDIR /app COPY ./frontend/package.json ./frontend/package-lock.json ./ RUN npm install COPY ./frontend ./ -COPY ./configs/docker-backend.config.json ./src/backend.config.json +COPY ./configs/canvas.config.json ./src/configs/canvas.config.json +COPY ./configs/docker-backend.config.json ./src/configs/backend.config.json SHELL ["/bin/bash", "-c"] # Clear the entrypoint diff --git a/frontend/src/canvas/Canvas.js b/frontend/src/canvas/Canvas.js index e29ae923..804bd622 100644 --- a/frontend/src/canvas/Canvas.js +++ b/frontend/src/canvas/Canvas.js @@ -18,7 +18,6 @@ const Canvas = props => { const maxScale = 40; - const canvasRef = useRef(null) const canvasPositionRef = useRef(null) const canvasScaleRef = useRef(null) @@ -410,7 +409,7 @@ const Canvas = props => { return null; } if (props.selectedColorId === -1) { - let color = canvasRef.current + let color = props.canvasRef.current .getContext("2d") .getImageData( props.selectedPositionX, @@ -434,7 +433,7 @@ const Canvas = props => { return null; } if (props.selectedColorId === -1) { - let color = canvasRef.current + let color = props.canvasRef.current .getContext("2d") .getImageData( props.selectedPositionX, @@ -543,7 +542,7 @@ const Canvas = props => { return () => { window.removeEventListener("mousemove", setFromEvent); }; - }, [props.selectedColorId, pixelSelect, props.nftSelectionMode, nftSelectionStarted, nftSelectionPositionX, nftSelectionPositionY, nftSelectionWidth, nftSelectionHeight, height, width, props.canvasRef, nftSelectionEndX, nftSelectionEndY, nftSelectionStartX, nftSelectionStartY]); + }, [props.selectedColorId, pixelSelect, props.nftSelectionMode, nftSelectionStarted, nftSelectionPositionX, nftSelectionPositionY, nftSelectionWidth, nftSelectionHeight, height, width, props.canvasRef, nftSelectionEndX, nftSelectionEndY, nftSelectionStartX, nftSelectionStartY, props]); // TODO: both place options return ( diff --git a/frontend/src/configs/canvas.config.json b/frontend/src/configs/canvas.config.json index 20b71057..a8c24d4a 100644 --- a/frontend/src/configs/canvas.config.json +++ b/frontend/src/configs/canvas.config.json @@ -1,7 +1,7 @@ { "canvas": { - "width": 35, - "height": 24 + "width": 256, + "height": 256 }, "colors": [ "010001", diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index e9992ddc..77feec43 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -8,8 +8,13 @@ mod quests { pub mod interfaces; pub mod pixel_quest; pub mod template_quest; + pub mod unruggable_quest; - use interfaces::{IQuest, IPixelQuest, QuestClaimed, IQuestDispatcher, IQuestDispatcherTrait}; + use interfaces::{ + IQuest, IPixelQuest, IUnruggableQuest, QuestClaimed, IQuestDispatcher, + IQuestDispatcherTrait, IUnruggableMemecoin, IUnruggableMemecoinDispatcher, + IUnruggableMemecoinDispatcherTrait + }; } mod templates { @@ -44,7 +49,8 @@ mod username_store { } mod mocks { - pub mod erc20_mock; + pub(crate) mod erc20_mock; + pub(crate) mod unruggable_token; } #[cfg(test)] diff --git a/onchain/src/mocks/unruggable_token.cairo b/onchain/src/mocks/unruggable_token.cairo new file mode 100644 index 00000000..b10029a7 --- /dev/null +++ b/onchain/src/mocks/unruggable_token.cairo @@ -0,0 +1,52 @@ +// +// https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/tests/mocks/erc20_mocks.cairo +// + +#[starknet::contract] +pub mod UnruggableMock { + use art_peace::quests::interfaces::IUnruggableMemecoin; + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + is_launched: bool, + owner: ContractAddress, + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, name: ByteArray, symbol: ByteArray, owner: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.owner.write(owner); + self.is_launched.write(true); + } + + #[abi(embed_v0)] + impl UnruggableImpl of IUnruggableMemecoin { + fn owner(self: @ContractState) -> ContractAddress { + self.owner.read() + } + fn is_launched(self: @ContractState) -> bool { + self.is_launched.read() + } + } +} diff --git a/onchain/src/quests/interfaces.cairo b/onchain/src/quests/interfaces.cairo index 5010679a..7d200194 100644 --- a/onchain/src/quests/interfaces.cairo +++ b/onchain/src/quests/interfaces.cairo @@ -26,3 +26,25 @@ pub trait IPixelQuest { fn is_color(self: @TContractState) -> bool; fn color(self: @TContractState) -> u8; } + +#[starknet::interface] +pub trait IUnruggableQuest { + fn is_claimed(self: @TContractState, user: starknet::ContractAddress) -> bool; +} + +#[starknet::interface] +pub trait IUnruggableMemecoin { + // ************************************ + // * Ownership + // ************************************ + fn owner(self: @TState) -> ContractAddress; + + // ************************************ + // * Additional functions + // ************************************ + /// Checks whether token has launched + /// + /// # Returns + /// bool: whether token has launched + fn is_launched(self: @TState) -> bool; +} diff --git a/onchain/src/quests/unruggable_quest.cairo b/onchain/src/quests/unruggable_quest.cairo new file mode 100644 index 00000000..50cad795 --- /dev/null +++ b/onchain/src/quests/unruggable_quest.cairo @@ -0,0 +1,84 @@ +#[starknet::contract] +pub mod UnruggableQuest { + use starknet::{ContractAddress, get_caller_address}; + use art_peace::{IArtPeaceDispatcher, IArtPeaceDispatcherTrait}; + use art_peace::quests::{ + IQuest, IUnruggableQuest, IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait, + QuestClaimed + }; + + #[storage] + struct Storage { + art_peace: ContractAddress, + reward: u32, + claimed: LegacyMap, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + QuestClaimed: QuestClaimed, + } + + #[derive(Drop, Serde)] + pub struct UnruggableQuestInitParams { + pub art_peace: ContractAddress, + pub reward: u32, + } + + #[constructor] + fn constructor(ref self: ContractState, init_params: UnruggableQuestInitParams) { + self.art_peace.write(init_params.art_peace); + self.reward.write(init_params.reward); + } + + #[abi(embed_v0)] + impl UnruggableQuestImpl of IUnruggableQuest { + fn is_claimed(self: @ContractState, user: ContractAddress) -> bool { + self.claimed.read(user) + } + } + + #[abi(embed_v0)] + impl UnruggableQuest of IQuest { + fn get_reward(self: @ContractState) -> u32 { + self.reward.read() + } + + fn is_claimable( + self: @ContractState, user: ContractAddress, calldata: Span + ) -> bool { + if self.claimed.read(user) { + return false; + } + + let coin_address_as_felt252: felt252 = *calldata.at(0); + let coin = IUnruggableMemecoinDispatcher { + contract_address: coin_address_as_felt252.try_into().unwrap() + }; + + if coin.owner() != user { + return false; + } + + if coin.is_launched() != true { + return false; + } + + true + } + + fn claim(ref self: ContractState, user: ContractAddress, calldata: Span) -> u32 { + assert(get_caller_address() == self.art_peace.read(), 'Only ArtPeace can claim quests'); + + assert(self.is_claimable(user, calldata), 'quest not claimable'); + + self.claimed.write(user, true); + let reward = self.reward.read(); + self.emit(QuestClaimed { user, reward, calldata }); + + reward + } + } +} + diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index dc08d8f0..ca739a4d 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -1,7 +1,9 @@ use art_peace::{IArtPeaceDispatcher, IArtPeaceDispatcherTrait}; use art_peace::ArtPeace::InitParams; use art_peace::quests::pixel_quest::PixelQuest::PixelQuestInitParams; +use art_peace::quests::unruggable_quest::UnruggableQuest::UnruggableQuestInitParams; use art_peace::mocks::erc20_mock::SnakeERC20Mock; +use art_peace::mocks::unruggable_token::UnruggableMock; use art_peace::tests::utils; use art_peace::nfts::interfaces::{ IArtPeaceNFTMinterDispatcher, IArtPeaceNFTMinterDispatcherTrait, ICanvasNFTStoreDispatcher, @@ -36,6 +38,10 @@ fn ERC20_MOCK_CONTRACT() -> ContractAddress { contract_address_const::<'erc20mock'>() } +fn UNRUGGABLE_MOCK_CONTRACT() -> ContractAddress { + contract_address_const::<'unruggable'>() +} + fn EMPTY_CALLDATA() -> Span { array![].span() } @@ -186,6 +192,15 @@ fn deploy_pixel_quests_main(pixel_quest: snf::ContractClass) -> Array ContractAddress { + let mut unruggable_calldata = array![]; + UnruggableQuestInitParams { art_peace: ART_PEACE_CONTRACT(), reward: 20, } + .serialize(ref unruggable_calldata); + let main_unruggable_quest = unruggable_quest.deploy(@unruggable_calldata).unwrap(); + + main_unruggable_quest +} + fn deploy_nft_contract() -> ContractAddress { let contract = snf::declare("CanvasNFT"); let mut calldata = array![]; @@ -215,6 +230,22 @@ fn deploy_erc20_mock() -> ContractAddress { contract_addr } +fn deploy_unruggable_mock() -> ContractAddress { + let contract = snf::declare("UnruggableMock"); + let name: ByteArray = "Unruggable mock"; + let symbol: ByteArray = "UNRUGGABLE"; + let owner: ContractAddress = get_contract_address(); + + let mut calldata: Array = array![]; + Serde::serialize(@name, ref calldata); + Serde::serialize(@symbol, ref calldata); + Serde::serialize(@owner, ref calldata); + + let contract_addr = contract.deploy_at(@calldata, UNRUGGABLE_MOCK_CONTRACT()).unwrap(); + + contract_addr +} + fn warp_to_next_available_time(art_peace: IArtPeaceDispatcher) { let last_time = art_peace.get_last_placed_time(); snf::start_warp(CheatTarget::One(art_peace.contract_address), last_time + TIME_BETWEEN_PIXELS); @@ -277,7 +308,7 @@ fn place_pixel_test() { } #[test] -fn deploy_quest_test() { +fn deploy_pixel_quest_test() { let pixel_quest = snf::declare("PixelQuest"); let daily_quests = deploy_pixel_quests_daily(pixel_quest); let main_quests = deploy_pixel_quests_main(pixel_quest); @@ -293,6 +324,29 @@ fn deploy_quest_test() { ); } +#[test] +fn deploy_unruggable_quest_test() { + let unruggable_quest = snf::declare("UnruggableQuest"); + let main_unruggable_quest = deploy_unruggable_quest_main(unruggable_quest); + + let art_peace = IArtPeaceDispatcher { + contract_address: deploy_with_quests_contract( + array![].span(), array![main_unruggable_quest].span() + ) + }; + + let zero_address = contract_address_const::<0>(); + + assert!( + art_peace.get_days_quests(0) == array![zero_address, zero_address, zero_address].span(), + "Daily quests were not set correctly" + ); + assert!( + art_peace.get_main_quests() == array![main_unruggable_quest].span(), + "Main quests were not set correctly" + ); +} + // TODO: Daily quest test day 2, stats, other fields ... #[test] @@ -383,6 +437,27 @@ fn pixel_quests_test() { ); } +#[test] +fn unruggable_quest_test() { + let unruggable_quest = snf::declare("UnruggableQuest"); + let main_unruggable_quest = deploy_unruggable_quest_main(unruggable_quest); + + let art_peace = IArtPeaceDispatcher { + contract_address: deploy_with_quests_contract( + array![].span(), array![main_unruggable_quest].span() + ) + }; + + let unruggable_token = deploy_unruggable_mock(); + + let calldata: Span = array![unruggable_token.try_into().unwrap()].span(); + art_peace.claim_main_quest(0, calldata); + + assert!( + art_peace.get_extra_pixels_count() == 20, "Extra pixels are wrong after main quest claim" + ); +} + #[test] fn template_full_basic_test() { let art_peace = IArtPeaceDispatcher { contract_address: deploy_contract() }; diff --git a/tests/integration/local/run.sh b/tests/integration/local/run.sh index a190d19d..aa83c6ec 100755 --- a/tests/integration/local/run.sh +++ b/tests/integration/local/run.sh @@ -31,6 +31,7 @@ touch $REDIS_LOG_FILE redis-server 2>&1 > $REDIS_LOG_FILE & REDIS_PID=$! sleep 2 # Wait for redis to start; TODO: Check if redis is actually running +redis-cli del canvas # Start the art-peace-db with postgres echo "Starting art-peace-db ..."