props.castVote(props.index)}
>
{props.userVote === props.index && (
diff --git a/frontend/src/tabs/voting/Voting.js b/frontend/src/tabs/voting/Voting.js
index 4a10b4a1..fd513e93 100644
--- a/frontend/src/tabs/voting/Voting.js
+++ b/frontend/src/tabs/voting/Voting.js
@@ -148,7 +148,9 @@ const Voting = (props) => {
>
Voting has ended
props.startNextDay()}
>
{props.timeLeftInDay}
@@ -168,7 +170,9 @@ const Voting = (props) => {
>
Time left to vote
props.startNextDay()}
>
{props.timeLeftInDay}
diff --git a/frontend/src/utils/TimerInjector.js b/frontend/src/utils/TimerInjector.js
index 9ee2879f..854ccad0 100644
--- a/frontend/src/utils/TimerInjector.js
+++ b/frontend/src/utils/TimerInjector.js
@@ -106,7 +106,11 @@ export const TimerInjector = ({ children, props, isLastDay, endTimestamp }) => {
const minutesFinal = Math.floor((difference / 1000 / 60) % 60);
const secondsFinal = Math.floor((difference / 1000) % 60);
- const formattedTimeLeft = `${hoursFinal.toString().padStart(2, '0')}:${minutesFinal.toString().padStart(2, '0')}:${secondsFinal.toString().padStart(2, '0')}`;
+ const formattedTimeLeft = `${hoursFinal
+ .toString()
+ .padStart(2, '0')}:${minutesFinal
+ .toString()
+ .padStart(2, '0')}:${secondsFinal.toString().padStart(2, '0')}`;
setTimeLeftInDay(formattedTimeLeft);
};
calculateTimeLeft();
diff --git a/onchain/src/art_peace.cairo b/onchain/src/art_peace.cairo
index 7735dcba..b8afced2 100644
--- a/onchain/src/art_peace.cairo
+++ b/onchain/src/art_peace.cairo
@@ -8,14 +8,15 @@ pub mod ArtPeace {
use art_peace::quests::interfaces::{IQuestDispatcher, IQuestDispatcherTrait};
use art_peace::nfts::interfaces::{
IArtPeaceNFTMinter, NFTMetadata, NFTMintParams, ICanvasNFTAdditionalDispatcher,
- ICanvasNFTAdditionalDispatcherTrait
+ ICanvasNFTAdditionalDispatcherTrait, ICanvasNFTLikeAndUnlike,
+ ICanvasNFTLikeAndUnlikeDispatcher, ICanvasNFTLikeAndUnlikeDispatcherTrait
};
use art_peace::templates::component::TemplateStoreComponent;
use art_peace::templates::interfaces::{
ITemplateVerifier, ITemplateStore, FactionTemplateMetadata, TemplateMetadata
};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
-
+ use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait};
component!(path: TemplateStoreComponent, storage: templates, event: TemplateEvent);
#[abi(embed_v0)]
@@ -84,6 +85,8 @@ pub mod ArtPeace {
chain_faction_templates: LegacyMap::,
#[substorage(v0)]
templates: TemplateStoreComponent::Storage,
+ // Map: (user's address, nft_id) -> boolean indicate the user has liked the nft or not
+ liked_nfts: LegacyMap::<(ContractAddress, u256), bool>,
}
#[event]
@@ -97,8 +100,6 @@ pub mod ArtPeace {
FactionPixelsPlaced: FactionPixelsPlaced,
ChainFactionPixelsPlaced: ChainFactionPixelsPlaced,
ExtraPixelsPlaced: ExtraPixelsPlaced,
- DailyQuestClaimed: DailyQuestClaimed,
- MainQuestClaimed: MainQuestClaimed,
VoteColor: VoteColor,
FactionCreated: FactionCreated,
FactionLeaderChanged: FactionLeaderChanged,
@@ -111,10 +112,11 @@ pub mod ArtPeace {
FactionTemplateRemoved: FactionTemplateRemoved,
ChainFactionTemplateAdded: ChainFactionTemplateAdded,
ChainFactionTemplateRemoved: ChainFactionTemplateRemoved,
- HostAwardedUser: HostAwardedUser,
// TODO: Integrate template event
#[flat]
TemplateEvent: TemplateStoreComponent::Event,
+ #[flat]
+ ExtraPixelsAwardedEvent: ExtraPixelsAwarded
}
#[derive(Drop, starknet::Event)]
@@ -180,6 +182,15 @@ pub mod ArtPeace {
extra_pixels: u32,
}
+ #[derive(Drop, starknet::Event)]
+ enum ExtraPixelsAwarded {
+ DailyQuest: DailyQuestClaimed,
+ MainQuest: MainQuestClaimed,
+ HostAwardedUser: HostAwardedUser,
+ LikeNft: LikeNftAwarded,
+ }
+
+
#[derive(Drop, starknet::Event)]
pub struct DailyQuestClaimed {
#[key]
@@ -202,6 +213,20 @@ pub mod ArtPeace {
pub calldata: Span,
}
+ #[derive(Drop, starknet::Event)]
+ struct HostAwardedUser {
+ #[key]
+ user: ContractAddress,
+ amount: u32,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ struct LikeNftAwarded {
+ #[key]
+ user: ContractAddress,
+ amount: u32,
+ }
+
#[derive(Drop, starknet::Event)]
struct VoteColor {
#[key]
@@ -295,13 +320,6 @@ pub mod ArtPeace {
template_id: u32,
}
- #[derive(Drop, starknet::Event)]
- struct HostAwardedUser {
- #[key]
- user: ContractAddress,
- amount: u32,
- }
-
#[derive(Drop, Serde)]
pub struct InitParams {
pub host: ContractAddress,
@@ -363,7 +381,7 @@ pub mod ArtPeace {
let test_address = starknet::contract_address_const::<
0x328ced46664355fc4b885ae7011af202313056a7e3d44827fb24c9d3206aaa0
>();
- self.extra_pixels.write(test_address, 1000);
+ self.extra_pixels.write(test_address, 100000);
}
self.devmode.write(init_params.devmode);
@@ -859,7 +877,12 @@ pub mod ArtPeace {
self.extra_pixels.read(starknet::get_caller_address()) + reward
);
}
- self.emit(DailyQuestClaimed { day_index, quest_id, user, reward, calldata });
+ self
+ .emit(
+ ExtraPixelsAwarded::DailyQuest(
+ DailyQuestClaimed { day_index, quest_id, user, reward, calldata }
+ )
+ );
}
fn claim_main_quest(ref self: ContractState, quest_id: u32, calldata: Span) {
@@ -875,7 +898,12 @@ pub mod ArtPeace {
self.extra_pixels.read(starknet::get_caller_address()) + reward
);
}
- self.emit(MainQuestClaimed { quest_id, user, reward, calldata });
+ self
+ .emit(
+ ExtraPixelsAwarded::MainQuest(
+ MainQuestClaimed { quest_id, user, reward, calldata }
+ )
+ );
}
fn get_nft_contract(self: @ContractState) -> ContractAddress {
@@ -898,7 +926,7 @@ pub mod ArtPeace {
template_metadata.position < self.canvas_width.read() * self.canvas_height.read(),
'Template position out of bounds'
);
- let MAX_TEMPLATE_SIZE: u128 = 64;
+ let MAX_TEMPLATE_SIZE: u128 = 256;
let MIN_TEMPLATE_SIZE: u128 = 5;
assert(
template_metadata.width >= MIN_TEMPLATE_SIZE
@@ -943,7 +971,7 @@ pub mod ArtPeace {
template_metadata.position < self.canvas_width.read() * self.canvas_height.read(),
'Template position out of bounds'
);
- let MAX_TEMPLATE_SIZE: u128 = 64;
+ let MAX_TEMPLATE_SIZE: u128 = 256;
let MIN_TEMPLATE_SIZE: u128 = 5;
assert(
template_metadata.width >= MIN_TEMPLATE_SIZE
@@ -1031,13 +1059,17 @@ pub mod ArtPeace {
fn host_award_user(ref self: ContractState, user: starknet::ContractAddress, amount: u32) {
assert(starknet::get_caller_address() == self.host.read(), 'Host awards user');
self.extra_pixels.write(user, self.extra_pixels.read(user) + amount);
- self.emit(HostAwardedUser { user, amount });
+ self.emit(ExtraPixelsAwarded::HostAwardedUser(HostAwardedUser { user, amount }));
}
fn host_change_end_time(ref self: ContractState, new_end_time: u64) {
assert(starknet::get_caller_address() == self.host.read(), 'Host changes end time');
self.end_time.write(new_end_time);
}
+
+ fn already_liked_nft(self: @ContractState, user: ContractAddress, nft_id: u256) -> bool {
+ self.liked_nfts.read((user, nft_id))
+ }
}
#[abi(embed_v0)]
@@ -1059,7 +1091,7 @@ pub mod ArtPeace {
self.check_game_running();
// TODO: To config?
let MIN_NFT_SIZE: u128 = 1;
- let MAX_NFT_SIZE: u128 = 64;
+ let MAX_NFT_SIZE: u128 = 256;
assert(
mint_params.width >= MIN_NFT_SIZE && mint_params.width <= MAX_NFT_SIZE,
'NFT width out of bounds'
@@ -1262,6 +1294,26 @@ pub mod ArtPeace {
}
}
+ #[abi(embed_v0)]
+ impl ArtPeaceCanvasNFTLikeAndUnlike of ICanvasNFTLikeAndUnlike {
+ fn like_nft(ref self: ContractState, token_id: u256) {
+ let caller = starknet::get_caller_address();
+ assert(!self.already_liked_nft(caller, token_id), 'already liked this nft');
+ let nft_address = self.nft_contract.read();
+ ICanvasNFTLikeAndUnlikeDispatcher { contract_address: nft_address }.like_nft(token_id);
+ let nft_owner = IERC721Dispatcher { contract_address: nft_address }.owner_of(token_id);
+
+ // award the minter of the nft 1 extra pixel each time someone likes the nft
+ self.extra_pixels.write(nft_owner, self.extra_pixels.read(nft_owner) + 1);
+ self.emit(ExtraPixelsAwarded::LikeNft(LikeNftAwarded { user: nft_owner, amount: 1 }));
+ self.liked_nfts.write((caller, token_id), true);
+ }
+ fn unlike_nft(ref self: ContractState, token_id: u256) {
+ ICanvasNFTLikeAndUnlikeDispatcher { contract_address: self.nft_contract.read() }
+ .unlike_nft(token_id);
+ }
+ }
+
/// Internals
fn finalize_color_votes(ref self: ContractState) {
let daily_new_colors_count = self.daily_new_colors_count.read();
diff --git a/onchain/src/interfaces.cairo b/onchain/src/interfaces.cairo
index be586289..fbbb7a87 100644
--- a/onchain/src/interfaces.cairo
+++ b/onchain/src/interfaces.cairo
@@ -124,8 +124,11 @@ pub trait IArtPeace {
fn claim_today_quest(ref self: TContractState, quest_id: u32, calldata: Span);
fn claim_main_quest(ref self: TContractState, quest_id: u32, calldata: Span);
- // NFT info
+ // NFT-related info
fn get_nft_contract(self: @TContractState) -> starknet::ContractAddress;
+ fn already_liked_nft(
+ self: @TContractState, user: starknet::ContractAddress, nft_id: u256
+ ) -> bool;
// Templates
fn add_faction_template(
diff --git a/onchain/src/multi_canvas.cairo b/onchain/src/multi_canvas.cairo
index 9deebd0f..87bb7e35 100644
--- a/onchain/src/multi_canvas.cairo
+++ b/onchain/src/multi_canvas.cairo
@@ -46,6 +46,8 @@ pub mod MultiCanvas {
const MAX_COLOR_COUNT: u32 = 25;
const MIN_SIZE: u128 = 16;
const MAX_SIZE: u128 = 1024;
+ const MIN_STENCIL_SIZE: u128 = 5;
+ const MAX_STENCIL_SIZE: u128 = 256;
#[derive(Drop, Serde)]
pub struct CanvasInitParams {
@@ -444,6 +446,10 @@ pub mod MultiCanvas {
fn add_stencil(ref self: ContractState, canvas_id: u32, stencil: StencilMetadata) -> u32 {
let stencil_id = self.stencil_counts.read(canvas_id);
+ assert(stencil.width >= MIN_STENCIL_SIZE, 'Stencil too small');
+ assert(stencil.height >= MIN_STENCIL_SIZE, 'Stencil too small');
+ assert(stencil.width <= MAX_STENCIL_SIZE, 'Stencil too large');
+ assert(stencil.height <= MAX_STENCIL_SIZE, 'Stencil too large');
self.stencils.write((canvas_id, stencil_id), stencil.clone());
self.stencil_counts.write(canvas_id, stencil_id + 1);
self.emit(StencilAdded { canvas_id, stencil_id, stencil });
diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo
index e7d74f73..d29f0c0e 100644
--- a/onchain/src/tests/art_peace.cairo
+++ b/onchain/src/tests/art_peace.cairo
@@ -3,7 +3,8 @@ use art_peace::ArtPeace::InitParams;
use art_peace::tests::utils;
use art_peace::nfts::interfaces::{
IArtPeaceNFTMinterDispatcher, IArtPeaceNFTMinterDispatcherTrait, ICanvasNFTStoreDispatcher,
- ICanvasNFTStoreDispatcherTrait, NFTMintParams, NFTMetadata
+ ICanvasNFTStoreDispatcherTrait, NFTMintParams, NFTMetadata, ICanvasNFTLikeAndUnlikeDispatcher,
+ ICanvasNFTLikeAndUnlikeDispatcherTrait
};
use art_peace::templates::interfaces::{
ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher,
@@ -431,6 +432,33 @@ fn nft_set_base_uri_test() {
assert!(nft_meta.token_uri(0) == new_expected_uri, "NFT URI is not correct after change");
}
+#[test]
+fn nft_like_nft_test() {
+ let art_peace = IArtPeaceDispatcher { contract_address: deploy_contract() };
+ let nft_minter = IArtPeaceNFTMinterDispatcher { contract_address: art_peace.contract_address };
+ let nft_like = ICanvasNFTLikeAndUnlikeDispatcher {
+ contract_address: art_peace.contract_address
+ };
+ snf::start_prank(CheatTarget::One(nft_minter.contract_address), utils::HOST());
+ nft_minter.add_nft_contract(utils::NFT_CONTRACT());
+ snf::stop_prank(CheatTarget::One(nft_minter.contract_address));
+
+ let mint_params = NFTMintParams { position: 10, width: 16, height: 16, name: 'test' };
+ snf::start_prank(CheatTarget::One(nft_minter.contract_address), utils::PLAYER1());
+ nft_minter.mint_nft(mint_params);
+ snf::stop_prank(CheatTarget::One(nft_minter.contract_address));
+
+ let extra_pixels_count = art_peace.get_user_extra_pixels_count(utils::PLAYER1());
+ assert_eq!(extra_pixels_count, 0, "should not have any extra pixel");
+
+ snf::start_prank(CheatTarget::One(nft_minter.contract_address), utils::PLAYER1());
+ nft_like.like_nft(0);
+ snf::stop_prank(CheatTarget::One(nft_minter.contract_address));
+
+ let extra_pixels_count = art_peace.get_user_extra_pixels_count(utils::PLAYER1());
+ assert_eq!(extra_pixels_count, 1, "should increase by one");
+}
+
#[test]
fn deposit_reward_test() {
let art_peace_address = deploy_contract();