From 79452ececb3c908398264a6694645dc10195cb2d Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 11 Apr 2024 23:28:37 +0200 Subject: [PATCH 1/3] deposit reward when adding new template --- onchain/Scarb.lock | 6 +++++ onchain/Scarb.toml | 1 + onchain/src/templates/component.cairo | 35 ++++++++++++++++++++++++++- onchain/src/templates/interface.cairo | 9 ++++++- onchain/src/tests/art_peace.cairo | 4 ++- 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/onchain/Scarb.lock b/onchain/Scarb.lock index 576c9446..e6e8e9f8 100644 --- a/onchain/Scarb.lock +++ b/onchain/Scarb.lock @@ -5,9 +5,15 @@ version = 1 name = "art_peace" version = "0.1.0" dependencies = [ + "openzeppelin", "snforge_std", ] +[[package]] +name = "openzeppelin" +version = "0.11.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a83f36b23f1af6e160288962be4a2701c3ecbcda" + [[package]] name = "snforge_std" version = "0.21.0" diff --git a/onchain/Scarb.toml b/onchain/Scarb.toml index 28b58fd0..ad672acb 100644 --- a/onchain/Scarb.toml +++ b/onchain/Scarb.toml @@ -7,6 +7,7 @@ edition = "2023_11" [dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.21.0" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.11.0" } starknet = "2.6.3" [scripts] diff --git a/onchain/src/templates/component.cairo b/onchain/src/templates/component.cairo index e8f658fa..a4491669 100644 --- a/onchain/src/templates/component.cairo +++ b/onchain/src/templates/component.cairo @@ -1,6 +1,11 @@ #[starknet::component] pub mod TemplateStoreComponent { + use core::num::traits::Zero; + use starknet::ContractAddress; + use art_peace::templates::interface::{ITemplateStore, TemplateMetadata}; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + #[storage] struct Storage { @@ -54,11 +59,15 @@ pub mod TemplateStoreComponent { // TODO: Return idx of the template? fn add_template( - ref self: ComponentState, template_metadata: TemplateMetadata + ref self: ComponentState, + template_metadata: TemplateMetadata, + reward_token: ContractAddress, + reward_amount: u256 ) { let template_id = self.templates_count.read(); self.templates.write(template_id, template_metadata); self.templates_count.write(template_id + 1); + self._deposit(starknet::get_caller_address(), reward_token, reward_amount); self.emit(TemplateAdded { id: template_id, metadata: template_metadata }); } @@ -66,4 +75,28 @@ pub mod TemplateStoreComponent { self.completed_templates.read(template_id) } } + + #[generate_trait] + impl InternalImpl< + TContractState, +HasComponent + > of InternalTrait { + fn _deposit( + ref self: ComponentState, + template_proposer: ContractAddress, + reward_token: ContractAddress, + reward_amount: u256 + ) { + let caller_address = starknet::get_caller_address(); + let contract_address = starknet::get_contract_address(); + assert(!template_proposer.is_zero(), 'Invalid caller'); + // Next line is commented for current test not to revert + // assert(!reward_token.is_zero(), 'Invalid token'); + let erc20_dispatcher = IERC20Dispatcher { contract_address: reward_token }; + let allowance = erc20_dispatcher.allowance(caller_address, contract_address); + assert(allowance >= reward_amount, 'Insufficient allowance'); + let success = erc20_dispatcher + .transfer_from(caller_address, contract_address, reward_amount); + assert(success, 'Transfer failed'); + } + } } diff --git a/onchain/src/templates/interface.cairo b/onchain/src/templates/interface.cairo index 44b9d7d1..54e67071 100644 --- a/onchain/src/templates/interface.cairo +++ b/onchain/src/templates/interface.cairo @@ -1,3 +1,5 @@ +use starknet::ContractAddress; + #[derive(Drop, Copy, Serde, starknet::Store)] pub struct TemplateMetadata { pub hash: felt252, @@ -18,7 +20,12 @@ pub trait ITemplateStore { 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); + fn add_template( + ref self: TContractState, + template_metadata: TemplateMetadata, + reward_token: ContractAddress, + reward_amount: u256 + ); // Returns whether the template is complete. fn is_template_complete(self: @TContractState, template_id: u32) -> bool; } diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index 8b21f2b3..2a584cef 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -262,7 +262,9 @@ fn template_full_basic_test() { reward_token: contract_address_const::<0>(), }; - template_store.add_template(template_metadata); + let reward_token: ContractAddress = contract_address_const::<0x0>(); + + template_store.add_template(template_metadata, reward_token, 0); 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"); From 6b187151d441f402615191bc4977ebc50c037bfd Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 11 Apr 2024 23:31:24 +0200 Subject: [PATCH 2/3] fix --- onchain/src/templates/component.cairo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/onchain/src/templates/component.cairo b/onchain/src/templates/component.cairo index a4491669..6fe15236 100644 --- a/onchain/src/templates/component.cairo +++ b/onchain/src/templates/component.cairo @@ -89,8 +89,7 @@ pub mod TemplateStoreComponent { let caller_address = starknet::get_caller_address(); let contract_address = starknet::get_contract_address(); assert(!template_proposer.is_zero(), 'Invalid caller'); - // Next line is commented for current test not to revert - // assert(!reward_token.is_zero(), 'Invalid token'); + assert(!reward_token.is_zero(), 'Invalid token'); let erc20_dispatcher = IERC20Dispatcher { contract_address: reward_token }; let allowance = erc20_dispatcher.allowance(caller_address, contract_address); assert(allowance >= reward_amount, 'Insufficient allowance'); From 96e3a2faa27c4bf7e04f0304bd032d481f688332 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Sun, 14 Apr 2024 13:37:05 +0200 Subject: [PATCH 3/3] add tests for reward deposits --- onchain/src/lib.cairo | 5 ++ onchain/src/mocks.cairo | 1 + onchain/src/mocks/erc20_mock.cairo | 42 +++++++++++++++++ onchain/src/templates/component.cairo | 12 +++-- onchain/src/templates/interface.cairo | 7 +-- onchain/src/tests/art_peace.cairo | 67 +++++++++++++++++++++++---- onchain/src/tests/utils.cairo | 23 +++++++++ 7 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 onchain/src/mocks.cairo create mode 100644 onchain/src/mocks/erc20_mock.cairo create mode 100644 onchain/src/tests/utils.cairo diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index d04c0c45..d944a136 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -20,7 +20,12 @@ mod templates { }; } +mod mocks { + pub mod erc20_mock; +} + #[cfg(test)] mod tests { mod art_peace; + mod utils; } diff --git a/onchain/src/mocks.cairo b/onchain/src/mocks.cairo new file mode 100644 index 00000000..1cdba4b0 --- /dev/null +++ b/onchain/src/mocks.cairo @@ -0,0 +1 @@ +pub mod erc20_mock; diff --git a/onchain/src/mocks/erc20_mock.cairo b/onchain/src/mocks/erc20_mock.cairo new file mode 100644 index 00000000..8bd72de4 --- /dev/null +++ b/onchain/src/mocks/erc20_mock.cairo @@ -0,0 +1,42 @@ +// +// https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/tests/mocks/erc20_mocks.cairo +// + +#[starknet::contract] +pub mod SnakeERC20Mock { + 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 { + #[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, + initial_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.erc20._mint(recipient, initial_supply); + } +} diff --git a/onchain/src/templates/component.cairo b/onchain/src/templates/component.cairo index 6fe15236..e992461b 100644 --- a/onchain/src/templates/component.cairo +++ b/onchain/src/templates/component.cairo @@ -59,15 +59,17 @@ pub mod TemplateStoreComponent { // TODO: Return idx of the template? fn add_template( - ref self: ComponentState, - template_metadata: TemplateMetadata, - reward_token: ContractAddress, - reward_amount: u256 + 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._deposit(starknet::get_caller_address(), reward_token, reward_amount); + self + ._deposit( + starknet::get_caller_address(), + template_metadata.reward_token, + template_metadata.reward + ); self.emit(TemplateAdded { id: template_id, metadata: template_metadata }); } diff --git a/onchain/src/templates/interface.cairo b/onchain/src/templates/interface.cairo index 54e67071..a9403a0a 100644 --- a/onchain/src/templates/interface.cairo +++ b/onchain/src/templates/interface.cairo @@ -20,12 +20,7 @@ pub trait ITemplateStore { 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, - reward_token: ContractAddress, - reward_amount: u256 - ); + 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; } diff --git a/onchain/src/tests/art_peace.cairo b/onchain/src/tests/art_peace.cairo index 2a584cef..c692412b 100644 --- a/onchain/src/tests/art_peace.cairo +++ b/onchain/src/tests/art_peace.cairo @@ -4,13 +4,17 @@ use art_peace::templates::interface::{ ITemplateStoreDispatcher, ITemplateStoreDispatcherTrait, ITemplateVerifierDispatcher, ITemplateVerifierDispatcherTrait, TemplateMetadata }; +use art_peace::mocks::erc20_mock::SnakeERC20Mock; +use art_peace::tests::utils; use core::poseidon::PoseidonTrait; use core::hash::{HashStateTrait, HashStateExTrait}; +use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use snforge_std as snf; use snforge_std::{CheatTarget, ContractClassTrait}; -use starknet::{ContractAddress, contract_address_const}; +use starknet::{ContractAddress, contract_address_const, get_contract_address}; const DAY_IN_SECONDS: u64 = consteval_int!(60 * 60 * 24); const WIDTH: u128 = 100; @@ -21,6 +25,10 @@ fn ART_PEACE_CONTRACT() -> ContractAddress { contract_address_const::<'ArtPeace'>() } +fn ERC20_MOCK_CONTRACT() -> ContractAddress { + contract_address_const::<'erc20mock'>() +} + fn EMPTY_CALLDATA() -> Span { array![].span() } @@ -57,6 +65,24 @@ fn deploy_contract() -> ContractAddress { contract_addr } +fn deploy_erc20_mock() -> ContractAddress { + let contract = snf::declare("SnakeERC20Mock"); + let name: ByteArray = "erc20 mock"; + let symbol: ByteArray = "ERC20MOCK"; + let initial_supply: u256 = 10 * utils::pow_256(10, 18); + let recipient: ContractAddress = get_contract_address(); + + let mut calldata: Array = array![]; + Serde::serialize(@name, ref calldata); + Serde::serialize(@symbol, ref calldata); + Serde::serialize(@initial_supply, ref calldata); + Serde::serialize(@recipient, ref calldata); + + let contract_addr = contract.deploy_at(@calldata, ERC20_MOCK_CONTRACT()).unwrap(); + + contract_addr +} + fn deploy_with_quests_contract( daily_quests: Span, main_quests: Span ) -> ContractAddress { @@ -250,21 +276,16 @@ fn template_full_basic_test() { assert!(template_store.get_templates_count() == 0, "Templates count is not 0"); + let erc20_mock: ContractAddress = deploy_erc20_mock(); + // 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>(), + hash: template_hash, position: 0, width: 2, height: 2, reward: 0, reward_token: erc20_mock, }; - let reward_token: ContractAddress = contract_address_const::<0x0>(); - - template_store.add_template(template_metadata, reward_token, 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"); @@ -349,6 +370,32 @@ fn increase_day_panic_test() { snf::start_warp(CheatTarget::One(art_peace_address), DAY_IN_SECONDS - 1); art_peace.increase_day_index(); } + +#[test] +fn deposit_reward_test() { + let art_peace_address = deploy_contract(); + let art_peace = IArtPeaceDispatcher { contract_address: art_peace_address }; + let template_store = ITemplateStoreDispatcher { contract_address: art_peace.contract_address }; + + let erc20_mock: ContractAddress = deploy_erc20_mock(); + let reward_amount: u256 = 1 * utils::pow_256(10, 18); + + // 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: reward_amount, + reward_token: erc20_mock, + }; + + IERC20Dispatcher { contract_address: erc20_mock }.approve(art_peace_address, reward_amount); + + template_store.add_template(template_metadata); +} // TODO: test invalid template inputs diff --git a/onchain/src/tests/utils.cairo b/onchain/src/tests/utils.cairo new file mode 100644 index 00000000..1d9a66bc --- /dev/null +++ b/onchain/src/tests/utils.cairo @@ -0,0 +1,23 @@ +use core::num::traits::Zero; + +// Math +pub(crate) fn pow_256(self: u256, mut exponent: u8) -> u256 { + if self.is_zero() { + return 0; + } + let mut result = 1; + let mut base = self; + + loop { + if exponent & 1 == 1 { + result = result * base; + } + + exponent = exponent / 2; + if exponent == 0 { + break result; + } + + base = base * base; + } +}