From 327399bc3e988e3423d494a9e19e4e676f9cca7a Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Mon, 8 Apr 2024 16:29:43 -0500 Subject: [PATCH 1/4] Initial set of responsive frontend parts and cleanup warnings when compiling frontend --- frontend/package-lock.json | 41 ++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/App.css | 19 +++++++++++- frontend/src/App.js | 36 ++++++++++++++++++---- frontend/src/canvas/Canvas.js | 17 +++++------ frontend/src/canvas/PixelSelector.css | 6 ++++ frontend/src/canvas/PixelSelector.js | 2 +- frontend/src/canvas/TemplateOverlay.js | 2 -- frontend/src/tabs/ExpandableTab.js | 1 + frontend/src/tabs/NFTs.js | 2 +- frontend/src/tabs/TabsFooter.css | 15 ++++++++-- frontend/src/tabs/TabsFooter.js | 8 +++-- frontend/src/tabs/Templates.js | 2 +- frontend/src/tabs/Voting.js | 2 +- frontend/src/utils/Window.js | 12 ++++---- postgres/init.sql | 1 + 16 files changed, 134 insertions(+), 33 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3177351f..a7634e38 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "get-starknet": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-responsive": "^10.0.0", "react-scripts": "5.0.1", "react-use-websocket": "^4.8.1", "starknet": "^5.24.3" @@ -6308,6 +6309,11 @@ } } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/css-minimizer-webpack-plugin": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", @@ -9149,6 +9155,11 @@ "node": ">=10.17.0" } }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -12155,6 +12166,14 @@ "tmpl": "1.0.5" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -14677,6 +14696,23 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz", + "integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15522,6 +15558,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index b2f65680..6b85ccc2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "get-starknet": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-responsive": "^10.0.0", "react-scripts": "5.0.1", "react-use-websocket": "^4.8.1", "starknet": "^5.24.3" diff --git a/frontend/src/App.css b/frontend/src/App.css index 16e2640b..5efc4766 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -14,7 +14,7 @@ z-index: 100; right: 0; top: 0; - width: max(22rem, 25%); + width: max(25rem, 30%); margin: 0.5rem; padding: 0; @@ -26,6 +26,14 @@ font-size: 1.2rem; } +.App__panel--tablet { + width: max(28rem, 40%); +} + +.App__panel--portrait { + width: max(28rem, 60%); +} + .App__footer { position: fixed; z-index: 101; @@ -40,3 +48,12 @@ pointer-events: none; } + +.App__logo--mobile { + position: fixed; + z-index: 99; + left: 1rem; + top: 1rem; + width: min(8rem, 15%); + image-rendering: pixelated; +} diff --git a/frontend/src/App.js b/frontend/src/App.js index f13c1ef9..f42e38eb 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useMediaQuery } from 'react-responsive' import './App.css'; import Canvas from './canvas/Canvas.js'; import PixelSelector from './canvas/PixelSelector.js'; @@ -6,10 +7,32 @@ import SelectedPixelPanel from './canvas/SelectedPixelPanel.js'; import TabsFooter from './tabs/TabsFooter.js'; import TabPanel from './tabs/TabPanel.js'; import { usePreventZoom } from './utils/Window.js'; +import logo from './resources/logo.png'; function App() { + // Window management usePreventZoom(); + const isDesktopOrLaptop = useMediaQuery({ + query: '(min-width: 1224px)' + }) + const isBigScreen = useMediaQuery({ query: '(min-width: 1824px)' }) + const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1224px)' }) + const isPortrait = useMediaQuery({ query: '(orientation: portrait)' }) + // TODO: Consider using in sizing stuff + const isRetina = useMediaQuery({ query: '(min-resolution: 2dppx)' }) + // TODO: height checks ? + + const getDeviceTypeInfo = () => { + return { + isDesktopOrLaptop: isDesktopOrLaptop, + isBigScreen: isBigScreen, + isTabletOrMobile: isTabletOrMobile, + isPortrait: isPortrait, + isRetina: isRetina + } + } + // Pixel selection data const [selectedColorId, setSelectedColorId] = useState(-1); const [pixelSelectedMode, setPixelSelectedMode] = useState(false); @@ -35,15 +58,18 @@ function App() { return (
-
- { pixelSelectedMode && ( + { !isDesktopOrLaptop && ( + logo + )} +
+ { (!isPortrait ? pixelSelectedMode : pixelSelectedMode && activeTab === tabs[0]) && ( )} - +
- - + +
); diff --git a/frontend/src/canvas/Canvas.js b/frontend/src/canvas/Canvas.js index c2d88e49..20323506 100644 --- a/frontend/src/canvas/Canvas.js +++ b/frontend/src/canvas/Canvas.js @@ -63,7 +63,7 @@ const Canvas = props => { setDragStartY(e.clientY) } - const handlePointerUp = (e) => { + const handlePointerUp = () => { setIsDragging(false) setDragStartX(0) setDragStartY(0) @@ -90,12 +90,12 @@ const Canvas = props => { const [setup, setSetup] = useState(false) const [pixelPlacedBy, setPixelPlacedBy] = useState("") - const draw = (ctx, imageData) => { + const draw = useCallback((ctx, imageData) => { ctx.canvas.width = width ctx.canvas.height = height ctx.putImageData(imageData, 0, 0) // TODO: Use image-rendering for supported browsers? - } + }, [width, height]) useEffect(() => { if (setup) { @@ -122,7 +122,7 @@ const Canvas = props => { let value = (byte >> (oneByteBitOffset - bitOffset)) & 0b11111 dataArray.push(value) } else { - let byte = colorData[bytePos] << 8 | colorData[bytePos + 1] + let byte = (colorData[bytePos] << 8) | colorData[bytePos + 1] let value = (byte >> (twoByteBitOffset - bitOffset)) & 0b11111 dataArray.push(value) } @@ -152,7 +152,7 @@ const Canvas = props => { }) } // TODO: Return a cleanup function to close the websocket / ... - }, [draw, readyState]) + }, [readyState, sendJsonMessage, setup, colors, width, height, backendUrl, draw]) useEffect(() => { if (lastJsonMessage) { @@ -166,7 +166,7 @@ const Canvas = props => { context.fillStyle = color context.fillRect(x, y, 1, 1) } - }, [lastJsonMessage]) + }, [lastJsonMessage, colors, width]) const pixelSelect = useCallback((clientX, clientY) => { const canvas = canvasRef.current @@ -189,17 +189,14 @@ const Canvas = props => { }).then(response => { return response.text() }).then(data => { - // TODO: not working // TODO: Cache pixel info & clear cache on update from websocket // TODO: Dont query if hover select ( until 1s after hover? ) setPixelPlacedBy(data) }).catch(error => { console.error(error) - //TODO: Handle error }); - // TODO: Create a border around the selected pixel - }, [props.setSelectedPositionX, props.setSelectedPositionY, props.setPixelSelectedMode, setPixelPlacedBy, width, height, props.selectedColorId, props.pixelSelectedMode, props.selectedPositionX, props.selectedPositionY]) + }, [props, width, height, backendUrl]) const pixelClicked = (e) => { pixelSelect(e.clientX, e.clientY) diff --git a/frontend/src/canvas/PixelSelector.css b/frontend/src/canvas/PixelSelector.css index 1e36e4df..b17cc6b6 100644 --- a/frontend/src/canvas/PixelSelector.css +++ b/frontend/src/canvas/PixelSelector.css @@ -5,6 +5,11 @@ pointer-events: fill; } +.PixelSelector--portrait { + font-size: min(1.4rem, 3vw); + margin: 1rem; +} + .PixelSelector__button { padding: 0 1rem; border-radius: 1rem; @@ -14,6 +19,7 @@ align-items: center; justify-content: center; + /* TODO: style this better? */ background-image: linear-gradient(to bottom right, rgba(50, 50, 50, 0.25), rgba(50, 50, 50, 0.5)); box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1); diff --git a/frontend/src/canvas/PixelSelector.js b/frontend/src/canvas/PixelSelector.js index 5502514e..6856d32f 100644 --- a/frontend/src/canvas/PixelSelector.js +++ b/frontend/src/canvas/PixelSelector.js @@ -67,7 +67,7 @@ const PixelSelector = (props) => { }, [getTimeTillNextPlacement]); return ( -
+
{ selectorMode &&
diff --git a/frontend/src/canvas/TemplateOverlay.js b/frontend/src/canvas/TemplateOverlay.js index 75fff072..e6802a9a 100644 --- a/frontend/src/canvas/TemplateOverlay.js +++ b/frontend/src/canvas/TemplateOverlay.js @@ -10,8 +10,6 @@ const TemplateOverlay = props => { const width = props.templateWidth const height = templateImage.length / width const scaler = 8 - const scalerFactor = 1 / scaler - const translater = (scaler - 1) / 2 const draw = (ctx, imageData) => { ctx.canvas.width = width * scaler diff --git a/frontend/src/tabs/ExpandableTab.js b/frontend/src/tabs/ExpandableTab.js index be656b8b..4baf84ba 100644 --- a/frontend/src/tabs/ExpandableTab.js +++ b/frontend/src/tabs/ExpandableTab.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import './ExpandableTab.css'; const ExpandableTab = props => { + // TODO: Close pixel selection when expanded const [expanded, setExpanded] = useState(false); const MainSection = props.mainSection; diff --git a/frontend/src/tabs/NFTs.js b/frontend/src/tabs/NFTs.js index 4913717c..6d6176c2 100644 --- a/frontend/src/tabs/NFTs.js +++ b/frontend/src/tabs/NFTs.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React from 'react' import './NFTs.css'; import ExpandableTab from './ExpandableTab.js'; diff --git a/frontend/src/tabs/TabsFooter.css b/frontend/src/tabs/TabsFooter.css index d76e21ab..0920046d 100644 --- a/frontend/src/tabs/TabsFooter.css +++ b/frontend/src/tabs/TabsFooter.css @@ -12,7 +12,7 @@ /* TODO: css -> scss? */ .TabsFooter__logo { - width: min(8rem, 10vw); + width: 10rem; pointer-events: fill; image-rendering: pixelated; @@ -25,9 +25,20 @@ } .TabsFooter__tab { - font-size: min(1.6rem, 2vw); + font-size: min(1.6rem, 1.6vw); text-shadow: 0 0 1rem #FFFFFF; pointer-events: all; + padding: 2rem 0; +} + +.TabsFooter__tab--portrait { + font-size: min(2rem, 2.2vw); + text-shadow: 0 0 1rem #FFFFFF; + pointer-events: all; + padding: 1.6rem 0.4rem; + border-radius: 0.5rem; + background: linear-gradient(to bottom right, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.6)); + box-shadow: 0 0 1rem 0.1rem rgba(0, 0, 0, 0.2); } .TabsFooter__main { diff --git a/frontend/src/tabs/TabsFooter.js b/frontend/src/tabs/TabsFooter.js index 036fee6f..2ae58f27 100644 --- a/frontend/src/tabs/TabsFooter.js +++ b/frontend/src/tabs/TabsFooter.js @@ -7,15 +7,17 @@ const TabsFooter = props => { return (
props.setActiveTab(props.tabs[0])} className="TabsFooter__main"> - logo -
Canvas
+ { props.getDeviceTypeInfo().isDesktopOrLaptop && + logo + } +
Canvas
{props.tabs.slice(1, props.tabs.length).map((type) => (
props.setActiveTab(type)} - className={"TabsFooter__tab " + (props.activeTab === type ? 'TabsFooter__tab--active' : '')} + className={"TabsFooter__tab " + (props.activeTab === type ? 'TabsFooter__tab--active ' : ' ') + (props.getDeviceTypeInfo().isPortrait ? 'TabsFooter__tab--portrait' : '')} > {type}
diff --git a/frontend/src/tabs/Templates.js b/frontend/src/tabs/Templates.js index 34667bc5..9f3d04da 100644 --- a/frontend/src/tabs/Templates.js +++ b/frontend/src/tabs/Templates.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React from 'react' import './Templates.css'; import ExpandableTab from './ExpandableTab.js'; diff --git a/frontend/src/tabs/Voting.js b/frontend/src/tabs/Voting.js index 8549e64b..96842f59 100644 --- a/frontend/src/tabs/Voting.js +++ b/frontend/src/tabs/Voting.js @@ -53,7 +53,7 @@ const Voting = props => {
Color
Count
-
+
{colors.map((color, index) => (
{ diff --git a/frontend/src/utils/Window.js b/frontend/src/utils/Window.js index fecc32f9..3fe530b9 100644 --- a/frontend/src/utils/Window.js +++ b/frontend/src/utils/Window.js @@ -6,12 +6,12 @@ export function usePreventZoom(scrollCheck = true, keyboardCheck = true) { if ( keyboardCheck && e.ctrlKey && - (e.keyCode == "61" || - e.keyCode == "107" || - e.keyCode == "173" || - e.keyCode == "109" || - e.keyCode == "187" || - e.keyCode == "189") + (e.keyCode === "61" || + e.keyCode === "107" || + e.keyCode === "173" || + e.keyCode === "109" || + e.keyCode === "187" || + e.keyCode === "189") ) { e.preventDefault(); } diff --git a/postgres/init.sql b/postgres/init.sql index 1cf2c8cb..920ab513 100644 --- a/postgres/init.sql +++ b/postgres/init.sql @@ -41,6 +41,7 @@ CREATE TABLE Days ( ); CREATE INDEX days_dayIndex_index ON Days (dayIndex); +-- TODO: Remove completedStatus & status from Quests? CREATE TABLE Quests ( key integer NOT NULL PRIMARY KEY, name text NOT NULL, From f227ed6935dfa12f6afc9323a5312d33bbf19ca8 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 10 Apr 2024 11:04:45 -0500 Subject: [PATCH 2/4] Initial setup for templates --- README.md | 2 + onchain/Scarb.toml | 1 - onchain/src/art_peace.cairo | 184 ++++++++++++++++---------- onchain/src/interface.cairo | 27 ++-- onchain/src/lib.cairo | 9 +- onchain/src/templates/component.cairo | 62 +++++++++ onchain/src/templates/interface.cairo | 32 +++++ onchain/src/tests/art_peace.cairo | 108 ++++++++++++++- 8 files changed, 343 insertions(+), 82 deletions(-) create mode 100644 onchain/src/templates/component.cairo create mode 100644 onchain/src/templates/interface.cairo diff --git a/README.md b/README.md index e4fd8f11..3f05f367 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,8 @@ However, it might be worth running only certain components for development/testi - [Diagrams](./docs/diagrams/) - [r/place technical document](https://www.redditinc.com/blog/how-we-built-rplace) +- [Telegram](https://t.me/+rd1pvEo8T2w2ZDRh) +- [OnlyDust](https://app.onlydust.com/p/artpeace) ## Contributors ✨ diff --git a/onchain/Scarb.toml b/onchain/Scarb.toml index b5b37777..99ed1283 100644 --- a/onchain/Scarb.toml +++ b/onchain/Scarb.toml @@ -1,7 +1,6 @@ [package] name = "art_peace" version = "0.1.0" -edition = "2023_11" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html diff --git a/onchain/src/art_peace.cairo b/onchain/src/art_peace.cairo index b6226a43..bbb5d5bf 100644 --- a/onchain/src/art_peace.cairo +++ b/onchain/src/art_peace.cairo @@ -1,39 +1,56 @@ #[starknet::contract] pub mod ArtPeace { use starknet::ContractAddress; - use art_peace::IArtPeace; - use art_peace::quests::interface::{IQuestDispatcher, IQuestDispatcherTrait}; + use art_peace::{IArtPeace, Pixel}; + use art_peace::quests::{IQuestDispatcher, IQuestDispatcherTrait}; + use art_peace::templates::component::TemplateStoreComponent; + use art_peace::templates::{ITemplateVerifier, TemplateMetadata}; + + component!(path: TemplateStoreComponent, storage: templates, event: TemplateEvent); + + #[abi(embed_v0)] + impl TemplateStoreComponentImpl = TemplateStoreComponent::TemplateStoreImpl; #[storage] struct Storage { - canvas: LegacyMap::, + canvas: LegacyMap::, canvas_width: u128, canvas_height: u128, total_pixels: u128, - // Maps the users contract address to the last time they placed a pixel + + // Map: user's address -> last time they placed a pixel last_placed_time: LegacyMap::, time_between_pixels: u64, - // Maps the users contract address to the amount of extra pixels they have + // Map: user's address -> amount of extra pixels they have extra_pixels: LegacyMap::, - color_count: u32, - color_palette: LegacyMap::, + + color_count: u8, + // Map: color index -> color value in RGBA + color_palette: LegacyMap::, + creation_time: u64, end_time: u64, day_index: u32, - max_days: u32, - // TODO: Optimize index to be a single u128? - // Maps the (day_index, quest_id) to the quest contract address + + // Map: (day_index, quest_id) -> quest contract address daily_quests: LegacyMap::<(u32, u32), ContractAddress>, main_quests_count: u32, + // Map: quest index -> quest contract address main_quests: LegacyMap::, - // Maps (user addr, day index) to the amount of pixels placed that day - user_pixels_placed: LegacyMap::<(ContractAddress, u32), u32>, + + // Map: (day_index, user's address, color index) -> amount of pixels placed + user_pixels_placed: LegacyMap::<(u32, ContractAddress, u8), u32>, + + #[substorage(v0)] + templates: TemplateStoreComponent::Storage, } #[event] #[derive(Drop, starknet::Event)] enum Event { PixelPlaced: PixelPlaced, + #[flat] + TemplateEvent: TemplateStoreComponent::Event, } #[derive(Drop, starknet::Event)] @@ -44,9 +61,10 @@ pub mod ArtPeace { pos: u128, #[key] day: u32, - color: u32, + color: u8, } + // TODO: Span or Array for each? #[derive(Drop, Serde)] pub struct InitParams { pub canvas_width: u128, @@ -59,36 +77,27 @@ pub mod ArtPeace { } #[constructor] - fn constructor(ref self: ContractState, init_params: InitParams,) { + fn constructor(ref self: ContractState, init_params: InitParams) { self.canvas_width.write(init_params.canvas_width); self.canvas_height.write(init_params.canvas_height); self.total_pixels.write(init_params.canvas_width * init_params.canvas_height); self.time_between_pixels.write(init_params.time_between_pixels); - self.color_count.write(init_params.color_palette.len()); - let mut i = 0; - while i < init_params - .color_palette - .len() { - self.color_palette.write(i, *init_params.color_palette.at(i)); - i += 1; - }; + let color_count: u8 = init_params.color_palette.len().try_into().unwrap(); + self.color_count.write(color_count); + let mut i: u8 = 0; + while i < color_count { + self.color_palette.write(i, *init_params.color_palette.at(i.into())); + i += 1; + }; - // TODO: To config - let daily_quests_count = self.get_daily_quest_count(); self.creation_time.write(starknet::get_block_timestamp()); self.end_time.write(init_params.end_time); self.day_index.write(0); - // TODO: change name to quest_days? - let (mut days, rem_quests) = DivRem::div_rem( - init_params.daily_quests.len(), daily_quests_count - ); - if rem_quests > 0 { - days += 1; - } - self.max_days.write(days); + // TODO: To config + let daily_quests_count = self.get_daily_quest_count(); let mut i = 0; while i < init_params .daily_quests @@ -110,11 +119,19 @@ pub mod ArtPeace { #[abi(embed_v0)] impl ArtPeaceImpl of IArtPeace { - fn get_pixel(self: @ContractState, pos: u128) -> u32 { + fn get_pixel(self: @ContractState, pos: u128) -> Pixel { self.canvas.read(pos) } - fn get_pixel_xy(self: @ContractState, x: u128, y: u128) -> u32 { + fn get_pixel_color(self: @ContractState, pos: u128) -> u8 { + self.canvas.read(pos).color + } + + fn get_pixel_owner(self: @ContractState, pos: u128) -> ContractAddress { + self.canvas.read(pos).owner + } + + fn get_pixel_xy(self: @ContractState, x: u128, y: u128) -> Pixel { let pos = x + y * self.canvas_width.read(); self.canvas.read(pos) } @@ -131,7 +148,7 @@ pub mod ArtPeace { self.total_pixels.read() } - fn place_pixel(ref self: ContractState, pos: u128, color: u32) { + fn place_pixel(ref self: ContractState, pos: u128, color: u8) { let now = starknet::get_block_timestamp(); assert!(now <= self.end_time.read()); assert!(pos < self.total_pixels.read()); @@ -140,21 +157,22 @@ pub mod ArtPeace { let caller = starknet::get_caller_address(); // TODO: Only if the user has placed a pixel before? assert!(now - self.last_placed_time.read(caller) >= self.time_between_pixels.read()); - self.canvas.write(pos, color); + let pixel = Pixel { color, owner: caller }; + self.canvas.write(pos, pixel); self.last_placed_time.write(caller, now); let day = self.day_index.read(); self .user_pixels_placed - .write((caller, day), self.user_pixels_placed.read((caller, day)) + 1); + .write((day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1); self.emit(PixelPlaced { placed_by: caller, pos, day, color }); } - fn place_pixel_xy(ref self: ContractState, x: u128, y: u128, color: u32) { + fn place_pixel_xy(ref self: ContractState, x: u128, y: u128, color: u8) { let pos = x + y * self.canvas_width.read(); self.place_pixel(pos, color); } - fn place_extra_pixels(ref self: ContractState, positions: Array, colors: Array) { + fn place_extra_pixels(ref self: ContractState, positions: Array, colors: Array) { let now = starknet::get_block_timestamp(); assert!(now <= self.end_time.read()); let pixel_count = positions.len(); @@ -164,20 +182,19 @@ pub mod ArtPeace { assert!(pixel_count <= extra_pixels); let color_palette_count = self.color_count.read(); let total_pixels = self.total_pixels.read(); + let day = self.day_index.read(); let mut i = 0; while i < pixel_count { let pos = *positions.at(i); let color = *colors.at(i); assert!(pos < total_pixels); assert!(color < color_palette_count); - self.canvas.write(pos, color); + let pixel = Pixel { color, owner: caller }; + self.canvas.write(pos, pixel); + self.user_pixels_placed.write((day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1); i += 1; }; self.extra_pixels.write(caller, extra_pixels - pixel_count); - let day = self.day_index.read(); - self - .user_pixels_placed - .write((caller, day), self.user_pixels_placed.read((caller, day)) + pixel_count); //TODO: to extra pixel self.emit(ExtraPixelsPlaced { placed_by: caller, positions, day, colors }); } @@ -201,7 +218,7 @@ pub mod ArtPeace { self.extra_pixels.read(user) } - fn get_color_count(self: @ContractState) -> u32 { + fn get_color_count(self: @ContractState) -> u8 { self.color_count.read() } @@ -228,10 +245,6 @@ pub mod ArtPeace { self.day_index.read() } - fn get_max_days(self: @ContractState) -> u32 { - self.max_days.read() - } - fn get_daily_quest_count(self: @ContractState) -> core::zeroable::NonZero:: { // TODO: hardcoded 3 daily quests 3 @@ -264,22 +277,6 @@ pub mod ArtPeace { quests.span() } - fn get_daily_quests(self: @ContractState) -> Span { - let mut i = 0; - let mut quests = array![]; - let max_days = self.max_days.read(); - let quest_count = self.get_daily_quest_count().into(); - while i < max_days { - let mut j = 0; - while j < quest_count { - quests.append(self.daily_quests.read((i, j))); - j += 1; - }; - i += 1; - }; - quests.span() - } - fn get_main_quest_count(self: @ContractState) -> u32 { self.main_quests_count.read() } @@ -352,8 +349,13 @@ pub mod ArtPeace { let mut i = 0; let mut total = 0; let last_day = self.day_index.read() + 1; + let color_count = self.color_count.read(); while i < last_day { - total += self.user_pixels_placed.read((user, i)); + let mut j = 0; + while j < color_count { + total += self.user_pixels_placed.read((i, user, j)); + j += 1; + }; i += 1; }; total @@ -362,7 +364,55 @@ pub mod ArtPeace { fn get_user_pixels_placed_day( self: @ContractState, user: ContractAddress, day: u32 ) -> u32 { - self.user_pixels_placed.read((user, day)) + let mut total = 0; + let color_count = self.color_count.read(); + let mut i = 0; + while i < color_count { + total += self.user_pixels_placed.read((day, user, i)); + i += 1; + }; + total + } + + fn get_user_pixels_placed_day_color( + self: @ContractState, user: ContractAddress, day: u32, color: u8 + ) -> u32 { + self.user_pixels_placed.read((day, user, color)) + } + } + + #[abi(embed_v0)] + impl ArtPeaceTemplateVerifier of ITemplateVerifier { + // TODO: Check template function + fn complete_template(ref self: ContractState, template_id: u32, template_image: Span) { + assert!(template_id < self.get_templates_count()); + assert!(!self.is_template_complete(template_id)); + // TODO: ensure template_image matches the template size & hash + let template_metadata: TemplateMetadata = self.get_template(template_id); + let non_zero_width: core::zeroable::NonZero:: = template_metadata.width.try_into().unwrap(); + let (template_pos_y, template_pos_x) = DivRem::div_rem(template_metadata.position, non_zero_width); + let canvas_width = self.canvas_width.read(); + let (mut x, mut y) = (0, 0); + let mut matches = 0; + while y < template_metadata.height { + x = 0; + while x < template_metadata.width { + let pos = template_pos_x + x + (template_pos_y + y) * canvas_width; + let color = *template_image.at((x + y * template_metadata.width).try_into().unwrap()); + if color == self.canvas.read(pos).color { + matches += 1; + } + x += 1; + }; + y += 1; + }; + + // TODO: Allow some threshold? + if matches == template_metadata.width * template_metadata.height { + self.templates.completed_templates.write(template_id, true); + // TODO: Distribute rewards + // self.emit(Event::TemplateEvent::TemplateCompleted { template_id }); + } } } } diff --git a/onchain/src/interface.cairo b/onchain/src/interface.cairo index dd0e7712..5d1b785a 100644 --- a/onchain/src/interface.cairo +++ b/onchain/src/interface.cairo @@ -1,18 +1,28 @@ +#[derive(Drop, Serde, starknet::Store)] +pub struct Pixel { + // Color index in the palette + color: u8, + // The person that placed the pixel + owner: starknet::ContractAddress, +} + // TODO: Tests for all // TODO: Split into components : existing w/ canvas and user info, quests, stats, etc. #[starknet::interface] pub trait IArtPeace { // Get canvas info - fn get_pixel(self: @TContractState, pos: u128) -> u32; - fn get_pixel_xy(self: @TContractState, x: u128, y: u128) -> u32; + fn get_pixel(self: @TContractState, pos: u128) -> Pixel; + fn get_pixel_color(self: @TContractState, pos: u128) -> u8; + fn get_pixel_owner(self: @TContractState, pos: u128) -> starknet::ContractAddress; + fn get_pixel_xy(self: @TContractState, x: u128, y: u128) -> Pixel; fn get_width(self: @TContractState) -> u128; fn get_height(self: @TContractState) -> u128; fn get_total_pixels(self: @TContractState) -> u128; // Place pixels on the canvas - fn place_pixel(ref self: TContractState, pos: u128, color: u32); - fn place_pixel_xy(ref self: TContractState, x: u128, y: u128, color: u32); - fn place_extra_pixels(ref self: TContractState, positions: Array, colors: Array); + fn place_pixel(ref self: TContractState, pos: u128, color: u8); + fn place_pixel_xy(ref self: TContractState, x: u128, y: u128, color: u8); + fn place_extra_pixels(ref self: TContractState, positions: Array, colors: Array); // Get placement info fn get_last_placed_time(self: @TContractState) -> u64; @@ -22,14 +32,13 @@ pub trait IArtPeace { fn get_user_extra_pixels_count(self: @TContractState, user: starknet::ContractAddress) -> u32; // Get color info - fn get_color_count(self: @TContractState) -> u32; + fn get_color_count(self: @TContractState) -> u8; fn get_colors(self: @TContractState) -> Array; // Get timing info fn get_creation_time(self: @TContractState) -> u64; fn get_end_time(self: @TContractState) -> u64; fn get_day(self: @TContractState) -> u32; - fn get_max_days(self: @TContractState) -> u32; // Get quest info fn get_daily_quest_count(self: @TContractState) -> core::zeroable::NonZero::; @@ -38,7 +47,6 @@ pub trait IArtPeace { ) -> starknet::ContractAddress; fn get_days_quests(self: @TContractState, day_index: u32) -> Span; fn get_today_quests(self: @TContractState) -> Span; - fn get_daily_quests(self: @TContractState) -> Span; fn get_main_quest_count(self: @TContractState) -> u32; fn get_main_quest(self: @TContractState, quest_id: u32) -> starknet::ContractAddress; @@ -54,4 +62,7 @@ pub trait IArtPeace { fn get_user_pixels_placed_day( self: @TContractState, user: starknet::ContractAddress, day: u32 ) -> u32; + fn get_user_pixels_placed_day_color( + self: @TContractState, user: starknet::ContractAddress, day: u32, color: u8 + ) -> u32; } diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index 5af0a2d6..61a02f1e 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -1,7 +1,7 @@ pub mod art_peace; pub mod interface; use art_peace::ArtPeace; -use interface::{IArtPeace, IArtPeaceDispatcher, IArtPeaceDispatcherTrait}; +use interface::{IArtPeace, IArtPeaceDispatcher, IArtPeaceDispatcherTrait, Pixel}; mod quests { pub mod interface; @@ -10,6 +10,13 @@ mod quests { use interface::{IQuest, QuestClaimed, IQuestDispatcher, IQuestDispatcherTrait}; } +mod templates { + pub mod interface; + mod component; + + use interface::{TemplateMetadata, ITemplateVerifier, ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait}; +} + #[cfg(test)] mod tests { mod art_peace; diff --git a/onchain/src/templates/component.cairo b/onchain/src/templates/component.cairo new file mode 100644 index 00000000..3778a64a --- /dev/null +++ b/onchain/src/templates/component.cairo @@ -0,0 +1,62 @@ +#[starknet::component] +mod TemplateStoreComponent { + use art_peace::templates::interface::{ITemplateStore, TemplateMetadata}; + + #[storage] + struct Storage { + templates_count: u32, + // Map: template_id -> template_metadata + templates: LegacyMap::, + // Map: template_id -> is_completed + completed_templates: LegacyMap::, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + TemplateAdded: TemplateAdded, + TemplateCompleted: TemplateCompleted, + } + + #[derive(Drop, starknet::Event)] + struct TemplateAdded { + #[key] + id: u32, + metadata: TemplateMetadata, + } + + #[derive(Drop, starknet::Event)] + struct TemplateCompleted { + #[key] + id: u32, + // TODO: Users rewarded, ... + } + + #[embeddable_as(TemplateStoreImpl)] + impl TemplateStore> of ITemplateStore> { + fn get_templates_count(self: @ComponentState) -> u32 { + return self.templates_count.read(); + } + + fn get_template(self: @ComponentState, template_id: u32) -> TemplateMetadata { + return self.templates.read(template_id); + } + + fn get_template_hash(self: @ComponentState, template_id: u32) -> felt252 { + let metadata: TemplateMetadata = self.templates.read(template_id); + return metadata.hash; + } + + // TODO: Return idx of the template? + fn add_template(ref self: ComponentState, template_metadata: TemplateMetadata) { + let template_id = self.templates_count.read(); + self.templates.write(template_id, template_metadata); + self.templates_count.write(template_id + 1); + self.emit(TemplateAdded { id: template_id, metadata: template_metadata }); + } + + fn is_template_complete(self: @ComponentState, template_id: u32) -> bool { + return self.completed_templates.read(template_id); + } + } +} diff --git a/onchain/src/templates/interface.cairo b/onchain/src/templates/interface.cairo new file mode 100644 index 00000000..55c1b66d --- /dev/null +++ b/onchain/src/templates/interface.cairo @@ -0,0 +1,32 @@ +#[derive(Drop, Copy, Serde, starknet::Store)] +pub struct TemplateMetadata { + hash: felt252, + position: u128, + width: u128, + height: u128, + reward: u256, + reward_token: starknet::ContractAddress +} + +#[starknet::interface] +pub trait ITemplateStore { + // Returns the number of templates stored in the contract state. + fn get_templates_count(self: @TContractState) -> u32; + // Returns the template metadata stored in the contract state. + fn get_template(self: @TContractState, template_id: u32) -> TemplateMetadata; + // Returns the template image hash stored in the contract state. + fn get_template_hash(self: @TContractState, template_id: u32) -> felt252; + // Stores a new template image into the contract state w/ metadata. + // If the reward/token are set, then the contract escrows the reward for the template. + fn add_template(ref self: TContractState, template_metadata: TemplateMetadata); + // Returns whether the template is complete. + fn is_template_complete(self: @TContractState, template_id: u32) -> bool; +} + +#[starknet::interface] +pub trait ITemplateVerifier { + // Verifies the template is complete, and if so, sets the template as complete. + // If there was a reward escrowed, it is transferred to the builders. + // Passed template_image contains the full image, and is used to verify the template. + fn complete_template(ref self: TContractState, template_id: u32, template_image: Span); +} diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index d8dd9163..e082acc1 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -1,5 +1,6 @@ use art_peace::{IArtPeaceDispatcher, IArtPeaceDispatcherTrait}; use art_peace::ArtPeace::InitParams; +use art_peace::templates::{ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait, TemplateMetadata}; use snforge_std as snf; use snforge_std::{CheatTarget, ContractClassTrait}; @@ -110,6 +111,7 @@ fn deploy_test() { // TODO: event spy? // TODO: all getters & setters // TODO: Tests assert in code +// TODO: Check pixel owner #[test] fn place_pixel_test() { @@ -120,8 +122,8 @@ fn place_pixel_test() { let pos = x + y * WIDTH; let color = 0x5; art_peace.place_pixel(pos, color); - assert!(art_peace.get_pixel(pos) == color, "Pixel was not placed correctly at pos"); - assert!(art_peace.get_pixel_xy(x, y) == color, "Pixel was not placed correctly at xy"); + assert!(art_peace.get_pixel_color(pos) == color, "Pixel was not placed correctly at pos"); + assert!(art_peace.get_pixel_xy(x, y).color == color, "Pixel was not placed correctly at xy"); warp_to_next_available_time(art_peace); let x = 15; @@ -129,8 +131,8 @@ fn place_pixel_test() { let pos = x + y * WIDTH; let color = 0x7; art_peace.place_pixel_xy(x, y, color); - assert!(art_peace.get_pixel_xy(x, y) == color, "Pixel xy was not placed correctly at xy"); - assert!(art_peace.get_pixel(pos) == color, "Pixel xy was not placed correctly at pos"); + assert!(art_peace.get_pixel_xy(x, y).color == color, "Pixel xy was not placed correctly at xy"); + assert!(art_peace.get_pixel(pos).color == color, "Pixel xy was not placed correctly at pos"); } #[test] @@ -144,7 +146,7 @@ fn deploy_quest_test() { }; assert!( - art_peace.get_daily_quests() == daily_quests.span(), "Daily quests were not set correctly" + art_peace.get_days_quests(0) == daily_quests.span(), "Daily quests were not set correctly" ); assert!( art_peace.get_main_quests() == main_quests.span(), "Main quests were not set correctly" @@ -206,3 +208,99 @@ fn pixel_quest_test() { art_peace.get_extra_pixels_count() == 30, "Extra pixels are wrong after main quest claim" ); } + +use core::poseidon::PoseidonTrait; +use core::hash::{HashStateTrait, HashStateExTrait}; + +// TODO: Move to src +fn compute_template_hash(template: Span) -> felt252 { + let template_len = template.len(); + if template_len == 0 { + return 0; + } + + let mut hasher = PoseidonTrait::new(); + let mut i = 0; + while i < template_len { + hasher = hasher.update_with(*template.at(i)); + i += 1; + }; + hasher.finalize() +} + +#[test] +fn template_full_basic_test() { + let art_peace = IArtPeaceDispatcher { contract_address: deploy_contract() }; + let template_store = ITemplateStoreDispatcher { contract_address: art_peace.contract_address }; + let template_verifier = ITemplateVerifierDispatcher { contract_address: art_peace.contract_address }; + + assert!(template_store.get_templates_count() == 0, "Templates count is not 0"); + + // 2x2 template image + let template_image = array![1, 2, 3, 4]; + let template_hash = compute_template_hash(template_image.span()); + let template_metadata = TemplateMetadata { + hash: template_hash, + position: 0, + width: 2, + height: 2, + reward: 0, + reward_token: contract_address_const::<0>(), + }; + + template_store.add_template(template_metadata); + + assert!(template_store.get_templates_count() == 1, "Templates count is not 1"); + assert!( + template_store.get_template_hash(0) == template_hash, "Template hash is not correct" + ); + assert!( + template_store.is_template_complete(0) == false, "Template is completed before it should be (base)" + ); + + warp_to_next_available_time(art_peace); + let x = 0; + let y = 0; + let pos = x + y * WIDTH; + let color = 1; + art_peace.place_pixel(pos, color); + template_verifier.complete_template(0, template_image.span()); + assert!( + template_store.is_template_complete(0) == false, "Template is completed before it should be (1)" + ); + + warp_to_next_available_time(art_peace); + let x = 1; + let y = 0; + let pos = x + y * WIDTH; + let color = 2; + art_peace.place_pixel(pos, color); + template_verifier.complete_template(0, template_image.span()); + assert!( + template_store.is_template_complete(0) == false, "Template is completed before it should be (2)" + ); + + warp_to_next_available_time(art_peace); + let x = 0; + let y = 1; + let pos = x + y * WIDTH; + let color = 3; + art_peace.place_pixel(pos, color); + template_verifier.complete_template(0, template_image.span()); + assert!( + template_store.is_template_complete(0) == false, "Template is completed before it should be (3)" + ); + + warp_to_next_available_time(art_peace); + let x = 1; + let y = 1; + let pos = x + y * WIDTH; + let color = 4; + art_peace.place_pixel(pos, color); + template_verifier.complete_template(0, template_image.span()); + assert!( + template_store.is_template_complete(0) == true, "Template is not completed after it should be" + ); +} + +// TODO: test invalid template inputs From be42a384e45d20e5f144455ccaf8245f8cf6dc30 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 10 Apr 2024 11:17:29 -0500 Subject: [PATCH 3/4] Scarb fmt --- onchain/src/art_peace.cairo | 57 ++++++++++++++++----------- onchain/src/lib.cairo | 5 ++- onchain/src/templates/component.cairo | 16 +++++--- onchain/src/tests/art_peace.cairo | 30 ++++++++------ 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/onchain/src/art_peace.cairo b/onchain/src/art_peace.cairo index bbb5d5bf..11f79653 100644 --- a/onchain/src/art_peace.cairo +++ b/onchain/src/art_peace.cairo @@ -9,7 +9,8 @@ pub mod ArtPeace { component!(path: TemplateStoreComponent, storage: templates, event: TemplateEvent); #[abi(embed_v0)] - impl TemplateStoreComponentImpl = TemplateStoreComponent::TemplateStoreImpl; + impl TemplateStoreComponentImpl = + TemplateStoreComponent::TemplateStoreImpl; #[storage] struct Storage { @@ -17,30 +18,24 @@ pub mod ArtPeace { canvas_width: u128, canvas_height: u128, total_pixels: u128, - // Map: user's address -> last time they placed a pixel last_placed_time: LegacyMap::, time_between_pixels: u64, // Map: user's address -> amount of extra pixels they have extra_pixels: LegacyMap::, - color_count: u8, // Map: color index -> color value in RGBA color_palette: LegacyMap::, - creation_time: u64, end_time: u64, day_index: u32, - // Map: (day_index, quest_id) -> quest contract address daily_quests: LegacyMap::<(u32, u32), ContractAddress>, main_quests_count: u32, // Map: quest index -> quest contract address main_quests: LegacyMap::, - // Map: (day_index, user's address, color index) -> amount of pixels placed user_pixels_placed: LegacyMap::<(u32, ContractAddress, u8), u32>, - #[substorage(v0)] templates: TemplateStoreComponent::Storage, } @@ -163,7 +158,9 @@ pub mod ArtPeace { let day = self.day_index.read(); self .user_pixels_placed - .write((day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1); + .write( + (day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1 + ); self.emit(PixelPlaced { placed_by: caller, pos, day, color }); } @@ -191,7 +188,11 @@ pub mod ArtPeace { assert!(color < color_palette_count); let pixel = Pixel { color, owner: caller }; self.canvas.write(pos, pixel); - self.user_pixels_placed.write((day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1); + self + .user_pixels_placed + .write( + (day, caller, color), self.user_pixels_placed.read((day, caller, color)) + 1 + ); i += 1; }; self.extra_pixels.write(caller, extra_pixels - pixel_count); @@ -389,29 +390,37 @@ pub mod ArtPeace { assert!(!self.is_template_complete(template_id)); // TODO: ensure template_image matches the template size & hash let template_metadata: TemplateMetadata = self.get_template(template_id); - let non_zero_width: core::zeroable::NonZero:: = template_metadata.width.try_into().unwrap(); - let (template_pos_y, template_pos_x) = DivRem::div_rem(template_metadata.position, non_zero_width); + let non_zero_width: core::zeroable::NonZero:: = template_metadata + .width + .try_into() + .unwrap(); + let (template_pos_y, template_pos_x) = DivRem::div_rem( + template_metadata.position, non_zero_width + ); let canvas_width = self.canvas_width.read(); let (mut x, mut y) = (0, 0); let mut matches = 0; - while y < template_metadata.height { - x = 0; - while x < template_metadata.width { - let pos = template_pos_x + x + (template_pos_y + y) * canvas_width; - let color = *template_image.at((x + y * template_metadata.width).try_into().unwrap()); - if color == self.canvas.read(pos).color { - matches += 1; - } - x += 1; + while y < template_metadata + .height { + x = 0; + while x < template_metadata + .width { + let pos = template_pos_x + x + (template_pos_y + y) * canvas_width; + let color = *template_image + .at((x + y * template_metadata.width).try_into().unwrap()); + if color == self.canvas.read(pos).color { + matches += 1; + } + x += 1; + }; + y += 1; }; - y += 1; - }; // TODO: Allow some threshold? if matches == template_metadata.width * template_metadata.height { self.templates.completed_templates.write(template_id, true); - // TODO: Distribute rewards - // self.emit(Event::TemplateEvent::TemplateCompleted { template_id }); + // TODO: Distribute rewards + // self.emit(Event::TemplateEvent::TemplateCompleted { template_id }); } } } diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index 61a02f1e..a49dc556 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -14,7 +14,10 @@ mod templates { pub mod interface; mod component; - use interface::{TemplateMetadata, ITemplateVerifier, ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait}; + use interface::{ + TemplateMetadata, ITemplateVerifier, ITemplateStoreDispatcher, + ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait + }; } #[cfg(test)] diff --git a/onchain/src/templates/component.cairo b/onchain/src/templates/component.cairo index 3778a64a..f783e886 100644 --- a/onchain/src/templates/component.cairo +++ b/onchain/src/templates/component.cairo @@ -29,16 +29,20 @@ mod TemplateStoreComponent { struct TemplateCompleted { #[key] id: u32, - // TODO: Users rewarded, ... + // TODO: Users rewarded, ... } #[embeddable_as(TemplateStoreImpl)] - impl TemplateStore> of ITemplateStore> { + impl TemplateStore< + TContractState, +HasComponent + > of ITemplateStore> { fn get_templates_count(self: @ComponentState) -> u32 { return self.templates_count.read(); } - fn get_template(self: @ComponentState, template_id: u32) -> TemplateMetadata { + fn get_template( + self: @ComponentState, template_id: u32 + ) -> TemplateMetadata { return self.templates.read(template_id); } @@ -46,9 +50,11 @@ mod TemplateStoreComponent { let metadata: TemplateMetadata = self.templates.read(template_id); return metadata.hash; } - + // TODO: Return idx of the template? - fn add_template(ref self: ComponentState, template_metadata: TemplateMetadata) { + fn add_template( + ref self: ComponentState, template_metadata: TemplateMetadata + ) { let template_id = self.templates_count.read(); self.templates.write(template_id, template_metadata); self.templates_count.write(template_id + 1); diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index e082acc1..d227c9af 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -1,6 +1,9 @@ use art_peace::{IArtPeaceDispatcher, IArtPeaceDispatcherTrait}; use art_peace::ArtPeace::InitParams; -use art_peace::templates::{ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait, TemplateMetadata}; +use art_peace::templates::{ + ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, + ITemplateVerifierDispatcherTrait, TemplateMetadata +}; use snforge_std as snf; use snforge_std::{CheatTarget, ContractClassTrait}; @@ -232,7 +235,9 @@ fn compute_template_hash(template: Span) -> felt252 { fn template_full_basic_test() { let art_peace = IArtPeaceDispatcher { contract_address: deploy_contract() }; let template_store = ITemplateStoreDispatcher { contract_address: art_peace.contract_address }; - let template_verifier = ITemplateVerifierDispatcher { contract_address: art_peace.contract_address }; + let template_verifier = ITemplateVerifierDispatcher { + contract_address: art_peace.contract_address + }; assert!(template_store.get_templates_count() == 0, "Templates count is not 0"); @@ -251,11 +256,10 @@ fn template_full_basic_test() { template_store.add_template(template_metadata); assert!(template_store.get_templates_count() == 1, "Templates count is not 1"); + assert!(template_store.get_template_hash(0) == template_hash, "Template hash is not correct"); assert!( - template_store.get_template_hash(0) == template_hash, "Template hash is not correct" - ); - assert!( - template_store.is_template_complete(0) == false, "Template is completed before it should be (base)" + template_store.is_template_complete(0) == false, + "Template is completed before it should be (base)" ); warp_to_next_available_time(art_peace); @@ -266,7 +270,8 @@ fn template_full_basic_test() { art_peace.place_pixel(pos, color); template_verifier.complete_template(0, template_image.span()); assert!( - template_store.is_template_complete(0) == false, "Template is completed before it should be (1)" + template_store.is_template_complete(0) == false, + "Template is completed before it should be (1)" ); warp_to_next_available_time(art_peace); @@ -277,7 +282,8 @@ fn template_full_basic_test() { art_peace.place_pixel(pos, color); template_verifier.complete_template(0, template_image.span()); assert!( - template_store.is_template_complete(0) == false, "Template is completed before it should be (2)" + template_store.is_template_complete(0) == false, + "Template is completed before it should be (2)" ); warp_to_next_available_time(art_peace); @@ -288,7 +294,8 @@ fn template_full_basic_test() { art_peace.place_pixel(pos, color); template_verifier.complete_template(0, template_image.span()); assert!( - template_store.is_template_complete(0) == false, "Template is completed before it should be (3)" + template_store.is_template_complete(0) == false, + "Template is completed before it should be (3)" ); warp_to_next_available_time(art_peace); @@ -299,8 +306,9 @@ fn template_full_basic_test() { art_peace.place_pixel(pos, color); template_verifier.complete_template(0, template_image.span()); assert!( - template_store.is_template_complete(0) == true, "Template is not completed after it should be" + template_store.is_template_complete(0) == true, + "Template is not completed after it should be" ); } - // TODO: test invalid template inputs + From 1c1a2dc7bde20874de3e4d42de1f1068162edba9 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 10 Apr 2024 11:19:24 -0500 Subject: [PATCH 4/4] Scarb fmt 2 --- onchain/src/tests/art_peace.cairo | 1 + 1 file changed, 1 insertion(+) diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index d227c9af..ce0fa0a9 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -312,3 +312,4 @@ fn template_full_basic_test() { } // TODO: test invalid template inputs +