Skip to content

Commit

Permalink
[feat] Deposit reward when adding a new template (#41)
Browse files Browse the repository at this point in the history
* deposit_reward

* fix

* idiomatic imports

* remove underscore for internal fn

* address_comments
  • Loading branch information
TAdev0 authored Apr 15, 2024
1 parent f2b6d11 commit e04b0eb
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 8 deletions.
5 changes: 5 additions & 0 deletions onchain/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ mod nfts {
};
}

mod mocks {
pub mod erc20_mock;
}

#[cfg(test)]
mod tests {
mod art_peace;
pub(crate) mod utils;
}
42 changes: 42 additions & 0 deletions onchain/src/mocks/erc20_mock.cairo
Original file line number Diff line number Diff line change
@@ -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<ContractState>;
#[abi(embed_v0)]
impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
impl InternalImpl = ERC20Component::InternalImpl<ContractState>;

#[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);
}
}
31 changes: 31 additions & 0 deletions onchain/src/templates/component.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#[starknet::component]
pub mod TemplateStoreComponent {
use art_peace::templates::interfaces::{ITemplateStore, TemplateMetadata};
use core::num::traits::Zero;
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use starknet::{ContractAddress, get_caller_address};

#[storage]
struct Storage {
Expand Down Expand Up @@ -59,11 +62,39 @@ pub mod TemplateStoreComponent {
let template_id = self.templates_count.read();
self.templates.write(template_id, template_metadata);
self.templates_count.write(template_id + 1);

if !template_metadata.reward_token.is_zero() && template_metadata.reward != 0 {
self.deposit(template_metadata.reward_token, template_metadata.reward);
}

self.emit(TemplateAdded { id: template_id, metadata: template_metadata });
}

fn is_template_complete(self: @ComponentState<TContractState>, template_id: u32) -> bool {
self.completed_templates.read(template_id)
}
}

#[generate_trait]
impl InternalImpl<
TContractState, +HasComponent<TContractState>
> of InternalTrait<TContractState> {
fn deposit(
ref self: ComponentState<TContractState>,
reward_token: ContractAddress,
reward_amount: u256
) {
let caller_address = get_caller_address();
let contract_address = starknet::get_contract_address();
assert(!get_caller_address().is_zero(), 'Invalid caller');

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');
}
}
}
4 changes: 3 additions & 1 deletion onchain/src/templates/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use starknet::ContractAddress;

#[derive(Drop, Copy, Serde, starknet::Store)]
pub struct TemplateMetadata {
pub hash: felt252,
pub position: u128,
pub width: u128,
pub height: u128,
pub reward: u256,
pub reward_token: starknet::ContractAddress
pub reward_token: ContractAddress
}

#[starknet::interface]
Expand Down
72 changes: 65 additions & 7 deletions onchain/src/tests/art_peace.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use art_peace::{IArtPeaceDispatcher, IArtPeaceDispatcherTrait};
use art_peace::ArtPeace::InitParams;
use art_peace::quests::pixel_quest::PixelQuest::PixelQuestInitParams;
use art_peace::mocks::erc20_mock::SnakeERC20Mock;
use art_peace::tests::utils;
use art_peace::nfts::interfaces::{
IArtPeaceNFTMinterDispatcher, IArtPeaceNFTMinterDispatcherTrait, ICanvasNFTStoreDispatcher,
ICanvasNFTStoreDispatcherTrait, NFTMintParams, NFTMetadata
Expand All @@ -13,10 +15,13 @@ use art_peace::templates::interfaces::{
use core::poseidon::PoseidonTrait;
use core::hash::{HashStateTrait, HashStateExTrait};

use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait};
use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait};

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;
Expand All @@ -27,6 +32,10 @@ fn ART_PEACE_CONTRACT() -> ContractAddress {
contract_address_const::<'ArtPeace'>()
}

fn ERC20_MOCK_CONTRACT() -> ContractAddress {
contract_address_const::<'erc20mock'>()
}

fn EMPTY_CALLDATA() -> Span<felt252> {
array![].span()
}
Expand Down Expand Up @@ -190,6 +199,25 @@ fn deploy_nft_contract() -> ContractAddress {
contract.deploy_at(@calldata, NFT_CONTRACT()).unwrap()
}


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<felt252> = 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 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);
Expand Down Expand Up @@ -368,16 +396,13 @@ 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,
};

template_store.add_template(template_metadata);
Expand Down Expand Up @@ -505,3 +530,36 @@ fn nft_mint_test() {
assert!(nft.balance_of(PLAYER1()) == 0, "NFT balance is not correct after transfer");
assert!(nft.balance_of(PLAYER2()) == 1, "NFT balance is not correct after transfer");
}

#[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);

let art_peace_token_balance = IERC20Dispatcher { contract_address: erc20_mock }
.balance_of(art_peace_address);

assert!(
art_peace_token_balance == reward_amount, "reward wrongly distributed when adding template"
);
}
23 changes: 23 additions & 0 deletions onchain/src/tests/utils.cairo
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit e04b0eb

Please sign in to comment.