From 08dbf1fa0d8d694fcdd7ee2271f6493bd1470822 Mon Sep 17 00:00:00 2001 From: Starknet Dev Date: Tue, 10 Dec 2024 21:19:02 +0000 Subject: [PATCH 1/7] add logic for free games --- contracts/dojo_mainnet.toml | 4 - contracts/src/lib.cairo | 27 +- contracts/src/ls15_components/constants.cairo | 2 +- .../src/ls15_components/interfaces.cairo | 2 + .../src/ls15_components/libs/store.cairo | 2 +- .../src/ls15_components/loot_survivor.cairo | 30 +- .../models/loot_survivor.cairo | 6 +- .../ls15_components/models/tournament.cairo | 7 + .../src/ls15_components/tests/helpers.cairo | 6 +- .../ls15_components/tests/interfaces.cairo | 27 +- .../tests/test_tournament.cairo | 365 +++++------ .../tests/tournament_mock.cairo | 35 +- .../src/ls15_components/tournament.cairo | 577 +++++++++++++----- contracts/src/presets/ls_tournament.cairo | 120 +++- tournament-ui/src/App.tsx | 3 +- .../src/containers/RegisterToken.tsx | 28 +- tournament-ui/src/generated/contracts.gen.ts | 3 +- tournament-ui/src/useSystemCalls.ts | 4 +- 18 files changed, 842 insertions(+), 406 deletions(-) diff --git a/contracts/dojo_mainnet.toml b/contracts/dojo_mainnet.toml index 6242611..e476188 100644 --- a/contracts/dojo_mainnet.toml +++ b/contracts/dojo_mainnet.toml @@ -33,10 +33,6 @@ world_address = "0x02b127646258e21186e6c7e6234f42583d0d19bf88a57776a404c2cefeb42 [init_call_args] "tournament-LSTournament" = [ - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", - "0x018108b32cea514a78ef1b0e4a0753e855cdf620bc0565202c02456f618c4dc4", - "0x2a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b", "1", # safe_mode "1" # test_mode ] diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index c4cdbb6..eb85185 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,29 +1,32 @@ mod ls15_components { pub mod constants; mod interfaces; - // mod loot_survivor; + mod loot_survivor; mod libs { pub mod store; pub mod utils; } pub mod models { - // pub mod loot_survivor; + pub mod loot_survivor; pub mod tournament; } pub mod tournament; mod tests { - // pub mod eth_mock; - // pub mod lords_mock; - // pub mod erc20_mock; - // pub mod erc721_mock; - // #[cfg(test)] - // mod helpers; - // pub mod loot_survivor_mock; - // pub mod pragma_mock; - // pub mod tournament_mock; + pub mod libs { + pub mod store; + } + pub mod eth_mock; + pub mod lords_mock; + pub mod erc20_mock; + pub mod erc721_mock; + #[cfg(test)] + mod helpers; + pub mod loot_survivor_mock; + pub mod pragma_mock; + pub mod tournament_mock; #[cfg(test)] mod test_tournament; - // pub mod interfaces; + pub mod interfaces; // #[cfg(test)] // mod test_tournament_stress_tests; } diff --git a/contracts/src/ls15_components/constants.cairo b/contracts/src/ls15_components/constants.cairo index 410d785..447d941 100644 --- a/contracts/src/ls15_components/constants.cairo +++ b/contracts/src/ls15_components/constants.cairo @@ -11,7 +11,7 @@ pub const MIN_REGISTRATION_PERIOD: u32 = 300; // 5 minutes pub const MAX_REGISTRATION_PERIOD: u32 = 2592000; // 1 month pub const MIN_TOURNAMENT_LENGTH: u32 = 3600; // 1 hour pub const MAX_TOURNAMENT_LENGTH: u32 = 15552000; // 6 months -pub const MIN_SUBMISSION_PERIOD: u32 = 1800; // 30 mins +pub const MIN_SUBMISSION_PERIOD: u32 = 86400; // 1 day pub const MAX_SUBMISSION_PERIOD: u32 = 1209600; // 2 weeks pub const GAME_EXPIRATION_PERIOD: u32 = 864000; // 10 days diff --git a/contracts/src/ls15_components/interfaces.cairo b/contracts/src/ls15_components/interfaces.cairo index f54ee15..96e3afe 100644 --- a/contracts/src/ls15_components/interfaces.cairo +++ b/contracts/src/ls15_components/interfaces.cairo @@ -2,6 +2,7 @@ use starknet::ContractAddress; use adventurer::{adventurer::Adventurer, adventurer_meta::AdventurerMetadata, bag::Bag}; use dojo::world::{WorldStorage, WorldStorageTrait, IWorldDispatcher}; use tournament::presets::ls_tournament::{ILSTournamentDispatcher}; +use tournament::ls15_components::models::tournament::FreeGameTokenType; use tournament::ls15_components::libs::utils::ZERO; @@ -34,6 +35,7 @@ pub trait ILootSurvivor { fn get_adventurer_meta(self: @TState, adventurer_id: felt252) -> AdventurerMetadata; fn get_bag(self: @TState, adventurer_id: felt252) -> Bag; fn get_cost_to_play(self: @TState) -> u128; + fn free_game_available(self: @TState, token_type: FreeGameTokenType, token_id: u128) -> bool; fn new_game( ref self: TState, client_reward_address: ContractAddress, diff --git a/contracts/src/ls15_components/libs/store.cairo b/contracts/src/ls15_components/libs/store.cairo index 41e88b1..674592e 100644 --- a/contracts/src/ls15_components/libs/store.cairo +++ b/contracts/src/ls15_components/libs/store.cairo @@ -96,7 +96,7 @@ pub impl StoreImpl of StoreTrait { } // Setters // - + // Tournament #[inline(always)] diff --git a/contracts/src/ls15_components/loot_survivor.cairo b/contracts/src/ls15_components/loot_survivor.cairo index bd382eb..d08206e 100644 --- a/contracts/src/ls15_components/loot_survivor.cairo +++ b/contracts/src/ls15_components/loot_survivor.cairo @@ -1,5 +1,8 @@ use starknet::ContractAddress; -use tournament::ls15_components::models::loot_survivor::{Adventurer, AdventurerMetadata, Bag}; +use tournament::ls15_components::models::loot_survivor::{ + Adventurer, AdventurerMetadataStorage, Bag +}; +use adventurer::{adventurer_meta::AdventurerMetadata}; #[starknet::interface] trait ILootSurvivor { @@ -20,7 +23,7 @@ trait ILootSurvivor { ) -> felt252; fn set_adventurer(ref self: TState, adventurer_id: felt252, adventurer: Adventurer); fn set_adventurer_meta( - ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadata + ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadataStorage ); fn set_bag(ref self: TState, adventurer_id: felt252, bag: Bag); } @@ -37,12 +40,14 @@ pub mod loot_survivor_component { use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; use dojo::contract::components::world_provider::{IWorldProvider}; + use adventurer::{adventurer_meta::AdventurerMetadata}; + use tournament::ls15_components::models::loot_survivor::{ - Adventurer, AdventurerMetadata, Bag, Stats, Equipment, Item, AdventurerModel, + Adventurer, AdventurerMetadataStorage, Bag, Stats, Equipment, Item, AdventurerModel, AdventurerMetaModel, BagModel, GameCountModel, Contracts }; use tournament::ls15_components::interfaces::{WorldTrait, WorldImpl}; - use tournament::ls15_components::libs::store::{Store, StoreTrait}; + use tournament::ls15_components::tests::libs::store::{Store, StoreTrait}; use tournament::ls15_components::libs::utils::{pow}; use openzeppelin_introspection::src5::SRC5Component; @@ -94,7 +99,18 @@ pub mod loot_survivor_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - store.get_adventurer_meta_model(adventurer_id).adventurer_meta + let adventurer_meta = store.get_adventurer_meta_model(adventurer_id).adventurer_meta; + let formatted_adventurer_meta = AdventurerMetadata { + birth_date: adventurer_meta.birth_date, + death_date: adventurer_meta.death_date, + level_seed: adventurer_meta.level_seed, + item_specials_seed: adventurer_meta.item_specials_seed, + rank_at_death: adventurer_meta.rank_at_death, + delay_stat_reveal: adventurer_meta.delay_stat_reveal, + golden_token_id: adventurer_meta.golden_token_id, + launch_tournament_winner_token_id: 0, + }; + formatted_adventurer_meta } fn get_bag(self: @ComponentState, adventurer_id: felt252) -> Bag { @@ -187,7 +203,7 @@ pub mod loot_survivor_component { @AdventurerModel { adventurer_id: adventurer_id.into(), adventurer } ); - let adventurer_meta = AdventurerMetadata { + let adventurer_meta = AdventurerMetadataStorage { birth_date: get_block_timestamp().into(), death_date: 0, level_seed: 0, @@ -222,7 +238,7 @@ pub mod loot_survivor_component { fn set_adventurer_meta( ref self: ComponentState, adventurer_id: felt252, - adventurer_meta: AdventurerMetadata + adventurer_meta: AdventurerMetadataStorage ) { let mut world = WorldTrait::storage( self.get_contract().world_dispatcher(), @"tournament" diff --git a/contracts/src/ls15_components/models/loot_survivor.cairo b/contracts/src/ls15_components/models/loot_survivor.cairo index 48570dc..7157c4e 100644 --- a/contracts/src/ls15_components/models/loot_survivor.cairo +++ b/contracts/src/ls15_components/models/loot_survivor.cairo @@ -41,7 +41,7 @@ pub struct Adventurer { } #[derive(Drop, Copy, Serde, Introspect)] -pub struct AdventurerMetadata { +pub struct AdventurerMetadataStorage { pub birth_date: u64, // 64 bits in storage pub death_date: u64, // 64 bits in storage pub level_seed: u64, // 64 bits in storage @@ -49,7 +49,7 @@ pub struct AdventurerMetadata { pub rank_at_death: u8, // 2 bits in storage pub delay_stat_reveal: bool, // 1 bit in storage pub golden_token_id: u8, // 8 bits in storage - // launch_tournament_winner_token_id: u128, // 32 bits in storage + // pub launch_tournament_winner_token_id: u128, // 32 bits in storage } @@ -97,7 +97,7 @@ pub struct AdventurerModel { pub struct AdventurerMetaModel { #[key] pub adventurer_id: felt252, - pub adventurer_meta: AdventurerMetadata, + pub adventurer_meta: AdventurerMetadataStorage, } #[dojo::model] diff --git a/contracts/src/ls15_components/models/tournament.cairo b/contracts/src/ls15_components/models/tournament.cairo index 43953a7..471dbb4 100644 --- a/contracts/src/ls15_components/models/tournament.cairo +++ b/contracts/src/ls15_components/models/tournament.cairo @@ -67,6 +67,11 @@ pub enum EntryStatus { Submitted, } +#[derive(Copy, Drop, PartialEq, Serde)] +pub enum FreeGameTokenType { + GoldenToken, + LaunchTournamentChampion, +} /// /// Model @@ -203,6 +208,8 @@ pub struct TournamentConfig { pub lords: ContractAddress, pub loot_survivor: ContractAddress, pub oracle: ContractAddress, + pub golden_token: ContractAddress, + pub blobert: ContractAddress, pub safe_mode: bool, pub test_mode: bool } diff --git a/contracts/src/ls15_components/tests/helpers.cairo b/contracts/src/ls15_components/tests/helpers.cairo index 4d2e48d..35e48da 100644 --- a/contracts/src/ls15_components/tests/helpers.cairo +++ b/contracts/src/ls15_components/tests/helpers.cairo @@ -13,7 +13,7 @@ use tournament::ls15_components::tests::interfaces::{ ITournamentMockDispatcher, ITournamentMockDispatcherTrait }; use adventurer::{adventurer::Adventurer, equipment::Equipment, item::Item, stats::Stats}; -use tournament::ls15_components::models::loot_survivor::AdventurerMetadata; +use tournament::ls15_components::models::loot_survivor::AdventurerMetadataStorage; use tournament::ls15_components::models::tournament::{ERC20Data, ERC721Data, Token, TokenDataType}; // @@ -70,8 +70,8 @@ pub fn create_dead_adventurer_with_xp(xp: u16) -> Adventurer { } } -pub fn create_adventurer_metadata_with_death_date(death_date: u64) -> AdventurerMetadata { - AdventurerMetadata { +pub fn create_adventurer_metadata_with_death_date(death_date: u64) -> AdventurerMetadataStorage { + AdventurerMetadataStorage { birth_date: get_block_timestamp().into(), death_date: death_date, level_seed: 0, diff --git a/contracts/src/ls15_components/tests/interfaces.cairo b/contracts/src/ls15_components/tests/interfaces.cairo index 5881e9c..1e7f5f1 100644 --- a/contracts/src/ls15_components/tests/interfaces.cairo +++ b/contracts/src/ls15_components/tests/interfaces.cairo @@ -1,14 +1,18 @@ use adventurer::{ - adventurer::{Adventurer, ImplAdventurer}, adventurer_meta::{ImplAdventurerMetadata}, bag::Bag + adventurer::{Adventurer, ImplAdventurer}, + adventurer_meta::{AdventurerMetadata, ImplAdventurerMetadata}, bag::Bag }; -use tournament::ls15_components::models::loot_survivor::AdventurerMetadata; +use tournament::ls15_components::models::loot_survivor::AdventurerMetadataStorage; use tournament::ls15_components::models::tournament::{ - TournamentModel, Token, Premium, TokenDataType, GatedType, GatedSubmissionType + TournamentModel, Token, Premium, TokenDataType, GatedType, GatedSubmissionType, + FreeGameTokenType }; use tournament::ls15_components::interfaces::{DataType, PragmaPricesResponse}; use starknet::ContractAddress; -use dojo::world::IWorldDispatcher; +use dojo::world::{WorldStorage, WorldStorageTrait, IWorldDispatcher}; + +use tournament::ls15_components::libs::utils::ZERO; #[starknet::interface] pub trait IERC20Mock { @@ -115,7 +119,11 @@ pub trait ITournamentMock { ref self: TState, tournament_id: u64, gated_submission_type: Option ); fn start_tournament( - ref self: TState, tournament_id: u64, start_all: bool, start_count: Option + ref self: TState, + tournament_id: u64, + start_all: bool, + start_count: Option, + client_reward_address: ContractAddress ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( @@ -133,8 +141,12 @@ pub trait ITournamentMock { lords_address: ContractAddress, loot_survivor_address: ContractAddress, oracle_address: ContractAddress, + golden_token: ContractAddress, + blobert: ContractAddress, safe_mode: bool, - test_mode: bool + test_mode: bool, + test_erc20: ContractAddress, + test_erc721: ContractAddress, ); } @@ -188,6 +200,7 @@ pub trait ILootSurvivorMock { fn get_adventurer_meta(self: @TState, adventurer_id: felt252) -> AdventurerMetadata; fn get_bag(self: @TState, adventurer_id: felt252) -> Bag; fn get_cost_to_play(self: @TState) -> u128; + fn free_game_available(self: @TState, token_type: FreeGameTokenType, token_id: u128) -> bool; fn new_game( ref self: TState, client_reward_address: ContractAddress, @@ -201,7 +214,7 @@ pub trait ILootSurvivorMock { ) -> felt252; fn set_adventurer(ref self: TState, adventurer_id: felt252, adventurer: Adventurer); fn set_adventurer_meta( - ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadata + ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadataStorage ); fn set_bag(ref self: TState, adventurer_id: felt252, bag: Bag); diff --git a/contracts/src/ls15_components/tests/test_tournament.cairo b/contracts/src/ls15_components/tests/test_tournament.cairo index 219b674..bd5965f 100644 --- a/contracts/src/ls15_components/tests/test_tournament.cairo +++ b/contracts/src/ls15_components/tests/test_tournament.cairo @@ -1,6 +1,6 @@ use core::option::Option; use starknet::{ContractAddress, get_block_timestamp, testing}; -use dojo::world::WorldStorage; +use dojo::world::{WorldStorage}; use dojo_cairo_test::{ spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef, WorldStorageTestTrait @@ -11,7 +11,7 @@ use tournament::ls15_components::constants::{ MIN_TOURNAMENT_LENGTH, MAX_TOURNAMENT_LENGTH }; -use tournament::ls15_components::interfaces::{WorldTrait}; +use tournament::ls15_components::tests::interfaces::WorldTrait; use tournament::ls15_components::models::{ loot_survivor::{ @@ -22,8 +22,8 @@ use tournament::ls15_components::models::{ m_TournamentEntriesAddressModel, m_TournamentStartsAddressModel, m_TournamentStartIdsModel, m_TournamentEntriesModel, m_TournamentScoresModel, m_TournamentTotalsModel, m_TournamentPrizeKeysModel, m_PrizesModel, m_TokenModel, m_TournamentConfig, ERC20Data, - ERC721Data, Token, Premium, GatedToken, EntryCriteria, TokenDataType, GatedType, - GatedEntryType, GatedSubmissionType + ERC721Data, Premium, GatedToken, EntryCriteria, TokenDataType, GatedType, GatedEntryType, + GatedSubmissionType } }; @@ -31,12 +31,12 @@ use tournament::tests::{ utils, constants::{ OWNER, TOURNAMENT_NAME, TOURNAMENT_DESCRIPTION, STARTING_BALANCE, TEST_START_TIME, - TEST_END_TIME + TEST_END_TIME, ZERO }, }; use tournament::ls15_components::tests::helpers::{ approve_game_costs, create_basic_tournament, create_adventurer_metadata_with_death_date, - create_dead_adventurer_with_xp, register_tokens_for_test + create_dead_adventurer_with_xp }; use tournament::ls15_components::tests::{ erc20_mock::{erc20_mock}, interfaces::{IERC20MockDispatcher, IERC20MockDispatcherTrait}, @@ -195,7 +195,57 @@ pub fn setup() -> ( loot_survivor.contract_address, pragma.contract_address, false, - false + false, + erc20.contract_address, + erc721.contract_address, + ); + loot_survivor + .initializer(eth.contract_address, lords.contract_address, pragma.contract_address); + + // mint tokens + utils::impersonate(OWNER()); + eth.mint(OWNER(), STARTING_BALANCE); + lords.mint(OWNER(), STARTING_BALANCE); + erc20.mint(OWNER(), STARTING_BALANCE); + erc721.mint(OWNER(), 1); + + // drop all events + utils::drop_all_events(world.dispatcher.contract_address); + utils::drop_all_events(tournament.contract_address); + utils::drop_all_events(loot_survivor.contract_address); + + (world, tournament, loot_survivor, pragma, eth, lords, erc20, erc721) +} + +pub fn setup_safe_mode() -> ( + WorldStorage, + ITournamentMockDispatcher, + ILootSurvivorMockDispatcher, + IPragmaMockDispatcher, + IERC20MockDispatcher, + IERC20MockDispatcher, + IERC20MockDispatcher, + IERC721MockDispatcher, +) { + let (mut world, mut eth, mut lords) = setup_uninitialized(); + + let tournament = world.tournament_mock_dispatcher(); + let loot_survivor = world.loot_survivor_mock_dispatcher(); + let pragma = world.pragma_mock_dispatcher(); + let erc20 = world.erc20_mock_dispatcher(); + let erc721 = world.erc721_mock_dispatcher(); + + // initialize contracts + tournament + .initializer( + eth.contract_address, + lords.contract_address, + loot_survivor.contract_address, + pragma.contract_address, + true, + false, + erc20.contract_address, + erc721.contract_address, ); loot_survivor .initializer(eth.contract_address, lords.contract_address, pragma.contract_address); @@ -378,7 +428,6 @@ fn test_create_tournament_with_prizes() { utils::impersonate(OWNER()); let tournament_id = create_basic_tournament(tournament); - register_tokens_for_test(tournament, erc20, erc721); erc20.approve(tournament.contract_address, STARTING_BALANCE); erc721.approve(tournament.contract_address, 1); @@ -400,31 +449,31 @@ fn test_create_tournament_with_prizes() { assert(erc721.balance_of(OWNER()) == 0, 'Invalid balance'); } -#[test] -#[should_panic(expected: ('prize token not registered', 'ENTRYPOINT_FAILED'))] -fn test_create_tournament_with_prizes_token_not_registered() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); - - utils::impersonate(OWNER()); - create_basic_tournament(tournament); - erc20.approve(tournament.contract_address, 1); - erc721.approve(tournament.contract_address, 1); - - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament - .add_prize( - 1, - erc20.contract_address, - TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), - 1 - ); - tournament - .add_prize( - 1, erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 - ); -} +// #[test] +// #[should_panic(expected: ('prize token not registered', 'ENTRYPOINT_FAILED'))] +// fn test_create_tournament_with_prizes_token_not_registered() { +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// setup(); + +// utils::impersonate(OWNER()); +// create_basic_tournament(tournament); +// erc20.approve(tournament.contract_address, 1); +// erc721.approve(tournament.contract_address, 1); + +// erc20.approve(tournament.contract_address, STARTING_BALANCE); +// erc721.approve(tournament.contract_address, 1); +// tournament +// .add_prize( +// 1, +// erc20.contract_address, +// TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), +// 1 +// ); +// tournament +// .add_prize( +// 1, erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 +// ); +// } #[test] #[should_panic(expected: ('prize position too large', 'ENTRYPOINT_FAILED'))] @@ -434,7 +483,6 @@ fn test_create_tournament_with_prizes_position_too_large() { utils::impersonate(OWNER()); let tournament_id = create_basic_tournament(tournament); - register_tokens_for_test(tournament, erc20, erc721); erc20.approve(tournament.contract_address, STARTING_BALANCE); erc721.approve(tournament.contract_address, 1); @@ -457,11 +505,10 @@ fn test_create_tournament_with_prizes_position_too_large() { #[test] #[should_panic(expected: ('premium distributions too long', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_with_premiums_too_long() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = + let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, _erc721) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); let entry_premium = Premium { token: erc20.contract_address, @@ -486,11 +533,10 @@ fn test_create_tournament_with_premiums_too_long() { #[test] #[should_panic(expected: ('premium distributions not 100%', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_with_premiums_not_100() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = + let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, _erc721) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); let entry_premium = Premium { token: erc20.contract_address, @@ -541,7 +587,7 @@ fn test_create_gated_tournament_with_unsettled_tournament() { // Start first tournament approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None); + tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); // Try to create a second tournament gated by the first (unsettled) tournament let gated_type = GatedType::tournament(array![first_tournament_id].span()); @@ -599,7 +645,7 @@ fn test_create_tournament_gated_by_multiple_tournaments() { tournament.enter_tournament(first_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None); + tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -611,7 +657,7 @@ fn test_create_tournament_gated_by_multiple_tournaments() { tournament.enter_tournament(second_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(second_tournament_id, false, Option::None); + tournament.start_tournament(second_tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(20); @@ -696,11 +742,11 @@ fn test_create_tournament_gated_accounts() { utils::impersonate(OWNER()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(allowed_player); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // Verify entries were successful let entries = tournament.tournament_entries(tournament_id); @@ -711,65 +757,65 @@ fn test_create_tournament_gated_accounts() { // Test registering tokens // -#[test] -fn test_register_token() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); - - utils::impersonate(OWNER()); - erc20.approve(tournament.contract_address, 1); - erc721.approve(tournament.contract_address, 1); - let tokens = array![ - Token { - token: erc20.contract_address, - token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) - }, - Token { - token: erc721.contract_address, - token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) - }, - ]; - - tournament.register_tokens(tokens); - assert(erc20.balance_of(OWNER()) == 1000000000000000000000, 'Invalid balance'); - assert(erc721.balance_of(OWNER()) == 1, 'Invalid balance'); - assert(tournament.is_token_registered(erc20.contract_address), 'Invalid registration'); - assert(tournament.is_token_registered(erc721.contract_address), 'Invalid registration'); -} - -#[test] -#[should_panic(expected: ('token already registered', 'ENTRYPOINT_FAILED'))] -fn test_register_token_already_registered() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); - - utils::impersonate(OWNER()); - erc20.approve(tournament.contract_address, 1); - erc721.approve(tournament.contract_address, 1); - let tokens = array![ - Token { - token: erc20.contract_address, - token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) - }, - Token { - token: erc721.contract_address, - token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) - }, - ]; - - tournament.register_tokens(tokens); - let tokens = array![ - Token { - token: erc20.contract_address, - token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) - }, - Token { - token: erc721.contract_address, - token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) - }, - ]; - tournament.register_tokens(tokens); -} +// #[test] +// fn test_register_token() { +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// setup(); + +// utils::impersonate(OWNER()); +// erc20.approve(tournament.contract_address, 1); +// erc721.approve(tournament.contract_address, 1); +// let tokens = array![ +// Token { +// token: erc20.contract_address, +// token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) +// }, +// Token { +// token: erc721.contract_address, +// token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) +// }, +// ]; + +// tournament.register_tokens(tokens); +// assert(erc20.balance_of(OWNER()) == 1000000000000000000000, 'Invalid balance'); +// assert(erc721.balance_of(OWNER()) == 1, 'Invalid balance'); +// assert(tournament.is_token_registered(erc20.contract_address), 'Invalid registration'); +// assert(tournament.is_token_registered(erc721.contract_address), 'Invalid registration'); +// } + +// #[test] +// #[should_panic(expected: ('token already registered', 'ENTRYPOINT_FAILED'))] +// fn test_register_token_already_registered() { +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// setup(); + +// utils::impersonate(OWNER()); +// erc20.approve(tournament.contract_address, 1); +// erc721.approve(tournament.contract_address, 1); +// let tokens = array![ +// Token { +// token: erc20.contract_address, +// token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) +// }, +// Token { +// token: erc721.contract_address, +// token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) +// }, +// ]; + +// tournament.register_tokens(tokens); +// let tokens = array![ +// Token { +// token: erc20.contract_address, +// token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }) +// }, +// Token { +// token: erc721.contract_address, +// token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }) +// }, +// ]; +// tournament.register_tokens(tokens); +// } // // Test entering tournaments @@ -823,7 +869,7 @@ fn test_enter_tournament_wrong_submission_type() { tournament.enter_tournament(first_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None); + tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -876,7 +922,7 @@ fn test_start_tournament() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // check tournament entries assert(tournament.tournament_entries(tournament_id) == 1, 'Invalid entries'); @@ -914,8 +960,8 @@ fn test_start_tournament_entry_already_started() { approve_game_costs(eth, lords, tournament, 2); - tournament.start_tournament(tournament_id, false, Option::None); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); } // @@ -937,7 +983,7 @@ fn test_submit_scores() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -979,7 +1025,7 @@ fn test_submit_multiple_scores() { approve_game_costs(eth, lords, tournament, 4); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1030,7 +1076,7 @@ fn test_submit_scores_tiebreaker() { approve_game_costs(eth, lords, tournament, 2); - tournament.start_tournament(tournament_id, true, Option::None); + tournament.start_tournament(tournament_id, true, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1081,7 +1127,7 @@ fn test_submit_scores_after_submission_period() { // Start tournament approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -1124,7 +1170,7 @@ fn test_submit_scores_before_tournament_ends() { // Start tournament approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -1177,15 +1223,15 @@ fn test_submit_scores_replace_lower_score() { utils::impersonate(OWNER()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player2); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player3); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1238,7 +1284,7 @@ fn test_distribute_prizes_with_prizes() { utils::impersonate(OWNER()); let tournament_id = create_basic_tournament(tournament); - register_tokens_for_test(tournament, erc20, erc721); + // register_tokens_for_test(tournament, erc20, erc721); erc20.approve(tournament.contract_address, STARTING_BALANCE); erc721.approve(tournament.contract_address, 1); @@ -1263,7 +1309,7 @@ fn test_distribute_prizes_with_prizes() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1299,7 +1345,7 @@ fn test_distribute_prizes_prize_already_claimed() { utils::impersonate(OWNER()); let tournament_id = create_basic_tournament(tournament); - register_tokens_for_test(tournament, erc20, erc721); + // register_tokens_for_test(tournament, erc20, erc721); erc20.approve(tournament.contract_address, STARTING_BALANCE); erc721.approve(tournament.contract_address, 1); @@ -1324,7 +1370,7 @@ fn test_distribute_prizes_prize_already_claimed() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1342,19 +1388,12 @@ fn test_distribute_prizes_prize_already_claimed() { #[test] fn test_distribute_prizes_with_gated_tokens_criteria() { let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, + _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, mut erc721, ) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); + // register_tokens_for_test(tournament, erc20, erc721); let gated_type = GatedType::token( GatedToken { @@ -1389,7 +1428,7 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { approve_game_costs(eth, lords, tournament, 2); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // check tournament entries assert(tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); @@ -1417,19 +1456,12 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { #[test] fn test_distribute_prizes_with_gated_tokens_uniform() { let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, + _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, mut erc721, ) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); + // register_tokens_for_test(tournament, erc20, erc721); let gated_type = GatedType::token( GatedToken { token: erc721.contract_address, entry_type: GatedEntryType::uniform(3), } @@ -1459,7 +1491,7 @@ fn test_distribute_prizes_with_gated_tokens_uniform() { approve_game_costs(eth, lords, tournament, 3); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // check tournament entries assert(tournament.tournament_entries(tournament_id) == 3, 'Invalid entries'); @@ -1512,7 +1544,7 @@ fn test_distribute_prizes_with_gated_tournaments() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1554,7 +1586,7 @@ fn test_distribute_prizes_with_gated_tournaments() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp( current_time + 1 + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into() @@ -1571,19 +1603,12 @@ fn test_distribute_prizes_with_gated_tournaments() { #[test] fn test_distribute_prizes_with_premiums() { let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, + _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, ) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); + // register_tokens_for_test(tournament, erc20, erc721); let entry_premium = Premium { token: erc20.contract_address, @@ -1625,7 +1650,7 @@ fn test_distribute_prizes_with_premiums() { approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1645,19 +1670,11 @@ fn test_distribute_prizes_with_premiums() { #[test] fn test_distribute_prizes_with_premium_creator_fee() { let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, + _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, ) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); // Create premium with 10% creator fee and 90% to winner let entry_premium = Premium { @@ -1697,7 +1714,7 @@ fn test_distribute_prizes_with_premium_creator_fee() { utils::impersonate(OWNER()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // Verify creator fee distribution (10% of 200 total = 20) assert(erc20.balance_of(OWNER()) == creator_initial_balance + 20, 'Invalid creator fee'); @@ -1706,7 +1723,7 @@ fn test_distribute_prizes_with_premium_creator_fee() { eth.mint(player2, STARTING_BALANCE); lords.mint(player2, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1735,19 +1752,11 @@ fn test_distribute_prizes_with_premium_creator_fee() { #[test] fn test_distribute_prizes_with_premium_multiple_winners() { let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, + _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, ) = setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); // Create premium with 10% creator fee and split remaining 90% between top 3: // 1st: 50%, 2nd: 30%, 3rd: 20% @@ -1806,25 +1815,25 @@ fn test_distribute_prizes_with_premium_multiple_winners() { // Start games for all players utils::impersonate(OWNER()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player2); eth.mint(player2, STARTING_BALANCE); lords.mint(player2, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player3); eth.mint(player3, STARTING_BALANCE); lords.mint(player3, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player4); eth.mint(player4, STARTING_BALANCE); lords.mint(player4, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1873,7 +1882,6 @@ fn test_tournament_with_no_submissions() { setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); // Create tournament with prizes and premium let entry_premium = Premium { @@ -1940,19 +1948,19 @@ fn test_tournament_with_no_submissions() { utils::impersonate(OWNER()); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player2); eth.mint(player2, STARTING_BALANCE); lords.mint(player2, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); utils::impersonate(player3); eth.mint(player3, STARTING_BALANCE); lords.mint(player3, STARTING_BALANCE); approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None); + tournament.start_tournament(tournament_id, false, Option::None, ZERO()); // Move to after tournament and submission period without any score submissions testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -1985,7 +1993,6 @@ fn test_tournament_with_no_starts() { setup(); utils::impersonate(OWNER()); - register_tokens_for_test(tournament, erc20, erc721); // Create tournament with prizes and premium let entry_premium = Premium { diff --git a/contracts/src/ls15_components/tests/tournament_mock.cairo b/contracts/src/ls15_components/tests/tournament_mock.cairo index f2d85c8..e569477 100644 --- a/contracts/src/ls15_components/tests/tournament_mock.cairo +++ b/contracts/src/ls15_components/tests/tournament_mock.cairo @@ -1,7 +1,7 @@ use starknet::ContractAddress; use dojo::world::IWorldDispatcher; use tournament::ls15_components::models::tournament::{ - TournamentModel, Token, Premium, TokenDataType, GatedType, GatedSubmissionType + TournamentModel, Premium, TokenDataType, GatedType, GatedSubmissionType }; #[starknet::interface] @@ -15,6 +15,8 @@ pub trait ITournamentMock { fn tournament_prize_keys(self: @TState, tournament_id: u64) -> Array; fn top_scores(self: @TState, tournament_id: u64) -> Array; fn is_token_registered(self: @TState, token: ContractAddress) -> bool; + // TODO: add for V2 (only ERC721 tokens) + // fn register_tokens(ref self: TState, tokens: Array); fn create_tournament( ref self: TState, name: felt252, @@ -26,12 +28,17 @@ pub trait ITournamentMock { gated_type: Option, entry_premium: Option, ) -> u64; - fn register_tokens(ref self: TState, tokens: Array); fn enter_tournament( ref self: TState, tournament_id: u64, gated_submission_type: Option ); fn start_tournament( - ref self: TState, tournament_id: u64, start_all: bool, start_count: Option + ref self: TState, + tournament_id: u64, + start_all: bool, + start_count: Option, + client_reward_address: ContractAddress, + golden_token_free_game_ids: Option>, + blobert_free_game_ids: Option>, ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( @@ -49,8 +56,12 @@ pub trait ITournamentMock { lords_address: ContractAddress, loot_survivor_address: ContractAddress, oracle_address: ContractAddress, + golden_token: ContractAddress, + blobert: ContractAddress, safe_mode: bool, - test_mode: bool + test_mode: bool, + test_erc20: ContractAddress, + test_erc721: ContractAddress, ); } @@ -62,8 +73,12 @@ trait ITournamentMockInit { lords_address: ContractAddress, loot_survivor_address: ContractAddress, oracle_address: ContractAddress, + golden_token: ContractAddress, + blobert: ContractAddress, safe_mode: bool, - test_mode: bool + test_mode: bool, + test_erc20: ContractAddress, + test_erc721: ContractAddress, ); } @@ -99,8 +114,12 @@ pub mod tournament_mock { lords_address: ContractAddress, loot_survivor_address: ContractAddress, oracle_address: ContractAddress, + golden_token: ContractAddress, + blobert: ContractAddress, safe_mode: bool, - test_mode: bool + test_mode: bool, + test_erc20: ContractAddress, + test_erc721: ContractAddress, ) { self .tournament @@ -109,9 +128,13 @@ pub mod tournament_mock { lords_address, loot_survivor_address, oracle_address, + golden_token, + blobert, safe_mode, test_mode ); + self.tournament.initialize_erc20(test_erc20, "Test ERC20", "TERC20"); + self.tournament.initialize_erc721(test_erc721, "Test ERC721", "TERC721"); } } } diff --git a/contracts/src/ls15_components/tournament.cairo b/contracts/src/ls15_components/tournament.cairo index ce80474..1761226 100644 --- a/contracts/src/ls15_components/tournament.cairo +++ b/contracts/src/ls15_components/tournament.cairo @@ -1,6 +1,6 @@ use starknet::ContractAddress; use tournament::ls15_components::models::tournament::{ - TournamentModel, GatedType, Premium, Token, GatedSubmissionType, TokenDataType + TournamentModel, GatedType, Premium, GatedSubmissionType, TokenDataType }; /// @@ -15,7 +15,8 @@ trait ITournament { fn tournament_prize_keys(self: @TState, tournament_id: u64) -> Array; fn top_scores(self: @TState, tournament_id: u64) -> Array; fn is_token_registered(self: @TState, token: ContractAddress) -> bool; - fn register_tokens(ref self: TState, tokens: Array); + // TODO: add for V2 (only ERC721 tokens) + // fn register_tokens(ref self: TState, tokens: Array); fn create_tournament( ref self: TState, name: felt252, @@ -31,7 +32,13 @@ trait ITournament { ref self: TState, tournament_id: u64, gated_submission_type: Option ); fn start_tournament( - ref self: TState, tournament_id: u64, start_all: bool, start_count: Option + ref self: TState, + tournament_id: u64, + start_all: bool, + start_count: Option, + client_reward_address: ContractAddress, + golden_token_free_game_ids: Option>, + blobert_free_game_ids: Option>, ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( @@ -55,11 +62,10 @@ pub mod tournament_component { use core::num::traits::Zero; use tournament::ls15_components::constants::{ - VRF_COST_PER_GAME, TWO_POW_128, MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, - MIN_TOURNAMENT_LENGTH, MAX_TOURNAMENT_LENGTH, MIN_SUBMISSION_PERIOD, MAX_SUBMISSION_PERIOD, + VRF_COST_PER_GAME, MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, MIN_TOURNAMENT_LENGTH, + MAX_TOURNAMENT_LENGTH, MIN_SUBMISSION_PERIOD, MAX_SUBMISSION_PERIOD, TEST_MIN_REGISTRATION_PERIOD, TEST_MIN_SUBMISSION_PERIOD, TEST_MIN_TOURNAMENT_LENGTH, - GAME_EXPIRATION_PERIOD, ETHEREUM_ADDRESS, LORDS_ADDRESS, SURVIVORS_ADDRESS, ETH_SAFE_AMOUNT, - LORDS_SAFE_AMOUNT + GAME_EXPIRATION_PERIOD, ETHEREUM_ADDRESS, ETH_SAFE_AMOUNT, LORDS_SAFE_AMOUNT }; use tournament::ls15_components::interfaces::{ ILootSurvivorDispatcher, ILootSurvivorDispatcherTrait, IPragmaABIDispatcher, @@ -71,7 +77,7 @@ pub mod tournament_component { TournamentStartIdsModel, TournamentScoresModel, TournamentTotalsModel, TournamentPrizeKeysModel, PrizesModel, TokenModel, TournamentConfig, TokenDataType, EntryStatus, GatedType, GatedSubmissionType, GatedEntryType, GatedToken, Premium, ERC20Data, - Token + ERC721Data, FreeGameTokenType }; use tournament::ls15_components::interfaces::{WorldTrait, WorldImpl,}; use tournament::ls15_components::libs::store::{Store, StoreTrait}; @@ -85,14 +91,8 @@ pub mod tournament_component { contract_address_const }; - use openzeppelin_token::erc20::interface::{ - IERC20Dispatcher, IERC20DispatcherTrait, IERC20MetadataDispatcher, - IERC20MetadataDispatcherTrait - }; - use openzeppelin_token::erc721::interface::{ - IERC721Dispatcher, IERC721DispatcherTrait, IERC721MetadataDispatcher, - IERC721MetadataDispatcherTrait - }; + use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use openzeppelin_token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait}; use adventurer::{adventurer::Adventurer}; @@ -119,6 +119,7 @@ pub mod tournament_component { pub const PREMIUM_DISTRIBUTIONS_NOT_100: felt252 = 'premium distributions not 100%'; pub const SUBMISSION_PERIOD_TOO_SHORT: felt252 = 'submission period too short'; pub const SUBMISSION_PERIOD_TOO_LONG: felt252 = 'submission period too long'; + pub const NOT_TOKEN_OWNER: felt252 = 'not token owner'; // // Register Tokens // @@ -148,6 +149,7 @@ pub mod tournament_component { pub const ADDRESS_ENTRIES_STARTED: felt252 = 'address entries started'; pub const START_COUNT_TOO_LARGE: felt252 = 'start count too large'; pub const TOURNAMENT_PERIOD_TOO_LONG: felt252 = 'period too long to start all'; + pub const FREE_GAME_NOT_AVAILABLE: felt252 = 'free game not available'; // // Submit Scores // @@ -232,13 +234,14 @@ pub mod tournament_component { self._is_token_registered(ref store, token) } - fn register_tokens(ref self: ComponentState, tokens: Array) { - let mut world = WorldTrait::storage( - self.get_contract().world_dispatcher(), @"tournament" - ); - let mut store: Store = StoreTrait::new(world); - self._register_tokens(ref store, tokens); - } + // TODO: add for V2 (use Ekubo tokens) + // fn register_tokens(ref self: ComponentState, tokens: Array) { + // let mut world = WorldTrait::storage( + // self.get_contract().world_dispatcher(), @"tournament" + // ); + // let mut store: Store = StoreTrait::new(world); + // self._register_tokens(ref store, tokens); + // } fn create_tournament( ref self: ComponentState, @@ -346,6 +349,9 @@ pub mod tournament_component { tournament_id: u64, start_all: bool, start_count: Option, + client_reward_address: ContractAddress, + golden_token_free_game_ids: Option>, + blobert_free_game_ids: Option>, ) { let mut world = WorldTrait::storage( self.get_contract().world_dispatcher(), @"tournament" @@ -358,12 +364,79 @@ pub mod tournament_component { self._assert_tournament_period_within_max(ref store, tournament_id); } - let total_entries = store.get_total_entries(tournament_id); + let tournament_config = store.get_tournament_config(get_contract_address()); - // handle formatiing of premium config into prize keys - if (!total_entries.premiums_formatted) { - self._format_premium_config_into_prize_keys(ref store, tournament_id); - } + let mut ls_dispatcher = ILootSurvivorDispatcher { + contract_address: tournament_config.loot_survivor + }; + + let mut free_games = 0; + + match golden_token_free_game_ids { + Option::Some(token_ids) => { + let mut game_index = 0; + loop { + if game_index == token_ids.len() { + break; + } + let golden_token_id = *token_ids.at(game_index); + self + ._assert_token_owner( + tournament_config.golden_token, + golden_token_id, + get_caller_address() + ); + // check if golden token free game is available + let free_game_available = ls_dispatcher + .free_game_available( + FreeGameTokenType::GoldenToken, golden_token_id.low + ); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan golden tokens + IERC721Dispatcher { contract_address: tournament_config.golden_token } + .transfer_from( + get_caller_address(), + get_contract_address(), + *token_ids.at(game_index) + ); + free_games += 1; + game_index += 1; + } + }, + Option::None => {}, + }; + + match blobert_free_game_ids { + Option::Some(token_ids) => { + let mut game_index = 0; + loop { + if game_index == token_ids.len() { + break; + } + let blobert_token_id = *token_ids.at(game_index); + self + ._assert_token_owner( + tournament_config.blobert, blobert_token_id, get_caller_address() + ); + // check if caller has blobert + let free_game_available = ls_dispatcher + .free_game_available( + FreeGameTokenType::LaunchTournamentChampion, blobert_token_id.low + ); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan bloberts + IERC721Dispatcher { contract_address: tournament_config.blobert } + .transfer_from( + get_caller_address(), + get_contract_address(), + *token_ids.at(game_index) + ); + free_games += 1; + game_index += 1; + } + }, + Option::None => {}, + }; let mut entries = 0; @@ -394,10 +467,6 @@ pub mod tournament_component { } // define contract interfaces - let tournament_config = store.get_tournament_config(get_contract_address()); - let mut ls_dispatcher = ILootSurvivorDispatcher { - contract_address: tournament_config.loot_survivor - }; let lords_dispatcher: IERC20Dispatcher = IERC20Dispatcher { contract_address: tournament_config.lords }; @@ -407,14 +476,11 @@ pub mod tournament_component { // get current game cost let cost_to_play = ls_dispatcher.get_cost_to_play(); + let entries_cost = (entries.into() - free_games.into()) * cost_to_play.into(); // transfer base game cost lords_dispatcher - .transfer_from( - get_caller_address(), - get_contract_address(), - entries.into() * cost_to_play.into() - ); + .transfer_from(get_caller_address(), get_contract_address(), entries_cost); // transfer VRF cost let vrf_cost = self @@ -423,8 +489,7 @@ pub mod tournament_component { .transfer_from(get_caller_address(), get_contract_address(), vrf_cost.into()); // set the approvals according to entries - lords_dispatcher - .approve(tournament_config.loot_survivor, entries.into() * cost_to_play.into()); + lords_dispatcher.approve(tournament_config.loot_survivor, entries_cost); eth_dispatcher.approve(tournament_config.loot_survivor, vrf_cost.into()); let tournament = store.get_tournament(tournament_id); @@ -447,17 +512,123 @@ pub mod tournament_component { if entry_index == address_entries.entry_count { break; } - let game_id = ls_dispatcher - .new_game( - get_contract_address(), - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); + let mut game_id = 0; + match golden_token_free_game_ids { + Option::Some(mut token_ids) => { + let popped_token_id = token_ids.pop_front(); + match popped_token_id { + Option::Some(token_id) => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + token_id.low.try_into().unwrap(), + true, + contract_address_const::<0>(), + 0, + address + ); + }, + Option::None => { + match blobert_free_game_ids { + Option::Some(mut token_ids) => { + let popped_token_id = token_ids.pop_front(); + match popped_token_id { + Option::Some(token_id) => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + token_id.low, + address + ); + }, + Option::None => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); + }, + } + }, + Option::None => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); + }, + } + }, + } + }, + Option::None => { + match blobert_free_game_ids { + Option::Some(mut token_ids) => { + let popped_token_id = token_ids.pop_front(); + match popped_token_id { + Option::Some(token_id) => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + token_id.low, + address + ); + }, + Option::None => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); + }, + } + }, + Option::None => { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); + }, + } + }, + } game_ids.append(game_id.try_into().unwrap()); let game = TournamentGameModel { tournament_id, @@ -490,7 +661,7 @@ pub mod tournament_component { } let game_id = ls_dispatcher .new_game( - get_contract_address(), + client_reward_address, 12, // wand tournament.name, 0, @@ -538,6 +709,13 @@ pub mod tournament_component { // assert submission period is not over self._assert_tournament_not_settled(ref tournament); + let total_entries = store.get_total_entries(tournament_id); + + // handle formatiing of premium config into prize keys + if (!total_entries.premiums_formatted) { + self._format_premium_config_into_prize_keys(ref store, tournament_id); + } + let tournament_config = store.get_tournament_config(get_contract_address()); let mut ls_dispatcher = ILootSurvivorDispatcher { contract_address: tournament_config.loot_survivor @@ -644,14 +822,17 @@ pub mod tournament_component { +Drop > of InternalTrait { // - // INITIALIZE + // INITIALIZE COMPONENT // + fn initialize( self: @ComponentState, eth: ContractAddress, lords: ContractAddress, loot_survivor: ContractAddress, oracle: ContractAddress, + golden_token: ContractAddress, + blobert: ContractAddress, safe_mode: bool, test_mode: bool ) { @@ -659,6 +840,7 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); + // Store the config store .set_tournament_config( @TournamentConfig { @@ -667,10 +849,83 @@ pub mod tournament_component { lords, loot_survivor, oracle, + golden_token, + blobert, safe_mode, test_mode } ); + store + .set_token( + @TokenModel { + token: eth, + name: "Ether", + symbol: "ETH", + token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }), + is_registered: true + } + ); + store + .set_token( + @TokenModel { + token: eth, + name: "Ether", + symbol: "ETH", + token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }), + is_registered: true + } + ); + } + + // + // INITIALIZE TOKENS + // + + fn initialize_erc20( + self: @ComponentState, + token: ContractAddress, + name: ByteArray, + symbol: ByteArray, + ) { + let mut world = WorldTrait::storage( + self.get_contract().world_dispatcher(), @"tournament" + ); + let mut store: Store = StoreTrait::new(world); + assert(!self._is_token_registered(ref store, token), Errors::TOKEN_ALREADY_REGISTERED); + store + .set_token( + @TokenModel { + token: token, + name: name, + symbol: symbol, + token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }), + is_registered: true + } + ); + } + + + fn initialize_erc721( + self: @ComponentState, + token: ContractAddress, + name: ByteArray, + symbol: ByteArray + ) { + let mut world = WorldTrait::storage( + self.get_contract().world_dispatcher(), @"tournament" + ); + let mut store: Store = StoreTrait::new(world); + assert(!self._is_token_registered(ref store, token), Errors::TOKEN_ALREADY_REGISTERED); + store + .set_token( + @TokenModel { + token: token, + name: name, + symbol: symbol, + token_data_type: TokenDataType::erc721(ERC721Data { token_id: 1 }), + is_registered: true + } + ); } // @@ -1030,6 +1285,16 @@ pub mod tournament_component { assert(!claimed, Errors::PRIZE_ALREADY_CLAIMED); } + fn _assert_token_owner( + self: @ComponentState, + token: ContractAddress, + token_id: u256, + account: ContractAddress + ) { + let owner = self._get_owner(token, token_id); + assert(owner == account, Errors::NOT_TOKEN_OWNER); + } + fn _assert_gated_token_owner( self: @ComponentState, token: ContractAddress, @@ -1301,105 +1566,113 @@ pub mod tournament_component { store.set_tournament_totals(@tournament_totals); new_tournament_id } - fn _register_tokens( - ref self: ComponentState, ref store: Store, tokens: Array - ) { - let num_tokens = tokens.len(); - let mut token_index = 0; - let safe_mode = store.get_tournament_config(get_contract_address()).safe_mode; - loop { - if token_index == num_tokens { - break; - } - let token = *tokens.at(token_index); - - assert( - !self._is_token_registered(ref store, token.token), - Errors::TOKEN_ALREADY_REGISTERED - ); - - if (safe_mode) { - assert( - token.token == ETHEREUM_ADDRESS() - || token.token == LORDS_ADDRESS() - || token.token == SURVIVORS_ADDRESS(), - Errors::INVALID_TOKEN_FOR_SAFE_MODE - ); - } - - let mut name = ""; - let mut symbol = ""; - match token.token_data_type.into() { - TokenDataType::erc20(_) => { - let token_dispatcher = IERC20Dispatcher { contract_address: token.token }; - let token_dispatcher_metadata = IERC20MetadataDispatcher { - contract_address: token.token - }; - name = token_dispatcher_metadata.name(); - symbol = token_dispatcher_metadata.symbol(); - // check that the contract is approved for the minimal amount - let allowance = token_dispatcher - .allowance(get_caller_address(), get_contract_address()); - assert(allowance == 1, Errors::INVALID_TOKEN_ALLOWANCES); - // take a reading of the current balance (incase contract has assets - // already) - let current_balance = token_dispatcher.balance_of(get_contract_address()); - // trnsfer a minimal amount to the contract - token_dispatcher - .transfer_from(get_caller_address(), get_contract_address(), 1); - // take a reading of the new balance - let new_balance = token_dispatcher.balance_of(get_contract_address()); - assert(new_balance == current_balance + 1, Errors::INVALID_TOKEN_BALANCES); - // transfer back the minimal amount - token_dispatcher.transfer(get_caller_address(), 1); - // check the total supply is legitimate - let total_supply = token_dispatcher.total_supply(); - assert(total_supply < TWO_POW_128.into(), Errors::TOKEN_SUPPLY_TOO_LARGE); - }, - TokenDataType::erc721(token_data_type) => { - let token_dispatcher = IERC721Dispatcher { contract_address: token.token }; - let token_dispatcher_metadata = IERC721MetadataDispatcher { - contract_address: token.token - }; - name = token_dispatcher_metadata.name(); - symbol = token_dispatcher_metadata.symbol(); - // check that the contract is approved for the specific id - let approved = token_dispatcher - .get_approved(token_data_type.token_id.into()); - assert(approved == get_contract_address(), Errors::INVALID_TOKEN_APPROVALS); - // transfer a specific id to the contract - token_dispatcher - .transfer_from( - get_caller_address(), - get_contract_address(), - token_data_type.token_id.into() - ); - // check the balance of the contract - let balance = token_dispatcher.balance_of(get_contract_address()); - assert(balance == 1, Errors::INVALID_TOKEN_BALANCES); - let owner = token_dispatcher.owner_of(token_data_type.token_id.into()); - assert(owner == get_contract_address(), Errors::INVALID_TOKEN_OWNER); - // transfer back the token - token_dispatcher - .transfer_from( - get_contract_address(), - get_caller_address(), - token_data_type.token_id.into() - ); - }, - } - let token_model = TokenModel { - token: token.token, - name, - symbol, - token_data_type: token.token_data_type, - is_registered: true - }; - store.set_token(@token_model); - token_index += 1; - } - } + // TODO: add for V2 (only ERC721 tokens) + // fn _register_tokens( + // ref self: ComponentState, ref store: Store, tokens: Array + // ) { + // let num_tokens = tokens.len(); + // let mut token_index = 0; + // let safe_mode = store.get_tournament_config(get_contract_address()).safe_mode; + // loop { + // if token_index == num_tokens { + // break; + // } + // let token = *tokens.at(token_index); + + // assert( + // !self._is_token_registered(ref store, token.token), + // Errors::TOKEN_ALREADY_REGISTERED + // ); + + // if (safe_mode) { + // assert( + // token.token == ETHEREUM_ADDRESS() + // || token.token == LORDS_ADDRESS() + // || token.token == SURVIVORS_ADDRESS(), + // Errors::INVALID_TOKEN_FOR_SAFE_MODE + // ); + // } + + // let mut name = ""; + // let mut symbol = ""; + + // match token.token_data_type.into() { + // TokenDataType::erc20(_) => { + // let token_dispatcher = IERC20Dispatcher { contract_address: token.token + // }; + // let token_dispatcher_metadata = IERC20MetadataDispatcher { + // contract_address: token.token + // }; + // name = token_dispatcher_metadata.name(); + // symbol = token_dispatcher_metadata.symbol(); + // // check that the contract is approved for the minimal amount + // let allowance = token_dispatcher + // .allowance(get_caller_address(), get_contract_address()); + // assert(allowance == 1, Errors::INVALID_TOKEN_ALLOWANCES); + // // take a reading of the current balance (incase contract has assets + // // already) + // let current_balance = + // token_dispatcher.balance_of(get_contract_address()); + // // trnsfer a minimal amount to the contract + // token_dispatcher + // .transfer_from(get_caller_address(), get_contract_address(), 1); + // // take a reading of the new balance + // let new_balance = token_dispatcher.balance_of(get_contract_address()); + // assert(new_balance == current_balance + 1, + // Errors::INVALID_TOKEN_BALANCES); + // // transfer back the minimal amount + // token_dispatcher.transfer(get_caller_address(), 1); + // // check the total supply is legitimate + // let total_supply = token_dispatcher.total_supply(); + // assert(total_supply < TWO_POW_128.into(), + // Errors::TOKEN_SUPPLY_TOO_LARGE); + // }, + // TokenDataType::erc721(token_data_type) => { + // let token_dispatcher = IERC721Dispatcher { contract_address: token.token + // }; + // let token_dispatcher_metadata = IERC721MetadataDispatcher { + // contract_address: token.token + // }; + // name = token_dispatcher_metadata.name(); + // symbol = token_dispatcher_metadata.symbol(); + // // check that the contract is approved for the specific id + // let approved = token_dispatcher + // .get_approved(token_data_type.token_id.into()); + // assert(approved == get_contract_address(), + // Errors::INVALID_TOKEN_APPROVALS); + // // transfer a specific id to the contract + // token_dispatcher + // .transfer_from( + // get_caller_address(), + // get_contract_address(), + // token_data_type.token_id.into() + // ); + // // check the balance of the contract + // let balance = token_dispatcher.balance_of(get_contract_address()); + // assert(balance == 1, Errors::INVALID_TOKEN_BALANCES); + // let owner = token_dispatcher.owner_of(token_data_type.token_id.into()); + // assert(owner == get_contract_address(), Errors::INVALID_TOKEN_OWNER); + // // transfer back the token + // token_dispatcher + // .transfer_from( + // get_contract_address(), + // get_caller_address(), + // token_data_type.token_id.into() + // ); + // }, + // } + // let token_model = TokenModel { + // token: token.token, + // name, + // symbol, + // token_data_type: token.token_data_type, + // is_registered: true + // }; + // store.set_token(@token_model); + // token_index += 1; + // } + // } fn _format_premium_config_into_prize_keys( ref self: ComponentState, ref store: Store, tournament_id: u64 @@ -1576,6 +1849,10 @@ pub mod tournament_component { }, TokenDataType::erc721(token_data) => { let token_dispatcher = IERC721Dispatcher { contract_address: token }; + self + ._assert_token_owner( + token, token_data.token_id.into(), get_caller_address() + ); token_dispatcher .transfer_from( get_caller_address(), get_contract_address(), token_data.token_id.into() diff --git a/contracts/src/presets/ls_tournament.cairo b/contracts/src/presets/ls_tournament.cairo index ca616eb..06e69b6 100644 --- a/contracts/src/presets/ls_tournament.cairo +++ b/contracts/src/presets/ls_tournament.cairo @@ -1,7 +1,7 @@ use starknet::ContractAddress; use dojo::world::IWorldDispatcher; use tournament::ls15_components::models::tournament::{ - TournamentModel, Token, Premium, TokenDataType, GatedType, GatedSubmissionType + TournamentModel, Premium, TokenDataType, GatedType, GatedSubmissionType }; #[starknet::interface] @@ -15,6 +15,8 @@ pub trait ILSTournament { fn tournament_prize_keys(self: @TState, tournament_id: u64) -> Array; fn top_scores(self: @TState, tournament_id: u64) -> Array; fn is_token_registered(self: @TState, token: ContractAddress) -> bool; + // TODO: add for V2 (only ERC721 tokens) + // fn register_tokens(ref self: TState, tokens: Array); fn create_tournament( ref self: TState, name: felt252, @@ -26,7 +28,6 @@ pub trait ILSTournament { gated_type: Option, entry_premium: Option, ) -> u64; - fn register_tokens(ref self: TState, tokens: Array); fn enter_tournament( ref self: TState, tournament_id: u64, gated_submission_type: Option ); @@ -46,7 +47,7 @@ pub trait ILSTournament { #[dojo::contract] pub mod LSTournament { - use starknet::ContractAddress; + use starknet::{contract_address_const}; use tournament::ls15_components::tournament::tournament_component; component!(path: tournament_component, storage: tournament, event: TournamentEvent); @@ -68,24 +69,111 @@ pub mod LSTournament { TournamentEvent: tournament_component::Event, } - fn dojo_init( - ref self: ContractState, - eth_address: ContractAddress, - lords_address: ContractAddress, - loot_survivor_address: ContractAddress, - oracle_address: ContractAddress, - safe_mode: bool, - test_mode: bool - ) { + fn dojo_init(ref self: ContractState, safe_mode: bool, test_mode: bool,) { self .tournament .initialize( - eth_address, - lords_address, - loot_survivor_address, - oracle_address, + contract_address_const::< + 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 + >(), // eth + contract_address_const::< + 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49 + >(), // lords + contract_address_const::< + 0x018108b32cea514a78ef1b0e4a0753e855cdf620bc0565202c02456f618c4dc4 + >(), // loot survivor + contract_address_const::< + 0x2a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b + >(), // oracle + contract_address_const::< + 0x04f5e296c805126637552cf3930e857f380e7c078e8f00696de4fc8545356b1d + >(), // golden token + contract_address_const::< + 0x00539f522b29ae9251dbf7443c7a950cf260372e69efab3710a11bf17a9599f1 + >(), // blobert safe_mode, test_mode ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 + >(), + "Ether", + "ETH" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49 + >(), + "Lords", + "LORDS" + ); + self + .tournament + .initialize_erc721( + contract_address_const::< + 0x018108b32cea514a78ef1b0e4a0753e855cdf620bc0565202c02456f618c4dc4 + >(), + "Loot Survivor", + "LSVR" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d + >(), + "Starknet Token", + "STRK" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 + >(), + "USD Coin", + "USDC" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x4878d1148318a31829523ee9c6a5ee563af6cd87f90a30809e5b0d27db8a9b + >(), + "Standard Weighted Adalian Yield", + "SWAY" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x410466536b5ae074f7fea81e5533b8134a9fa08b3dd077dd9db08f64997d113 + >(), + "Paper", + "PAPER" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x75afe6402ad5a5c20dd25e10ec3b3986acaa647b77e4ae24b0cbc9a54a27a87 + >(), + "Ekubo Protocol", + "EKUBO" + ); + self + .tournament + .initialize_erc20( + contract_address_const::< + 0x3b405a98c9e795d427fe82cdeeeed803f221b52471e3a757574a2b4180793ee + >(), + "STARKNET BROTHER", + "BROTHER" + ); } } diff --git a/tournament-ui/src/App.tsx b/tournament-ui/src/App.tsx index 465667e..3fa8f9b 100644 --- a/tournament-ui/src/App.tsx +++ b/tournament-ui/src/App.tsx @@ -34,7 +34,8 @@ function App() { const { getERC20BalanceGeneral } = useSystemCalls(); const [tokenBalance, setTokenBalance] = useState>({}); - const isMainnet = selectedChainConfig.chainId === "SN_MAINNET"; + // const isMainnet = selectedChainConfig.chainId === "SN_MAINNET"; + const isMainnet = false; // Getters useGetTournamentCountsQuery(tournament); diff --git a/tournament-ui/src/containers/RegisterToken.tsx b/tournament-ui/src/containers/RegisterToken.tsx index 9739b18..fbe7fdd 100644 --- a/tournament-ui/src/containers/RegisterToken.tsx +++ b/tournament-ui/src/containers/RegisterToken.tsx @@ -38,10 +38,8 @@ const RegisterToken = () => { mintErc20, mintErc721, getERC20Balance, - approveErc20, - approveErc721, - approveEth, - approveLords, + approveERC20General, + approveERC721General, mintEth, mintLords, getEthBalance, @@ -119,13 +117,13 @@ const RegisterToken = () => { const handleRegisterToken = async () => { if (tokenType !== null) { if (tokenType === TokenDataEnum.erc20) { - if (tokenAddress === padAddress(eth)) { - await approveEth(tournament, 1n, 0n); - } else if (tokenAddress === padAddress(lords)) { - await approveLords(tournament, 1n, 0n); - } else { - await approveErc20(tournament, 1n, 0n); - } + const tokenDataType = new CairoCustomEnum({ + erc20: { + token_amount: 1, + }, + erc721: undefined, + }) as TokenDataTypeEnum; + await approveERC20General({ token: tokenAddress, tokenDataType }); await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 second await registerTokens([ { @@ -139,7 +137,13 @@ const RegisterToken = () => { }, ]); } else { - await approveErc721(tournament, BigInt(tokenId), 0n); + const tokenDataType = new CairoCustomEnum({ + erc20: undefined, + erc721: { + token_id: tokenId, + }, + }) as TokenDataTypeEnum; + await approveERC721General({ token: tokenAddress, tokenDataType }); await registerTokens([ { token: tokenAddress, diff --git a/tournament-ui/src/generated/contracts.gen.ts b/tournament-ui/src/generated/contracts.gen.ts index 3bb2de3..d063c06 100644 --- a/tournament-ui/src/generated/contracts.gen.ts +++ b/tournament-ui/src/generated/contracts.gen.ts @@ -1390,13 +1390,14 @@ export async function setupWorld(provider: DojoProvider) { snAccount: Account | AccountInterface, tokens: Array ) => { + console.log(tokens); try { return await provider.execute( snAccount, { contractName: "LSTournament", entrypoint: "register_tokens", - calldata: [tokens], + calldata: CallData.compile([tokens]), }, "tournament" ); diff --git a/tournament-ui/src/useSystemCalls.ts b/tournament-ui/src/useSystemCalls.ts index 8faa041..316efba 100644 --- a/tournament-ui/src/useSystemCalls.ts +++ b/tournament-ui/src/useSystemCalls.ts @@ -49,9 +49,7 @@ export const useSystemCalls = () => { // Tournament const registerTokens = async (tokens: Token[]) => { - const entityId = getEntityIdFromKeys([ - BigInt(tournament_mock.contractAddress), - ]); + const entityId = getEntityIdFromKeys([BigInt(tournament)]); const transactionId = uuidv4(); From 292af1645d249901d15280ddc63048daa77529d9 Mon Sep 17 00:00:00 2001 From: Starknet Dev Date: Wed, 11 Dec 2024 18:50:45 +0000 Subject: [PATCH 2/7] add golden token and blobert support with test coverage --- .../src/ls15_components/loot_survivor.cairo | 45 +- .../models/loot_survivor.cairo | 11 + .../ls15_components/models/tournament.cairo | 2 +- .../src/ls15_components/tests/helpers.cairo | 10 + .../ls15_components/tests/interfaces.cairo | 5 +- .../ls15_components/tests/libs/store.cairo | 16 +- .../tests/test_tournament.cairo | 1298 ++++++++++------- .../tests/tournament_mock.cairo | 4 +- .../src/ls15_components/tournament.cairo | 427 +++--- 9 files changed, 1093 insertions(+), 725 deletions(-) diff --git a/contracts/src/ls15_components/loot_survivor.cairo b/contracts/src/ls15_components/loot_survivor.cairo index d08206e..27d5580 100644 --- a/contracts/src/ls15_components/loot_survivor.cairo +++ b/contracts/src/ls15_components/loot_survivor.cairo @@ -2,6 +2,7 @@ use starknet::ContractAddress; use tournament::ls15_components::models::loot_survivor::{ Adventurer, AdventurerMetadataStorage, Bag }; +use tournament::ls15_components::models::tournament::FreeGameTokenType; use adventurer::{adventurer_meta::AdventurerMetadata}; #[starknet::interface] @@ -10,6 +11,9 @@ trait ILootSurvivor { fn get_adventurer_meta(self: @TState, adventurer_id: felt252) -> AdventurerMetadata; fn get_bag(self: @TState, adventurer_id: felt252) -> Bag; fn get_cost_to_play(self: @TState) -> u128; + fn free_game_available( + self: @TState, free_game_type: FreeGameTokenType, token_id: u128 + ) -> bool; fn new_game( ref self: TState, client_reward_address: ContractAddress, @@ -26,6 +30,7 @@ trait ILootSurvivor { ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadataStorage ); fn set_bag(ref self: TState, adventurer_id: felt252, bag: Bag); + fn set_free_game_available(self: @TState, free_game_type: FreeGameTokenType, token_id: u128); } /// @@ -44,8 +49,9 @@ pub mod loot_survivor_component { use tournament::ls15_components::models::loot_survivor::{ Adventurer, AdventurerMetadataStorage, Bag, Stats, Equipment, Item, AdventurerModel, - AdventurerMetaModel, BagModel, GameCountModel, Contracts + AdventurerMetaModel, BagModel, GameCountModel, FreeGameAvailableModel, Contracts }; + use tournament::ls15_components::models::tournament::FreeGameTokenType; use tournament::ls15_components::interfaces::{WorldTrait, WorldImpl}; use tournament::ls15_components::tests::libs::store::{Store, StoreTrait}; use tournament::ls15_components::libs::utils::{pow}; @@ -125,6 +131,16 @@ pub mod loot_survivor_component { 50000000000000000000 } + fn free_game_available( + self: @ComponentState, free_game_type: FreeGameTokenType, token_id: u128 + ) -> bool { + let mut world = WorldTrait::storage( + self.get_contract().world_dispatcher(), @"tournament" + ); + let mut store: Store = StoreTrait::new(world); + store.get_free_game_available_model(free_game_type, token_id).available + } + fn new_game( ref self: ComponentState, client_reward_address: ContractAddress, @@ -143,11 +159,15 @@ pub mod loot_survivor_component { let contracts = store.get_contracts_model(get_contract_address()); let cost_to_play = self.get_cost_to_play(); // transfer base game cost - let lords_dispatcher: IERC20Dispatcher = IERC20Dispatcher { - contract_address: contracts.lords - }; - lords_dispatcher - .transfer_from(get_caller_address(), get_contract_address(), cost_to_play.into()); + if (golden_token_id.is_zero() && launch_tournament_winner_token_id.is_zero()) { + let lords_dispatcher: IERC20Dispatcher = IERC20Dispatcher { + contract_address: contracts.lords + }; + lords_dispatcher + .transfer_from( + get_caller_address(), get_contract_address(), cost_to_play.into() + ); + } // transfer VRF cost let eth_dispatcher: IERC20Dispatcher = IERC20Dispatcher { @@ -255,6 +275,19 @@ pub mod loot_survivor_component { let mut store: Store = StoreTrait::new(world); store.set_bag_model(@BagModel { adventurer_id, bag }); } + + fn set_free_game_available( + self: @ComponentState, free_game_type: FreeGameTokenType, token_id: u128 + ) { + let mut world = WorldTrait::storage( + self.get_contract().world_dispatcher(), @"tournament" + ); + let mut store: Store = StoreTrait::new(world); + store + .set_free_game_available_model( + @FreeGameAvailableModel { free_game_type, token_id, available: true } + ); + } } #[generate_trait] diff --git a/contracts/src/ls15_components/models/loot_survivor.cairo b/contracts/src/ls15_components/models/loot_survivor.cairo index 7157c4e..c5508b8 100644 --- a/contracts/src/ls15_components/models/loot_survivor.cairo +++ b/contracts/src/ls15_components/models/loot_survivor.cairo @@ -1,4 +1,5 @@ use starknet::ContractAddress; +use tournament::ls15_components::models::tournament::FreeGameTokenType; // dojo compatible structs @@ -116,6 +117,16 @@ pub struct GameCountModel { pub game_count: u128, } +#[dojo::model] +#[derive(Copy, Drop, Serde)] +pub struct FreeGameAvailableModel { + #[key] + pub free_game_type: FreeGameTokenType, + #[key] + pub token_id: u128, + pub available: bool, +} + #[dojo::model] #[derive(Copy, Drop, Serde)] pub struct Contracts { diff --git a/contracts/src/ls15_components/models/tournament.cairo b/contracts/src/ls15_components/models/tournament.cairo index 471dbb4..ff545cb 100644 --- a/contracts/src/ls15_components/models/tournament.cairo +++ b/contracts/src/ls15_components/models/tournament.cairo @@ -67,7 +67,7 @@ pub enum EntryStatus { Submitted, } -#[derive(Copy, Drop, PartialEq, Serde)] +#[derive(Copy, Drop, PartialEq, Introspect, Serde)] pub enum FreeGameTokenType { GoldenToken, LaunchTournamentChampion, diff --git a/contracts/src/ls15_components/tests/helpers.cairo b/contracts/src/ls15_components/tests/helpers.cairo index 35e48da..eaa3b90 100644 --- a/contracts/src/ls15_components/tests/helpers.cairo +++ b/contracts/src/ls15_components/tests/helpers.cairo @@ -44,6 +44,16 @@ pub fn approve_game_costs( eth.approve(tournament.contract_address, entries * 200000000000000); } +pub fn approve_free_game_cost( + eth: IERC20MockDispatcher, + golden_token: IERC721MockDispatcher, + token_id: u256, + tournament: ITournamentMockDispatcher +) { + eth.approve(tournament.contract_address, 200000000000000); + golden_token.approve(tournament.contract_address, token_id); +} + pub fn create_dead_adventurer_with_xp(xp: u16) -> Adventurer { Adventurer { health: 0, diff --git a/contracts/src/ls15_components/tests/interfaces.cairo b/contracts/src/ls15_components/tests/interfaces.cairo index 1e7f5f1..0c9b80d 100644 --- a/contracts/src/ls15_components/tests/interfaces.cairo +++ b/contracts/src/ls15_components/tests/interfaces.cairo @@ -123,7 +123,9 @@ pub trait ITournamentMock { tournament_id: u64, start_all: bool, start_count: Option, - client_reward_address: ContractAddress + client_reward_address: ContractAddress, + golden_token_free_game_ids: Span, + blobert_free_game_ids: Span, ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( @@ -217,6 +219,7 @@ pub trait ILootSurvivorMock { ref self: TState, adventurer_id: felt252, adventurer_meta: AdventurerMetadataStorage ); fn set_bag(ref self: TState, adventurer_id: felt252, bag: Bag); + fn set_free_game_available(ref self: TState, free_game_type: FreeGameTokenType, token_id: u128); fn initializer( ref self: TState, diff --git a/contracts/src/ls15_components/tests/libs/store.cairo b/contracts/src/ls15_components/tests/libs/store.cairo index bd53864..18394d5 100644 --- a/contracts/src/ls15_components/tests/libs/store.cairo +++ b/contracts/src/ls15_components/tests/libs/store.cairo @@ -3,13 +3,14 @@ use dojo::world::{WorldStorage}; use dojo::model::{ModelStorage}; use tournament::ls15_components::models::loot_survivor::{ - AdventurerModel, AdventurerMetaModel, BagModel, GameCountModel, Contracts + AdventurerModel, AdventurerMetaModel, BagModel, GameCountModel, FreeGameAvailableModel, + Contracts }; use tournament::ls15_components::models::tournament::{ TournamentTotalsModel, TournamentModel, TournamentEntriesModel, TournamentPrizeKeysModel, PrizesModel, TournamentScoresModel, TokenModel, TournamentEntriesAddressModel, TournamentEntryAddressesModel, TournamentStartsAddressModel, TournamentGameModel, - TournamentConfig, TournamentStartIdsModel + TournamentConfig, TournamentStartIdsModel, FreeGameTokenType }; #[derive(Copy, Drop)] @@ -50,6 +51,12 @@ pub impl StoreImpl of StoreTrait { (self.world.read_model(contract)) } + #[inline(always)] + fn get_free_game_available_model( + ref self: Store, free_game_type: FreeGameTokenType, token_id: u128 + ) -> FreeGameAvailableModel { + (self.world.read_model((free_game_type, token_id),)) + } #[inline(always)] fn get_contracts_model(ref self: Store, contract: ContractAddress) -> Contracts { @@ -150,6 +157,11 @@ pub impl StoreImpl of StoreTrait { self.world.write_model(model); } + #[inline(always)] + fn set_free_game_available_model(ref self: Store, model: @FreeGameAvailableModel) { + self.world.write_model(model); + } + #[inline(always)] fn set_contracts_model(ref self: Store, model: @Contracts) { self.world.write_model(model); diff --git a/contracts/src/ls15_components/tests/test_tournament.cairo b/contracts/src/ls15_components/tests/test_tournament.cairo index bd5965f..bfb24f9 100644 --- a/contracts/src/ls15_components/tests/test_tournament.cairo +++ b/contracts/src/ls15_components/tests/test_tournament.cairo @@ -15,7 +15,8 @@ use tournament::ls15_components::tests::interfaces::WorldTrait; use tournament::ls15_components::models::{ loot_survivor::{ - m_AdventurerModel, m_AdventurerMetaModel, m_BagModel, m_GameCountModel, m_Contracts + m_AdventurerModel, m_AdventurerMetaModel, m_BagModel, m_GameCountModel, + m_FreeGameAvailableModel, m_Contracts }, tournament::{ m_TournamentModel, m_TournamentGameModel, m_TournamentEntryAddressesModel, @@ -23,7 +24,7 @@ use tournament::ls15_components::models::{ m_TournamentEntriesModel, m_TournamentScoresModel, m_TournamentTotalsModel, m_TournamentPrizeKeysModel, m_PrizesModel, m_TokenModel, m_TournamentConfig, ERC20Data, ERC721Data, Premium, GatedToken, EntryCriteria, TokenDataType, GatedType, GatedEntryType, - GatedSubmissionType + GatedSubmissionType, FreeGameTokenType } }; @@ -35,8 +36,8 @@ use tournament::tests::{ }, }; use tournament::ls15_components::tests::helpers::{ - approve_game_costs, create_basic_tournament, create_adventurer_metadata_with_death_date, - create_dead_adventurer_with_xp + approve_game_costs, approve_free_game_cost, create_basic_tournament, + create_adventurer_metadata_with_death_date, create_dead_adventurer_with_xp }; use tournament::ls15_components::tests::{ erc20_mock::{erc20_mock}, interfaces::{IERC20MockDispatcher, IERC20MockDispatcherTrait}, @@ -59,6 +60,21 @@ use tournament::ls15_components::tests::{ use openzeppelin_token::erc721::interface; use openzeppelin_token::erc721::{ERC721Component::{Transfer, Approval,}}; +#[derive(Drop)] +struct TestContracts { + world: WorldStorage, + tournament: ITournamentMockDispatcher, + loot_survivor: ILootSurvivorMockDispatcher, + pragma: IPragmaMockDispatcher, + eth: IERC20MockDispatcher, + lords: IERC20MockDispatcher, + erc20: IERC20MockDispatcher, + erc721: IERC721MockDispatcher, + golden_token: IERC721MockDispatcher, + blobert: IERC721MockDispatcher, +} + + // // events helpers // @@ -100,7 +116,13 @@ fn assert_only_event_approval( // Setup // -fn setup_uninitialized() -> (WorldStorage, IERC20MockDispatcher, IERC20MockDispatcher) { +fn setup_uninitialized() -> ( + WorldStorage, + IERC20MockDispatcher, + IERC20MockDispatcher, + IERC721MockDispatcher, + IERC721MockDispatcher +) { testing::set_block_number(1); testing::set_block_timestamp(1); @@ -111,6 +133,7 @@ fn setup_uninitialized() -> (WorldStorage, IERC20MockDispatcher, IERC20MockDispa TestResource::Model(m_AdventurerMetaModel::TEST_CLASS_HASH.try_into().unwrap()), TestResource::Model(m_BagModel::TEST_CLASS_HASH.try_into().unwrap()), TestResource::Model(m_GameCountModel::TEST_CLASS_HASH.try_into().unwrap()), + TestResource::Model(m_FreeGameAvailableModel::TEST_CLASS_HASH.try_into().unwrap()), TestResource::Model(m_Contracts::TEST_CLASS_HASH.try_into().unwrap()), // tournament models TestResource::Model(m_TournamentModel::TEST_CLASS_HASH.try_into().unwrap()), @@ -166,20 +189,19 @@ fn setup_uninitialized() -> (WorldStorage, IERC20MockDispatcher, IERC20MockDispa let contract = utils::deploy(erc20_mock::TEST_CLASS_HASH, 'salt5', call_data); let mut lords = IERC20MockDispatcher { contract_address: contract }; - (world, eth, lords) + let call_data: Array = array![]; + let contract = utils::deploy(erc721_mock::TEST_CLASS_HASH, 'salt6', call_data); + let mut golden_token = IERC721MockDispatcher { contract_address: contract }; + + let call_data: Array = array![]; + let contract = utils::deploy(erc721_mock::TEST_CLASS_HASH, 'salt7', call_data); + let mut blobert = IERC721MockDispatcher { contract_address: contract }; + + (world, eth, lords, golden_token, blobert) } -pub fn setup() -> ( - WorldStorage, - ITournamentMockDispatcher, - ILootSurvivorMockDispatcher, - IPragmaMockDispatcher, - IERC20MockDispatcher, - IERC20MockDispatcher, - IERC20MockDispatcher, - IERC721MockDispatcher, -) { - let (mut world, mut eth, mut lords) = setup_uninitialized(); +pub fn setup() -> TestContracts { + let (mut world, mut eth, mut lords, mut golden_token, mut blobert) = setup_uninitialized(); let tournament = world.tournament_mock_dispatcher(); let loot_survivor = world.loot_survivor_mock_dispatcher(); @@ -194,6 +216,8 @@ pub fn setup() -> ( lords.contract_address, loot_survivor.contract_address, pragma.contract_address, + golden_token.contract_address, + blobert.contract_address, false, false, erc20.contract_address, @@ -208,26 +232,26 @@ pub fn setup() -> ( lords.mint(OWNER(), STARTING_BALANCE); erc20.mint(OWNER(), STARTING_BALANCE); erc721.mint(OWNER(), 1); + golden_token.mint(OWNER(), 1); + blobert.mint(OWNER(), 1); + + // set LS free games + loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + + loot_survivor.set_free_game_available(FreeGameTokenType::LaunchTournamentChampion, 1); // drop all events utils::drop_all_events(world.dispatcher.contract_address); utils::drop_all_events(tournament.contract_address); utils::drop_all_events(loot_survivor.contract_address); - (world, tournament, loot_survivor, pragma, eth, lords, erc20, erc721) + TestContracts { + world, tournament, loot_survivor, pragma, eth, lords, erc20, erc721, golden_token, blobert, + } } -pub fn setup_safe_mode() -> ( - WorldStorage, - ITournamentMockDispatcher, - ILootSurvivorMockDispatcher, - IPragmaMockDispatcher, - IERC20MockDispatcher, - IERC20MockDispatcher, - IERC20MockDispatcher, - IERC721MockDispatcher, -) { - let (mut world, mut eth, mut lords) = setup_uninitialized(); +pub fn setup_safe_mode() -> TestContracts { + let (mut world, mut eth, mut lords, mut golden_token, mut blobert) = setup_uninitialized(); let tournament = world.tournament_mock_dispatcher(); let loot_survivor = world.loot_survivor_mock_dispatcher(); @@ -242,6 +266,8 @@ pub fn setup_safe_mode() -> ( lords.contract_address, loot_survivor.contract_address, pragma.contract_address, + golden_token.contract_address, + blobert.contract_address, true, false, erc20.contract_address, @@ -256,13 +282,22 @@ pub fn setup_safe_mode() -> ( lords.mint(OWNER(), STARTING_BALANCE); erc20.mint(OWNER(), STARTING_BALANCE); erc721.mint(OWNER(), 1); + golden_token.mint(OWNER(), 1); + blobert.mint(OWNER(), 1); + + // set LS free games + loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + + loot_survivor.set_free_game_available(FreeGameTokenType::LaunchTournamentChampion, 1); // drop all events utils::drop_all_events(world.dispatcher.contract_address); utils::drop_all_events(tournament.contract_address); utils::drop_all_events(loot_survivor.contract_address); - (world, tournament, loot_survivor, pragma, eth, lords, erc20, erc721) + TestContracts { + world, tournament, loot_survivor, pragma, eth, lords, erc20, erc721, golden_token, blobert, + } } // @@ -271,54 +306,56 @@ pub fn setup_safe_mode() -> ( #[test] fn test_initializer() { - let ( - _world, _tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, mut erc721, - ) = - setup(); + let contracts = setup(); - assert(loot_survivor.symbol() == "LSVR", 'Symbol is wrong'); + assert(contracts.loot_survivor.symbol() == "LSVR", 'Symbol is wrong'); assert( - loot_survivor.supports_interface(interface::IERC721_ID) == true, 'should support IERC721_ID' + contracts.loot_survivor.supports_interface(interface::IERC721_ID) == true, + 'should support IERC721_ID' ); assert( - loot_survivor.supports_interface(interface::IERC721_METADATA_ID) == true, + contracts.loot_survivor.supports_interface(interface::IERC721_METADATA_ID) == true, 'should support METADATA' ); - assert(erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); - assert(erc721.balance_of(OWNER()) == 1, 'Invalid balance'); - assert(eth.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); - assert(lords.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); + assert(contracts.erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); + assert(contracts.erc721.balance_of(OWNER()) == 1, 'Invalid balance'); + assert(contracts.eth.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); + assert(contracts.lords.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); } + // // Test creating tournaments // #[test] fn test_create_tournament() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - let tournament_data = tournament.tournament(tournament_id); + let tournament_data = contracts.tournament.tournament(tournament_id); assert(tournament_data.name == TOURNAMENT_NAME(), 'Invalid tournament name'); assert( tournament_data.description == TOURNAMENT_DESCRIPTION(), 'Invalid tournament description' ); - assert(tournament_data.start_time == TEST_START_TIME().into(), 'Invalid tournament start time'); + assert( + tournament_data.start_time == TEST_START_TIME().into(), 'Invalid tournament start time' + ); assert(tournament_data.end_time == TEST_END_TIME().into(), 'Invalid tournament end time'); assert(tournament_data.gated_type == Option::None, 'Invalid tournament gated token'); assert(tournament_data.entry_premium == Option::None, 'Invalid entry premium'); - assert(tournament.total_tournaments() == 1, 'Invalid tournaments count'); + assert(contracts.tournament.total_tournaments() == 1, 'Invalid tournaments count'); } #[test] #[should_panic(expected: ('start time too close', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_start_time_too_close() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -334,9 +371,10 @@ fn test_create_tournament_start_time_too_close() { #[test] #[should_panic(expected: ('start time too far', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_start_time_too_far() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -352,9 +390,10 @@ fn test_create_tournament_start_time_too_far() { #[test] #[should_panic(expected: ('tournament too short', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_end_time_too_close() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -370,9 +409,10 @@ fn test_create_tournament_end_time_too_close() { #[test] #[should_panic(expected: ('tournament too long', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_end_time_too_far() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -388,9 +428,10 @@ fn test_create_tournament_end_time_too_far() { #[test] #[should_panic(expected: ('submission period too short', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_submission_period_too_short() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -406,9 +447,10 @@ fn test_create_tournament_submission_period_too_short() { #[test] #[should_panic(expected: ('submission period too long', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_submission_period_too_long() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -423,36 +465,38 @@ fn test_create_tournament_submission_period_too_long() { #[test] fn test_create_tournament_with_prizes() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 1 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 ); - assert(erc20.balance_of(OWNER()) == 0, 'Invalid balance'); - assert(erc721.balance_of(OWNER()) == 0, 'Invalid balance'); + assert(contracts.erc20.balance_of(OWNER()) == 0, 'Invalid balance'); + assert(contracts.erc721.balance_of(OWNER()) == 0, 'Invalid balance'); } // #[test] // #[should_panic(expected: ('prize token not registered', 'ENTRYPOINT_FAILED'))] // fn test_create_tournament_with_prizes_token_not_registered() { -// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721, +// _golden_token, _blobert) = // setup(); // utils::impersonate(OWNER()); @@ -478,25 +522,26 @@ fn test_create_tournament_with_prizes() { #[test] #[should_panic(expected: ('prize position too large', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_with_prizes_position_too_large() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 2 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 2 ); @@ -505,19 +550,19 @@ fn test_create_tournament_with_prizes_position_too_large() { #[test] #[should_panic(expected: ('premium distributions too long', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_with_premiums_too_long() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, _erc721) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 1, token_distribution: array![100, 0].span(), creator_fee: 0, }; - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -533,19 +578,19 @@ fn test_create_tournament_with_premiums_too_long() { #[test] #[should_panic(expected: ('premium distributions not 100%', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_with_premiums_not_100() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, _erc721) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 1, token_distribution: array![95].span(), creator_fee: 0, }; - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -561,13 +606,13 @@ fn test_create_tournament_with_premiums_not_100() { #[test] #[should_panic(expected: ('tournament not settled', 'ENTRYPOINT_FAILED'))] fn test_create_gated_tournament_with_unsettled_tournament() { - let (_world, mut tournament, _loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create first tournament - let first_tournament_id = tournament + let first_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -580,14 +625,18 @@ fn test_create_gated_tournament_with_unsettled_tournament() { ); // Enter first tournament - tournament.enter_tournament(first_tournament_id, Option::None); + contracts.tournament.enter_tournament(first_tournament_id, Option::None); // Move to tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start first tournament - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament( + first_tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // Try to create a second tournament gated by the first (unsettled) tournament let gated_type = GatedType::tournament(array![first_tournament_id].span()); @@ -595,7 +644,8 @@ fn test_create_gated_tournament_with_unsettled_tournament() { let current_time = get_block_timestamp(); // This should panic because the first tournament hasn't been settled yet - tournament + contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -610,13 +660,13 @@ fn test_create_gated_tournament_with_unsettled_tournament() { #[test] fn test_create_tournament_gated_by_multiple_tournaments() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create first tournament - let first_tournament_id = tournament + let first_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -629,7 +679,8 @@ fn test_create_tournament_gated_by_multiple_tournaments() { ); // Create second tournament - let second_tournament_id = tournament + let second_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -642,27 +693,35 @@ fn test_create_tournament_gated_by_multiple_tournaments() { ); // Enter and complete first tournament - tournament.enter_tournament(first_tournament_id, Option::None); + contracts.tournament.enter_tournament(first_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament( + first_tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(10); - loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(first_tournament_id, array![1]); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.tournament.submit_scores(first_tournament_id, array![1]); // Enter and complete second tournament testing::set_block_timestamp(1); - tournament.enter_tournament(second_tournament_id, Option::None); + contracts.tournament.enter_tournament(second_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(second_tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament( + second_tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(20); - loot_survivor.set_adventurer(2, submitted_adventurer); - tournament.submit_scores(second_tournament_id, array![2]); + contracts.loot_survivor.set_adventurer(2, submitted_adventurer); + contracts.tournament.submit_scores(second_tournament_id, array![2]); // Settle tournaments testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -673,7 +732,8 @@ fn test_create_tournament_gated_by_multiple_tournaments() { ); let current_time = get_block_timestamp(); - let gated_tournament_id = tournament + let gated_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -686,22 +746,23 @@ fn test_create_tournament_gated_by_multiple_tournaments() { ); // Verify the gated tournament was created with correct parameters - let gated_tournament = tournament.tournament(gated_tournament_id); - assert(gated_tournament.gated_type == Option::Some(gated_type), 'Invalid tournament gate type'); + let gated_tournament = contracts.tournament.tournament(gated_tournament_id); + assert( + gated_tournament.gated_type == Option::Some(gated_type), 'Invalid tournament gate type' + ); let gated_submission_type = GatedSubmissionType::game_id(array![1, 2].span()); // This should succeed since we completed both required tournaments - tournament.enter_tournament(gated_tournament_id, Option::Some(gated_submission_type)); + contracts.tournament.enter_tournament(gated_tournament_id, Option::Some(gated_submission_type)); // Verify entry was successful - let entries = tournament.tournament_entries(gated_tournament_id); + let entries = contracts.tournament.tournament_entries(gated_tournament_id); assert(entries == 1, 'Invalid entry count'); } #[test] fn test_create_tournament_gated_accounts() { - let (_world, mut tournament, _loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); @@ -712,7 +773,8 @@ fn test_create_tournament_gated_accounts() { // Create tournament gated by account list let gated_type = GatedType::address(allowed_accounts); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -725,31 +787,37 @@ fn test_create_tournament_gated_accounts() { ); // Verify tournament was created with correct gating - let tournament_data = tournament.tournament(tournament_id); - assert(tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gate type'); + let tournament_data = contracts.tournament.tournament(tournament_id); + assert( + tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gate type' + ); // Allowed account (owner) can enter - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Allowed player can enter utils::impersonate(allowed_player); - eth.mint(allowed_player, STARTING_BALANCE); - lords.mint(allowed_player, STARTING_BALANCE); - tournament.enter_tournament(tournament_id, Option::None); + contracts.eth.mint(allowed_player, STARTING_BALANCE); + contracts.lords.mint(allowed_player, STARTING_BALANCE); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament entries testing::set_block_timestamp(TEST_START_TIME().into()); utils::impersonate(OWNER()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(allowed_player); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // Verify entries were successful - let entries = tournament.tournament_entries(tournament_id); + let entries = contracts.tournament.tournament_entries(tournament_id); assert(entries == 2, 'Invalid entry count'); } @@ -759,7 +827,8 @@ fn test_create_tournament_gated_accounts() { // #[test] // fn test_register_token() { -// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut +// erc721,) = // setup(); // utils::impersonate(OWNER()); @@ -786,7 +855,8 @@ fn test_create_tournament_gated_accounts() { // #[test] // #[should_panic(expected: ('token already registered', 'ENTRYPOINT_FAILED'))] // fn test_register_token_already_registered() { -// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = +// let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721, +// _golden_token, _blobert) = // setup(); // utils::impersonate(OWNER()); @@ -823,37 +893,37 @@ fn test_create_tournament_gated_accounts() { #[test] fn test_enter_tournament() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); } #[test] #[should_panic(expected: ('tournament already started', 'ENTRYPOINT_FAILED'))] fn test_enter_tournament_already_started() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, _erc20, _erc721) = setup(); + let contracts = setup(); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); testing::set_block_timestamp(TEST_START_TIME().into()); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); } #[test] #[should_panic(expected: ('invalid gated submission type', 'ENTRYPOINT_FAILED'))] fn test_enter_tournament_wrong_submission_type() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // First create and complete a tournament that will be used as a gate - let first_tournament_id = tournament + let first_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -866,15 +936,19 @@ fn test_enter_tournament_wrong_submission_type() { ); // Complete the first tournament - tournament.enter_tournament(first_tournament_id, Option::None); + contracts.tournament.enter_tournament(first_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(first_tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament( + first_tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); let submitted_adventurer = create_dead_adventurer_with_xp(10); - loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(first_tournament_id, array![1]); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.tournament.submit_scores(first_tournament_id, array![1]); // Settle first tournament testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -883,7 +957,8 @@ fn test_enter_tournament_wrong_submission_type() { let gated_type = GatedType::tournament(array![first_tournament_id].span()); let current_time = get_block_timestamp(); - let gated_tournament_id = tournament + let gated_tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -900,7 +975,7 @@ fn test_enter_tournament_wrong_submission_type() { // This should panic because we're using token_id submission type for a tournament-gated // tournament - tournament.enter_tournament(gated_tournament_id, Option::Some(wrong_submission_type)); + contracts.tournament.enter_tournament(gated_tournament_id, Option::Some(wrong_submission_type)); } // @@ -909,102 +984,284 @@ fn test_enter_tournament_wrong_submission_type() { #[test] fn test_start_tournament() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // check tournament entries - assert(tournament.tournament_entries(tournament_id) == 1, 'Invalid entries'); + assert(contracts.tournament.tournament_entries(tournament_id) == 1, 'Invalid entries'); // check owner now has game token - assert(loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); // check lords and eth balances of loot survivor after starting assert( - lords.balance_of(loot_survivor.contract_address) == 50000000000000000000, - 'Invalid - balance' + contracts + .lords + .balance_of(contracts.loot_survivor.contract_address) == 50000000000000000000, + 'Invalid balance' + ); + assert( + contracts.eth.balance_of(contracts.loot_survivor.contract_address) == 200000000000000, + 'Invalid balance' ); - assert(eth.balance_of(loot_survivor.contract_address) == 200000000000000, 'Invalid balance'); // check lords and eth balances of owner after starting assert( - lords.balance_of(OWNER()) == STARTING_BALANCE - 50000000000000000000, 'Invalid - balance' + contracts.lords.balance_of(OWNER()) == STARTING_BALANCE - 50000000000000000000, + 'Invalid balance' + ); + assert( + contracts.eth.balance_of(OWNER()) == STARTING_BALANCE - 200000000000000, + 'Invalid balance' ); - assert(eth.balance_of(OWNER()) == STARTING_BALANCE - 200000000000000, 'Invalid balance'); } #[test] #[should_panic(expected: ('all entries started', 'ENTRYPOINT_FAILED'))] fn test_start_tournament_entry_already_started() { - let (_world, mut tournament, _loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); + + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); +} + +#[test] +fn test_start_tournament_with_free_game() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + + approve_free_game_cost(contracts.eth, contracts.golden_token, 1, contracts.tournament); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![1].span(), array![].span() + ); + + // check tournament entries + assert(contracts.tournament.tournament_entries(tournament_id) == 1, 'Invalid entries'); + + // check golden tokens have returned back + assert(contracts.golden_token.owner_of(1) == OWNER(), 'Invalid owner'); +} + +#[test] +fn test_start_tournament_with_free_game_multiple() { + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 2); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); + + contracts.golden_token.mint(OWNER(), 2); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + contracts.golden_token.approve(contracts.tournament.contract_address, 2); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![1, 2].span(), array![].span() + ); + + // check tournament entries + assert(contracts.tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); + + // check golden tokens have returned back + assert(contracts.golden_token.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.golden_token.owner_of(2) == OWNER(), 'Invalid owner'); } +#[test] +fn test_start_tournament_with_free_game_multiple_and_lords() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::LaunchTournamentChampion, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::LaunchTournamentChampion, 2); + + contracts.golden_token.mint(OWNER(), 2); + contracts.blobert.mint(OWNER(), 2); + + contracts.eth.approve(contracts.tournament.contract_address, 1000000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + contracts.golden_token.approve(contracts.tournament.contract_address, 2); + contracts.blobert.approve(contracts.tournament.contract_address, 1); + contracts.blobert.approve(contracts.tournament.contract_address, 2); + contracts.lords.approve(contracts.tournament.contract_address, 50000000000000000000); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![1, 2].span(), array![1, 2].span() + ); + + // check tournament entries + assert(contracts.tournament.tournament_entries(tournament_id) == 5, 'Invalid entries'); + + // check golden tokens and bloberts have returned back + assert(contracts.golden_token.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.golden_token.owner_of(2) == OWNER(), 'Invalid owner'); + assert(contracts.blobert.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.blobert.owner_of(2) == OWNER(), 'Invalid owner'); +} + +#[test] +#[should_panic(expected: ('too many free games', 'ENTRYPOINT_FAILED'))] +fn test_start_tournament_with_free_game_over_start_count() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); + + contracts.golden_token.mint(OWNER(), 2); + + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + contracts.golden_token.approve(contracts.tournament.contract_address, 2); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::Some(1), ZERO(), array![1, 2].span(), array![].span() + ); +} + +#[test] +#[should_panic(expected: ('too many free games', 'ENTRYPOINT_FAILED'))] +fn test_start_tournament_with_free_game_over_entries() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); + + contracts.golden_token.mint(OWNER(), 2); + + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + contracts.golden_token.approve(contracts.tournament.contract_address, 2); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![1, 2].span(), array![].span() + ); +} // // Test submitting scores // #[test] fn test_submit_scores() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); - let scores = tournament.top_scores(tournament_id); + contracts.tournament.submit_scores(tournament_id, array![1]); + let scores = contracts.tournament.top_scores(tournament_id); assert(scores.len() == 1, 'Invalid scores length'); assert(*scores.at(0) == 1, 'Invalid score'); } #[test] fn test_submit_multiple_scores() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1016,34 +1273,36 @@ fn test_submit_multiple_scores() { Option::None, // zero entry premium ); - tournament.enter_tournament(tournament_id, Option::None); - tournament.enter_tournament(tournament_id, Option::None); - tournament.enter_tournament(tournament_id, Option::None); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 4); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 4); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); let submitted_adventurer = create_dead_adventurer_with_xp(2); - loot_survivor.set_adventurer(2, submitted_adventurer); + contracts.loot_survivor.set_adventurer(2, submitted_adventurer); let submitted_adventurer = create_dead_adventurer_with_xp(5); - loot_survivor.set_adventurer(3, submitted_adventurer); + contracts.loot_survivor.set_adventurer(3, submitted_adventurer); let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(4, submitted_adventurer); + contracts.loot_survivor.set_adventurer(4, submitted_adventurer); - tournament.submit_scores(tournament_id, array![3, 2, 1]); - let scores = tournament.top_scores(tournament_id); + contracts.tournament.submit_scores(tournament_id, array![3, 2, 1]); + let scores = contracts.tournament.top_scores(tournament_id); assert(scores.len() == 3, 'Invalid scores length'); assert(*scores.at(0) == 3, 'Invalid score'); assert(*scores.at(1) == 2, 'Invalid score'); @@ -1052,11 +1311,12 @@ fn test_submit_multiple_scores() { #[test] fn test_submit_scores_tiebreaker() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); + utils::impersonate(OWNER()); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1069,31 +1329,33 @@ fn test_submit_scores_tiebreaker() { ); // Complete tournament with tied scores but different death dates - tournament.enter_tournament(tournament_id, Option::None); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 2); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); - tournament.start_tournament(tournament_id, true, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, true, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); let adventurer1 = create_dead_adventurer_with_xp(1); let adventurer2 = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, adventurer1); - loot_survivor.set_adventurer(2, adventurer2); + contracts.loot_survivor.set_adventurer(1, adventurer1); + contracts.loot_survivor.set_adventurer(2, adventurer2); // Same score (1) but different death timestamps let adventurer1_metadata = create_adventurer_metadata_with_death_date(100); let adventurer2_metadata = create_adventurer_metadata_with_death_date(50); - loot_survivor.set_adventurer_meta(1, adventurer1_metadata); - loot_survivor.set_adventurer_meta(2, adventurer2_metadata); + contracts.loot_survivor.set_adventurer_meta(1, adventurer1_metadata); + contracts.loot_survivor.set_adventurer_meta(2, adventurer2_metadata); - tournament.submit_scores(tournament_id, array![2, 1]); + contracts.tournament.submit_scores(tournament_id, array![2, 1]); - let scores = tournament.top_scores(tournament_id); + let scores = contracts.tournament.top_scores(tournament_id); assert(*scores.at(0) == 2, 'Wrong tiebreaker winner'); assert(*scores.at(1) == 1, 'Wrong tiebreaker loser'); } @@ -1101,13 +1363,13 @@ fn test_submit_scores_tiebreaker() { #[test] #[should_panic(expected: ('tournament already settled', 'ENTRYPOINT_FAILED'))] fn test_submit_scores_after_submission_period() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create tournament with specific timing - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1120,37 +1382,39 @@ fn test_submit_scores_after_submission_period() { ); // Enter tournament - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Move to tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start tournament - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); // Move timestamp to after submission period ends // Tournament end (3 + MIN_REGISTRATION_PERIOD) + submission period testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); // This should panic with 'tournament already settled' - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); } #[test] #[should_panic(expected: ('tournament not ended', 'ENTRYPOINT_FAILED'))] fn test_submit_scores_before_tournament_ends() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create tournament with future start time - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1163,33 +1427,35 @@ fn test_submit_scores_before_tournament_ends() { ); // Enter tournament - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Set timestamp before tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start tournament - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); // Attempt to submit scores before tournament starts // This should panic with 'tournament not started' - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); } #[test] fn test_submit_scores_replace_lower_score() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create tournament with multiple top scores - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1206,32 +1472,38 @@ fn test_submit_scores_replace_lower_score() { let player3 = starknet::contract_address_const::<0x789>(); // Enter tournament with all players - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player2); - eth.mint(player2, STARTING_BALANCE); - lords.mint(player2, STARTING_BALANCE); - tournament.enter_tournament(tournament_id, Option::None); + contracts.eth.mint(player2, STARTING_BALANCE); + contracts.lords.mint(player2, STARTING_BALANCE); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player3); - eth.mint(player3, STARTING_BALANCE); - lords.mint(player3, STARTING_BALANCE); - tournament.enter_tournament(tournament_id, Option::None); + contracts.eth.mint(player3, STARTING_BALANCE); + contracts.lords.mint(player3, STARTING_BALANCE); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament for all players testing::set_block_timestamp(TEST_START_TIME().into()); utils::impersonate(OWNER()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player2); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player3); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1240,23 +1512,23 @@ fn test_submit_scores_replace_lower_score() { let mid_score = create_dead_adventurer_with_xp(10); let high_score = create_dead_adventurer_with_xp(15); - loot_survivor.set_adventurer(1, low_score); // Owner's adventurer - loot_survivor.set_adventurer(2, mid_score); // Player2's adventurer - loot_survivor.set_adventurer(3, high_score); // Player3's adventurer + contracts.loot_survivor.set_adventurer(1, low_score); // Owner's adventurer + contracts.loot_survivor.set_adventurer(2, mid_score); // Player2's adventurer + contracts.loot_survivor.set_adventurer(3, high_score); // Player3's adventurer utils::impersonate(OWNER()); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); // // Verify initial rankings - let scores = tournament.top_scores(tournament_id); + let scores = contracts.tournament.top_scores(tournament_id); assert(scores.len() == 1, 'Invalid scores length'); assert(*scores.at(0) == 1, 'Wrong top score'); // owner utils::impersonate(player2); - tournament.submit_scores(tournament_id, array![1, 3, 2]); + contracts.tournament.submit_scores(tournament_id, array![1, 3, 2]); // Verify updated rankings - let updated_scores = tournament.top_scores(tournament_id); + let updated_scores = contracts.tournament.top_scores(tournament_id); assert(updated_scores.len() == 3, 'Invalid updated scores length'); assert(*updated_scores.at(0) == 1, 'Wrong new top score'); // Owner assert(*updated_scores.at(1) == 3, 'Wrong new second score'); // Player3 @@ -1269,142 +1541,128 @@ fn test_submit_scores_replace_lower_score() { #[test] fn test_distribute_prizes_with_prizes() { - let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); // register_tokens_for_test(tournament, erc20, erc721); - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 1 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 ); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); - tournament.distribute_prizes(tournament_id, array![1, 2]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2]); // check balances of owner after claiming prizes - assert(erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); - assert(erc721.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); + assert(contracts.erc721.owner_of(1) == OWNER(), 'Invalid owner'); } #[test] #[should_panic(expected: ('prize already claimed', 'ENTRYPOINT_FAILED'))] fn test_distribute_prizes_prize_already_claimed() { - let ( - _world, - mut tournament, - mut loot_survivor, - _pragma, - mut eth, - mut lords, - mut erc20, - mut erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); - let tournament_id = create_basic_tournament(tournament); + let tournament_id = create_basic_tournament(contracts.tournament); // register_tokens_for_test(tournament, erc20, erc721); - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 1 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 ); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); - tournament.distribute_prizes(tournament_id, array![1, 2]); - tournament.distribute_prizes(tournament_id, array![1, 2]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2]); } #[test] fn test_distribute_prizes_with_gated_tokens_criteria() { - let ( - _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, mut erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // register_tokens_for_test(tournament, erc20, erc721); let gated_type = GatedType::token( GatedToken { - token: erc721.contract_address, + token: contracts.erc721.contract_address, entry_type: GatedEntryType::criteria( array![EntryCriteria { token_id: 1, entry_count: 2 }].span() ), } ); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1416,58 +1674,62 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { Option::None, // zero entry premium ); - let tournament_data = tournament.tournament(tournament_id); + let tournament_data = contracts.tournament.tournament(tournament_id); assert( tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gated token' ); let gated_submission_type = GatedSubmissionType::token_id(1); - tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 2); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // check tournament entries - assert(tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); + assert(contracts.tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); // check owner now has game token - assert(loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); - assert(loot_survivor.owner_of(2) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(2) == OWNER(), 'Invalid owner'); // check lords and eth balances of loot survivor after starting assert( - lords.balance_of(loot_survivor.contract_address) == 2 * 50000000000000000000, + contracts.lords.balance_of(contracts.loot_survivor.contract_address) == 2 + * 50000000000000000000, 'Invalid balance' ); assert( - eth.balance_of(loot_survivor.contract_address) == 2 * 200000000000000, 'Invalid balance' + contracts.eth.balance_of(contracts.loot_survivor.contract_address) == 2 * 200000000000000, + 'Invalid balance' ); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); } #[test] fn test_distribute_prizes_with_gated_tokens_uniform() { - let ( - _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, mut erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // register_tokens_for_test(tournament, erc20, erc721); let gated_type = GatedType::token( - GatedToken { token: erc721.contract_address, entry_type: GatedEntryType::uniform(3), } + GatedToken { + token: contracts.erc721.contract_address, entry_type: GatedEntryType::uniform(3), + } ); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1479,54 +1741,58 @@ fn test_distribute_prizes_with_gated_tokens_uniform() { Option::None, // zero entry premium ); - let tournament_data = tournament.tournament(tournament_id); + let tournament_data = contracts.tournament.tournament(tournament_id); assert( tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gated token' ); let gated_submission_type = GatedSubmissionType::token_id(1); - tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 3); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 3); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // check tournament entries - assert(tournament.tournament_entries(tournament_id) == 3, 'Invalid entries'); + assert(contracts.tournament.tournament_entries(tournament_id) == 3, 'Invalid entries'); // check owner now has game token - assert(loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); - assert(loot_survivor.owner_of(2) == OWNER(), 'Invalid owner'); - assert(loot_survivor.owner_of(3) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(1) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(2) == OWNER(), 'Invalid owner'); + assert(contracts.loot_survivor.owner_of(3) == OWNER(), 'Invalid owner'); // check lords and eth balances of loot survivor after starting assert( - lords.balance_of(loot_survivor.contract_address) == 3 * 50000000000000000000, + contracts.lords.balance_of(contracts.loot_survivor.contract_address) == 3 + * 50000000000000000000, 'Invalid balance' ); assert( - eth.balance_of(loot_survivor.contract_address) == 3 * 200000000000000, 'Invalid balance' + contracts.eth.balance_of(contracts.loot_survivor.contract_address) == 3 * 200000000000000, + 'Invalid balance' ); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); } #[test] fn test_distribute_prizes_with_gated_tournaments() { - let (_world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, _erc20, _erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // create a standard tournament with one winner - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1538,21 +1804,23 @@ fn test_distribute_prizes_with_gated_tournaments() { Option::None, // zero entry premium ); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -1561,7 +1829,8 @@ fn test_distribute_prizes_with_gated_tournaments() { let current_time = get_block_timestamp(); - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1573,20 +1842,22 @@ fn test_distribute_prizes_with_gated_tournaments() { Option::None, // zero entry premium ); - let tournament_data = tournament.tournament(tournament_id); + let tournament_data = contracts.tournament.tournament(tournament_id); assert( tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gated token' ); // submit game id 1 let gated_submission_type = GatedSubmissionType::game_id(array![1].span()); - tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); testing::set_block_timestamp(current_time + MIN_REGISTRATION_PERIOD.into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp( current_time + 1 + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into() @@ -1595,29 +1866,27 @@ fn test_distribute_prizes_with_gated_tournaments() { // this is now adventurer 2 // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(2, submitted_adventurer); + contracts.loot_survivor.set_adventurer(2, submitted_adventurer); - tournament.submit_scores(tournament_id, array![2]); + contracts.tournament.submit_scores(tournament_id, array![2]); } #[test] fn test_distribute_prizes_with_premiums() { - let ( - _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // register_tokens_for_test(tournament, erc20, erc721); let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 1, token_distribution: array![100].span(), creator_fee: 0, }; - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1629,62 +1898,66 @@ fn test_distribute_prizes_with_premiums() { Option::Some(entry_premium), // zero entry premium ); - let tournament_data = tournament.tournament(tournament_id); + let tournament_data = contracts.tournament.tournament(tournament_id); assert( tournament_data.entry_premium == Option::Some(entry_premium), 'Invalid entry premium' ); // handle approval for the premium - erc20.approve(tournament.contract_address, 1); + contracts.erc20.approve(contracts.tournament.contract_address, 1); - tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); // check owner now has 1 less premium token - assert(erc20.balance_of(OWNER()) == STARTING_BALANCE - 1, 'Invalid balance'); + assert(contracts.erc20.balance_of(OWNER()) == STARTING_BALANCE - 1, 'Invalid balance'); // check tournament now has premium funds - assert(erc20.balance_of(tournament.contract_address) == 1, 'Invalid balance'); + assert( + contracts.erc20.balance_of(contracts.tournament.contract_address) == 1, + 'Invalid + balance' + ); testing::set_block_timestamp(TEST_START_TIME().into()); - approve_game_costs(eth, lords, tournament, 1); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // set data to a dead adventurer with 1 xp let submitted_adventurer = create_dead_adventurer_with_xp(1); - loot_survivor.set_adventurer(1, submitted_adventurer); + contracts.loot_survivor.set_adventurer(1, submitted_adventurer); - tournament.submit_scores(tournament_id, array![1]); + contracts.tournament.submit_scores(tournament_id, array![1]); testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); - tournament.distribute_prizes(tournament_id, array![1]); + contracts.tournament.distribute_prizes(tournament_id, array![1]); // check owner now has all premium funds back - assert(erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); + assert(contracts.erc20.balance_of(OWNER()) == STARTING_BALANCE, 'Invalid balance'); } #[test] fn test_distribute_prizes_with_premium_creator_fee() { - let ( - _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create premium with 10% creator fee and 90% to winner let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 100, // 100 tokens per entry token_distribution: array![100].span(), // 100% to winner creator_fee: 10, // 10% creator fee }; - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1698,76 +1971,83 @@ fn test_distribute_prizes_with_premium_creator_fee() { // Enter tournament with two players utils::impersonate(OWNER()); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); let player2 = starknet::contract_address_const::<0x456>(); utils::impersonate(player2); - erc20.mint(player2, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player2, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament and submit scores testing::set_block_timestamp(TEST_START_TIME().into()); - let creator_initial_balance = erc20.balance_of(OWNER()); + let creator_initial_balance = contracts.erc20.balance_of(OWNER()); utils::impersonate(OWNER()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); - - // Verify creator fee distribution (10% of 200 total = 20) - assert(erc20.balance_of(OWNER()) == creator_initial_balance + 20, 'Invalid creator fee'); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player2); - eth.mint(player2, STARTING_BALANCE); - lords.mint(player2, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player2, STARTING_BALANCE); + contracts.lords.mint(player2, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); // Set scores (player2 wins) let winner_adventurer = create_dead_adventurer_with_xp(10); let loser_adventurer = create_dead_adventurer_with_xp(5); - loot_survivor.set_adventurer(1, loser_adventurer); - loot_survivor.set_adventurer(2, winner_adventurer); + contracts.loot_survivor.set_adventurer(1, loser_adventurer); + contracts.loot_survivor.set_adventurer(2, winner_adventurer); utils::impersonate(OWNER()); - tournament.submit_scores(tournament_id, array![2]); + contracts.tournament.submit_scores(tournament_id, array![2]); + + // Verify creator fee distribution (10% of 200 total = 20) + assert( + contracts.erc20.balance_of(OWNER()) == creator_initial_balance + 20, + 'Invalid creator + fee' + ); // Check initial balances - let winner_initial_balance = erc20.balance_of(player2); + let winner_initial_balance = contracts.erc20.balance_of(player2); // Distribute rewards testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); - tournament.distribute_prizes(tournament_id, array![1]); + contracts.tournament.distribute_prizes(tournament_id, array![1]); // Verify winner prize distribution (90% of 200 total = 180) assert( - erc20.balance_of(player2) == winner_initial_balance + 180, 'Invalid winner distribution' + contracts.erc20.balance_of(player2) == winner_initial_balance + 180, + 'Invalid winner distribution' ); } #[test] fn test_distribute_prizes_with_premium_multiple_winners() { - let ( - _world, mut tournament, mut loot_survivor, _pragma, mut eth, mut lords, mut erc20, _erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create premium with 10% creator fee and split remaining 90% between top 3: // 1st: 50%, 2nd: 30%, 3rd: 20% let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 100, // 100 tokens per entry token_distribution: array![50, 30, 20].span(), // Distribution percentages creator_fee: 10, // 10% creator fee }; - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1786,54 +2066,62 @@ fn test_distribute_prizes_with_premium_multiple_winners() { // Owner enters utils::impersonate(OWNER()); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Player 2 enters utils::impersonate(player2); - erc20.mint(player2, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player2, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Player 3 enters utils::impersonate(player3); - erc20.mint(player3, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player3, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Player 4 enters utils::impersonate(player4); - erc20.mint(player4, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player4, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament testing::set_block_timestamp(TEST_START_TIME().into()); - let third_initial = erc20.balance_of(OWNER()); + let third_initial = contracts.erc20.balance_of(OWNER()); // Start games for all players utils::impersonate(OWNER()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player2); - eth.mint(player2, STARTING_BALANCE); - lords.mint(player2, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player2, STARTING_BALANCE); + contracts.lords.mint(player2, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player3); - eth.mint(player3, STARTING_BALANCE); - lords.mint(player3, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player3, STARTING_BALANCE); + contracts.lords.mint(player3, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player4); - eth.mint(player4, STARTING_BALANCE); - lords.mint(player4, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player4, STARTING_BALANCE); + contracts.lords.mint(player4, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1843,23 +2131,23 @@ fn test_distribute_prizes_with_premium_multiple_winners() { let third_place = create_dead_adventurer_with_xp(50); let fourth_place = create_dead_adventurer_with_xp(25); - loot_survivor.set_adventurer(2, first_place); // player2's adventurer - loot_survivor.set_adventurer(3, second_place); // player3's adventurer - loot_survivor.set_adventurer(1, third_place); // owner's adventurer - loot_survivor.set_adventurer(4, fourth_place); // player4's adventurer + contracts.loot_survivor.set_adventurer(2, first_place); // player2's adventurer + contracts.loot_survivor.set_adventurer(3, second_place); // player3's adventurer + contracts.loot_survivor.set_adventurer(1, third_place); // owner's adventurer + contracts.loot_survivor.set_adventurer(4, fourth_place); // player4's adventurer // Submit scores utils::impersonate(player2); - tournament.submit_scores(tournament_id, array![2, 3, 1]); + contracts.tournament.submit_scores(tournament_id, array![2, 3, 1]); // Store initial balances - let first_initial = erc20.balance_of(player2); - let second_initial = erc20.balance_of(player3); + let first_initial = contracts.erc20.balance_of(player2); + let second_initial = contracts.erc20.balance_of(player3); // Distribute rewards testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); // 3 premium prizes - tournament.distribute_prizes(tournament_id, array![1, 2, 3]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2, 3]); // Total pool = 4 players * 100 tokens = 400 tokens // Creator fee = 10% of 400 = 40 tokens @@ -1869,29 +2157,38 @@ fn test_distribute_prizes_with_premium_multiple_winners() { // 3rd place (20%) = 72 tokens // Verify winner distributions - assert(erc20.balance_of(player2) == first_initial + 180, 'Invalid first distribution'); - assert(erc20.balance_of(player3) == second_initial + 108, 'Invalid second distribution'); - assert(erc20.balance_of(OWNER()) == third_initial + 72 + 40, 'Invalid third distribution'); + assert( + contracts.erc20.balance_of(player2) == first_initial + 180, 'Invalid first + distribution' + ); + assert( + contracts.erc20.balance_of(player3) == second_initial + 108, + 'Invalid second + distribution' + ); + assert( + contracts.erc20.balance_of(OWNER()) == third_initial + 72 + 40, + 'Invalid third + distribution' + ); } #[test] fn test_tournament_with_no_submissions() { - let ( - _world, mut tournament, _loot_survivor, _pragma, mut eth, mut lords, mut erc20, mut erc721, - ) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create tournament with prizes and premium let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 100, token_distribution: array![100].span(), // 100% to winner creator_fee: 10, // 10% creator fee }; - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -1904,19 +2201,21 @@ fn test_tournament_with_no_submissions() { ); // Add some prizes - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 1 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 ); @@ -1926,41 +2225,47 @@ fn test_tournament_with_no_submissions() { let player3 = starknet::contract_address_const::<0x789>(); // Enter tournament with all players - erc20.mint(OWNER(), 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(OWNER(), 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player2); - erc20.mint(player2, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player2, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player3); - erc20.mint(player3, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player3, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament for all players testing::set_block_timestamp(TEST_START_TIME().into()); // Store initial balances - let creator_initial = erc20.balance_of(OWNER()); + let creator_initial = contracts.erc20.balance_of(OWNER()); utils::impersonate(OWNER()); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player2); - eth.mint(player2, STARTING_BALANCE); - lords.mint(player2, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player2, STARTING_BALANCE); + contracts.lords.mint(player2, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); utils::impersonate(player3); - eth.mint(player3, STARTING_BALANCE); - lords.mint(player3, STARTING_BALANCE); - approve_game_costs(eth, lords, tournament, 1); - tournament.start_tournament(tournament_id, false, Option::None, ZERO()); + contracts.eth.mint(player3, STARTING_BALANCE); + contracts.lords.mint(player3, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + contracts + .tournament + .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); // Move to after tournament and submission period without any score submissions testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -1968,41 +2273,41 @@ fn test_tournament_with_no_submissions() { // Distribute rewards utils::impersonate(OWNER()); // 2 deposited prizes and 1 tournament premium prize - tournament.distribute_prizes(tournament_id, array![1, 2, 3]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2, 3]); // Verify final state - let final_scores = tournament.top_scores(tournament_id); + let final_scores = contracts.tournament.top_scores(tournament_id); assert(final_scores.len() == 0, 'Should have no scores'); // Verify first caller gets all prizes // creator also gets the prize balance back (STARTING BALANCE) assert( - erc20.balance_of(OWNER()) == creator_initial + 300 + STARTING_BALANCE, + contracts.erc20.balance_of(OWNER()) == creator_initial + 300 + STARTING_BALANCE, 'Invalid owner refund' ); - assert(erc20.balance_of(player2) == 0, 'Invalid player2 refund'); - assert(erc20.balance_of(player3) == 0, 'Invalid player3 refund'); + assert(contracts.erc20.balance_of(player2) == 0, 'Invalid player2 refund'); + assert(contracts.erc20.balance_of(player3) == 0, 'Invalid player3 refund'); // Verify prize returns to tournament creator - assert(erc721.owner_of(1) == OWNER(), 'Prize should return to caller'); + assert(contracts.erc721.owner_of(1) == OWNER(), 'Prize should return to caller'); } #[test] fn test_tournament_with_no_starts() { - let (_world, mut tournament, _loot_survivor, _pragma, _eth, _lords, mut erc20, mut erc721,) = - setup(); + let contracts = setup(); utils::impersonate(OWNER()); // Create tournament with prizes and premium let entry_premium = Premium { - token: erc20.contract_address, + token: contracts.erc20.contract_address, token_amount: 100, token_distribution: array![100].span(), // 100% to winner creator_fee: 10, // 10% creator fee }; - let tournament_id = tournament + let tournament_id = contracts + .tournament .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), @@ -2015,19 +2320,21 @@ fn test_tournament_with_no_starts() { ); // Add some prizes - erc20.approve(tournament.contract_address, STARTING_BALANCE); - erc721.approve(tournament.contract_address, 1); - tournament + contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); + contracts.erc721.approve(contracts.tournament.contract_address, 1); + contracts + .tournament .add_prize( tournament_id, - erc20.contract_address, + contracts.erc20.contract_address, TokenDataType::erc20(ERC20Data { token_amount: STARTING_BALANCE.low }), 1 ); - tournament + contracts + .tournament .add_prize( tournament_id, - erc721.contract_address, + contracts.erc721.contract_address, TokenDataType::erc721(ERC721Data { token_id: 1 }), 1 ); @@ -2037,25 +2344,25 @@ fn test_tournament_with_no_starts() { let player3 = starknet::contract_address_const::<0x789>(); // Enter tournament with all players - erc20.mint(OWNER(), 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(OWNER(), 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player2); - erc20.mint(player2, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player2, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); utils::impersonate(player3); - erc20.mint(player3, 100); - erc20.approve(tournament.contract_address, 100); - tournament.enter_tournament(tournament_id, Option::None); + contracts.erc20.mint(player3, 100); + contracts.erc20.approve(contracts.tournament.contract_address, 100); + contracts.tournament.enter_tournament(tournament_id, Option::None); // Start tournament for all players testing::set_block_timestamp(TEST_START_TIME().into()); // Store initial balances - let creator_initial = erc20.balance_of(OWNER()); + let creator_initial = contracts.erc20.balance_of(OWNER()); // Move to after tournament and submission period without any score submissions testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -2063,22 +2370,21 @@ fn test_tournament_with_no_starts() { // Distribute rewards utils::impersonate(OWNER()); // 2 deposited prizes and 1 tournament premium prize - tournament.distribute_prizes(tournament_id, array![1, 2, 3]); + contracts.tournament.distribute_prizes(tournament_id, array![1, 2, 3]); // Verify final state - let final_scores = tournament.top_scores(tournament_id); + let final_scores = contracts.tournament.top_scores(tournament_id); assert(final_scores.len() == 0, 'Should have no scores'); // Verify first caller gets all prizes // creator also gets the prize balance back (STARTING BALANCE) assert( - erc20.balance_of(OWNER()) == creator_initial + 300 + STARTING_BALANCE, + contracts.erc20.balance_of(OWNER()) == creator_initial + 300 + STARTING_BALANCE, 'Invalid owner refund' ); - assert(erc20.balance_of(player2) == 0, 'Invalid player2 refund'); - assert(erc20.balance_of(player3) == 0, 'Invalid player3 refund'); + assert(contracts.erc20.balance_of(player2) == 0, 'Invalid player2 refund'); + assert(contracts.erc20.balance_of(player3) == 0, 'Invalid player3 refund'); // Verify prize returns to tournament creator - assert(erc721.owner_of(1) == OWNER(), 'Prize should return to caller'); + assert(contracts.erc721.owner_of(1) == OWNER(), 'Prize should return to caller'); } - diff --git a/contracts/src/ls15_components/tests/tournament_mock.cairo b/contracts/src/ls15_components/tests/tournament_mock.cairo index e569477..0d80992 100644 --- a/contracts/src/ls15_components/tests/tournament_mock.cairo +++ b/contracts/src/ls15_components/tests/tournament_mock.cairo @@ -37,8 +37,8 @@ pub trait ITournamentMock { start_all: bool, start_count: Option, client_reward_address: ContractAddress, - golden_token_free_game_ids: Option>, - blobert_free_game_ids: Option>, + golden_token_free_game_ids: Option>, + blobert_free_game_ids: Option>, ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( diff --git a/contracts/src/ls15_components/tournament.cairo b/contracts/src/ls15_components/tournament.cairo index 1761226..2c980e9 100644 --- a/contracts/src/ls15_components/tournament.cairo +++ b/contracts/src/ls15_components/tournament.cairo @@ -37,8 +37,8 @@ trait ITournament { start_all: bool, start_count: Option, client_reward_address: ContractAddress, - golden_token_free_game_ids: Option>, - blobert_free_game_ids: Option>, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span, ); fn submit_scores(ref self: TState, tournament_id: u64, game_ids: Array); fn add_prize( @@ -150,6 +150,7 @@ pub mod tournament_component { pub const START_COUNT_TOO_LARGE: felt252 = 'start count too large'; pub const TOURNAMENT_PERIOD_TOO_LONG: felt252 = 'period too long to start all'; pub const FREE_GAME_NOT_AVAILABLE: felt252 = 'free game not available'; + pub const TOO_MANY_FREE_GAMES: felt252 = 'too many free games'; // // Submit Scores // @@ -350,8 +351,8 @@ pub mod tournament_component { start_all: bool, start_count: Option, client_reward_address: ContractAddress, - golden_token_free_game_ids: Option>, - blobert_free_game_ids: Option>, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span, ) { let mut world = WorldTrait::storage( self.get_contract().world_dispatcher(), @"tournament" @@ -359,6 +360,11 @@ pub mod tournament_component { let mut store: Store = StoreTrait::new(world); // assert tournament is active self._assert_tournament_active(ref store, tournament_id); + // assert not too many free games if start count supplied + self + ._assert_free_game_ids_not_larger_than_start_count( + start_count, golden_token_free_game_token_ids, blobert_free_game_token_ids + ); // if starting all games, assert the tournament period is within max if (start_all) { self._assert_tournament_period_within_max(ref store, tournament_id); @@ -370,73 +376,7 @@ pub mod tournament_component { contract_address: tournament_config.loot_survivor }; - let mut free_games = 0; - - match golden_token_free_game_ids { - Option::Some(token_ids) => { - let mut game_index = 0; - loop { - if game_index == token_ids.len() { - break; - } - let golden_token_id = *token_ids.at(game_index); - self - ._assert_token_owner( - tournament_config.golden_token, - golden_token_id, - get_caller_address() - ); - // check if golden token free game is available - let free_game_available = ls_dispatcher - .free_game_available( - FreeGameTokenType::GoldenToken, golden_token_id.low - ); - assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); - // flash loan golden tokens - IERC721Dispatcher { contract_address: tournament_config.golden_token } - .transfer_from( - get_caller_address(), - get_contract_address(), - *token_ids.at(game_index) - ); - free_games += 1; - game_index += 1; - } - }, - Option::None => {}, - }; - - match blobert_free_game_ids { - Option::Some(token_ids) => { - let mut game_index = 0; - loop { - if game_index == token_ids.len() { - break; - } - let blobert_token_id = *token_ids.at(game_index); - self - ._assert_token_owner( - tournament_config.blobert, blobert_token_id, get_caller_address() - ); - // check if caller has blobert - let free_game_available = ls_dispatcher - .free_game_available( - FreeGameTokenType::LaunchTournamentChampion, blobert_token_id.low - ); - assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); - // flash loan bloberts - IERC721Dispatcher { contract_address: tournament_config.blobert } - .transfer_from( - get_caller_address(), - get_contract_address(), - *token_ids.at(game_index) - ); - free_games += 1; - game_index += 1; - } - }, - Option::None => {}, - }; + // first get the number of entries for calculations and allowed starts let mut entries = 0; @@ -466,6 +406,58 @@ pub mod tournament_component { }; } + // assert not free games not longer + self + ._assert_free_game_ids_not_larger_than_entries( + entries, golden_token_free_game_token_ids, blobert_free_game_token_ids + ); + + let mut free_games = 0; + + let mut golden_token_index = 0; + loop { + if golden_token_index == golden_token_free_game_token_ids.len() { + break; + } + let golden_token_id = *golden_token_free_game_token_ids.at(golden_token_index); + self + ._assert_token_owner( + tournament_config.golden_token, golden_token_id, get_caller_address() + ); + // check if golden token free game is available + let free_game_available = ls_dispatcher + .free_game_available(FreeGameTokenType::GoldenToken, golden_token_id.low); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan golden tokens + IERC721Dispatcher { contract_address: tournament_config.golden_token } + .transfer_from(get_caller_address(), get_contract_address(), golden_token_id); + free_games += 1; + golden_token_index += 1; + }; + + let mut blobert_token_index = 0; + loop { + if blobert_token_index == blobert_free_game_token_ids.len() { + break; + } + let blobert_token_id = *blobert_free_game_token_ids.at(blobert_token_index); + self + ._assert_token_owner( + tournament_config.blobert, blobert_token_id, get_caller_address() + ); + // check if caller has blobert + let free_game_available = ls_dispatcher + .free_game_available( + FreeGameTokenType::LaunchTournamentChampion, blobert_token_id.low + ); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan bloberts + IERC721Dispatcher { contract_address: tournament_config.blobert } + .transfer_from(get_caller_address(), get_contract_address(), blobert_token_id); + free_games += 1; + blobert_token_index += 1; + }; + // define contract interfaces let lords_dispatcher: IERC20Dispatcher = IERC20Dispatcher { contract_address: tournament_config.lords @@ -513,121 +505,48 @@ pub mod tournament_component { break; } let mut game_id = 0; - match golden_token_free_game_ids { - Option::Some(mut token_ids) => { - let popped_token_id = token_ids.pop_front(); - match popped_token_id { - Option::Some(token_id) => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - token_id.low.try_into().unwrap(), - true, - contract_address_const::<0>(), - 0, - address - ); - }, - Option::None => { - match blobert_free_game_ids { - Option::Some(mut token_ids) => { - let popped_token_id = token_ids.pop_front(); - match popped_token_id { - Option::Some(token_id) => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - token_id.low, - address - ); - }, - Option::None => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); - }, - } - }, - Option::None => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); - }, - } - }, - } - }, - Option::None => { - match blobert_free_game_ids { - Option::Some(mut token_ids) => { - let popped_token_id = token_ids.pop_front(); - match popped_token_id { - Option::Some(token_id) => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - token_id.low, - address - ); - }, - Option::None => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); - }, - } - }, - Option::None => { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); - }, - } - }, + let mut golden_token_index = 0; + let mut blobert_token_index = 0; + if (golden_token_index != golden_token_free_game_token_ids.len()) { + let token_id = *golden_token_free_game_token_ids.at(golden_token_index); + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + (token_id.low).try_into().unwrap(), + true, + contract_address_const::<0>(), + 0, + address + ); + golden_token_index += 1; + } else if (blobert_token_index != blobert_free_game_token_ids.len()) { + let token_id = *blobert_free_game_token_ids.at(blobert_token_index); + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + token_id.low, + address + ); + blobert_token_index += 1; + } else { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); } game_ids.append(game_id.try_into().unwrap()); let game = TournamentGameModel { @@ -654,22 +573,55 @@ pub mod tournament_component { let mut start_index = store .get_tournament_starts(tournament_id, get_caller_address()) .start_count; + let mut game_id = 0; let mut game_ids = ArrayTrait::::new(); loop { if start_index == entries { break; } - let game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - get_caller_address() - ); + let mut golden_token_index = 0; + let mut blobert_token_index = 0; + if (golden_token_index != golden_token_free_game_token_ids.len()) { + let token_id = *golden_token_free_game_token_ids.at(golden_token_index); + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + (token_id.low).try_into().unwrap(), + true, + contract_address_const::<0>(), + 0, + get_caller_address() + ); + golden_token_index += 1; + } else if (blobert_token_index != blobert_free_game_token_ids.len()) { + let token_id = *blobert_free_game_token_ids.at(blobert_token_index); + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + token_id.low, + get_caller_address() + ); + blobert_token_index += 1; + } else { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + tournament.name, + 0, + true, + contract_address_const::<0>(), + 0, + get_caller_address() + ); + } game_ids.append(game_id.try_into().unwrap()); let game = TournamentGameModel { tournament_id, @@ -690,6 +642,36 @@ pub mod tournament_component { tournament_id, address: get_caller_address(), start_count: entries }; store.set_address_starts(@address_starts); + + // send the free game tokens back if (any were available to use) + + let mut golden_token_index = 0; + loop { + if golden_token_index == golden_token_free_game_token_ids.len() { + break; + } + IERC721Dispatcher { contract_address: tournament_config.golden_token } + .transfer_from( + get_contract_address(), + get_caller_address(), + *golden_token_free_game_token_ids.at(golden_token_index) + ); + golden_token_index += 1; + }; + + let mut blobert_token_index = 0; + loop { + if blobert_token_index == blobert_free_game_token_ids.len() { + break; + } + IERC721Dispatcher { contract_address: tournament_config.blobert } + .transfer_from( + get_contract_address(), + get_caller_address(), + *blobert_free_game_token_ids.at(blobert_token_index) + ); + blobert_token_index += 1; + }; } } @@ -855,26 +837,6 @@ pub mod tournament_component { test_mode } ); - store - .set_token( - @TokenModel { - token: eth, - name: "Ether", - symbol: "ETH", - token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }), - is_registered: true - } - ); - store - .set_token( - @TokenModel { - token: eth, - name: "Ether", - symbol: "ETH", - token_data_type: TokenDataType::erc20(ERC20Data { token_amount: 1 }), - is_registered: true - } - ); } // @@ -1257,6 +1219,37 @@ pub mod tournament_component { ); } + fn _assert_free_game_ids_not_larger_than_start_count( + self: @ComponentState, + start_count: Option, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span + ) { + match start_count { + Option::Some(start_count) => { + assert( + golden_token_free_game_token_ids.len() + + blobert_free_game_token_ids.len() <= start_count.try_into().unwrap(), + Errors::TOO_MANY_FREE_GAMES + ); + }, + Option::None => {}, + } + } + + fn _assert_free_game_ids_not_larger_than_entries( + self: @ComponentState, + entries: u64, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span + ) { + assert( + golden_token_free_game_token_ids.len() + + blobert_free_game_token_ids.len() <= entries.try_into().unwrap(), + Errors::TOO_MANY_FREE_GAMES + ); + } + fn _assert_scores_count_valid( self: @ComponentState, ref tournament: TournamentModel, From 17e8d22807d78498d0bced53333eb1bdbf940d25 Mon Sep 17 00:00:00 2001 From: Starknet Dev Date: Wed, 11 Dec 2024 18:57:46 +0000 Subject: [PATCH 3/7] fix build --- tournament-ui/src/App.tsx | 4 ---- tournament-ui/src/containers/RegisterToken.tsx | 2 +- tournament-ui/src/useSystemCalls.ts | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tournament-ui/src/App.tsx b/tournament-ui/src/App.tsx index 3fa8f9b..2c910f3 100644 --- a/tournament-ui/src/App.tsx +++ b/tournament-ui/src/App.tsx @@ -19,15 +19,11 @@ import { useSubscribeTournamentCountsQuery, } from "@/hooks/useSdkQueries"; import { useSystemCalls } from "@/useSystemCalls"; -import { useDojo } from "@/DojoContext"; import { Toaster } from "@/components/ui/toaster"; import { useTournamentContracts } from "@/hooks/useTournamentContracts"; import { useConfig } from "@/hooks/useConfig"; function App() { - const { - setup: { selectedChainConfig }, - } = useDojo(); const { account } = useAccount(); useConfig(); const { tournament, eth, lords } = useTournamentContracts(); diff --git a/tournament-ui/src/containers/RegisterToken.tsx b/tournament-ui/src/containers/RegisterToken.tsx index fbe7fdd..6a9fe5d 100644 --- a/tournament-ui/src/containers/RegisterToken.tsx +++ b/tournament-ui/src/containers/RegisterToken.tsx @@ -19,7 +19,7 @@ import { useTournamentContracts } from "@/hooks/useTournamentContracts"; const RegisterToken = () => { const { account } = useAccount(); - const { tournament, eth, lords } = useTournamentContracts(); + const { eth, lords } = useTournamentContracts(); const erc20_mock = useDojoSystem("erc20_mock").contractAddress ?? "0x0"; const erc721_mock = useDojoSystem("erc721_mock").contractAddress ?? "0x0"; const [tokenType, setTokenType] = useState(null); diff --git a/tournament-ui/src/useSystemCalls.ts b/tournament-ui/src/useSystemCalls.ts index 316efba..536fdd5 100644 --- a/tournament-ui/src/useSystemCalls.ts +++ b/tournament-ui/src/useSystemCalls.ts @@ -16,7 +16,6 @@ import { byteArray, CallData, } from "starknet"; -import { useDojoSystem } from "@/hooks/useDojoSystem"; import { useToast } from "@/hooks/useToast"; import useUIStore from "@/hooks/useUIStore"; import { useOptimisticUpdates } from "@/hooks/useOptimisticUpdates"; @@ -29,7 +28,6 @@ export function selectTournament(client: any, isMainnet: boolean): any { export const useSystemCalls = () => { const state = useDojoStore((state) => state); - const tournament_mock = useDojoSystem("tournament_mock"); const { setup: { client, selectedChainConfig }, From 472d466c6ecb5560b49ffb85844ee8ab2d0f8682 Mon Sep 17 00:00:00 2001 From: Starknet Dev Date: Sun, 15 Dec 2024 17:26:54 +0000 Subject: [PATCH 4/7] updates --- .../bindings/typescript/contracts.gen.ts | 1247 ++++++++++++++++- contracts/bindings/typescript/models.gen.ts | 628 +++++++-- contracts/dojo_slot.toml | 1 + contracts/manifest_slot.json | 1114 +++++++++++++-- contracts/scripts/deploy_slot.sh | 8 +- contracts/src/lib.cairo | 16 +- contracts/src/ls15_components/constants.cairo | 6 +- .../src/ls15_components/interfaces.cairo | 18 - .../src/ls15_components/libs/store.cairo | 24 +- .../ls15_components/models/tournament.cairo | 4 +- .../src/ls15_components/tests/helpers.cairo | 7 +- .../ls15_components/tests/interfaces.cairo | 2 + .../tests/{ => mocks}/erc20_mock.cairo | 0 .../tests/{ => mocks}/erc721_mock.cairo | 0 .../tests/{ => mocks}/eth_mock.cairo | 0 .../{ => mocks}/loot_survivor_mock.cairo | 0 .../tests/{ => mocks}/lords_mock.cairo | 0 .../tests/{ => mocks}/pragma_mock.cairo | 0 .../tests/{ => mocks}/tournament_mock.cairo | 2 + .../tests/test_tournament.cairo | 626 +++++++-- .../src/ls15_components/tournament.cairo | 891 +++++++----- contracts/src/presets/ls_tournament.cairo | 18 + contracts/src/tests/constants.cairo | 14 +- tournament-ui/.env | 2 +- tournament-ui/bun.lockb | Bin 223604 -> 224606 bytes tournament-ui/package.json | 1 + tournament-ui/public/blobert.png | Bin 0 -> 1189 bytes tournament-ui/public/golden-token.png | Bin 0 -> 26017 bytes tournament-ui/src/App.tsx | 59 +- tournament-ui/src/components/Countdown.tsx | 42 +- tournament-ui/src/components/Header.tsx | 37 +- tournament-ui/src/components/Icons.tsx | 11 + .../src/components/create/TopScores.tsx | 93 ++ .../components/create/TournamentDetails.tsx | 64 + .../components/create/TournamentEntryFee.tsx | 230 +++ .../components/create/TournamentGating.tsx | 202 +++ .../components/create/TournamentPrizes.tsx | 192 +++ .../src/components/create/TournamentType.tsx | 220 +++ .../src/components/dialogs/inputs/Token.tsx | 5 + .../src/components/overview/EndTable.tsx | 39 +- .../src/components/overview/LiveTable.tsx | 41 +- .../src/components/overview/UpcomingRow.tsx | 15 + .../src/components/overview/UpcomingTable.tsx | 48 +- .../src/components/tournament/ClaimPrizes.tsx | 67 + .../components/tournament/EnterTournament.tsx | 121 ++ .../components/tournament/EntriesTable.tsx | 90 ++ .../src/components/tournament/EntryRow.tsx | 20 + .../src/components/tournament/GamesTable.tsx | 92 ++ .../src/components/tournament/ScoreTable.tsx | 109 +- .../components/tournament/StartTournament.tsx | 454 ++++++ .../components/tournament/SubmitScores.tsx | 142 ++ tournament-ui/src/config.ts | 18 + tournament-ui/src/containers/Create.tsx | 421 +----- tournament-ui/src/containers/Overview.tsx | 6 +- tournament-ui/src/containers/Tournament.tsx | 747 +++------- tournament-ui/src/generated/contracts.gen.ts | 36 +- tournament-ui/src/generated/models.gen.ts | 8 + tournament-ui/src/hooks/graphql/queries.ts | 36 +- tournament-ui/src/hooks/useController.ts | 76 +- tournament-ui/src/hooks/useFreeGames.tsx | 146 ++ .../src/hooks/useOptimisticUpdates.ts | 16 + .../src/hooks/useTournamentContracts.tsx | 10 +- tournament-ui/src/hooks/useUIStore.ts | 12 + tournament-ui/src/hooks/useVRFCost.ts | 2 + tournament-ui/src/index.css | 11 +- tournament-ui/src/lib/constants.ts | 0 tournament-ui/src/lib/dojo/hooks/useSdkGet.ts | 3 + tournament-ui/src/lib/types.ts | 2 + tournament-ui/src/lib/utils/index.ts | 27 + tournament-ui/src/useSystemCalls.ts | 109 +- tournament-ui/tailwind.config.js | 42 +- 71 files changed, 6781 insertions(+), 1969 deletions(-) rename contracts/src/ls15_components/tests/{ => mocks}/erc20_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/erc721_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/eth_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/loot_survivor_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/lords_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/pragma_mock.cairo (100%) rename contracts/src/ls15_components/tests/{ => mocks}/tournament_mock.cairo (98%) create mode 100644 tournament-ui/public/blobert.png create mode 100644 tournament-ui/public/golden-token.png create mode 100644 tournament-ui/src/components/create/TopScores.tsx create mode 100644 tournament-ui/src/components/create/TournamentDetails.tsx create mode 100644 tournament-ui/src/components/create/TournamentEntryFee.tsx create mode 100644 tournament-ui/src/components/create/TournamentGating.tsx create mode 100644 tournament-ui/src/components/create/TournamentPrizes.tsx create mode 100644 tournament-ui/src/components/create/TournamentType.tsx create mode 100644 tournament-ui/src/components/dialogs/inputs/Token.tsx create mode 100644 tournament-ui/src/components/tournament/ClaimPrizes.tsx create mode 100644 tournament-ui/src/components/tournament/EnterTournament.tsx create mode 100644 tournament-ui/src/components/tournament/EntriesTable.tsx create mode 100644 tournament-ui/src/components/tournament/EntryRow.tsx create mode 100644 tournament-ui/src/components/tournament/GamesTable.tsx create mode 100644 tournament-ui/src/components/tournament/StartTournament.tsx create mode 100644 tournament-ui/src/components/tournament/SubmitScores.tsx create mode 100644 tournament-ui/src/hooks/useFreeGames.tsx create mode 100644 tournament-ui/src/lib/constants.ts diff --git a/contracts/bindings/typescript/contracts.gen.ts b/contracts/bindings/typescript/contracts.gen.ts index 0fc30d6..38fbae3 100644 --- a/contracts/bindings/typescript/contracts.gen.ts +++ b/contracts/bindings/typescript/contracts.gen.ts @@ -4,6 +4,778 @@ import * as models from "./models.gen"; export async function setupWorld(provider: DojoProvider) { + const loot_survivor_mock_balanceOf = async (account: string) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "balance_of", + calldata: [account], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_ownerOf = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "owner_of", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_safeTransferFrom = async (snAccount: Account | AccountInterface, from: string, to: string, tokenId: BigNumberish, data: Array) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "safe_transfer_from", + calldata: [from, to, tokenId, data], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_transferFrom = async (snAccount: Account | AccountInterface, from: string, to: string, tokenId: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "transfer_from", + calldata: [from, to, tokenId], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_approve = async (snAccount: Account | AccountInterface, to: string, tokenId: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "approve", + calldata: [to, tokenId], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_setApprovalForAll = async (snAccount: Account | AccountInterface, operator: string, approved: boolean) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "set_approval_for_all", + calldata: [operator, approved], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_getApproved = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "get_approved", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_isApprovedForAll = async (owner: string, operator: string) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "is_approved_for_all", + calldata: [owner, operator], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_supportsInterface = async (interfaceId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "supports_interface", + calldata: [interfaceId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_name = async () => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "name", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_symbol = async () => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "symbol", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_tokenUri = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "token_uri", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_getAdventurer = async (adventurerId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "get_adventurer", + calldata: [adventurerId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_getAdventurerMeta = async (adventurerId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "get_adventurer_meta", + calldata: [adventurerId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_getBag = async (adventurerId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "get_bag", + calldata: [adventurerId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_getCostToPlay = async () => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "get_cost_to_play", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_freeGameAvailable = async (freeGameType: models.FreeGameTokenType, tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "free_game_available", + calldata: [freeGameType, tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_newGame = async (snAccount: Account | AccountInterface, clientRewardAddress: string, weapon: BigNumberish, name: BigNumberish, goldenTokenId: BigNumberish, delayReveal: boolean, customRenderer: string, launchTournamentWinnerTokenId: BigNumberish, mintTo: string) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "new_game", + calldata: [clientRewardAddress, weapon, name, goldenTokenId, delayReveal, customRenderer, launchTournamentWinnerTokenId, mintTo], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_setAdventurer = async (snAccount: Account | AccountInterface, adventurerId: BigNumberish, adventurer: models.InputAdventurer) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "set_adventurer", + calldata: [adventurerId, adventurer], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_setAdventurerMeta = async (snAccount: Account | AccountInterface, adventurerId: BigNumberish, adventurerMeta: models.InputAdventurerMetadataStorage) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "set_adventurer_meta", + calldata: [adventurerId, adventurerMeta], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_setBag = async (snAccount: Account | AccountInterface, adventurerId: BigNumberish, bag: models.InputBag) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "set_bag", + calldata: [adventurerId, bag], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_setFreeGameAvailable = async (freeGameType: models.FreeGameTokenType, tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "loot_survivor_mock", + entrypoint: "set_free_game_available", + calldata: [freeGameType, tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const loot_survivor_mock_initializer = async (snAccount: Account | AccountInterface, ethAddress: string, lordsAddress: string, pragmaAddress: string) => { + try { + return await provider.execute( + snAccount, + { + contractName: "loot_survivor_mock", + entrypoint: "initializer", + calldata: [ethAddress, lordsAddress, pragmaAddress], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const pragma_mock_getDataMedian = async (dataType: models.DataType) => { + try { + return await provider.call("tournament", { + contractName: "pragma_mock", + entrypoint: "get_data_median", + calldata: [dataType], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_mint = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "eth_mock", + entrypoint: "mint", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_totalSupply = async () => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "total_supply", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_balanceOf = async (account: string) => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "balance_of", + calldata: [account], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_allowance = async (owner: string, spender: string) => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "allowance", + calldata: [owner, spender], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_transfer = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "eth_mock", + entrypoint: "transfer", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_transferFrom = async (snAccount: Account | AccountInterface, sender: string, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "eth_mock", + entrypoint: "transfer_from", + calldata: [sender, recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_approve = async (snAccount: Account | AccountInterface, spender: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "eth_mock", + entrypoint: "approve", + calldata: [spender, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_name = async () => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "name", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_symbol = async () => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "symbol", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const eth_mock_decimals = async () => { + try { + return await provider.call("tournament", { + contractName: "eth_mock", + entrypoint: "decimals", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_totalSupply = async () => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "total_supply", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_balanceOf = async (account: string) => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "balance_of", + calldata: [account], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_allowance = async (owner: string, spender: string) => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "allowance", + calldata: [owner, spender], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_transfer = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "lords_mock", + entrypoint: "transfer", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_transferFrom = async (snAccount: Account | AccountInterface, sender: string, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "lords_mock", + entrypoint: "transfer_from", + calldata: [sender, recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_approve = async (snAccount: Account | AccountInterface, spender: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "lords_mock", + entrypoint: "approve", + calldata: [spender, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_name = async () => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "name", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_symbol = async () => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "symbol", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_decimals = async () => { + try { + return await provider.call("tournament", { + contractName: "lords_mock", + entrypoint: "decimals", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const lords_mock_mint = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "lords_mock", + entrypoint: "mint", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_mint = async (snAccount: Account | AccountInterface, recipient: string, tokenId: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc721_mock", + entrypoint: "mint", + calldata: [recipient, tokenId], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_balanceOf = async (account: string) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "balance_of", + calldata: [account], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_ownerOf = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "owner_of", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_safeTransferFrom = async (snAccount: Account | AccountInterface, from: string, to: string, tokenId: BigNumberish, data: Array) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc721_mock", + entrypoint: "safe_transfer_from", + calldata: [from, to, tokenId, data], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_transferFrom = async (snAccount: Account | AccountInterface, from: string, to: string, tokenId: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc721_mock", + entrypoint: "transfer_from", + calldata: [from, to, tokenId], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_approve = async (snAccount: Account | AccountInterface, to: string, tokenId: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc721_mock", + entrypoint: "approve", + calldata: [to, tokenId], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_setApprovalForAll = async (snAccount: Account | AccountInterface, operator: string, approved: boolean) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc721_mock", + entrypoint: "set_approval_for_all", + calldata: [operator, approved], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_getApproved = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "get_approved", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_isApprovedForAll = async (owner: string, operator: string) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "is_approved_for_all", + calldata: [owner, operator], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_supportsInterface = async (interfaceId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "supports_interface", + calldata: [interfaceId], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_name = async () => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "name", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_symbol = async () => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "symbol", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const erc721_mock_tokenUri = async (tokenId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "erc721_mock", + entrypoint: "token_uri", + calldata: [tokenId], + }); + } catch (error) { + console.error(error); + } + }; + const LSTournament_totalTournaments = async () => { try { return await provider.call("tournament", { @@ -16,10 +788,194 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_tournament = async (tournamentId: BigNumberish) => { + const LSTournament_tournament = async (tournamentId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "LSTournament", + entrypoint: "tournament", + calldata: [tournamentId], + }); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_tournamentEntries = async (tournamentId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "LSTournament", + entrypoint: "tournament_entries", + calldata: [tournamentId], + }); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_tournamentPrizeKeys = async (tournamentId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "LSTournament", + entrypoint: "tournament_prize_keys", + calldata: [tournamentId], + }); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_topScores = async (tournamentId: BigNumberish) => { + try { + return await provider.call("tournament", { + contractName: "LSTournament", + entrypoint: "top_scores", + calldata: [tournamentId], + }); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_isTokenRegistered = async (token: string) => { + try { + return await provider.call("tournament", { + contractName: "LSTournament", + entrypoint: "is_token_registered", + calldata: [token], + }); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_createTournament = async (snAccount: Account | AccountInterface, name: BigNumberish, description: string, registrationStartTime: BigNumberish, registrationEndTime: BigNumberish, startTime: BigNumberish, endTime: BigNumberish, submissionPeriod: BigNumberish, winnersCount: BigNumberish, gatedType: models.CairoOption, entryPremium: models.CairoOption) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "create_tournament", + calldata: [name, description, registrationStartTime, registrationEndTime, startTime, endTime, submissionPeriod, winnersCount, gatedType, entryPremium], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_enterTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gatedSubmissionType: models.CairoOption) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "enter_tournament", + calldata: [tournamentId, gatedSubmissionType], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_startTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, startAll: boolean, startCount: models.CairoOption, clientRewardAddress: string, goldenTokenFreeGameTokenIds: Array, blobertFreeGameTokenIds: Array) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "start_tournament", + calldata: [tournamentId, startAll, startCount, clientRewardAddress, goldenTokenFreeGameTokenIds, blobertFreeGameTokenIds], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_submitScores = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gameIds: Array) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "submit_scores", + calldata: [tournamentId, gameIds], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_addPrize = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, token: string, tokenDataType: models.TokenDataTypeEnum, position: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "add_prize", + calldata: [tournamentId, token, tokenDataType, position], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const LSTournament_distributePrizes = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, prizeKeys: Array) => { + try { + return await provider.execute( + snAccount, + { + contractName: "LSTournament", + entrypoint: "distribute_prizes", + calldata: [tournamentId, prizeKeys], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const tournament_mock_initializer = async (snAccount: Account | AccountInterface, ethAddress: string, lordsAddress: string, lootSurvivorAddress: string, oracleAddress: string, goldenToken: string, blobert: string, safeMode: boolean, testMode: boolean, testErc20: string, testErc721: string) => { + try { + return await provider.execute( + snAccount, + { + contractName: "tournament_mock", + entrypoint: "initializer", + calldata: [ethAddress, lordsAddress, lootSurvivorAddress, oracleAddress, goldenToken, blobert, safeMode, testMode, testErc20, testErc721], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const tournament_mock_totalTournaments = async () => { + try { + return await provider.call("tournament", { + contractName: "tournament_mock", + entrypoint: "total_tournaments", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const tournament_mock_tournament = async (tournamentId: BigNumberish) => { try { return await provider.call("tournament", { - contractName: "LSTournament", + contractName: "tournament_mock", entrypoint: "tournament", calldata: [tournamentId], }); @@ -28,10 +984,10 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_tournamentEntries = async (tournamentId: BigNumberish) => { + const tournament_mock_tournamentEntries = async (tournamentId: BigNumberish) => { try { return await provider.call("tournament", { - contractName: "LSTournament", + contractName: "tournament_mock", entrypoint: "tournament_entries", calldata: [tournamentId], }); @@ -40,10 +996,10 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_tournamentPrizeKeys = async (tournamentId: BigNumberish) => { + const tournament_mock_tournamentPrizeKeys = async (tournamentId: BigNumberish) => { try { return await provider.call("tournament", { - contractName: "LSTournament", + contractName: "tournament_mock", entrypoint: "tournament_prize_keys", calldata: [tournamentId], }); @@ -52,10 +1008,10 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_topScores = async (tournamentId: BigNumberish) => { + const tournament_mock_topScores = async (tournamentId: BigNumberish) => { try { return await provider.call("tournament", { - contractName: "LSTournament", + contractName: "tournament_mock", entrypoint: "top_scores", calldata: [tournamentId], }); @@ -64,10 +1020,10 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_isTokenRegistered = async (token: string) => { + const tournament_mock_isTokenRegistered = async (token: string) => { try { return await provider.call("tournament", { - contractName: "LSTournament", + contractName: "tournament_mock", entrypoint: "is_token_registered", calldata: [token], }); @@ -76,14 +1032,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_registerTokens = async (snAccount: Account | AccountInterface, tokens: Array) => { + const tournament_mock_createTournament = async (snAccount: Account | AccountInterface, name: BigNumberish, description: string, registrationStartTime: BigNumberish, registrationEndTime: BigNumberish, startTime: BigNumberish, endTime: BigNumberish, submissionPeriod: BigNumberish, winnersCount: BigNumberish, gatedType: models.CairoOption, entryPremium: models.CairoOption) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "register_tokens", - calldata: [tokens], + contractName: "tournament_mock", + entrypoint: "create_tournament", + calldata: [name, description, registrationStartTime, registrationEndTime, startTime, endTime, submissionPeriod, winnersCount, gatedType, entryPremium], }, "tournament", ); @@ -92,14 +1048,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_createTournament = async (snAccount: Account | AccountInterface, name: BigNumberish, description: string, startTime: BigNumberish, endTime: BigNumberish, submissionPeriod: BigNumberish, winnersCount: BigNumberish, gatedType: models.CairoOption, entryPremium: models.CairoOption) => { + const tournament_mock_enterTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gatedSubmissionType: models.CairoOption) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "create_tournament", - calldata: [name, description, startTime, endTime, submissionPeriod, winnersCount, gatedType, entryPremium], + contractName: "tournament_mock", + entrypoint: "enter_tournament", + calldata: [tournamentId, gatedSubmissionType], }, "tournament", ); @@ -108,14 +1064,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_enterTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gatedSubmissionType: models.CairoOption) => { + const tournament_mock_startTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, startAll: boolean, startCount: models.CairoOption, clientRewardAddress: string, goldenTokenFreeGameTokenIds: Array, blobertFreeGameTokenIds: Array) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "enter_tournament", - calldata: [tournamentId, gatedSubmissionType], + contractName: "tournament_mock", + entrypoint: "start_tournament", + calldata: [tournamentId, startAll, startCount, clientRewardAddress, goldenTokenFreeGameTokenIds, blobertFreeGameTokenIds], }, "tournament", ); @@ -124,14 +1080,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_startTournament = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, startAll: boolean, startCount: models.CairoOption) => { + const tournament_mock_submitScores = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gameIds: Array) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "start_tournament", - calldata: [tournamentId, startAll, startCount], + contractName: "tournament_mock", + entrypoint: "submit_scores", + calldata: [tournamentId, gameIds], }, "tournament", ); @@ -140,14 +1096,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_submitScores = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, gameIds: Array) => { + const tournament_mock_addPrize = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, token: string, tokenDataType: models.TokenDataTypeEnum, position: BigNumberish) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "submit_scores", - calldata: [tournamentId, gameIds], + contractName: "tournament_mock", + entrypoint: "add_prize", + calldata: [tournamentId, token, tokenDataType, position], }, "tournament", ); @@ -156,14 +1112,14 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_addPrize = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, token: string, tokenDataType: models.TokenDataTypeEnum, position: BigNumberish) => { + const tournament_mock_distributePrizes = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, prizeKeys: Array) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "add_prize", - calldata: [tournamentId, token, tokenDataType, position], + contractName: "tournament_mock", + entrypoint: "distribute_prizes", + calldata: [tournamentId, prizeKeys], }, "tournament", ); @@ -172,14 +1128,98 @@ export async function setupWorld(provider: DojoProvider) { } }; - const LSTournament_distributePrizes = async (snAccount: Account | AccountInterface, tournamentId: BigNumberish, prizeKeys: Array) => { + const erc20_mock_mint = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { try { return await provider.execute( snAccount, { - contractName: "LSTournament", - entrypoint: "distribute_prizes", - calldata: [tournamentId, prizeKeys], + contractName: "erc20_mock", + entrypoint: "mint", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_totalSupply = async () => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "total_supply", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_balanceOf = async (account: string) => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "balance_of", + calldata: [account], + }); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_allowance = async (owner: string, spender: string) => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "allowance", + calldata: [owner, spender], + }); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_transfer = async (snAccount: Account | AccountInterface, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc20_mock", + entrypoint: "transfer", + calldata: [recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_transferFrom = async (snAccount: Account | AccountInterface, sender: string, recipient: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc20_mock", + entrypoint: "transfer_from", + calldata: [sender, recipient, amount], + }, + "tournament", + ); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_approve = async (snAccount: Account | AccountInterface, spender: string, amount: BigNumberish) => { + try { + return await provider.execute( + snAccount, + { + contractName: "erc20_mock", + entrypoint: "approve", + calldata: [spender, amount], }, "tournament", ); @@ -188,7 +1228,110 @@ export async function setupWorld(provider: DojoProvider) { } }; + const erc20_mock_name = async () => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "name", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_symbol = async () => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "symbol", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + + const erc20_mock_decimals = async () => { + try { + return await provider.call("tournament", { + contractName: "erc20_mock", + entrypoint: "decimals", + calldata: [], + }); + } catch (error) { + console.error(error); + } + }; + return { + loot_survivor_mock: { + balanceOf: loot_survivor_mock_balanceOf, + ownerOf: loot_survivor_mock_ownerOf, + safeTransferFrom: loot_survivor_mock_safeTransferFrom, + transferFrom: loot_survivor_mock_transferFrom, + approve: loot_survivor_mock_approve, + setApprovalForAll: loot_survivor_mock_setApprovalForAll, + getApproved: loot_survivor_mock_getApproved, + isApprovedForAll: loot_survivor_mock_isApprovedForAll, + supportsInterface: loot_survivor_mock_supportsInterface, + name: loot_survivor_mock_name, + symbol: loot_survivor_mock_symbol, + tokenUri: loot_survivor_mock_tokenUri, + getAdventurer: loot_survivor_mock_getAdventurer, + getAdventurerMeta: loot_survivor_mock_getAdventurerMeta, + getBag: loot_survivor_mock_getBag, + getCostToPlay: loot_survivor_mock_getCostToPlay, + freeGameAvailable: loot_survivor_mock_freeGameAvailable, + newGame: loot_survivor_mock_newGame, + setAdventurer: loot_survivor_mock_setAdventurer, + setAdventurerMeta: loot_survivor_mock_setAdventurerMeta, + setBag: loot_survivor_mock_setBag, + setFreeGameAvailable: loot_survivor_mock_setFreeGameAvailable, + initializer: loot_survivor_mock_initializer, + }, + pragma_mock: { + getDataMedian: pragma_mock_getDataMedian, + }, + eth_mock: { + mint: eth_mock_mint, + totalSupply: eth_mock_totalSupply, + balanceOf: eth_mock_balanceOf, + allowance: eth_mock_allowance, + transfer: eth_mock_transfer, + transferFrom: eth_mock_transferFrom, + approve: eth_mock_approve, + name: eth_mock_name, + symbol: eth_mock_symbol, + decimals: eth_mock_decimals, + }, + lords_mock: { + totalSupply: lords_mock_totalSupply, + balanceOf: lords_mock_balanceOf, + allowance: lords_mock_allowance, + transfer: lords_mock_transfer, + transferFrom: lords_mock_transferFrom, + approve: lords_mock_approve, + name: lords_mock_name, + symbol: lords_mock_symbol, + decimals: lords_mock_decimals, + mint: lords_mock_mint, + }, + erc721_mock: { + mint: erc721_mock_mint, + balanceOf: erc721_mock_balanceOf, + ownerOf: erc721_mock_ownerOf, + safeTransferFrom: erc721_mock_safeTransferFrom, + transferFrom: erc721_mock_transferFrom, + approve: erc721_mock_approve, + setApprovalForAll: erc721_mock_setApprovalForAll, + getApproved: erc721_mock_getApproved, + isApprovedForAll: erc721_mock_isApprovedForAll, + supportsInterface: erc721_mock_supportsInterface, + name: erc721_mock_name, + symbol: erc721_mock_symbol, + tokenUri: erc721_mock_tokenUri, + }, LSTournament: { totalTournaments: LSTournament_totalTournaments, tournament: LSTournament_tournament, @@ -196,7 +1339,6 @@ export async function setupWorld(provider: DojoProvider) { tournamentPrizeKeys: LSTournament_tournamentPrizeKeys, topScores: LSTournament_topScores, isTokenRegistered: LSTournament_isTokenRegistered, - registerTokens: LSTournament_registerTokens, createTournament: LSTournament_createTournament, enterTournament: LSTournament_enterTournament, startTournament: LSTournament_startTournament, @@ -204,5 +1346,32 @@ export async function setupWorld(provider: DojoProvider) { addPrize: LSTournament_addPrize, distributePrizes: LSTournament_distributePrizes, }, + tournament_mock: { + initializer: tournament_mock_initializer, + totalTournaments: tournament_mock_totalTournaments, + tournament: tournament_mock_tournament, + tournamentEntries: tournament_mock_tournamentEntries, + tournamentPrizeKeys: tournament_mock_tournamentPrizeKeys, + topScores: tournament_mock_topScores, + isTokenRegistered: tournament_mock_isTokenRegistered, + createTournament: tournament_mock_createTournament, + enterTournament: tournament_mock_enterTournament, + startTournament: tournament_mock_startTournament, + submitScores: tournament_mock_submitScores, + addPrize: tournament_mock_addPrize, + distributePrizes: tournament_mock_distributePrizes, + }, + erc20_mock: { + mint: erc20_mock_mint, + totalSupply: erc20_mock_totalSupply, + balanceOf: erc20_mock_balanceOf, + allowance: erc20_mock_allowance, + transfer: erc20_mock_transfer, + transferFrom: erc20_mock_transferFrom, + approve: erc20_mock_approve, + name: erc20_mock_name, + symbol: erc20_mock_symbol, + decimals: erc20_mock_decimals, + }, }; } \ No newline at end of file diff --git a/contracts/bindings/typescript/models.gen.ts b/contracts/bindings/typescript/models.gen.ts index ecf987b..b8aa917 100644 --- a/contracts/bindings/typescript/models.gen.ts +++ b/contracts/bindings/typescript/models.gen.ts @@ -15,29 +15,193 @@ type RemoveFieldOrder = T extends object 'fieldOrder' > : T; -// Type definition for `tournament::ls15_components::models::tournament::PrizesModelValue` struct -export interface PrizesModelValue { +// Type definition for `tournament::ls15_components::models::loot_survivor::AdventurerMetaModelValue` struct +export interface AdventurerMetaModelValue { fieldOrder: string[]; - token: string; - token_data_type: TokenDataTypeEnum; - payout_position: BigNumberish; - claimed: boolean; + adventurer_meta: AdventurerMetadataStorage; } -export type InputPrizesModelValue = RemoveFieldOrder; +export type InputAdventurerMetaModelValue = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::ERC721Data` struct -export interface ERC721Data { +// Type definition for `tournament::ls15_components::models::loot_survivor::AdventurerMetadataStorage` struct +export interface AdventurerMetadataStorage { + fieldOrder: string[]; + birth_date: BigNumberish; + death_date: BigNumberish; + level_seed: BigNumberish; + item_specials_seed: BigNumberish; + rank_at_death: BigNumberish; + delay_stat_reveal: boolean; + golden_token_id: BigNumberish; +} +export type InputAdventurerMetadataStorage = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::AdventurerMetaModel` struct +export interface AdventurerMetaModel { + fieldOrder: string[]; + adventurer_id: BigNumberish; + adventurer_meta: AdventurerMetadataStorage; +} +export type InputAdventurerMetaModel = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Item` struct +export interface Item { + fieldOrder: string[]; + id: BigNumberish; + xp: BigNumberish; +} +export type InputItem = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Equipment` struct +export interface Equipment { + fieldOrder: string[]; + weapon: Item; + chest: Item; + head: Item; + waist: Item; + foot: Item; + hand: Item; + neck: Item; + ring: Item; +} +export type InputEquipment = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Adventurer` struct +export interface Adventurer { + fieldOrder: string[]; + health: BigNumberish; + xp: BigNumberish; + gold: BigNumberish; + beast_health: BigNumberish; + stat_upgrades_available: BigNumberish; + stats: Stats; + equipment: Equipment; + battle_action_count: BigNumberish; + mutated: boolean; + awaiting_item_specials: boolean; +} +export type InputAdventurer = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Stats` struct +export interface Stats { + fieldOrder: string[]; + strength: BigNumberish; + dexterity: BigNumberish; + vitality: BigNumberish; + intelligence: BigNumberish; + wisdom: BigNumberish; + charisma: BigNumberish; + luck: BigNumberish; +} +export type InputStats = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::AdventurerModelValue` struct +export interface AdventurerModelValue { + fieldOrder: string[]; + adventurer: Adventurer; +} +export type InputAdventurerModelValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::AdventurerModel` struct +export interface AdventurerModel { + fieldOrder: string[]; + adventurer_id: BigNumberish; + adventurer: Adventurer; +} +export type InputAdventurerModel = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::BagModel` struct +export interface BagModel { + fieldOrder: string[]; + adventurer_id: BigNumberish; + bag: Bag; +} +export type InputBagModel = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Bag` struct +export interface Bag { + fieldOrder: string[]; + item_1: Item; + item_2: Item; + item_3: Item; + item_4: Item; + item_5: Item; + item_6: Item; + item_7: Item; + item_8: Item; + item_9: Item; + item_10: Item; + item_11: Item; + item_12: Item; + item_13: Item; + item_14: Item; + item_15: Item; + mutated: boolean; +} +export type InputBag = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::BagModelValue` struct +export interface BagModelValue { + fieldOrder: string[]; + bag: Bag; +} +export type InputBagModelValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::ContractsValue` struct +export interface ContractsValue { + fieldOrder: string[]; + eth: string; + lords: string; + oracle: string; +} +export type InputContractsValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::Contracts` struct +export interface Contracts { + fieldOrder: string[]; + contract: string; + eth: string; + lords: string; + oracle: string; +} +export type InputContracts = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::FreeGameAvailableModel` struct +export interface FreeGameAvailableModel { fieldOrder: string[]; + free_game_type: FreeGameTokenType; token_id: BigNumberish; + available: boolean; } -export type InputERC721Data = RemoveFieldOrder; +export type InputFreeGameAvailableModel = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::ERC20Data` struct -export interface ERC20Data { +// Type definition for `tournament::ls15_components::models::loot_survivor::FreeGameAvailableModelValue` struct +export interface FreeGameAvailableModelValue { fieldOrder: string[]; - token_amount: BigNumberish; + available: boolean; } -export type InputERC20Data = RemoveFieldOrder; +export type InputFreeGameAvailableModelValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::GameCountModelValue` struct +export interface GameCountModelValue { + fieldOrder: string[]; + game_count: BigNumberish; +} +export type InputGameCountModelValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::loot_survivor::GameCountModel` struct +export interface GameCountModel { + fieldOrder: string[]; + contract_address: string; + game_count: BigNumberish; +} +export type InputGameCountModel = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::tournament::ERC721Data` struct +export interface ERC721Data { + fieldOrder: string[]; + token_id: BigNumberish; +} +export type InputERC721Data = RemoveFieldOrder; // Type definition for `tournament::ls15_components::models::tournament::PrizesModel` struct export interface PrizesModel { @@ -50,15 +214,22 @@ export interface PrizesModel { } export type InputPrizesModel = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TokenModelValue` struct -export interface TokenModelValue { +// Type definition for `tournament::ls15_components::models::tournament::PrizesModelValue` struct +export interface PrizesModelValue { fieldOrder: string[]; - name: string; - symbol: string; + token: string; token_data_type: TokenDataTypeEnum; - is_registered: boolean; + payout_position: BigNumberish; + claimed: boolean; } -export type InputTokenModelValue = RemoveFieldOrder; +export type InputPrizesModelValue = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::tournament::ERC20Data` struct +export interface ERC20Data { + fieldOrder: string[]; + token_amount: BigNumberish; +} +export type InputERC20Data = RemoveFieldOrder; // Type definition for `tournament::ls15_components::models::tournament::TokenModel` struct export interface TokenModel { @@ -71,6 +242,16 @@ export interface TokenModel { } export type InputTokenModel = RemoveFieldOrder; +// Type definition for `tournament::ls15_components::models::tournament::TokenModelValue` struct +export interface TokenModelValue { + fieldOrder: string[]; + name: string; + symbol: string; + token_data_type: TokenDataTypeEnum; + is_registered: boolean; +} +export type InputTokenModelValue = RemoveFieldOrder; + // Type definition for `tournament::ls15_components::models::tournament::TournamentConfigValue` struct export interface TournamentConfigValue { fieldOrder: string[]; @@ -78,6 +259,8 @@ export interface TournamentConfigValue { lords: string; loot_survivor: string; oracle: string; + golden_token: string; + blobert: string; safe_mode: boolean; test_mode: boolean; } @@ -91,11 +274,20 @@ export interface TournamentConfig { lords: string; loot_survivor: string; oracle: string; + golden_token: string; + blobert: string; safe_mode: boolean; test_mode: boolean; } export type InputTournamentConfig = RemoveFieldOrder; +// Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesAddressModelValue` struct +export interface TournamentEntriesAddressModelValue { + fieldOrder: string[]; + entry_count: BigNumberish; +} +export type InputTournamentEntriesAddressModelValue = RemoveFieldOrder; + // Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesAddressModel` struct export interface TournamentEntriesAddressModel { fieldOrder: string[]; @@ -105,12 +297,15 @@ export interface TournamentEntriesAddressModel { } export type InputTournamentEntriesAddressModel = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesAddressModelValue` struct -export interface TournamentEntriesAddressModelValue { +// Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesModel` struct +export interface TournamentEntriesModel { fieldOrder: string[]; + tournament_id: BigNumberish; entry_count: BigNumberish; + premiums_formatted: boolean; + distribute_called: boolean; } -export type InputTournamentEntriesAddressModelValue = RemoveFieldOrder; +export type InputTournamentEntriesModel = RemoveFieldOrder; // Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesModelValue` struct export interface TournamentEntriesModelValue { @@ -121,16 +316,6 @@ export interface TournamentEntriesModelValue { } export type InputTournamentEntriesModelValue = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentEntriesModel` struct -export interface TournamentEntriesModel { - fieldOrder: string[]; - tournament_id: BigNumberish; - entry_count: BigNumberish; - premiums_formatted: boolean; - distribute_called: boolean; -} -export type InputTournamentEntriesModel = RemoveFieldOrder; - // Type definition for `tournament::ls15_components::models::tournament::TournamentEntryAddressesModelValue` struct export interface TournamentEntryAddressesModelValue { fieldOrder: string[]; @@ -164,15 +349,31 @@ export interface TournamentGameModelValue { } export type InputTournamentGameModelValue = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::Premium` struct -export interface Premium { +// Type definition for `tournament::ls15_components::models::tournament::TournamentModel` struct +export interface TournamentModel { + fieldOrder: string[]; + tournament_id: BigNumberish; + name: BigNumberish; + description: string; + creator: string; + registration_start_time: BigNumberish; + registration_end_time: BigNumberish; + start_time: BigNumberish; + end_time: BigNumberish; + submission_period: BigNumberish; + winners_count: BigNumberish; + gated_type: CairoOption; + entry_premium: CairoOption; +} +export type InputTournamentModel = RemoveFieldOrder; + +// Type definition for `tournament::ls15_components::models::tournament::GatedToken` struct +export interface GatedToken { fieldOrder: string[]; token: string; - token_amount: BigNumberish; - token_distribution: Array; - creator_fee: BigNumberish; + entry_type: GatedEntryType; } -export type InputPremium = RemoveFieldOrder; +export type InputGatedToken = RemoveFieldOrder; // Type definition for `tournament::ls15_components::models::tournament::EntryCriteria` struct export interface EntryCriteria { @@ -182,20 +383,14 @@ export interface EntryCriteria { } export type InputEntryCriteria = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::GatedToken` struct -export interface GatedToken { - fieldOrder: string[]; - token: string; - entry_type: GatedEntryType; -} -export type InputGatedToken = RemoveFieldOrder; - // Type definition for `tournament::ls15_components::models::tournament::TournamentModelValue` struct export interface TournamentModelValue { fieldOrder: string[]; name: BigNumberish; description: string; creator: string; + registration_start_time: BigNumberish; + registration_end_time: BigNumberish; start_time: BigNumberish; end_time: BigNumberish; submission_period: BigNumberish; @@ -205,36 +400,30 @@ export interface TournamentModelValue { } export type InputTournamentModelValue = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentModel` struct -export interface TournamentModel { +// Type definition for `tournament::ls15_components::models::tournament::Premium` struct +export interface Premium { fieldOrder: string[]; - tournament_id: BigNumberish; - name: BigNumberish; - description: string; - creator: string; - start_time: BigNumberish; - end_time: BigNumberish; - submission_period: BigNumberish; - winners_count: BigNumberish; - gated_type: CairoOption; - entry_premium: CairoOption; + token: string; + token_amount: BigNumberish; + token_distribution: Array; + creator_fee: BigNumberish; } -export type InputTournamentModel = RemoveFieldOrder; +export type InputPremium = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentPrizeKeysModel` struct -export interface TournamentPrizeKeysModel { +// Type definition for `tournament::ls15_components::models::tournament::TournamentPrizeKeysModelValue` struct +export interface TournamentPrizeKeysModelValue { fieldOrder: string[]; - tournament_id: BigNumberish; prize_keys: Array; } -export type InputTournamentPrizeKeysModel = RemoveFieldOrder; +export type InputTournamentPrizeKeysModelValue = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentPrizeKeysModelValue` struct -export interface TournamentPrizeKeysModelValue { +// Type definition for `tournament::ls15_components::models::tournament::TournamentPrizeKeysModel` struct +export interface TournamentPrizeKeysModel { fieldOrder: string[]; + tournament_id: BigNumberish; prize_keys: Array; } -export type InputTournamentPrizeKeysModelValue = RemoveFieldOrder; +export type InputTournamentPrizeKeysModel = RemoveFieldOrder; // Type definition for `tournament::ls15_components::models::tournament::TournamentScoresModelValue` struct export interface TournamentScoresModelValue { @@ -251,6 +440,13 @@ export interface TournamentScoresModel { } export type InputTournamentScoresModel = RemoveFieldOrder; +// Type definition for `tournament::ls15_components::models::tournament::TournamentStartIdsModelValue` struct +export interface TournamentStartIdsModelValue { + fieldOrder: string[]; + game_ids: Array; +} +export type InputTournamentStartIdsModelValue = RemoveFieldOrder; + // Type definition for `tournament::ls15_components::models::tournament::TournamentStartIdsModel` struct export interface TournamentStartIdsModel { fieldOrder: string[]; @@ -260,13 +456,6 @@ export interface TournamentStartIdsModel { } export type InputTournamentStartIdsModel = RemoveFieldOrder; -// Type definition for `tournament::ls15_components::models::tournament::TournamentStartIdsModelValue` struct -export interface TournamentStartIdsModelValue { - fieldOrder: string[]; - game_ids: Array; -} -export type InputTournamentStartIdsModelValue = RemoveFieldOrder; - // Type definition for `tournament::ls15_components::models::tournament::TournamentStartsAddressModelValue` struct export interface TournamentStartsAddressModelValue { fieldOrder: string[]; @@ -300,6 +489,12 @@ export interface TournamentTotalsModel { } export type InputTournamentTotalsModel = RemoveFieldOrder; +// Type definition for `tournament::ls15_components::models::tournament::FreeGameTokenType` enum +export enum FreeGameTokenType { + GoldenToken, + LaunchTournamentChampion, +} + // Type definition for `tournament::ls15_components::models::tournament::TokenDataType` enum export type TokenDataType = { erc20: ERC20Data; @@ -329,33 +524,51 @@ export type GatedTypeEnum = TypedCairoEnum; export interface SchemaType extends ISchemaType { tournament: { - PrizesModelValue: PrizesModelValue, + AdventurerMetaModelValue: AdventurerMetaModelValue, + AdventurerMetadataStorage: AdventurerMetadataStorage, + AdventurerMetaModel: AdventurerMetaModel, + Item: Item, + Equipment: Equipment, + Adventurer: Adventurer, + Stats: Stats, + AdventurerModelValue: AdventurerModelValue, + AdventurerModel: AdventurerModel, + BagModel: BagModel, + Bag: Bag, + BagModelValue: BagModelValue, + ContractsValue: ContractsValue, + Contracts: Contracts, + FreeGameAvailableModel: FreeGameAvailableModel, + FreeGameAvailableModelValue: FreeGameAvailableModelValue, + GameCountModelValue: GameCountModelValue, + GameCountModel: GameCountModel, ERC721Data: ERC721Data, - ERC20Data: ERC20Data, PrizesModel: PrizesModel, - TokenModelValue: TokenModelValue, + PrizesModelValue: PrizesModelValue, + ERC20Data: ERC20Data, TokenModel: TokenModel, + TokenModelValue: TokenModelValue, TournamentConfigValue: TournamentConfigValue, TournamentConfig: TournamentConfig, - TournamentEntriesAddressModel: TournamentEntriesAddressModel, TournamentEntriesAddressModelValue: TournamentEntriesAddressModelValue, - TournamentEntriesModelValue: TournamentEntriesModelValue, + TournamentEntriesAddressModel: TournamentEntriesAddressModel, TournamentEntriesModel: TournamentEntriesModel, + TournamentEntriesModelValue: TournamentEntriesModelValue, TournamentEntryAddressesModelValue: TournamentEntryAddressesModelValue, TournamentEntryAddressesModel: TournamentEntryAddressesModel, TournamentGameModel: TournamentGameModel, TournamentGameModelValue: TournamentGameModelValue, - Premium: Premium, - EntryCriteria: EntryCriteria, + TournamentModel: TournamentModel, GatedToken: GatedToken, + EntryCriteria: EntryCriteria, TournamentModelValue: TournamentModelValue, - TournamentModel: TournamentModel, - TournamentPrizeKeysModel: TournamentPrizeKeysModel, + Premium: Premium, TournamentPrizeKeysModelValue: TournamentPrizeKeysModelValue, + TournamentPrizeKeysModel: TournamentPrizeKeysModel, TournamentScoresModelValue: TournamentScoresModelValue, TournamentScoresModel: TournamentScoresModel, - TournamentStartIdsModel: TournamentStartIdsModel, TournamentStartIdsModelValue: TournamentStartIdsModelValue, + TournamentStartIdsModel: TournamentStartIdsModel, TournamentStartsAddressModelValue: TournamentStartsAddressModelValue, TournamentStartsAddressModel: TournamentStartsAddressModel, TournamentTotalsModelValue: TournamentTotalsModelValue, @@ -364,21 +577,137 @@ export interface SchemaType extends ISchemaType { } export const schema: SchemaType = { tournament: { - PrizesModelValue: { - fieldOrder: ['token', 'token_data_type', 'payout_position', 'claimed'], - token: "", - token_data_type: { fieldOrder: ['token_amount'], token_amount: 0, }, - payout_position: 0, - claimed: false, + AdventurerMetaModelValue: { + fieldOrder: ['adventurer_meta'], + adventurer_meta: { fieldOrder: ['birth_date', 'death_date', 'level_seed', 'item_specials_seed', 'rank_at_death', 'delay_stat_reveal', 'golden_token_id'], birth_date: 0, death_date: 0, level_seed: 0, item_specials_seed: 0, rank_at_death: 0, delay_stat_reveal: false, golden_token_id: 0, }, + }, + AdventurerMetadataStorage: { + fieldOrder: ['birth_date', 'death_date', 'level_seed', 'item_specials_seed', 'rank_at_death', 'delay_stat_reveal', 'golden_token_id'], + birth_date: 0, + death_date: 0, + level_seed: 0, + item_specials_seed: 0, + rank_at_death: 0, + delay_stat_reveal: false, + golden_token_id: 0, + }, + AdventurerMetaModel: { + fieldOrder: ['adventurer_id', 'adventurer_meta'], + adventurer_id: 0, + adventurer_meta: { fieldOrder: ['birth_date', 'death_date', 'level_seed', 'item_specials_seed', 'rank_at_death', 'delay_stat_reveal', 'golden_token_id'], birth_date: 0, death_date: 0, level_seed: 0, item_specials_seed: 0, rank_at_death: 0, delay_stat_reveal: false, golden_token_id: 0, }, + }, + Item: { + fieldOrder: ['id', 'xp'], + id: 0, + xp: 0, + }, + Equipment: { + fieldOrder: ['weapon', 'chest', 'head', 'waist', 'foot', 'hand', 'neck', 'ring'], + weapon: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + chest: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + head: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + waist: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + foot: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + hand: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + neck: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + ring: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + }, + Adventurer: { + fieldOrder: ['health', 'xp', 'gold', 'beast_health', 'stat_upgrades_available', 'stats', 'equipment', 'battle_action_count', 'mutated', 'awaiting_item_specials'], + health: 0, + xp: 0, + gold: 0, + beast_health: 0, + stat_upgrades_available: 0, + stats: { fieldOrder: ['strength', 'dexterity', 'vitality', 'intelligence', 'wisdom', 'charisma', 'luck'], strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0, }, + equipment: { fieldOrder: ['weapon', 'chest', 'head', 'waist', 'foot', 'hand', 'neck', 'ring'], weapon: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, chest: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, head: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, waist: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, foot: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, hand: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, neck: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, ring: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, }, + battle_action_count: 0, + mutated: false, + awaiting_item_specials: false, + }, + Stats: { + fieldOrder: ['strength', 'dexterity', 'vitality', 'intelligence', 'wisdom', 'charisma', 'luck'], + strength: 0, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 0, + luck: 0, + }, + AdventurerModelValue: { + fieldOrder: ['adventurer'], + adventurer: { fieldOrder: ['health', 'xp', 'gold', 'beast_health', 'stat_upgrades_available', 'stats', 'equipment', 'battle_action_count', 'mutated', 'awaiting_item_specials'], health: 0, xp: 0, gold: 0, beast_health: 0, stat_upgrades_available: 0, stats: { fieldOrder: ['strength', 'dexterity', 'vitality', 'intelligence', 'wisdom', 'charisma', 'luck'], strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0, }, equipment: { fieldOrder: ['weapon', 'chest', 'head', 'waist', 'foot', 'hand', 'neck', 'ring'], weapon: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, chest: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, head: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, waist: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, foot: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, hand: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, neck: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, ring: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, }, battle_action_count: 0, mutated: false, awaiting_item_specials: false, }, + }, + AdventurerModel: { + fieldOrder: ['adventurer_id', 'adventurer'], + adventurer_id: 0, + adventurer: { fieldOrder: ['health', 'xp', 'gold', 'beast_health', 'stat_upgrades_available', 'stats', 'equipment', 'battle_action_count', 'mutated', 'awaiting_item_specials'], health: 0, xp: 0, gold: 0, beast_health: 0, stat_upgrades_available: 0, stats: { fieldOrder: ['strength', 'dexterity', 'vitality', 'intelligence', 'wisdom', 'charisma', 'luck'], strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0, }, equipment: { fieldOrder: ['weapon', 'chest', 'head', 'waist', 'foot', 'hand', 'neck', 'ring'], weapon: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, chest: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, head: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, waist: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, foot: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, hand: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, neck: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, ring: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, }, battle_action_count: 0, mutated: false, awaiting_item_specials: false, }, + }, + BagModel: { + fieldOrder: ['adventurer_id', 'bag'], + adventurer_id: 0, + bag: { fieldOrder: ['item_1', 'item_2', 'item_3', 'item_4', 'item_5', 'item_6', 'item_7', 'item_8', 'item_9', 'item_10', 'item_11', 'item_12', 'item_13', 'item_14', 'item_15', 'mutated'], item_1: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_2: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_3: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_4: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_5: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_6: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_7: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_8: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_9: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_10: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_11: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_12: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_13: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_14: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_15: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, mutated: false, }, + }, + Bag: { + fieldOrder: ['item_1', 'item_2', 'item_3', 'item_4', 'item_5', 'item_6', 'item_7', 'item_8', 'item_9', 'item_10', 'item_11', 'item_12', 'item_13', 'item_14', 'item_15', 'mutated'], + item_1: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_2: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_3: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_4: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_5: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_6: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_7: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_8: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_9: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_10: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_11: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_12: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_13: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_14: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + item_15: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, + mutated: false, + }, + BagModelValue: { + fieldOrder: ['bag'], + bag: { fieldOrder: ['item_1', 'item_2', 'item_3', 'item_4', 'item_5', 'item_6', 'item_7', 'item_8', 'item_9', 'item_10', 'item_11', 'item_12', 'item_13', 'item_14', 'item_15', 'mutated'], item_1: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_2: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_3: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_4: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_5: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_6: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_7: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_8: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_9: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_10: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_11: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_12: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_13: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_14: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, item_15: { fieldOrder: ['id', 'xp'], id: 0, xp: 0, }, mutated: false, }, + }, + ContractsValue: { + fieldOrder: ['eth', 'lords', 'oracle'], + eth: "", + lords: "", + oracle: "", + }, + Contracts: { + fieldOrder: ['contract', 'eth', 'lords', 'oracle'], + contract: "", + eth: "", + lords: "", + oracle: "", + }, + FreeGameAvailableModel: { + fieldOrder: ['free_game_type', 'token_id', 'available'], + free_game_type: FreeGameTokenType.GoldenToken, + token_id: 0, + available: false, + }, + FreeGameAvailableModelValue: { + fieldOrder: ['available'], + available: false, + }, + GameCountModelValue: { + fieldOrder: ['game_count'], + game_count: 0, + }, + GameCountModel: { + fieldOrder: ['contract_address', 'game_count'], + contract_address: "", + game_count: 0, }, ERC721Data: { fieldOrder: ['token_id'], token_id: 0, }, - ERC20Data: { - fieldOrder: ['token_amount'], - token_amount: 0, - }, PrizesModel: { fieldOrder: ['prize_key', 'token', 'token_data_type', 'payout_position', 'claimed'], prize_key: 0, @@ -387,12 +716,16 @@ export const schema: SchemaType = { payout_position: 0, claimed: false, }, - TokenModelValue: { - fieldOrder: ['name', 'symbol', 'token_data_type', 'is_registered'], - name: "", - symbol: "", + PrizesModelValue: { + fieldOrder: ['token', 'token_data_type', 'payout_position', 'claimed'], + token: "", token_data_type: { fieldOrder: ['token_amount'], token_amount: 0, }, - is_registered: false, + payout_position: 0, + claimed: false, + }, + ERC20Data: { + fieldOrder: ['token_amount'], + token_amount: 0, }, TokenModel: { fieldOrder: ['token', 'name', 'symbol', 'token_data_type', 'is_registered'], @@ -402,44 +735,55 @@ export const schema: SchemaType = { token_data_type: { fieldOrder: ['token_amount'], token_amount: 0, }, is_registered: false, }, + TokenModelValue: { + fieldOrder: ['name', 'symbol', 'token_data_type', 'is_registered'], + name: "", + symbol: "", + token_data_type: { fieldOrder: ['token_amount'], token_amount: 0, }, + is_registered: false, + }, TournamentConfigValue: { - fieldOrder: ['eth', 'lords', 'loot_survivor', 'oracle', 'safe_mode', 'test_mode'], + fieldOrder: ['eth', 'lords', 'loot_survivor', 'oracle', 'golden_token', 'blobert', 'safe_mode', 'test_mode'], eth: "", lords: "", loot_survivor: "", oracle: "", + golden_token: "", + blobert: "", safe_mode: false, test_mode: false, }, TournamentConfig: { - fieldOrder: ['contract', 'eth', 'lords', 'loot_survivor', 'oracle', 'safe_mode', 'test_mode'], + fieldOrder: ['contract', 'eth', 'lords', 'loot_survivor', 'oracle', 'golden_token', 'blobert', 'safe_mode', 'test_mode'], contract: "", eth: "", lords: "", loot_survivor: "", oracle: "", + golden_token: "", + blobert: "", safe_mode: false, test_mode: false, }, + TournamentEntriesAddressModelValue: { + fieldOrder: ['entry_count'], + entry_count: 0, + }, TournamentEntriesAddressModel: { fieldOrder: ['tournament_id', 'address', 'entry_count'], tournament_id: 0, address: "", entry_count: 0, }, - TournamentEntriesAddressModelValue: { - fieldOrder: ['entry_count'], - entry_count: 0, - }, - TournamentEntriesModelValue: { - fieldOrder: ['entry_count', 'premiums_formatted', 'distribute_called'], + TournamentEntriesModel: { + fieldOrder: ['tournament_id', 'entry_count', 'premiums_formatted', 'distribute_called'], + tournament_id: 0, entry_count: 0, premiums_formatted: false, distribute_called: false, }, - TournamentEntriesModel: { - fieldOrder: ['tournament_id', 'entry_count', 'premiums_formatted', 'distribute_called'], - tournament_id: 0, + TournamentEntriesModelValue: { + fieldOrder: ['entry_count', 'premiums_formatted', 'distribute_called'], entry_count: 0, premiums_formatted: false, distribute_called: false, @@ -465,28 +809,14 @@ export const schema: SchemaType = { address: "", status: EntryStatus.Started, }, - Premium: { - fieldOrder: ['token', 'token_amount', 'token_distribution', 'creator_fee'], - token: "", - token_amount: 0, - token_distribution: [0], - creator_fee: 0, - }, - EntryCriteria: { - fieldOrder: ['token_id', 'entry_count'], - token_id: 0, - entry_count: 0, - }, - GatedToken: { - fieldOrder: ['token', 'entry_type'], - token: "", - entry_type: GatedEntryType.criteria, - }, - TournamentModelValue: { - fieldOrder: ['name', 'description', 'creator', 'start_time', 'end_time', 'submission_period', 'winners_count', 'gated_type', 'entry_premium'], + TournamentModel: { + fieldOrder: ['tournament_id', 'name', 'description', 'creator', 'registration_start_time', 'registration_end_time', 'start_time', 'end_time', 'submission_period', 'winners_count', 'gated_type', 'entry_premium'], + tournament_id: 0, name: 0, description: "", creator: "", + registration_start_time: 0, + registration_end_time: 0, start_time: 0, end_time: 0, submission_period: 0, @@ -494,12 +824,23 @@ export const schema: SchemaType = { gated_type: Option, entry_premium: Option, }, - TournamentModel: { - fieldOrder: ['tournament_id', 'name', 'description', 'creator', 'start_time', 'end_time', 'submission_period', 'winners_count', 'gated_type', 'entry_premium'], - tournament_id: 0, + GatedToken: { + fieldOrder: ['token', 'entry_type'], + token: "", + entry_type: GatedEntryType.criteria, + }, + EntryCriteria: { + fieldOrder: ['token_id', 'entry_count'], + token_id: 0, + entry_count: 0, + }, + TournamentModelValue: { + fieldOrder: ['name', 'description', 'creator', 'registration_start_time', 'registration_end_time', 'start_time', 'end_time', 'submission_period', 'winners_count', 'gated_type', 'entry_premium'], name: 0, description: "", creator: "", + registration_start_time: 0, + registration_end_time: 0, start_time: 0, end_time: 0, submission_period: 0, @@ -507,15 +848,22 @@ export const schema: SchemaType = { gated_type: Option, entry_premium: Option, }, - TournamentPrizeKeysModel: { - fieldOrder: ['tournament_id', 'prize_keys'], - tournament_id: 0, - prize_keys: [0], + Premium: { + fieldOrder: ['token', 'token_amount', 'token_distribution', 'creator_fee'], + token: "", + token_amount: 0, + token_distribution: [0], + creator_fee: 0, }, TournamentPrizeKeysModelValue: { fieldOrder: ['prize_keys'], prize_keys: [0], }, + TournamentPrizeKeysModel: { + fieldOrder: ['tournament_id', 'prize_keys'], + tournament_id: 0, + prize_keys: [0], + }, TournamentScoresModelValue: { fieldOrder: ['top_score_ids'], top_score_ids: [0], @@ -525,16 +873,16 @@ export const schema: SchemaType = { tournament_id: 0, top_score_ids: [0], }, + TournamentStartIdsModelValue: { + fieldOrder: ['game_ids'], + game_ids: [0], + }, TournamentStartIdsModel: { fieldOrder: ['tournament_id', 'address', 'game_ids'], tournament_id: 0, address: "", game_ids: [0], }, - TournamentStartIdsModelValue: { - fieldOrder: ['game_ids'], - game_ids: [0], - }, TournamentStartsAddressModelValue: { fieldOrder: ['start_count'], start_count: 0, diff --git a/contracts/dojo_slot.toml b/contracts/dojo_slot.toml index cecc86f..6380d30 100644 --- a/contracts/dojo_slot.toml +++ b/contracts/dojo_slot.toml @@ -42,6 +42,7 @@ private_key = "0x3e3979c1ed728490308054fe357a9f49cf67f80f9721f44cc57235129e090f4 "tournament-AdventurerMetaModel" = ["tournament-loot_survivor_mock"] "tournament-BagModel" = ["tournament-loot_survivor_mock"] "tournament-GameCountModel" = ["tournament-loot_survivor_mock"] +"tournament-FreeGameAvailableModel" = ["tournament-loot_survivor_mock"] "tournament-Contracts" = ["tournament-loot_survivor_mock"] [migration] diff --git a/contracts/manifest_slot.json b/contracts/manifest_slot.json index ec67967..88e3f65 100644 --- a/contracts/manifest_slot.json +++ b/contracts/manifest_slot.json @@ -1252,8 +1252,797 @@ }, "contracts": [ { - "address": "0x3287f5cd52f9ae131061f082a2a8f97f26fc074d3b029a3903fcb31c3046841", - "class_hash": "0x2e166f678f8f86d4bd535353ce6691fae3a8c38c8cd9c3e050149c4504e3d80", + "address": "0x1a4ee4c4b16cd36d2bb4cb861a622f8e814daf5035716517d76af76fa2a4dfa", + "class_hash": "0x32b12f5c4ce3a3e31017409f3ffc07857822e986634d492b7ccabb796333d08", + "abi": [ + { + "type": "impl", + "name": "LSTournament__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [] + }, + { + "type": "impl", + "name": "LSTournament__DeployedContractImpl", + "interface_name": "dojo::meta::interface::IDeployedResource" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::meta::interface::IDeployedResource", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [ + { + "name": "safe_mode", + "type": "core::bool" + }, + { + "name": "test_mode", + "type": "core::bool" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "TournamentImpl", + "interface_name": "tournament::ls15_components::tournament::ITournament" + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::EntryCriteria", + "members": [ + { + "name": "token_id", + "type": "core::integer::u128" + }, + { + "name": "entry_count", + "type": "core::integer::u64" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::GatedEntryType", + "variants": [ + { + "name": "criteria", + "type": "core::array::Span::" + }, + { + "name": "uniform", + "type": "core::integer::u64" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::GatedToken", + "members": [ + { + "name": "token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "entry_type", + "type": "tournament::ls15_components::models::tournament::GatedEntryType" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::GatedType", + "variants": [ + { + "name": "token", + "type": "tournament::ls15_components::models::tournament::GatedToken" + }, + { + "name": "tournament", + "type": "core::array::Span::" + }, + { + "name": "address", + "type": "core::array::Span::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "tournament::ls15_components::models::tournament::GatedType" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::Premium", + "members": [ + { + "name": "token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_amount", + "type": "core::integer::u128" + }, + { + "name": "token_distribution", + "type": "core::array::Span::" + }, + { + "name": "creator_fee", + "type": "core::integer::u8" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "tournament::ls15_components::models::tournament::Premium" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::TournamentModel", + "members": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "description", + "type": "core::byte_array::ByteArray" + }, + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "registration_start_time", + "type": "core::integer::u64" + }, + { + "name": "registration_end_time", + "type": "core::integer::u64" + }, + { + "name": "start_time", + "type": "core::integer::u64" + }, + { + "name": "end_time", + "type": "core::integer::u64" + }, + { + "name": "submission_period", + "type": "core::integer::u64" + }, + { + "name": "winners_count", + "type": "core::integer::u8" + }, + { + "name": "gated_type", + "type": "core::option::Option::" + }, + { + "name": "entry_premium", + "type": "core::option::Option::" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::GatedSubmissionType", + "variants": [ + { + "name": "token_id", + "type": "core::integer::u256" + }, + { + "name": "game_id", + "type": "core::array::Span::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "tournament::ls15_components::models::tournament::GatedSubmissionType" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::integer::u64" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::ERC20Data", + "members": [ + { + "name": "token_amount", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::ERC721Data", + "members": [ + { + "name": "token_id", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::TokenDataType", + "variants": [ + { + "name": "erc20", + "type": "tournament::ls15_components::models::tournament::ERC20Data" + }, + { + "name": "erc721", + "type": "tournament::ls15_components::models::tournament::ERC721Data" + } + ] + }, + { + "type": "interface", + "name": "tournament::ls15_components::tournament::ITournament", + "items": [ + { + "type": "function", + "name": "total_tournaments", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "tournament", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + } + ], + "outputs": [ + { + "type": "tournament::ls15_components::models::tournament::TournamentModel" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "tournament_entries", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + } + ], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "tournament_prize_keys", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + } + ], + "outputs": [ + { + "type": "core::array::Array::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "top_scores", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + } + ], + "outputs": [ + { + "type": "core::array::Array::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "is_token_registered", + "inputs": [ + { + "name": "token", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "create_tournament", + "inputs": [ + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "description", + "type": "core::byte_array::ByteArray" + }, + { + "name": "registration_start_time", + "type": "core::integer::u64" + }, + { + "name": "registration_end_time", + "type": "core::integer::u64" + }, + { + "name": "start_time", + "type": "core::integer::u64" + }, + { + "name": "end_time", + "type": "core::integer::u64" + }, + { + "name": "submission_period", + "type": "core::integer::u64" + }, + { + "name": "winners_count", + "type": "core::integer::u8" + }, + { + "name": "gated_type", + "type": "core::option::Option::" + }, + { + "name": "entry_premium", + "type": "core::option::Option::" + } + ], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "enter_tournament", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "gated_submission_type", + "type": "core::option::Option::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "start_tournament", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "start_all", + "type": "core::bool" + }, + { + "name": "start_count", + "type": "core::option::Option::" + }, + { + "name": "client_reward_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "golden_token_free_game_token_ids", + "type": "core::array::Span::" + }, + { + "name": "blobert_free_game_token_ids", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "submit_scores", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "game_ids", + "type": "core::array::Array::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "add_prize", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_data_type", + "type": "tournament::ls15_components::models::tournament::TokenDataType" + }, + { + "name": "position", + "type": "core::integer::u8" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "distribute_prizes", + "inputs": [ + { + "name": "tournament_id", + "type": "core::integer::u64" + }, + { + "name": "prize_keys", + "type": "core::array::Array::" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "tournament::ls15_components::tournament::tournament_component::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "tournament::presets::ls_tournament::LSTournament::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + }, + { + "name": "TournamentEvent", + "type": "tournament::ls15_components::tournament::tournament_component::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [], + "tag": "tournament-LSTournament", + "selector": "0x4d91e5e6083e568f5c87da757268b5836ab2964b5f032aea89770b0efc7c1d1", + "systems": [ + "dojo_init", + "upgrade", + "create_tournament", + "enter_tournament", + "start_tournament", + "submit_scores", + "add_prize", + "distribute_prizes" + ] + }, + { + "address": "0x16c1bf27e00787361984a4bc6b46d6804f5ca754342a3d3170d343766f94932", + "class_hash": "0x4042b4dcb5c61a667ac21c41e2c535960324c1eff1c1a1a1691ffb6981de5a", "abi": [ { "type": "impl", @@ -1315,7 +2104,7 @@ { "type": "impl", "name": "ERC20MockPublicImpl", - "interface_name": "tournament::ls15_components::tests::erc20_mock::IERC20MockPublic" + "interface_name": "tournament::ls15_components::tests::mocks::erc20_mock::IERC20MockPublic" }, { "type": "struct", @@ -1333,7 +2122,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::erc20_mock::IERC20MockPublic", + "name": "tournament::ls15_components::tests::mocks::erc20_mock::IERC20MockPublic", "items": [ { "type": "function", @@ -1726,7 +2515,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::erc20_mock::erc20_mock::Event", + "name": "tournament::ls15_components::tests::mocks::erc20_mock::erc20_mock::Event", "kind": "enum", "variants": [ { @@ -1761,8 +2550,8 @@ ] }, { - "address": "0x336d764c1f97d13bfb190d371d4b7471acd73eefbb796a25315131c34e49f5e", - "class_hash": "0x38f8afac7c8f5ae585271492af78c2072945659d577f64d3359eb8a63332ed0", + "address": "0x411e403ca4f0cf0f4dfbe4943ae4b5d55ed75fa225000cce96e46e19cc2a275", + "class_hash": "0x4e7e10de3725c9517603c2f4b8f8f87277bd3e13dd3765ec0b7e3a1c15229ae", "abi": [ { "type": "impl", @@ -1824,7 +2613,7 @@ { "type": "impl", "name": "ERC721MockPublicImpl", - "interface_name": "tournament::ls15_components::tests::erc721_mock::IERC721MockPublic" + "interface_name": "tournament::ls15_components::tests::mocks::erc721_mock::IERC721MockPublic" }, { "type": "struct", @@ -1842,7 +2631,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::erc721_mock::IERC721MockPublic", + "name": "tournament::ls15_components::tests::mocks::erc721_mock::IERC721MockPublic", "items": [ { "type": "function", @@ -2425,7 +3214,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::erc721_mock::erc721_mock::Event", + "name": "tournament::ls15_components::tests::mocks::erc721_mock::erc721_mock::Event", "kind": "enum", "variants": [ { @@ -2468,8 +3257,8 @@ ] }, { - "address": "0x256bdab53861ef520d857a1e3edb9253f415eceafb2ec609534099c29284cc6", - "class_hash": "0x3db02c4cc5d5846fea5f62e73e91da31263c1796fb397ce8ad90e83aabf1a41", + "address": "0x1b79c09503fd255f82f77d4d99ed5d07289835e0f418cdb0c72ad7bc547e39c", + "class_hash": "0xa43c958e5ccc59e95b76a37c66f4fe18567851d73fc4bc6b959fae220d8cb5", "abi": [ { "type": "impl", @@ -2531,7 +3320,7 @@ { "type": "impl", "name": "ERC20MockPublicImpl", - "interface_name": "tournament::ls15_components::tests::eth_mock::IERC20MockPublic" + "interface_name": "tournament::ls15_components::tests::mocks::eth_mock::IERC20MockPublic" }, { "type": "struct", @@ -2549,7 +3338,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::eth_mock::IERC20MockPublic", + "name": "tournament::ls15_components::tests::mocks::eth_mock::IERC20MockPublic", "items": [ { "type": "function", @@ -2942,7 +3731,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::eth_mock::eth_mock::Event", + "name": "tournament::ls15_components::tests::mocks::eth_mock::eth_mock::Event", "kind": "enum", "variants": [ { @@ -2977,8 +3766,8 @@ ] }, { - "address": "0x60b58d084173a70b02dfbdee17962a6435e8b4c6fcc21286adfc67d461c3725", - "class_hash": "0x7c13728455d61dc47cf58cdd4feca87a76408a5fddc1c02afe9608c1ced95fe", + "address": "0x2918d76373f69af5450b00d9e90880af478371cc8e4089cd8e570ee02d13dae", + "class_hash": "0x556b25990b3242520c7418c128237df3231da5d0ee5c0ff345fc7946033d573", "abi": [ { "type": "impl", @@ -3033,11 +3822,11 @@ { "type": "impl", "name": "LootSurvivorInitializerImpl", - "interface_name": "tournament::ls15_components::tests::loot_survivor_mock::ILootSurvivorMockInit" + "interface_name": "tournament::ls15_components::tests::mocks::loot_survivor_mock::ILootSurvivorMockInit" }, { "type": "interface", - "name": "tournament::ls15_components::tests::loot_survivor_mock::ILootSurvivorMockInit", + "name": "tournament::ls15_components::tests::mocks::loot_survivor_mock::ILootSurvivorMockInit", "items": [ { "type": "function", @@ -3276,7 +4065,7 @@ }, { "type": "struct", - "name": "tournament::ls15_components::models::loot_survivor::AdventurerMetadata", + "name": "adventurer::adventurer_meta::AdventurerMetadata", "members": [ { "name": "birth_date", @@ -3305,6 +4094,10 @@ { "name": "golden_token_id", "type": "core::integer::u8" + }, + { + "name": "launch_tournament_winner_token_id", + "type": "core::integer::u128" } ] }, @@ -3378,6 +4171,54 @@ } ] }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::FreeGameTokenType", + "variants": [ + { + "name": "GoldenToken", + "type": "()" + }, + { + "name": "LaunchTournamentChampion", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::loot_survivor::AdventurerMetadataStorage", + "members": [ + { + "name": "birth_date", + "type": "core::integer::u64" + }, + { + "name": "death_date", + "type": "core::integer::u64" + }, + { + "name": "level_seed", + "type": "core::integer::u64" + }, + { + "name": "item_specials_seed", + "type": "core::integer::u16" + }, + { + "name": "rank_at_death", + "type": "core::integer::u8" + }, + { + "name": "delay_stat_reveal", + "type": "core::bool" + }, + { + "name": "golden_token_id", + "type": "core::integer::u8" + } + ] + }, { "type": "interface", "name": "tournament::ls15_components::loot_survivor::ILootSurvivor", @@ -3409,7 +4250,7 @@ ], "outputs": [ { - "type": "tournament::ls15_components::models::loot_survivor::AdventurerMetadata" + "type": "adventurer::adventurer_meta::AdventurerMetadata" } ], "state_mutability": "view" @@ -3441,6 +4282,26 @@ ], "state_mutability": "view" }, + { + "type": "function", + "name": "free_game_available", + "inputs": [ + { + "name": "free_game_type", + "type": "tournament::ls15_components::models::tournament::FreeGameTokenType" + }, + { + "name": "token_id", + "type": "core::integer::u128" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, { "type": "function", "name": "new_game", @@ -3511,7 +4372,7 @@ }, { "name": "adventurer_meta", - "type": "tournament::ls15_components::models::loot_survivor::AdventurerMetadata" + "type": "tournament::ls15_components::models::loot_survivor::AdventurerMetadataStorage" } ], "outputs": [], @@ -3532,6 +4393,22 @@ ], "outputs": [], "state_mutability": "external" + }, + { + "type": "function", + "name": "set_free_game_available", + "inputs": [ + { + "name": "free_game_type", + "type": "tournament::ls15_components::models::tournament::FreeGameTokenType" + }, + { + "name": "token_id", + "type": "core::integer::u128" + } + ], + "outputs": [], + "state_mutability": "view" } ] }, @@ -4049,7 +4926,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::loot_survivor_mock::loot_survivor_mock::Event", + "name": "tournament::ls15_components::tests::mocks::loot_survivor_mock::loot_survivor_mock::Event", "kind": "enum", "variants": [ { @@ -4100,8 +4977,8 @@ ] }, { - "address": "0x44be98c35e88e65a46037e4492c35426c1e00a498d1fa0b2f3575f0418eb2cd", - "class_hash": "0x197f68fd5394e5481c58f6507e171f4faf986c3ef9636aa2a295e57e44e5282", + "address": "0x2238d84f457d5ea0c5353920d053383e2d977d81cdd9e8e2447dcccb207470b", + "class_hash": "0x7c5cbf46a1948471c4d4cb3327625a0f5011d895bf5e8a59f518e3fd096bd3f", "abi": [ { "type": "impl", @@ -4163,7 +5040,7 @@ { "type": "impl", "name": "ERC20MockPublicImpl", - "interface_name": "tournament::ls15_components::tests::lords_mock::IERC20MockPublic" + "interface_name": "tournament::ls15_components::tests::mocks::lords_mock::IERC20MockPublic" }, { "type": "struct", @@ -4181,7 +5058,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::lords_mock::IERC20MockPublic", + "name": "tournament::ls15_components::tests::mocks::lords_mock::IERC20MockPublic", "items": [ { "type": "function", @@ -4574,7 +5451,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::lords_mock::lords_mock::Event", + "name": "tournament::ls15_components::tests::mocks::lords_mock::lords_mock::Event", "kind": "enum", "variants": [ { @@ -4609,8 +5486,8 @@ ] }, { - "address": "0x3726e4d7f8e7630ec927f1e99f216bcf0f0235aeb3b2767cc7a7848b6e657b5", - "class_hash": "0x2c59c964f98599098c03cc5864e1c60c5c2a96eed89702e8e4d4b2453f6fa63", + "address": "0x1a01e7c510c8fd88f4aa42961ddee8497a36abaf5b718fa43fc70a099570426", + "class_hash": "0x39ef1a48d40fb9ce6a12e22b5466bdc0ddd2dee6ae30cffe7de10e316dabfe4", "abi": [ { "type": "impl", @@ -4665,7 +5542,7 @@ { "type": "impl", "name": "PragmaMockPublicImpl", - "interface_name": "tournament::ls15_components::tests::pragma_mock::IPragmaMockPublic" + "interface_name": "tournament::ls15_components::tests::mocks::pragma_mock::IPragmaMockPublic" }, { "type": "enum", @@ -4727,7 +5604,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::pragma_mock::IPragmaMockPublic", + "name": "tournament::ls15_components::tests::mocks::pragma_mock::IPragmaMockPublic", "items": [ { "type": "function", @@ -4846,7 +5723,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::pragma_mock::pragma_mock::Event", + "name": "tournament::ls15_components::tests::mocks::pragma_mock::pragma_mock::Event", "kind": "enum", "variants": [ { @@ -4870,8 +5747,8 @@ ] }, { - "address": "0x5cf5b1a48825c55ea9a359c9a2fc987e3fc496f8f72fb5393fd87c2ccf329bd", - "class_hash": "0x6f6056c3f7b22594c8b6f30af604a5c2a61a495622f40029b2ce7444d43e85b", + "address": "0x3440091b0f785aba618a54572edd3c039654fba446118056e612bbeea5339c1", + "class_hash": "0x1cb859ef54443d1528aa86280053eef54ee1d76a468578ad4c4806ace415e0", "abi": [ { "type": "impl", @@ -4926,7 +5803,7 @@ { "type": "impl", "name": "TournamentInitializerImpl", - "interface_name": "tournament::ls15_components::tests::tournament_mock::ITournamentMockInit" + "interface_name": "tournament::ls15_components::tests::mocks::tournament_mock::ITournamentMockInit" }, { "type": "enum", @@ -4944,7 +5821,7 @@ }, { "type": "interface", - "name": "tournament::ls15_components::tests::tournament_mock::ITournamentMockInit", + "name": "tournament::ls15_components::tests::mocks::tournament_mock::ITournamentMockInit", "items": [ { "type": "function", @@ -4966,6 +5843,14 @@ "name": "oracle_address", "type": "core::starknet::contract_address::ContractAddress" }, + { + "name": "golden_token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "blobert", + "type": "core::starknet::contract_address::ContractAddress" + }, { "name": "safe_mode", "type": "core::bool" @@ -4973,6 +5858,14 @@ { "name": "test_mode", "type": "core::bool" + }, + { + "name": "test_erc20", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "test_erc721", + "type": "core::starknet::contract_address::ContractAddress" } ], "outputs": [], @@ -5217,6 +6110,14 @@ "name": "creator", "type": "core::starknet::contract_address::ContractAddress" }, + { + "name": "registration_start_time", + "type": "core::integer::u64" + }, + { + "name": "registration_end_time", + "type": "core::integer::u64" + }, { "name": "start_time", "type": "core::integer::u64" @@ -5243,54 +6144,6 @@ } ] }, - { - "type": "struct", - "name": "tournament::ls15_components::models::tournament::ERC20Data", - "members": [ - { - "name": "token_amount", - "type": "core::integer::u128" - } - ] - }, - { - "type": "struct", - "name": "tournament::ls15_components::models::tournament::ERC721Data", - "members": [ - { - "name": "token_id", - "type": "core::integer::u128" - } - ] - }, - { - "type": "enum", - "name": "tournament::ls15_components::models::tournament::TokenDataType", - "variants": [ - { - "name": "erc20", - "type": "tournament::ls15_components::models::tournament::ERC20Data" - }, - { - "name": "erc721", - "type": "tournament::ls15_components::models::tournament::ERC721Data" - } - ] - }, - { - "type": "struct", - "name": "tournament::ls15_components::models::tournament::Token", - "members": [ - { - "name": "token", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "token_data_type", - "type": "tournament::ls15_components::models::tournament::TokenDataType" - } - ] - }, { "type": "struct", "name": "core::integer::u256", @@ -5357,6 +6210,50 @@ } ] }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::ERC20Data", + "members": [ + { + "name": "token_amount", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "tournament::ls15_components::models::tournament::ERC721Data", + "members": [ + { + "name": "token_id", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "tournament::ls15_components::models::tournament::TokenDataType", + "variants": [ + { + "name": "erc20", + "type": "tournament::ls15_components::models::tournament::ERC20Data" + }, + { + "name": "erc721", + "type": "tournament::ls15_components::models::tournament::ERC721Data" + } + ] + }, { "type": "interface", "name": "tournament::ls15_components::tournament::ITournament", @@ -5452,18 +6349,6 @@ ], "state_mutability": "view" }, - { - "type": "function", - "name": "register_tokens", - "inputs": [ - { - "name": "tokens", - "type": "core::array::Array::" - } - ], - "outputs": [], - "state_mutability": "external" - }, { "type": "function", "name": "create_tournament", @@ -5476,6 +6361,14 @@ "name": "description", "type": "core::byte_array::ByteArray" }, + { + "name": "registration_start_time", + "type": "core::integer::u64" + }, + { + "name": "registration_end_time", + "type": "core::integer::u64" + }, { "name": "start_time", "type": "core::integer::u64" @@ -5539,6 +6432,18 @@ { "name": "start_count", "type": "core::option::Option::" + }, + { + "name": "client_reward_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "golden_token_free_game_token_ids", + "type": "core::array::Span::" + }, + { + "name": "blobert_free_game_token_ids", + "type": "core::array::Span::" } ], "outputs": [], @@ -5645,7 +6550,7 @@ }, { "type": "event", - "name": "tournament::ls15_components::tests::tournament_mock::tournament_mock::Event", + "name": "tournament::ls15_components::tests::mocks::tournament_mock::tournament_mock::Event", "kind": "enum", "variants": [ { @@ -5672,7 +6577,6 @@ "systems": [ "initializer", "upgrade", - "register_tokens", "create_tournament", "enter_tournament", "start_tournament", @@ -5685,7 +6589,7 @@ "models": [ { "members": [], - "class_hash": "0x61f19ad26a2436c70b989673bc151f27bc424d6008df9f1cbf7d115a10a04ba", + "class_hash": "0x175121abda0cc6299a8668376fa6fcc55020c22aedbda7a47d477f5b4de30d9", "tag": "tournament-AdventurerMetaModel", "selector": "0x18ffc54ee4c182c9b51d3fa88072802008c993e755f5f381978ef2b3c6b510c" }, @@ -5707,6 +6611,12 @@ "tag": "tournament-Contracts", "selector": "0x2d9303acb9101422f51b713137c4616f4b9a056ebd5db0e2340927dee540175" }, + { + "members": [], + "class_hash": "0x56ae872b9a6adefe46160b72545df1ac53b959771c5ebda5cbeb2fda8071322", + "tag": "tournament-FreeGameAvailableModel", + "selector": "0x148e0460e93f8725aa4c9270658d3dfc2764ce8dc8f8c928738a0cd630f517f" + }, { "members": [], "class_hash": "0x65c2aaf54e11a19b9c9e46efe30e5dad4f04ef9cf583ab8b6c3553b796a4b07", @@ -5727,7 +6637,7 @@ }, { "members": [], - "class_hash": "0x71bd6392a00912a7252a5c6711c88c252ce519ae1ad8300206738343623a6e0", + "class_hash": "0x6c8cd421a29492799f81c1c8754c842ae485eac586aa8162c24d2dfe0cb8252", "tag": "tournament-TournamentConfig", "selector": "0x547cd590eea00da15c7206cf56bafbdacb70341a5648eef169c86d20c7ad77f" }, @@ -5757,7 +6667,7 @@ }, { "members": [], - "class_hash": "0x5321591258855c98a0237e65b77ab37caeac8165c9dfe057cf924b00808ca5a", + "class_hash": "0x2a5caae81f997ca91f32eb1f6f60e5cf1b61050dc97d987d6979d1be86a688e", "tag": "tournament-TournamentModel", "selector": "0x452449132e96ffddd83f75e1471014c0f4752fb593b27aafb0f425e9836c504" }, diff --git a/contracts/scripts/deploy_slot.sh b/contracts/scripts/deploy_slot.sh index 4520ff3..631a515 100755 --- a/contracts/scripts/deploy_slot.sh +++ b/contracts/scripts/deploy_slot.sh @@ -33,6 +33,8 @@ export ETH_ADDRESS=$(get_contract_address "tournament-eth_mock") export LORDS_ADDRESS=$(get_contract_address "tournament-lords_mock") export LOOT_SURVIVOR_ADDRESS=$(get_contract_address "tournament-loot_survivor_mock") export ORACLE_ADDRESS=$(get_contract_address "tournament-pragma_mock") +export TEST_ERC20=$(get_contract_address "tournament-erc20_mock") +export TEST_ERC721=$(get_contract_address "tournament-erc721_mock") #----------------- # initialize tournament @@ -42,12 +44,16 @@ echo "ETH_ADDRESS: $ETH_ADDRESS" echo "LORDS_ADDRESS: $LORDS_ADDRESS" echo "LOOT_SURVIVOR_ADDRESS: $LOOT_SURVIVOR_ADDRESS" echo "ORACLE_ADDRESS: $ORACLE_ADDRESS" +echo "TEST_ERC20: $TEST_ERC20" +echo "TEST_ERC721: $TEST_ERC721" echo "Waiting 10 seconds before execution..." sleep 10 -sozo -P slot execute tournament_mock initializer --calldata $ETH_ADDRESS,$LORDS_ADDRESS,$LOOT_SURVIVOR_ADDRESS,$ORACLE_ADDRESS,0,1 +sozo -P slot execute tournament_mock initializer --calldata $ETH_ADDRESS,$LORDS_ADDRESS,$LOOT_SURVIVOR_ADDRESS,$ORACLE_ADDRESS,$TEST_ERC721,$TEST_ERC721,0,1,$TEST_ERC20,$TEST_ERC721 sozo -P slot execute loot_survivor_mock initializer --calldata $ETH_ADDRESS,$LORDS_ADDRESS,$ORACLE_ADDRESS +sozo -P slot execute loot_survivor_mock set_free_game_available --calldata 0,1 +sozo -P slot execute loot_survivor_mock set_free_game_available --calldata 1,1 #------------------ echo "--- DONE! 👍" \ No newline at end of file diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index eb85185..56cc105 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -15,15 +15,17 @@ mod ls15_components { pub mod libs { pub mod store; } - pub mod eth_mock; - pub mod lords_mock; - pub mod erc20_mock; - pub mod erc721_mock; + pub mod mocks { + pub mod eth_mock; + pub mod lords_mock; + pub mod erc20_mock; + pub mod erc721_mock; + pub mod loot_survivor_mock; + pub mod pragma_mock; + pub mod tournament_mock; + } #[cfg(test)] mod helpers; - pub mod loot_survivor_mock; - pub mod pragma_mock; - pub mod tournament_mock; #[cfg(test)] mod test_tournament; pub mod interfaces; diff --git a/contracts/src/ls15_components/constants.cairo b/contracts/src/ls15_components/constants.cairo index 447d941..5b4e9be 100644 --- a/contracts/src/ls15_components/constants.cairo +++ b/contracts/src/ls15_components/constants.cairo @@ -7,7 +7,7 @@ pub const VRF_COST_PER_GAME: u32 = 50000000; // $0.50 with 8 decimals // // PRODUCTION VALUES -pub const MIN_REGISTRATION_PERIOD: u32 = 300; // 5 minutes +pub const MIN_REGISTRATION_PERIOD: u32 = 3600; // 1 hour pub const MAX_REGISTRATION_PERIOD: u32 = 2592000; // 1 month pub const MIN_TOURNAMENT_LENGTH: u32 = 3600; // 1 hour pub const MAX_TOURNAMENT_LENGTH: u32 = 15552000; // 6 months @@ -35,5 +35,9 @@ pub fn SURVIVORS_ADDRESS() -> ContractAddress { contract_address_const::<0x018108b32cea514a78ef1b0e4a0753e855cdf620bc0565202c02456f618c4dc4>() } +pub fn BEASTS_ADDRESS() -> ContractAddress { + contract_address_const::<0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd>() +} + pub const ETH_SAFE_AMOUNT: u128 = 1000000000000000; // 0.001 ETH pub const LORDS_SAFE_AMOUNT: u128 = 5000000000000000000; // 5 LORDS diff --git a/contracts/src/ls15_components/interfaces.cairo b/contracts/src/ls15_components/interfaces.cairo index 96e3afe..991c959 100644 --- a/contracts/src/ls15_components/interfaces.cairo +++ b/contracts/src/ls15_components/interfaces.cairo @@ -1,7 +1,6 @@ use starknet::ContractAddress; use adventurer::{adventurer::Adventurer, adventurer_meta::AdventurerMetadata, bag::Bag}; use dojo::world::{WorldStorage, WorldStorageTrait, IWorldDispatcher}; -use tournament::presets::ls_tournament::{ILSTournamentDispatcher}; use tournament::ls15_components::models::tournament::FreeGameTokenType; use tournament::ls15_components::libs::utils::ZERO; @@ -73,22 +72,5 @@ pub impl WorldImpl of WorldTrait { (WorldStorageTrait::new(dispatcher, namespace)) } - // - // addresses - // - - #[inline(always)] - fn ls_tournament_address(self: @WorldStorage) -> ContractAddress { - (self.contract_address(@"LSTournament")) - } - - // - // dispatchers - // - - #[inline(always)] - fn ls_tournament_dispatcher(self: @WorldStorage) -> ILSTournamentDispatcher { - (ILSTournamentDispatcher { contract_address: self.ls_tournament_address() }) - } } diff --git a/contracts/src/ls15_components/libs/store.cairo b/contracts/src/ls15_components/libs/store.cairo index 674592e..e4b495c 100644 --- a/contracts/src/ls15_components/libs/store.cairo +++ b/contracts/src/ls15_components/libs/store.cairo @@ -28,70 +28,70 @@ pub impl StoreImpl of StoreTrait { // Tournament #[inline(always)] - fn get_tournament_totals(ref self: Store, contract: ContractAddress) -> TournamentTotalsModel { + fn get_tournament_totals(self: Store, contract: ContractAddress) -> TournamentTotalsModel { (self.world.read_model(contract)) } #[inline(always)] - fn get_tournament(ref self: Store, tournament_id: u64) -> TournamentModel { + fn get_tournament(self: Store, tournament_id: u64) -> TournamentModel { (self.world.read_model(tournament_id)) } #[inline(always)] - fn get_total_entries(ref self: Store, tournament_id: u64) -> TournamentEntriesModel { + fn get_total_entries(self: Store, tournament_id: u64) -> TournamentEntriesModel { (self.world.read_model(tournament_id)) } #[inline(always)] fn get_address_entries( - ref self: Store, tournament_id: u64, account: ContractAddress + self: Store, tournament_id: u64, account: ContractAddress ) -> TournamentEntriesAddressModel { (self.world.read_model((tournament_id, account),)) } #[inline(always)] fn get_tournament_entry_addresses( - ref self: Store, tournament_id: u64 + self: Store, tournament_id: u64 ) -> TournamentEntryAddressesModel { (self.world.read_model(tournament_id)) } #[inline(always)] fn get_tournament_starts( - ref self: Store, tournament_id: u64, address: ContractAddress + self: Store, tournament_id: u64, address: ContractAddress ) -> TournamentStartsAddressModel { (self.world.read_model((tournament_id, address),)) } #[inline(always)] fn get_tournament_game( - ref self: Store, tournament_id: u64, game_id: felt252 + self: Store, tournament_id: u64, game_id: felt252 ) -> TournamentGameModel { (self.world.read_model((tournament_id, game_id),)) } #[inline(always)] - fn get_tournament_scores(ref self: Store, tournament_id: u64) -> TournamentScoresModel { + fn get_tournament_scores(self: Store, tournament_id: u64) -> TournamentScoresModel { (self.world.read_model(tournament_id)) } #[inline(always)] - fn get_prize_keys(ref self: Store, tournament_id: u64) -> TournamentPrizeKeysModel { + fn get_prize_keys(self: Store, tournament_id: u64) -> TournamentPrizeKeysModel { (self.world.read_model(tournament_id)) } #[inline(always)] - fn get_prize(ref self: Store, prize_key: u64) -> PrizesModel { + fn get_prize(self: Store, prize_key: u64) -> PrizesModel { (self.world.read_model(prize_key)) } #[inline(always)] - fn get_token(ref self: Store, token: ContractAddress) -> TokenModel { + fn get_token(self: Store, token: ContractAddress) -> TokenModel { (self.world.read_model(token)) } #[inline(always)] - fn get_tournament_config(ref self: Store, contract: ContractAddress) -> TournamentConfig { + fn get_tournament_config(self: Store, contract: ContractAddress) -> TournamentConfig { (self.world.read_model(contract)) } // Setters diff --git a/contracts/src/ls15_components/models/tournament.cairo b/contracts/src/ls15_components/models/tournament.cairo index ff545cb..0de1f2d 100644 --- a/contracts/src/ls15_components/models/tournament.cairo +++ b/contracts/src/ls15_components/models/tournament.cairo @@ -85,6 +85,8 @@ pub struct TournamentModel { pub name: felt252, pub description: ByteArray, pub creator: ContractAddress, + pub registration_start_time: u64, + pub registration_end_time: u64, pub start_time: u64, pub end_time: u64, pub submission_period: u64, @@ -211,5 +213,5 @@ pub struct TournamentConfig { pub golden_token: ContractAddress, pub blobert: ContractAddress, pub safe_mode: bool, - pub test_mode: bool + pub test_mode: bool, } diff --git a/contracts/src/ls15_components/tests/helpers.cairo b/contracts/src/ls15_components/tests/helpers.cairo index eaa3b90..940d2c9 100644 --- a/contracts/src/ls15_components/tests/helpers.cairo +++ b/contracts/src/ls15_components/tests/helpers.cairo @@ -1,7 +1,10 @@ use starknet::get_block_timestamp; use tournament::ls15_components::constants::MIN_SUBMISSION_PERIOD; use tournament::tests::{ - constants::{TOURNAMENT_NAME, TOURNAMENT_DESCRIPTION, TEST_START_TIME, TEST_END_TIME}, + constants::{ + TOURNAMENT_NAME, TOURNAMENT_DESCRIPTION, TEST_REGISTRATION_START_TIME, + TEST_REGISTRATION_END_TIME, TEST_START_TIME, TEST_END_TIME + }, }; use tournament::ls15_components::tests::interfaces::{ IERC20MockDispatcher, IERC20MockDispatcherTrait @@ -25,6 +28,8 @@ pub fn create_basic_tournament(tournament: ITournamentMockDispatcher) -> u64 { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), diff --git a/contracts/src/ls15_components/tests/interfaces.cairo b/contracts/src/ls15_components/tests/interfaces.cairo index 0c9b80d..789c4b6 100644 --- a/contracts/src/ls15_components/tests/interfaces.cairo +++ b/contracts/src/ls15_components/tests/interfaces.cairo @@ -107,6 +107,8 @@ pub trait ITournamentMock { ref self: TState, name: felt252, description: ByteArray, + registration_start_time: u64, + registration_end_time: u64, start_time: u64, end_time: u64, submission_period: u64, diff --git a/contracts/src/ls15_components/tests/erc20_mock.cairo b/contracts/src/ls15_components/tests/mocks/erc20_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/erc20_mock.cairo rename to contracts/src/ls15_components/tests/mocks/erc20_mock.cairo diff --git a/contracts/src/ls15_components/tests/erc721_mock.cairo b/contracts/src/ls15_components/tests/mocks/erc721_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/erc721_mock.cairo rename to contracts/src/ls15_components/tests/mocks/erc721_mock.cairo diff --git a/contracts/src/ls15_components/tests/eth_mock.cairo b/contracts/src/ls15_components/tests/mocks/eth_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/eth_mock.cairo rename to contracts/src/ls15_components/tests/mocks/eth_mock.cairo diff --git a/contracts/src/ls15_components/tests/loot_survivor_mock.cairo b/contracts/src/ls15_components/tests/mocks/loot_survivor_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/loot_survivor_mock.cairo rename to contracts/src/ls15_components/tests/mocks/loot_survivor_mock.cairo diff --git a/contracts/src/ls15_components/tests/lords_mock.cairo b/contracts/src/ls15_components/tests/mocks/lords_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/lords_mock.cairo rename to contracts/src/ls15_components/tests/mocks/lords_mock.cairo diff --git a/contracts/src/ls15_components/tests/pragma_mock.cairo b/contracts/src/ls15_components/tests/mocks/pragma_mock.cairo similarity index 100% rename from contracts/src/ls15_components/tests/pragma_mock.cairo rename to contracts/src/ls15_components/tests/mocks/pragma_mock.cairo diff --git a/contracts/src/ls15_components/tests/tournament_mock.cairo b/contracts/src/ls15_components/tests/mocks/tournament_mock.cairo similarity index 98% rename from contracts/src/ls15_components/tests/tournament_mock.cairo rename to contracts/src/ls15_components/tests/mocks/tournament_mock.cairo index 0d80992..32e85d4 100644 --- a/contracts/src/ls15_components/tests/tournament_mock.cairo +++ b/contracts/src/ls15_components/tests/mocks/tournament_mock.cairo @@ -21,6 +21,8 @@ pub trait ITournamentMock { ref self: TState, name: felt252, description: ByteArray, + registration_start_time: u64, + registration_end_time: u64, start_time: u64, end_time: u64, submission_period: u64, diff --git a/contracts/src/ls15_components/tests/test_tournament.cairo b/contracts/src/ls15_components/tests/test_tournament.cairo index bfb24f9..ba02dac 100644 --- a/contracts/src/ls15_components/tests/test_tournament.cairo +++ b/contracts/src/ls15_components/tests/test_tournament.cairo @@ -7,8 +7,8 @@ use dojo_cairo_test::{ }; use tournament::ls15_components::constants::{ - MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, MIN_SUBMISSION_PERIOD, MAX_SUBMISSION_PERIOD, - MIN_TOURNAMENT_LENGTH, MAX_TOURNAMENT_LENGTH + MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, MIN_SUBMISSION_PERIOD, + MAX_SUBMISSION_PERIOD, MIN_TOURNAMENT_LENGTH, MAX_TOURNAMENT_LENGTH }; use tournament::ls15_components::tests::interfaces::WorldTrait; @@ -31,30 +31,28 @@ use tournament::ls15_components::models::{ use tournament::tests::{ utils, constants::{ - OWNER, TOURNAMENT_NAME, TOURNAMENT_DESCRIPTION, STARTING_BALANCE, TEST_START_TIME, - TEST_END_TIME, ZERO + OWNER, TOURNAMENT_NAME, TOURNAMENT_DESCRIPTION, STARTING_BALANCE, + TEST_REGISTRATION_START_TIME, TEST_REGISTRATION_END_TIME, TEST_START_TIME, TEST_END_TIME, + ZERO }, }; use tournament::ls15_components::tests::helpers::{ approve_game_costs, approve_free_game_cost, create_basic_tournament, create_adventurer_metadata_with_death_date, create_dead_adventurer_with_xp }; -use tournament::ls15_components::tests::{ - erc20_mock::{erc20_mock}, interfaces::{IERC20MockDispatcher, IERC20MockDispatcherTrait}, +use tournament::ls15_components::tests::mocks::{ + erc20_mock::erc20_mock, + erc721_mock::erc721_mock, + tournament_mock::tournament_mock, + pragma_mock::pragma_mock, + loot_survivor_mock::loot_survivor_mock, }; -use tournament::ls15_components::tests::{ - erc721_mock::{erc721_mock}, interfaces::{IERC721MockDispatcher, IERC721MockDispatcherTrait}, -}; -use tournament::ls15_components::tests::{ - tournament_mock::{tournament_mock}, - interfaces::{ITournamentMockDispatcher, ITournamentMockDispatcherTrait}, -}; -use tournament::ls15_components::tests::{ - loot_survivor_mock::{loot_survivor_mock}, - interfaces::{ILootSurvivorMockDispatcher, ILootSurvivorMockDispatcherTrait}, -}; -use tournament::ls15_components::tests::{ - pragma_mock::{pragma_mock}, interfaces::{IPragmaMockDispatcher}, +use tournament::ls15_components::tests::interfaces::{ + ILootSurvivorMockDispatcher, ILootSurvivorMockDispatcherTrait, + ITournamentMockDispatcher, ITournamentMockDispatcherTrait, + IERC20MockDispatcher, IERC20MockDispatcherTrait, + IERC721MockDispatcher, IERC721MockDispatcherTrait, + IPragmaMockDispatcher, }; use openzeppelin_token::erc721::interface; @@ -340,9 +338,16 @@ fn test_create_tournament() { assert( tournament_data.description == TOURNAMENT_DESCRIPTION(), 'Invalid tournament description' ); + println!("registration start time: {}", tournament_data.registration_start_time); assert( - tournament_data.start_time == TEST_START_TIME().into(), 'Invalid tournament start time' + tournament_data.registration_start_time == TEST_REGISTRATION_START_TIME().into(), + 'Invalid registration start' ); + assert( + tournament_data.registration_end_time == TEST_REGISTRATION_END_TIME().into(), + 'Invalid registration end' + ); + assert(tournament_data.start_time == TEST_START_TIME().into(), 'Invalid tournament start time'); assert(tournament_data.end_time == TEST_END_TIME().into(), 'Invalid tournament end time'); assert(tournament_data.gated_type == Option::None, 'Invalid tournament gated token'); assert(tournament_data.entry_premium == Option::None, 'Invalid entry premium'); @@ -350,7 +355,7 @@ fn test_create_tournament() { } #[test] -#[should_panic(expected: ('start time too close', 'ENTRYPOINT_FAILED'))] +#[should_panic(expected: ('start time not in future', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_start_time_too_close() { let contracts = setup(); @@ -359,7 +364,9 @@ fn test_create_tournament_start_time_too_close() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), - 2, + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), + 0, TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), 1, // single top score @@ -369,8 +376,8 @@ fn test_create_tournament_start_time_too_close() { } #[test] -#[should_panic(expected: ('start time too far', 'ENTRYPOINT_FAILED'))] -fn test_create_tournament_start_time_too_far() { +#[should_panic(expected: ('registration period too short', 'ENTRYPOINT_FAILED'))] +fn test_create_tournament_registration_period_too_short() { let contracts = setup(); contracts @@ -378,7 +385,9 @@ fn test_create_tournament_start_time_too_far() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), - (TEST_START_TIME() + MAX_REGISTRATION_PERIOD).into(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_START_TIME().into() + 1, + TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), 1, // single top score @@ -388,7 +397,28 @@ fn test_create_tournament_start_time_too_far() { } #[test] -#[should_panic(expected: ('tournament too short', 'ENTRYPOINT_FAILED'))] +#[should_panic(expected: ('registration period too long', 'ENTRYPOINT_FAILED'))] +fn test_create_tournament_registration_period_too_long() { + let contracts = setup(); + + contracts + .tournament + .create_tournament( + TOURNAMENT_NAME(), + TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_START_TIME().into() + MAX_REGISTRATION_PERIOD.into(), + TEST_START_TIME().into() + MAX_REGISTRATION_PERIOD.into(), + TEST_END_TIME().into() + MAX_REGISTRATION_PERIOD.into(), + MIN_SUBMISSION_PERIOD.into(), + 1, // single top score + Option::None, // zero gated type + Option::None, // zero entry premium + ); +} + +#[test] +#[should_panic(expected: ('registration end too late', 'ENTRYPOINT_FAILED'))] fn test_create_tournament_end_time_too_close() { let contracts = setup(); @@ -397,8 +427,10 @@ fn test_create_tournament_end_time_too_close() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), - 2 + MIN_REGISTRATION_PERIOD.into(), - 2 + MIN_REGISTRATION_PERIOD.into(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), + MIN_REGISTRATION_PERIOD.into(), + MIN_REGISTRATION_PERIOD.into(), MIN_SUBMISSION_PERIOD.into(), 1, // single top score Option::None, // zero gated type @@ -416,6 +448,8 @@ fn test_create_tournament_end_time_too_far() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), (TEST_END_TIME() + MAX_TOURNAMENT_LENGTH).into(), MIN_SUBMISSION_PERIOD.into(), @@ -435,6 +469,8 @@ fn test_create_tournament_submission_period_too_short() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into() - 1, @@ -454,6 +490,8 @@ fn test_create_tournament_submission_period_too_long() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MAX_SUBMISSION_PERIOD.into() + 1, @@ -566,6 +604,8 @@ fn test_create_tournament_with_premiums_too_long() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -594,6 +634,8 @@ fn test_create_tournament_with_premiums_not_100() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -616,6 +658,8 @@ fn test_create_gated_tournament_with_unsettled_tournament() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -624,10 +668,12 @@ fn test_create_gated_tournament_with_unsettled_tournament() { Option::None, // zero entry premium ); + // Move to tournament start time + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Enter first tournament contracts.tournament.enter_tournament(first_tournament_id, Option::None); - // Move to tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start first tournament @@ -649,8 +695,13 @@ fn test_create_gated_tournament_with_unsettled_tournament() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), - current_time + MIN_REGISTRATION_PERIOD.into(), // start after first tournament - current_time + 1 + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into(), + current_time, // start after first tournament + current_time + MIN_REGISTRATION_PERIOD.into(), + current_time + MIN_REGISTRATION_PERIOD.into(), + current_time + + + MIN_REGISTRATION_PERIOD.into() + + MIN_TOURNAMENT_LENGTH.into(), MIN_SUBMISSION_PERIOD.into(), 1, Option::Some(gated_type), // Gate by first tournament @@ -670,6 +721,8 @@ fn test_create_tournament_gated_by_multiple_tournaments() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -684,6 +737,8 @@ fn test_create_tournament_gated_by_multiple_tournaments() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -692,9 +747,13 @@ fn test_create_tournament_gated_by_multiple_tournaments() { Option::None, ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Enter and complete first tournament contracts.tournament.enter_tournament(first_tournament_id, Option::None); + testing::set_block_timestamp(TEST_START_TIME().into()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament @@ -708,7 +767,7 @@ fn test_create_tournament_gated_by_multiple_tournaments() { contracts.tournament.submit_scores(first_tournament_id, array![1]); // Enter and complete second tournament - testing::set_block_timestamp(1); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); contracts.tournament.enter_tournament(second_tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); @@ -737,8 +796,13 @@ fn test_create_tournament_gated_by_multiple_tournaments() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + current_time, + current_time + MIN_REGISTRATION_PERIOD.into(), current_time + MIN_REGISTRATION_PERIOD.into(), - current_time + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into() + 1, + current_time + + + MIN_REGISTRATION_PERIOD.into() + + MIN_TOURNAMENT_LENGTH.into(), MIN_SUBMISSION_PERIOD.into(), 1, Option::Some(gated_type), @@ -747,8 +811,10 @@ fn test_create_tournament_gated_by_multiple_tournaments() { // Verify the gated tournament was created with correct parameters let gated_tournament = contracts.tournament.tournament(gated_tournament_id); - assert( - gated_tournament.gated_type == Option::Some(gated_type), 'Invalid tournament gate type' + assert(gated_tournament.gated_type == Option::Some(gated_type), 'Invalid tournament gate type'); + + testing::set_block_timestamp( + current_time + MIN_REGISTRATION_PERIOD.into() ); let gated_submission_type = GatedSubmissionType::game_id(array![1, 2].span()); @@ -778,6 +844,8 @@ fn test_create_tournament_gated_accounts() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -788,9 +856,10 @@ fn test_create_tournament_gated_accounts() { // Verify tournament was created with correct gating let tournament_data = contracts.tournament.tournament(tournament_id); - assert( - tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gate type' - ); + assert(tournament_data.gated_type == Option::Some(gated_type), 'Invalid tournament gate type'); + + // Start tournament entries + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); // Allowed account (owner) can enter contracts.tournament.enter_tournament(tournament_id, Option::None); @@ -801,26 +870,51 @@ fn test_create_tournament_gated_accounts() { contracts.lords.mint(allowed_player, STARTING_BALANCE); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament entries testing::set_block_timestamp(TEST_START_TIME().into()); utils::impersonate(OWNER()); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(allowed_player); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // Verify entries were successful let entries = contracts.tournament.tournament_entries(tournament_id); assert(entries == 2, 'Invalid entry count'); } +#[test] +fn test_create_tournament_season() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + contracts + .tournament + .create_tournament( + TOURNAMENT_NAME(), + TOURNAMENT_DESCRIPTION(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + MIN_SUBMISSION_PERIOD.into(), + 1, + Option::None, + Option::None, + ); +} + // // Test registering tokens // @@ -899,17 +993,7 @@ fn test_enter_tournament() { let tournament_id = create_basic_tournament(contracts.tournament); - contracts.tournament.enter_tournament(tournament_id, Option::None); -} - -#[test] -#[should_panic(expected: ('tournament already started', 'ENTRYPOINT_FAILED'))] -fn test_enter_tournament_already_started() { - let contracts = setup(); - - let tournament_id = create_basic_tournament(contracts.tournament); - - testing::set_block_timestamp(TEST_START_TIME().into()); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); contracts.tournament.enter_tournament(tournament_id, Option::None); } @@ -927,6 +1011,8 @@ fn test_enter_tournament_wrong_submission_type() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -935,9 +1021,13 @@ fn test_enter_tournament_wrong_submission_type() { Option::None, ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Complete the first tournament contracts.tournament.enter_tournament(first_tournament_id, Option::None); + testing::set_block_timestamp(TEST_START_TIME().into()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament @@ -962,8 +1052,13 @@ fn test_enter_tournament_wrong_submission_type() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + current_time, current_time + MIN_REGISTRATION_PERIOD.into(), - current_time + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into() + 1, + current_time + MIN_REGISTRATION_PERIOD.into(), + current_time + + + MIN_REGISTRATION_PERIOD.into() + + MIN_TOURNAMENT_LENGTH.into(), MIN_SUBMISSION_PERIOD.into(), 1, Option::Some(gated_type), @@ -973,11 +1068,41 @@ fn test_enter_tournament_wrong_submission_type() { // Try to enter with wrong submission type (token_id instead of game_id) let wrong_submission_type = GatedSubmissionType::token_id(1); + testing::set_block_timestamp( + current_time + MIN_REGISTRATION_PERIOD.into() + ); + // This should panic because we're using token_id submission type for a tournament-gated // tournament contracts.tournament.enter_tournament(gated_tournament_id, Option::Some(wrong_submission_type)); } +#[test] +fn test_enter_tournament_season() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = contracts + .tournament + .create_tournament( + TOURNAMENT_NAME(), + TOURNAMENT_DESCRIPTION(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + MIN_SUBMISSION_PERIOD.into(), + 1, + Option::None, + Option::None, + ); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.tournament.enter_tournament(tournament_id, Option::None); +} + // // Test starting tournaments // @@ -990,6 +1115,8 @@ fn test_start_tournament() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -998,7 +1125,9 @@ fn test_start_tournament() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // check tournament entries assert(contracts.tournament.tournament_entries(tournament_id) == 1, 'Invalid entries'); @@ -1022,8 +1151,7 @@ fn test_start_tournament() { 'Invalid balance' ); assert( - contracts.eth.balance_of(OWNER()) == STARTING_BALANCE - 200000000000000, - 'Invalid balance' + contracts.eth.balance_of(OWNER()) == STARTING_BALANCE - 200000000000000, 'Invalid balance' ); } @@ -1036,6 +1164,8 @@ fn test_start_tournament_entry_already_started() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1044,26 +1174,109 @@ fn test_start_tournament_entry_already_started() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); } #[test] -fn test_start_tournament_with_free_game() { +fn test_start_tournament_multiple_starts() { let contracts = setup(); utils::impersonate(OWNER()); let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::Some(1), ZERO(), array![].span(), array![].span() + ); + contracts + .tournament + .start_tournament( + tournament_id, false, Option::Some(1), ZERO(), array![].span(), array![].span() + ); +} + +#[test] +fn test_start_tournament_multiple_starts_multiple_addresses() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + // Create multiple players + let player2 = starknet::contract_address_const::<0x456>(); + let player3 = starknet::contract_address_const::<0x789>(); + + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + + utils::impersonate(player2); + contracts.eth.mint(player2, STARTING_BALANCE); + contracts.lords.mint(player2, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); + contracts.tournament.enter_tournament(tournament_id, Option::None); + + utils::impersonate(player3); + contracts.eth.mint(player3, STARTING_BALANCE); + contracts.lords.mint(player3, STARTING_BALANCE); + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 2); + contracts.tournament.enter_tournament(tournament_id, Option::None); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + utils::impersonate(OWNER()); + + contracts + .tournament + .start_tournament( + tournament_id, true, Option::Some(2), ZERO(), array![].span(), array![].span() + ); + + utils::impersonate(player3); + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); +} + +#[test] +fn test_start_tournament_with_free_game() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + testing::set_block_timestamp(TEST_START_TIME().into()); + approve_free_game_cost(contracts.eth, contracts.golden_token, 1, contracts.tournament); contracts @@ -1087,16 +1300,18 @@ fn test_start_tournament_with_free_game_multiple() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); - testing::set_block_timestamp(TEST_START_TIME().into()); - contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); contracts.golden_token.mint(OWNER(), 2); + testing::set_block_timestamp(TEST_START_TIME().into()); + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); contracts.golden_token.approve(contracts.tournament.contract_address, 1); contracts.golden_token.approve(contracts.tournament.contract_address, 2); @@ -1123,14 +1338,14 @@ fn test_start_tournament_with_free_game_multiple_and_lords() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); - testing::set_block_timestamp(TEST_START_TIME().into()); - contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::LaunchTournamentChampion, 1); @@ -1139,6 +1354,8 @@ fn test_start_tournament_with_free_game_multiple_and_lords() { contracts.golden_token.mint(OWNER(), 2); contracts.blobert.mint(OWNER(), 2); + testing::set_block_timestamp(TEST_START_TIME().into()); + contracts.eth.approve(contracts.tournament.contract_address, 1000000000000000); contracts.golden_token.approve(contracts.tournament.contract_address, 1); contracts.golden_token.approve(contracts.tournament.contract_address, 2); @@ -1171,16 +1388,18 @@ fn test_start_tournament_with_free_game_over_start_count() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); - testing::set_block_timestamp(TEST_START_TIME().into()); - contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); contracts.golden_token.mint(OWNER(), 2); + testing::set_block_timestamp(TEST_START_TIME().into()); + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); contracts.golden_token.approve(contracts.tournament.contract_address, 1); contracts.golden_token.approve(contracts.tournament.contract_address, 2); @@ -1201,15 +1420,17 @@ fn test_start_tournament_with_free_game_over_entries() { let tournament_id = create_basic_tournament(contracts.tournament); - contracts.tournament.enter_tournament(tournament_id, Option::None); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); - testing::set_block_timestamp(TEST_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); contracts.golden_token.mint(OWNER(), 2); + testing::set_block_timestamp(TEST_START_TIME().into()); + contracts.eth.approve(contracts.tournament.contract_address, 400000000000000); contracts.golden_token.approve(contracts.tournament.contract_address, 1); contracts.golden_token.approve(contracts.tournament.contract_address, 2); @@ -1220,6 +1441,84 @@ fn test_start_tournament_with_free_game_over_entries() { tournament_id, false, Option::None, ZERO(), array![1, 2].span(), array![].span() ); } + +#[test] +fn test_start_tournament_season() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = contracts + .tournament + .create_tournament( + TOURNAMENT_NAME(), + TOURNAMENT_DESCRIPTION(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + TEST_START_TIME().into(), + TEST_END_TIME().into(), + MIN_SUBMISSION_PERIOD.into(), + 1, + Option::None, + Option::None, + ); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + + approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); +} + +#[test] +fn test_start_tournament_with_free_game_multiple_starts() { + let contracts = setup(); + + utils::impersonate(OWNER()); + + let tournament_id = create_basic_tournament(contracts.tournament); + + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + + contracts.tournament.enter_tournament(tournament_id, Option::None); + contracts.tournament.enter_tournament(tournament_id, Option::None); + + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 1); + contracts.loot_survivor.set_free_game_available(FreeGameTokenType::GoldenToken, 2); + + contracts.golden_token.mint(OWNER(), 2); + + testing::set_block_timestamp(TEST_START_TIME().into()); + + contracts.eth.approve(contracts.tournament.contract_address, 200000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + + // start one free game so that start count rises by 1 + contracts.tournament.start_tournament(tournament_id, false, Option::Some(1), ZERO(), array![1].span(), array![].span()); + + contracts.eth.approve(contracts.tournament.contract_address, 200000000000000); + contracts.golden_token.approve(contracts.tournament.contract_address, 1); + + contracts + .tournament + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![1].span(), array![].span() + ); + + // check tournament entries + assert(contracts.tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); + + // check golden tokens have returned back + assert(contracts.golden_token.owner_of(1) == OWNER(), 'Invalid owner'); +} + + // // Test submitting scores // @@ -1232,6 +1531,8 @@ fn test_submit_scores() { let tournament_id = create_basic_tournament(contracts.tournament); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1240,7 +1541,9 @@ fn test_submit_scores() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1265,6 +1568,8 @@ fn test_submit_multiple_scores() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1273,6 +1578,8 @@ fn test_submit_multiple_scores() { Option::None, // zero entry premium ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); @@ -1284,7 +1591,9 @@ fn test_submit_multiple_scores() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1320,6 +1629,8 @@ fn test_submit_scores_tiebreaker() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1328,6 +1639,8 @@ fn test_submit_scores_tiebreaker() { Option::None, // zero entry premium ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Complete tournament with tied scores but different death dates contracts.tournament.enter_tournament(tournament_id, Option::None); contracts.tournament.enter_tournament(tournament_id, Option::None); @@ -1338,7 +1651,9 @@ fn test_submit_scores_tiebreaker() { contracts .tournament - .start_tournament(tournament_id, true, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, true, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1373,6 +1688,8 @@ fn test_submit_scores_after_submission_period() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), // start time TEST_END_TIME().into(), // end time MIN_SUBMISSION_PERIOD.into(), // submission period @@ -1381,17 +1698,20 @@ fn test_submit_scores_after_submission_period() { Option::None, // zero entry premium ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Enter tournament contracts.tournament.enter_tournament(tournament_id, Option::None); - // Move to tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start tournament approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -1418,6 +1738,8 @@ fn test_submit_scores_before_tournament_ends() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), // start time TEST_END_TIME().into(), // end time MIN_SUBMISSION_PERIOD.into(), @@ -1426,17 +1748,20 @@ fn test_submit_scores_before_tournament_ends() { Option::None, // zero entry premium ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Enter tournament contracts.tournament.enter_tournament(tournament_id, Option::None); - // Set timestamp before tournament start time testing::set_block_timestamp(TEST_START_TIME().into()); // Start tournament approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // Create adventurer with score let submitted_adventurer = create_dead_adventurer_with_xp(10); @@ -1459,6 +1784,8 @@ fn test_submit_scores_replace_lower_score() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1467,6 +1794,8 @@ fn test_submit_scores_replace_lower_score() { Option::None, ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Create multiple players let player2 = starknet::contract_address_const::<0x456>(); let player3 = starknet::contract_address_const::<0x789>(); @@ -1484,26 +1813,31 @@ fn test_submit_scores_replace_lower_score() { contracts.lords.mint(player3, STARTING_BALANCE); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament for all players testing::set_block_timestamp(TEST_START_TIME().into()); utils::impersonate(OWNER()); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player2); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player3); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1567,6 +1901,8 @@ fn test_distribute_prizes_with_prizes() { 1 ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1575,7 +1911,9 @@ fn test_distribute_prizes_with_prizes() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1622,6 +1960,8 @@ fn test_distribute_prizes_prize_already_claimed() { 1 ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1630,7 +1970,9 @@ fn test_distribute_prizes_prize_already_claimed() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1666,6 +2008,8 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1680,6 +2024,8 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { ); let gated_submission_type = GatedSubmissionType::token_id(1); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1688,7 +2034,9 @@ fn test_distribute_prizes_with_gated_tokens_criteria() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // check tournament entries assert(contracts.tournament.tournament_entries(tournament_id) == 2, 'Invalid entries'); @@ -1733,6 +2081,8 @@ fn test_distribute_prizes_with_gated_tokens_uniform() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1747,6 +2097,8 @@ fn test_distribute_prizes_with_gated_tokens_uniform() { ); let gated_submission_type = GatedSubmissionType::token_id(1); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1755,7 +2107,9 @@ fn test_distribute_prizes_with_gated_tokens_uniform() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // check tournament entries assert(contracts.tournament.tournament_entries(tournament_id) == 3, 'Invalid entries'); @@ -1796,6 +2150,8 @@ fn test_distribute_prizes_with_gated_tournaments() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1804,6 +2160,8 @@ fn test_distribute_prizes_with_gated_tournaments() { Option::None, // zero entry premium ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1812,7 +2170,9 @@ fn test_distribute_prizes_with_gated_tournaments() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1834,8 +2194,13 @@ fn test_distribute_prizes_with_gated_tournaments() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + current_time, + current_time + MIN_REGISTRATION_PERIOD.into(), current_time + MIN_REGISTRATION_PERIOD.into(), - current_time + 1 + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into(), + current_time + + + MIN_REGISTRATION_PERIOD.into() + + MIN_TOURNAMENT_LENGTH.into(), MIN_SUBMISSION_PERIOD.into(), 1, // single top score Option::Some(gated_type), // zero gated type @@ -1849,18 +2214,27 @@ fn test_distribute_prizes_with_gated_tournaments() { // submit game id 1 let gated_submission_type = GatedSubmissionType::game_id(array![1].span()); + testing::set_block_timestamp(current_time); + contracts.tournament.enter_tournament(tournament_id, Option::Some(gated_submission_type)); - testing::set_block_timestamp(current_time + MIN_REGISTRATION_PERIOD.into()); + testing::set_block_timestamp( + current_time + MIN_REGISTRATION_PERIOD.into() + ); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp( - current_time + 1 + MIN_REGISTRATION_PERIOD.into() + MIN_TOURNAMENT_LENGTH.into() + current_time + + + MIN_REGISTRATION_PERIOD.into() + + MIN_TOURNAMENT_LENGTH.into() ); // this is now adventurer 2 @@ -1890,6 +2264,8 @@ fn test_distribute_prizes_with_premiums() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1907,6 +2283,8 @@ fn test_distribute_prizes_with_premiums() { // handle approval for the premium contracts.erc20.approve(contracts.tournament.contract_address, 1); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + contracts.tournament.enter_tournament(tournament_id, Option::None); // check owner now has 1 less premium token @@ -1914,9 +2292,7 @@ fn test_distribute_prizes_with_premiums() { // check tournament now has premium funds assert( - contracts.erc20.balance_of(contracts.tournament.contract_address) == 1, - 'Invalid - balance' + contracts.erc20.balance_of(contracts.tournament.contract_address) == 1, 'Invalid balance' ); testing::set_block_timestamp(TEST_START_TIME().into()); @@ -1925,7 +2301,9 @@ fn test_distribute_prizes_with_premiums() { contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -1961,6 +2339,8 @@ fn test_distribute_prizes_with_premium_creator_fee() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -1969,6 +2349,8 @@ fn test_distribute_prizes_with_premium_creator_fee() { Option::Some(entry_premium), // premium with creator fee ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Enter tournament with two players utils::impersonate(OWNER()); contracts.erc20.approve(contracts.tournament.contract_address, 100); @@ -1980,16 +2362,17 @@ fn test_distribute_prizes_with_premium_creator_fee() { contracts.erc20.approve(contracts.tournament.contract_address, 100); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament and submit scores - testing::set_block_timestamp(TEST_START_TIME().into()); - let creator_initial_balance = contracts.erc20.balance_of(OWNER()); + testing::set_block_timestamp(TEST_START_TIME().into()); + utils::impersonate(OWNER()); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player2); contracts.eth.mint(player2, STARTING_BALANCE); @@ -1997,7 +2380,9 @@ fn test_distribute_prizes_with_premium_creator_fee() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -2051,6 +2436,8 @@ fn test_distribute_prizes_with_premium_multiple_winners() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -2059,6 +2446,8 @@ fn test_distribute_prizes_with_premium_multiple_winners() { Option::Some(entry_premium), // premium with distribution ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Create and enter with 4 players let player2 = starknet::contract_address_const::<0x456>(); let player3 = starknet::contract_address_const::<0x789>(); @@ -2087,7 +2476,6 @@ fn test_distribute_prizes_with_premium_multiple_winners() { contracts.erc20.approve(contracts.tournament.contract_address, 100); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament testing::set_block_timestamp(TEST_START_TIME().into()); let third_initial = contracts.erc20.balance_of(OWNER()); @@ -2097,7 +2485,9 @@ fn test_distribute_prizes_with_premium_multiple_winners() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player2); contracts.eth.mint(player2, STARTING_BALANCE); @@ -2105,7 +2495,9 @@ fn test_distribute_prizes_with_premium_multiple_winners() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player3); contracts.eth.mint(player3, STARTING_BALANCE); @@ -2113,7 +2505,9 @@ fn test_distribute_prizes_with_premium_multiple_winners() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player4); contracts.eth.mint(player4, STARTING_BALANCE); @@ -2121,7 +2515,9 @@ fn test_distribute_prizes_with_premium_multiple_winners() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); testing::set_block_timestamp(TEST_END_TIME().into()); @@ -2192,6 +2588,8 @@ fn test_tournament_with_no_submissions() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -2200,6 +2598,8 @@ fn test_tournament_with_no_submissions() { Option::Some(entry_premium), ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Add some prizes contracts.erc20.approve(contracts.tournament.contract_address, STARTING_BALANCE); contracts.erc721.approve(contracts.tournament.contract_address, 1); @@ -2239,17 +2639,18 @@ fn test_tournament_with_no_submissions() { contracts.erc20.approve(contracts.tournament.contract_address, 100); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament for all players - testing::set_block_timestamp(TEST_START_TIME().into()); - // Store initial balances let creator_initial = contracts.erc20.balance_of(OWNER()); + testing::set_block_timestamp(TEST_START_TIME().into()); + utils::impersonate(OWNER()); approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player2); contracts.eth.mint(player2, STARTING_BALANCE); @@ -2257,7 +2658,9 @@ fn test_tournament_with_no_submissions() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); utils::impersonate(player3); contracts.eth.mint(player3, STARTING_BALANCE); @@ -2265,7 +2668,9 @@ fn test_tournament_with_no_submissions() { approve_game_costs(contracts.eth, contracts.lords, contracts.tournament, 1); contracts .tournament - .start_tournament(tournament_id, false, Option::None, ZERO(), array![].span(), array![].span()); + .start_tournament( + tournament_id, false, Option::None, ZERO(), array![].span(), array![].span() + ); // Move to after tournament and submission period without any score submissions testing::set_block_timestamp((TEST_END_TIME() + MIN_SUBMISSION_PERIOD).into()); @@ -2311,6 +2716,8 @@ fn test_tournament_with_no_starts() { .create_tournament( TOURNAMENT_NAME(), TOURNAMENT_DESCRIPTION(), + TEST_REGISTRATION_START_TIME().into(), + TEST_REGISTRATION_END_TIME().into(), TEST_START_TIME().into(), TEST_END_TIME().into(), MIN_SUBMISSION_PERIOD.into(), @@ -2339,6 +2746,8 @@ fn test_tournament_with_no_starts() { 1 ); + testing::set_block_timestamp(TEST_REGISTRATION_START_TIME().into()); + // Create multiple players let player2 = starknet::contract_address_const::<0x456>(); let player3 = starknet::contract_address_const::<0x789>(); @@ -2358,9 +2767,6 @@ fn test_tournament_with_no_starts() { contracts.erc20.approve(contracts.tournament.contract_address, 100); contracts.tournament.enter_tournament(tournament_id, Option::None); - // Start tournament for all players - testing::set_block_timestamp(TEST_START_TIME().into()); - // Store initial balances let creator_initial = contracts.erc20.balance_of(OWNER()); diff --git a/contracts/src/ls15_components/tournament.cairo b/contracts/src/ls15_components/tournament.cairo index 2c980e9..65c6786 100644 --- a/contracts/src/ls15_components/tournament.cairo +++ b/contracts/src/ls15_components/tournament.cairo @@ -21,6 +21,8 @@ trait ITournament { ref self: TState, name: felt252, description: ByteArray, + registration_start_time: u64, + registration_end_time: u64, start_time: u64, end_time: u64, submission_period: u64, @@ -62,10 +64,10 @@ pub mod tournament_component { use core::num::traits::Zero; use tournament::ls15_components::constants::{ - VRF_COST_PER_GAME, MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, MIN_TOURNAMENT_LENGTH, - MAX_TOURNAMENT_LENGTH, MIN_SUBMISSION_PERIOD, MAX_SUBMISSION_PERIOD, - TEST_MIN_REGISTRATION_PERIOD, TEST_MIN_SUBMISSION_PERIOD, TEST_MIN_TOURNAMENT_LENGTH, - GAME_EXPIRATION_PERIOD, ETHEREUM_ADDRESS, ETH_SAFE_AMOUNT, LORDS_SAFE_AMOUNT + VRF_COST_PER_GAME, MIN_REGISTRATION_PERIOD, MAX_REGISTRATION_PERIOD, + MIN_TOURNAMENT_LENGTH, MAX_TOURNAMENT_LENGTH, MIN_SUBMISSION_PERIOD, MAX_SUBMISSION_PERIOD, TEST_MIN_REGISTRATION_PERIOD, TEST_MIN_SUBMISSION_PERIOD, + TEST_MIN_TOURNAMENT_LENGTH, GAME_EXPIRATION_PERIOD, ETHEREUM_ADDRESS, LORDS_ADDRESS, + SURVIVORS_ADDRESS, BEASTS_ADDRESS, ETH_SAFE_AMOUNT, LORDS_SAFE_AMOUNT }; use tournament::ls15_components::interfaces::{ ILootSurvivorDispatcher, ILootSurvivorDispatcherTrait, IPragmaABIDispatcher, @@ -107,8 +109,11 @@ pub mod tournament_component { // // Create Tournament // - pub const START_TIME_NOT_AFTER_MIN_REGISTRATION: felt252 = 'start time too close'; - pub const START_TIME_NOT_BEFORE_MAX_REGISTRATION: felt252 = 'start time too far'; + pub const START_TIME_NOT_IN_FUTURE: felt252 = 'start time not in future'; + pub const REGISTRATION_PERIOD_TOO_SHORT: felt252 = 'registration period too short'; + pub const REGISTRATION_PERIOD_TOO_LONG: felt252 = 'registration period too long'; + pub const REGISTRATION_START_TOO_LATE: felt252 = 'registration start too late'; + pub const REGISTRATION_END_TOO_LATE: felt252 = 'registration end too late'; pub const TOURNAMENT_TOO_SHORT: felt252 = 'tournament too short'; pub const TOURNAMENT_TOO_LONG: felt252 = 'tournament too long'; pub const ZERO_WINNERS_COUNT: felt252 = 'zero winners count'; @@ -133,6 +138,7 @@ pub mod tournament_component { // // Enter Tournament // + pub const NOT_WITHIN_REGISTRATION_PERIOD: felt252 = 'not within registration period'; pub const TOURNAMENT_ALREADY_STARTED: felt252 = 'tournament already started'; pub const TOURNAMENT_NOT_STARTED: felt252 = 'tournament not started'; pub const INVALID_GATED_SUBMISSION_TYPE: felt252 = 'invalid gated submission type'; @@ -163,6 +169,7 @@ pub mod tournament_component { // // Add Prize // + pub const TOURNAMENT_ENDED: felt252 = 'tournament ended'; pub const PRIZE_POSITION_TOO_LARGE: felt252 = 'prize position too large'; pub const PRIZE_TOKEN_NOT_REGISTERED: felt252 = 'prize token not registered'; pub const INVALID_SAFE_TOKEN_AMOUNT: felt252 = 'invalid safe token amount'; @@ -176,7 +183,6 @@ pub mod tournament_component { pub const PRIZE_ALREADY_CLAIMED: felt252 = 'prize already claimed'; } - #[embeddable_as(TournamentImpl)] impl Tournament< TContractState, @@ -232,7 +238,7 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - self._is_token_registered(ref store, token) + self._is_token_registered(store, token) } // TODO: add for V2 (use Ekubo tokens) @@ -244,10 +250,30 @@ pub mod tournament_component { // self._register_tokens(ref store, tokens); // } + /// @title Create tournament + /// @notice Allows a player to create a tournament. + /// @dev Registration times provide capability of seasons (overlaps of entry periods and + /// start periods). + /// @param self A reference to the ContractState object. + /// @param name A felt252 representing the name of the tournament. + /// @param description A ByteArray representing the description of the tournament. + /// @param registration_start_time A u64 representing the start time of the registration + /// period. + /// @param registration_end_time A u64 representing the end time of the registration period. + /// @param start_time A u64 representing the start time of the tournament. + /// @param end_time A u64 representing the end time of the tournament. + /// @param submission_period A u64 representing the length of the submission period. + /// @param winners_count A u8 representing the number of winners. + /// @param gated_type A Option representing the gated type of the tournament. + /// @param entry_premium A Option representing the entry premium of the tournament. + // TODO: check the safety of setting a large length array of entry criteria for gated tokens + // gated token entries must play using all entry allowances fn create_tournament( ref self: ComponentState, name: felt252, description: ByteArray, + registration_start_time: u64, + registration_end_time: u64, start_time: u64, end_time: u64, submission_period: u64, @@ -260,17 +286,29 @@ pub mod tournament_component { ); let mut store: Store = StoreTrait::new(world); - self._assert_start_time_after_min_registration(ref store, start_time); - self._assert_start_time_before_max_registration(start_time); - self._assert_tournament_length_not_too_short(ref store, end_time, start_time); + self._assert_future_start_time(registration_start_time, start_time); + self + ._assert_bigger_than_min_registration_period( + store, registration_start_time, registration_end_time + ); + self + ._assert_less_than_max_registration_period( + registration_start_time, registration_end_time + ); + self + ._assert_registration_start_not_after_tournament_start( + registration_start_time, start_time + ); + self._assert_registration_end_not_after_tournament_end(registration_end_time, end_time); + self._assert_tournament_length_not_too_short(store, end_time, start_time); self._assert_tournament_length_not_too_long(end_time, start_time); - self._assert_submission_period_larger_than_minimum(ref store, submission_period); + self._assert_submission_period_larger_than_minimum(store, submission_period); self._assert_submission_period_less_than_maximum(submission_period); self._assert_winners_count_greater_than_zero(winners_count); - self._assert_gated_type_validates(ref store, gated_type); + self._assert_gated_type_validates(store, gated_type); self ._assert_premium_token_registered_and_distribution_valid( - ref store, entry_premium.clone(), winners_count + store, entry_premium.clone(), winners_count ); // create a new tournament @@ -280,6 +318,8 @@ pub mod tournament_component { name, description, get_caller_address(), + registration_start_time, + registration_end_time, start_time, end_time, submission_period, @@ -289,6 +329,12 @@ pub mod tournament_component { ) } + /// @title Enter tournament + /// @notice Allows a player to enter a tournament for a particular tournament id. + /// @dev Requires a tournament to have already been created. + /// @param self A reference to the ContractState object. + /// @param tournament_id A u64 representing the unique ID of the tournament. + /// @param gated_submission_type A bool representing whether to start everyones games. // TODO: check the safety of setting a large length array of entry criteria for gated tokens // gated token entries must play using all entry allowances fn enter_tournament( @@ -302,7 +348,10 @@ pub mod tournament_component { let mut store: Store = StoreTrait::new(world); let tournament = store.get_tournament(tournament_id); // assert tournament has not started - self._assert_tournament_not_started(tournament.start_time, tournament_id); + self + ._assert_within_registration_period( + tournament.registration_start_time, tournament.registration_end_time + ); let mut entries: u64 = 1; @@ -311,8 +360,8 @@ pub mod tournament_component { match tournament.gated_type { Option::Some(gated_type) => { self - ._assert_gated_submission_qualifies( - ref store, + ._get_gated_entries( + store, gated_type, gated_submission_type, get_caller_address(), @@ -345,6 +394,19 @@ pub mod tournament_component { // TODO: can store multiple game ids in single felt with merkle tree? } + /// @title Start tournament + /// @notice Allows a player to start a tournament for a particular tournament id. + /// @dev Requires the player starting to have already entered. + /// @param self A reference to the ContractState object. + /// @param tournament_id A u64 representing the unique ID of the tournament. + /// @param start_all A bool representing whether to start everyones games. + /// @param start_count A u64 representing the number of games to start. + /// @param client_reward_address A contract address representing the address to send client + /// rewards. + /// @param golden_token_free_game_token_ids A span of u256 representing the token ids for + /// golden tokens. + /// @param blobert_free_game_token_ids A span of u256 representing the token ids for blobert + /// tokens. fn start_tournament( ref self: ComponentState, tournament_id: u64, @@ -358,34 +420,40 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - // assert tournament is active - self._assert_tournament_active(ref store, tournament_id); - // assert not too many free games if start count supplied + + self._assert_tournament_active(store, tournament_id); self ._assert_free_game_ids_not_larger_than_start_count( start_count, golden_token_free_game_token_ids, blobert_free_game_token_ids ); - // if starting all games, assert the tournament period is within max + if (start_all) { - self._assert_tournament_period_within_max(ref store, tournament_id); + self._assert_tournament_period_within_max(store, tournament_id); } - let tournament_config = store.get_tournament_config(get_contract_address()); - + // TODO: store the dispatchers + let mut tournament_config = store.get_tournament_config(get_contract_address()); let mut ls_dispatcher = ILootSurvivorDispatcher { contract_address: tournament_config.loot_survivor }; // first get the number of entries for calculations and allowed starts - let mut entries = 0; - if start_all { // if starting all games, assert there are entries that haven't started // get the total number of entries to mint let addresses = store.get_tournament_entry_addresses(tournament_id).addresses; - assert(addresses.len() > 0, Errors::ALL_ENTRIES_STARTED); - entries = self._calculate_total_entries(ref store, tournament_id, addresses); + let total_entries = self._calculate_total_entries(store, tournament_id, addresses); + match start_count { + Option::Some(start_count) => { + assert(total_entries >= start_count, Errors::START_COUNT_TOO_LARGE); + entries = start_count; + }, + Option::None => { + assert(total_entries > 0, Errors::ALL_ENTRIES_STARTED); + entries = total_entries; + }, + }; } else { let address_entries = store .get_address_entries(tournament_id, get_caller_address()) @@ -394,69 +462,33 @@ pub mod tournament_component { .get_tournament_starts(tournament_id, get_caller_address()) .start_count; assert(address_entries > address_starts, Errors::ALL_ENTRIES_STARTED); + let remaining_entries = address_entries - address_starts; match start_count { Option::Some(start_count) => { - assert(address_entries >= start_count, Errors::START_COUNT_TOO_LARGE); + assert(remaining_entries >= start_count, Errors::START_COUNT_TOO_LARGE); entries = start_count; }, Option::None => { - assert(address_entries > 0, Errors::ADDRESS_ENTRIES_STARTED); - entries = address_entries; + assert(remaining_entries > 0, Errors::ADDRESS_ENTRIES_STARTED); + entries = remaining_entries; }, }; } - // assert not free games not longer self ._assert_free_game_ids_not_larger_than_entries( entries, golden_token_free_game_token_ids, blobert_free_game_token_ids ); - let mut free_games = 0; - - let mut golden_token_index = 0; - loop { - if golden_token_index == golden_token_free_game_token_ids.len() { - break; - } - let golden_token_id = *golden_token_free_game_token_ids.at(golden_token_index); - self - ._assert_token_owner( - tournament_config.golden_token, golden_token_id, get_caller_address() - ); - // check if golden token free game is available - let free_game_available = ls_dispatcher - .free_game_available(FreeGameTokenType::GoldenToken, golden_token_id.low); - assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); - // flash loan golden tokens - IERC721Dispatcher { contract_address: tournament_config.golden_token } - .transfer_from(get_caller_address(), get_contract_address(), golden_token_id); - free_games += 1; - golden_token_index += 1; - }; + let free_games = self + ._flash_loan_free_game_tokens( + tournament_config, + ls_dispatcher, + golden_token_free_game_token_ids, + blobert_free_game_token_ids + ); - let mut blobert_token_index = 0; - loop { - if blobert_token_index == blobert_free_game_token_ids.len() { - break; - } - let blobert_token_id = *blobert_free_game_token_ids.at(blobert_token_index); - self - ._assert_token_owner( - tournament_config.blobert, blobert_token_id, get_caller_address() - ); - // check if caller has blobert - let free_game_available = ls_dispatcher - .free_game_available( - FreeGameTokenType::LaunchTournamentChampion, blobert_token_id.low - ); - assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); - // flash loan bloberts - IERC721Dispatcher { contract_address: tournament_config.blobert } - .transfer_from(get_caller_address(), get_contract_address(), blobert_token_id); - free_games += 1; - blobert_token_index += 1; - }; + let paid_starts = entries - free_games.into(); // define contract interfaces let lords_dispatcher: IERC20Dispatcher = IERC20Dispatcher { @@ -468,214 +500,95 @@ pub mod tournament_component { // get current game cost let cost_to_play = ls_dispatcher.get_cost_to_play(); - let entries_cost = (entries.into() - free_games.into()) * cost_to_play.into(); + let entries_cost = paid_starts.into() * cost_to_play.into(); - // transfer base game cost - lords_dispatcher + // check we actually need to pay for a game + if (paid_starts > 0) { + lords_dispatcher .transfer_from(get_caller_address(), get_contract_address(), entries_cost); + lords_dispatcher.approve(tournament_config.loot_survivor, entries_cost); + } - // transfer VRF cost + // transfer VRF cost (still need vrf cost for free games) let vrf_cost = self ._convert_usd_to_wei(tournament_config, entries.into() * VRF_COST_PER_GAME.into()); eth_dispatcher .transfer_from(get_caller_address(), get_contract_address(), vrf_cost.into()); - // set the approvals according to entries - lords_dispatcher.approve(tournament_config.loot_survivor, entries_cost); eth_dispatcher.approve(tournament_config.loot_survivor, vrf_cost.into()); let tournament = store.get_tournament(tournament_id); - // to avoid extra storage we are just providing defualt configs for the adventurers + let mut golden_token_index = 0; + let mut blobert_token_index = 0; + if start_all { - // if start all then we need to loop through stored addresses and mint games + // if start all then we need to loop through stored addresses and mint games // for each - let addresses = store.get_tournament_entry_addresses(tournament_id).addresses; - let mut address_index = 0; + let mut addresses = store.get_tournament_entry_addresses(tournament_id).addresses; + let mut total_start_index = 0; loop { - if address_index == addresses.len() { + if (total_start_index == entries) { break; } - let address = *addresses.at(address_index); - let mut address_entries = store.get_address_entries(tournament_id, address); - let mut entry_index = 0; - let mut game_ids = ArrayTrait::::new(); - loop { - if entry_index == address_entries.entry_count { - break; - } - let mut game_id = 0; - let mut golden_token_index = 0; - let mut blobert_token_index = 0; - if (golden_token_index != golden_token_free_game_token_ids.len()) { - let token_id = *golden_token_free_game_token_ids.at(golden_token_index); - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - (token_id.low).try_into().unwrap(), - true, - contract_address_const::<0>(), - 0, - address - ); - golden_token_index += 1; - } else if (blobert_token_index != blobert_free_game_token_ids.len()) { - let token_id = *blobert_free_game_token_ids.at(blobert_token_index); - game_id = ls_dispatcher - .new_game( + let result = addresses.pop_front(); + match result { + Option::Some(address) => { + self + ._start_game( + ref store, + tournament_id, + ref total_start_index, + entries, + address, + ref golden_token_index, + ref blobert_token_index, + golden_token_free_game_token_ids, + blobert_free_game_token_ids, + ls_dispatcher, client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - token_id.low, - address + tournament.name ); - blobert_token_index += 1; - } else { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - address - ); - } - game_ids.append(game_id.try_into().unwrap()); - let game = TournamentGameModel { - tournament_id, - game_id: game_id.try_into().unwrap(), - address: address, - status: EntryStatus::Started - }; - store.set_tournament_game(@game); - entry_index += 1; + }, + Option::None => { break; }, }; - let starts = TournamentStartIdsModel { tournament_id, address, game_ids }; - store.set_tournament_starts(@starts); - // set entries to 0 - address_entries.entry_count = 0; - store.set_address_entries(@address_entries); - address_index += 1; }; // set stored addresses to empty - let mut addresses = ArrayTrait::::new(); let addresses_model = TournamentEntryAddressesModel { tournament_id, addresses }; store.set_tournament_entry_addresses(@addresses_model); } else { - let mut start_index = store - .get_tournament_starts(tournament_id, get_caller_address()) - .start_count; - let mut game_id = 0; - let mut game_ids = ArrayTrait::::new(); - loop { - if start_index == entries { - break; - } - let mut golden_token_index = 0; - let mut blobert_token_index = 0; - if (golden_token_index != golden_token_free_game_token_ids.len()) { - let token_id = *golden_token_free_game_token_ids.at(golden_token_index); - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - (token_id.low).try_into().unwrap(), - true, - contract_address_const::<0>(), - 0, - get_caller_address() - ); - golden_token_index += 1; - } else if (blobert_token_index != blobert_free_game_token_ids.len()) { - let token_id = *blobert_free_game_token_ids.at(blobert_token_index); - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - token_id.low, - get_caller_address() - ); - blobert_token_index += 1; - } else { - game_id = ls_dispatcher - .new_game( - client_reward_address, - 12, // wand - tournament.name, - 0, - true, - contract_address_const::<0>(), - 0, - get_caller_address() - ); - } - game_ids.append(game_id.try_into().unwrap()); - let game = TournamentGameModel { + let mut total_start_index = 0; + self + ._start_game( + ref store, tournament_id, - game_id: game_id.try_into().unwrap(), - address: get_caller_address(), - status: EntryStatus::Started - }; - store.set_tournament_game(@game); - start_index += 1; - }; - // set stored started game ids and new entries (if no start count provided then - // starts = entries) - let starts = TournamentStartIdsModel { - tournament_id, address: get_caller_address(), game_ids - }; - store.set_tournament_starts(@starts); - let address_starts = TournamentStartsAddressModel { - tournament_id, address: get_caller_address(), start_count: entries - }; - store.set_address_starts(@address_starts); - - // send the free game tokens back if (any were available to use) - - let mut golden_token_index = 0; - loop { - if golden_token_index == golden_token_free_game_token_ids.len() { - break; - } - IERC721Dispatcher { contract_address: tournament_config.golden_token } - .transfer_from( - get_contract_address(), - get_caller_address(), - *golden_token_free_game_token_ids.at(golden_token_index) - ); - golden_token_index += 1; - }; - - let mut blobert_token_index = 0; - loop { - if blobert_token_index == blobert_free_game_token_ids.len() { - break; - } - IERC721Dispatcher { contract_address: tournament_config.blobert } - .transfer_from( - get_contract_address(), - get_caller_address(), - *blobert_free_game_token_ids.at(blobert_token_index) - ); - blobert_token_index += 1; - }; + ref total_start_index, + entries, + get_caller_address(), + ref golden_token_index, + ref blobert_token_index, + golden_token_free_game_token_ids, + blobert_free_game_token_ids, + ls_dispatcher, + client_reward_address, + tournament.name + ); } + + self + ._return_free_game_tokens( + ref tournament_config, + golden_token_free_game_token_ids, + blobert_free_game_token_ids + ); } - // for more efficient gas we assume that the game ids are in order of highest score + /// @title Submit scores + /// @notice Allows anyone to submit scores for a tournament for a particular tournament id. + /// @dev For more efficient gas we assume that the game ids are in order of highest score + /// @param self A reference to the ContractState object. + /// @param tournament_id A u64 representing the unique ID of the tournament. + /// @param game_ids A span of felt252 representing the game ids to submit. fn submit_scores( ref self: ComponentState, tournament_id: u64, game_ids: Array ) { @@ -684,11 +597,11 @@ pub mod tournament_component { ); let mut store: Store = StoreTrait::new(world); let mut tournament = store.get_tournament(tournament_id); - // assert tournament ended but not settled + self._assert_tournament_ended(ref tournament); - // assert the submitted scores are less than or equal to the winners count + self._assert_scores_count_valid(ref tournament, game_ids.len()); - // assert submission period is not over + self._assert_tournament_not_settled(ref tournament); let total_entries = store.get_total_entries(tournament_id); @@ -712,16 +625,16 @@ pub mod tournament_component { break; } let game_id = *game_ids.at(game_index); - self._assert_game_started_or_submitted(ref store, tournament_id, game_id); + self._assert_game_started_or_submitted(store, tournament_id, game_id); let adventurer = ls_dispatcher.get_adventurer(game_id.try_into().unwrap()); - let death_date = self.get_death_date_from_id(ref store, game_id); + let death_date = self.get_death_date_from_id(store, game_id); self._assert_valid_score(adventurer); self ._update_tournament_scores( - ref store, + store, tournament_id, game_id, adventurer.xp, @@ -739,6 +652,13 @@ pub mod tournament_component { ); } + /// @title Add prize + /// @notice Allows anyone to add a prize for a tournament for a particular tournament id. + /// @param self A reference to the ContractState object. + /// @param tournament_id A u64 representing the unique ID of the tournament. + /// @param token A contract address representing the token to add as a prize. + /// @param token_data_type A TokenDataType representing the type of token to add as a prize. + /// @param position A u8 representing the scoreboard position to distribute the prize to. fn add_prize( ref self: ComponentState, tournament_id: u64, @@ -750,13 +670,19 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - let tournament = store.get_tournament(tournament_id); - // assert tournament has not started - self._assert_tournament_not_started(tournament.start_time, tournament_id); + let mut tournament = store.get_tournament(tournament_id); + + self._assert_tournament_not_ended(ref tournament); self._deposit_prize(ref store, tournament_id, token, token_data_type, position); } + /// @title Distribute prizes + /// @notice Allows anyone to distribute prizes for a tournament for a particular tournament + /// id. + /// @param self A reference to the ContractState object. + /// @param tournament_id A u64 representing the unique ID of the tournament. + /// @param prize_keys An array of u64 representing the prize keys to distribute. fn distribute_prizes( ref self: ComponentState, tournament_id: u64, prize_keys: Array, ) { @@ -765,13 +691,13 @@ pub mod tournament_component { ); let mut store: Store = StoreTrait::new(world); // assert tournament settled - self._assert_tournament_settled(ref store, tournament_id); + self._assert_tournament_settled(store, tournament_id); self._assert_prize_keys_not_empty(prize_keys.span()); let mut total_entries = store.get_total_entries(tournament_id); - // if noone has started the tournament already, then we need to create the prize keys - // (this should already be taken into account in the provided list) + // if noone has submitted scores for the tournament already, then we need to create the + // prize keys if (!total_entries.premiums_formatted) { self._format_premium_config_into_prize_keys(ref store, tournament_id); } @@ -807,6 +733,17 @@ pub mod tournament_component { // INITIALIZE COMPONENT // + /// @title Initialize tournament + /// @notice Initializes the tournament component for storing its config. + /// @param self A copy to the ContractState object. + /// @param eth A contract address representing the ETH token. + /// @param lords A contract address representing the LORDS token. + /// @param loot_survivor A contract address representing the LOOT SURVIVOR contract. + /// @param oracle A contract address representing the oracle contract. + /// @param golden_token A contract address representing the GOLDEN token. + /// @param blobert A contract address representing the BLOBERT token. + /// @param safe_mode A bool representing whether to use safe mode. + /// @param test_mode A bool representing whether to use test mode. fn initialize( self: @ComponentState, eth: ContractAddress, @@ -834,7 +771,7 @@ pub mod tournament_component { golden_token, blobert, safe_mode, - test_mode + test_mode, } ); } @@ -843,6 +780,12 @@ pub mod tournament_component { // INITIALIZE TOKENS // + /// @title Initialize erc20 + /// @notice Initializes an erc20 token for registration. + /// @param self A copy to the ContractState object. + /// @param token A contract address representing the token. + /// @param name A byte array representing the name of the token. + /// @param symbol A byte array representing the symbol of the token. fn initialize_erc20( self: @ComponentState, token: ContractAddress, @@ -853,7 +796,7 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - assert(!self._is_token_registered(ref store, token), Errors::TOKEN_ALREADY_REGISTERED); + assert(!self._is_token_registered(store, token), Errors::TOKEN_ALREADY_REGISTERED); store .set_token( @TokenModel { @@ -866,7 +809,12 @@ pub mod tournament_component { ); } - + /// @title Initialize erc721 + /// @notice Initializes an erc721 token for registration. + /// @param self A copy to the ContractState object. + /// @param token A contract address representing the token. + /// @param name A byte array representing the name of the token. + /// @param symbol A byte array representing the symbol of the token. fn initialize_erc721( self: @ComponentState, token: ContractAddress, @@ -877,7 +825,7 @@ pub mod tournament_component { self.get_contract().world_dispatcher(), @"tournament" ); let mut store: Store = StoreTrait::new(world); - assert(!self._is_token_registered(ref store, token), Errors::TOKEN_ALREADY_REGISTERED); + assert(!self._is_token_registered(store, token), Errors::TOKEN_ALREADY_REGISTERED); store .set_token( @TokenModel { @@ -895,7 +843,7 @@ pub mod tournament_component { // fn get_score_from_id( - self: @ComponentState, ref store: Store, game_id: felt252 + self: @ComponentState, store: Store, game_id: felt252 ) -> u16 { let tournament_config = store.get_tournament_config(get_contract_address()); let ls_dispatcher = ILootSurvivorDispatcher { @@ -905,7 +853,7 @@ pub mod tournament_component { } fn get_death_date_from_id( - self: @ComponentState, ref store: Store, game_id: felt252 + self: @ComponentState, store: Store, game_id: felt252 ) -> u64 { let tournament_config = store.get_tournament_config(get_contract_address()); let ls_dispatcher = ILootSurvivorDispatcher { @@ -921,7 +869,7 @@ pub mod tournament_component { } fn _is_tournament_active( - self: @ComponentState, ref store: Store, tournament_id: u64 + self: @ComponentState, store: Store, tournament_id: u64 ) -> bool { let tournament = store.get_tournament(tournament_id); tournament.start_time <= get_block_timestamp() @@ -929,7 +877,7 @@ pub mod tournament_component { } fn _is_top_score( - self: @ComponentState, ref store: Store, tournament_id: u64, score: u16 + self: @ComponentState, store: Store, tournament_id: u64, score: u16 ) -> bool { let top_score_ids = self.top_scores(tournament_id); let num_scores = top_score_ids.len(); @@ -938,13 +886,13 @@ pub mod tournament_component { } else { let last_place_id = *top_score_ids.at(num_scores - 1); let last_place_score = self - .get_score_from_id(ref store, last_place_id.try_into().unwrap()); + .get_score_from_id(store, last_place_id.try_into().unwrap()); score >= last_place_score } } fn _is_token_registered( - self: @ComponentState, ref store: Store, token: ContractAddress + self: @ComponentState, store: Store, token: ContractAddress ) -> bool { store.get_token(token).is_registered } @@ -1030,8 +978,26 @@ pub mod tournament_component { // ASSERTIONS // - fn _assert_start_time_after_min_registration( - self: @ComponentState, ref store: Store, start_time: u64 + fn _assert_future_start_time( + self: @ComponentState, + registration_start_time: u64, + start_time: u64 + ) { + assert( + registration_start_time >= get_block_timestamp(), + Errors::START_TIME_NOT_IN_FUTURE + ); + assert( + start_time >= get_block_timestamp(), + Errors::START_TIME_NOT_IN_FUTURE + ); + } + + fn _assert_bigger_than_min_registration_period( + self: @ComponentState, + store: Store, + registration_start_time: u64, + registration_end_time: u64 ) { let test_mode = store.get_tournament_config(get_contract_address()).test_mode; let min_registration_period = if test_mode { @@ -1040,22 +1006,43 @@ pub mod tournament_component { MIN_REGISTRATION_PERIOD }; assert( - start_time >= get_block_timestamp() + min_registration_period.into(), - Errors::START_TIME_NOT_AFTER_MIN_REGISTRATION + registration_end_time - registration_start_time >= min_registration_period.into(), + Errors::REGISTRATION_PERIOD_TOO_SHORT ); } - fn _assert_start_time_before_max_registration( - self: @ComponentState, start_time: u64 + fn _assert_less_than_max_registration_period( + self: @ComponentState, + registration_start_time: u64, + registration_end_time: u64 ) { assert( - start_time <= get_block_timestamp() + MAX_REGISTRATION_PERIOD.into(), - Errors::START_TIME_NOT_BEFORE_MAX_REGISTRATION + registration_end_time - registration_start_time < MAX_REGISTRATION_PERIOD.into(), + Errors::REGISTRATION_PERIOD_TOO_LONG ); } + fn _assert_registration_start_not_after_tournament_start( + self: @ComponentState, + registration_start_time: u64, + tournament_start_time: u64 + ) { + assert( + registration_start_time <= tournament_start_time, + Errors::REGISTRATION_START_TOO_LATE + ); + } + + fn _assert_registration_end_not_after_tournament_end( + self: @ComponentState, + registration_end_time: u64, + tournament_end_time: u64 + ) { + assert(registration_end_time <= tournament_end_time, Errors::REGISTRATION_END_TOO_LATE); + } + fn _assert_tournament_length_not_too_short( - self: @ComponentState, ref store: Store, end_time: u64, start_time: u64 + self: @ComponentState, store: Store, end_time: u64, start_time: u64 ) { let test_mode = store.get_tournament_config(get_contract_address()).test_mode; let min_tournament_length = if test_mode { @@ -1077,7 +1064,7 @@ pub mod tournament_component { } fn _assert_submission_period_larger_than_minimum( - self: @ComponentState, ref store: Store, submission_period: u64 + self: @ComponentState, store: Store, submission_period: u64 ) { let test_mode = store.get_tournament_config(get_contract_address()).test_mode; let min_submission_period = if test_mode { @@ -1108,13 +1095,13 @@ pub mod tournament_component { fn _assert_premium_token_registered_and_distribution_valid( self: @ComponentState, - ref store: Store, + store: Store, premium: Option, winners_count: u8 ) { match premium { Option::Some(token) => { - self._assert_premium_token_registered(ref store, token.token); + self._assert_premium_token_registered(store, token.token); self ._assert_premium_token_distribution_length_not_too_long( token.token_distribution.len(), winners_count.into() @@ -1152,11 +1139,9 @@ pub mod tournament_component { } fn _assert_premium_token_registered( - self: @ComponentState, ref store: Store, token: ContractAddress + self: @ComponentState, store: Store, token: ContractAddress ) { - assert( - self._is_token_registered(ref store, token), Errors::PREMIUM_TOKEN_NOT_REGISTERED - ); + assert(self._is_token_registered(store, token), Errors::PREMIUM_TOKEN_NOT_REGISTERED); } fn _assert_premium_token_distribution_length_not_too_long( @@ -1172,20 +1157,32 @@ pub mod tournament_component { } fn _assert_prize_token_registered( - self: @ComponentState, ref store: Store, token: ContractAddress + self: @ComponentState, store: Store, token: ContractAddress ) { - assert(self._is_token_registered(ref store, token), Errors::PRIZE_TOKEN_NOT_REGISTERED); + assert(self._is_token_registered(store, token), Errors::PRIZE_TOKEN_NOT_REGISTERED); } - fn _assert_tournament_not_started( - self: @ComponentState, start_time: u64, tournament_id: u64 + // fn _assert_tournament_not_started( + // self: @ComponentState, start_time: u64, tournament_id: u64 + // ) { + // assert(start_time > get_block_timestamp(), Errors::TOURNAMENT_ALREADY_STARTED); + // } + + fn _assert_within_registration_period( + self: @ComponentState, + registration_start_time: u64, + registration_end_time: u64 ) { - assert(start_time > get_block_timestamp(), Errors::TOURNAMENT_ALREADY_STARTED); + assert( + registration_start_time <= get_block_timestamp() + && registration_end_time >= get_block_timestamp(), + Errors::NOT_WITHIN_REGISTRATION_PERIOD + ); } fn _assert_game_started_or_submitted( self: @ComponentState, - ref store: Store, + store: Store, tournament_id: u64, game_id: felt252 ) { @@ -1197,9 +1194,9 @@ pub mod tournament_component { } fn _assert_tournament_active( - self: @ComponentState, ref store: Store, tournament_id: u64 + self: @ComponentState, store: Store, tournament_id: u64 ) { - let is_active = self._is_tournament_active(ref store, tournament_id); + let is_active = self._is_tournament_active(store, tournament_id); assert(is_active, Errors::TOURNAMENT_NOT_ACTIVE); } @@ -1209,8 +1206,14 @@ pub mod tournament_component { assert(tournament.end_time <= get_block_timestamp(), Errors::TOURNAMENT_NOT_ENDED); } + fn _assert_tournament_not_ended( + self: @ComponentState, ref tournament: TournamentModel + ) { + assert(tournament.end_time > get_block_timestamp(), Errors::TOURNAMENT_ENDED); + } + fn _assert_tournament_period_within_max( - self: @ComponentState, ref store: Store, tournament_id: u64 + self: @ComponentState, store: Store, tournament_id: u64 ) { let tournament = store.get_tournament(tournament_id); assert( @@ -1261,10 +1264,7 @@ pub mod tournament_component { } fn _assert_prize_position_less_than_winners_count( - self: @ComponentState, - ref store: Store, - tournament_id: u64, - position: u8 + self: @ComponentState, store: Store, tournament_id: u64, position: u8 ) { let tournament = store.get_tournament(tournament_id); assert(position <= tournament.winners_count, Errors::PRIZE_POSITION_TOO_LARGE); @@ -1311,14 +1311,24 @@ pub mod tournament_component { } fn _assert_gated_type_validates( - self: @ComponentState, ref store: Store, gated_type: Option + self: @ComponentState, store: Store, gated_type: Option ) { match gated_type { Option::Some(gated_type) => { match gated_type { GatedType::token(token) => { + let safe_mode = store + .get_tournament_config(get_contract_address()) + .safe_mode; + if (safe_mode) { + assert( + token.token == SURVIVORS_ADDRESS() + || token.token == BEASTS_ADDRESS(), + Errors::INVALID_TOKEN_FOR_SAFE_MODE + ); + } assert( - self._is_token_registered(ref store, token.token), + self._is_token_registered(store, token.token), Errors::GATED_TOKEN_NOT_REGISTERED ) }, @@ -1330,7 +1340,7 @@ pub mod tournament_component { } self ._assert_tournament_settled( - ref store, *tournament_ids.at(loop_index) + store, *tournament_ids.at(loop_index) ); loop_index += 1; } @@ -1347,7 +1357,7 @@ pub mod tournament_component { } fn _assert_tournament_settled( - self: @ComponentState, ref store: Store, tournament_id: u64 + self: @ComponentState, store: Store, tournament_id: u64 ) { let tournament = store.get_tournament(tournament_id); assert( @@ -1377,9 +1387,9 @@ pub mod tournament_component { assert(prize_keys.len() > 0, Errors::NO_PRIZE_KEYS); } - fn _assert_gated_submission_qualifies( + fn _get_gated_entries( self: @ComponentState, - ref store: Store, + store: Store, gated_type: GatedType, gated_submission_type: Option, address: ContractAddress, @@ -1397,11 +1407,11 @@ pub mod tournament_component { // assert the owner owns game ids and has a top score in tournaments self ._assert_has_qualified_in_tournaments( - ref store, tournament_ids, gated_submission_type, address + store, tournament_ids, gated_submission_type, address ); }, GatedType::address(qualifying_addresses) => { - self._assert_qualifying_address(ref store, address, qualifying_addresses); + self._assert_qualifying_address(address, qualifying_addresses); }, } } @@ -1450,7 +1460,7 @@ pub mod tournament_component { fn _assert_has_qualified_in_tournaments( self: @ComponentState, - ref store: Store, + store: Store, tournament_ids: Span, gated_submission_type: Option, address: ContractAddress @@ -1485,7 +1495,7 @@ pub mod tournament_component { assert( self ._is_top_score( - ref store, *tournament_ids.at(loop_index), adventurer.xp + store, *tournament_ids.at(loop_index), adventurer.xp ), Errors::SUBMITTED_GAME_NOT_TOP_SCORE ); @@ -1500,7 +1510,6 @@ pub mod tournament_component { fn _assert_qualifying_address( self: @ComponentState, - ref store: Store, address: ContractAddress, qualifying_addresses: Span ) { @@ -1524,12 +1533,29 @@ pub mod tournament_component { // INTERNALS // + /// @title Create tournament + /// @notice Creates a tournament. + /// @param self A referance to the ContractState object. + /// @param store A referance to the Store object. + /// @param name A felt252 representing the name of the tournament. + /// @param description A byte array representing the description of the tournament. + /// @param creator A contract address representing the creator of the tournament. + /// @param registration_start_time A u64 representing the registration start time. + /// @param registration_end_time A u64 representing the registration end time. + /// @param start_time A u64 representing the start time. + /// @param end_time A u64 representing the end time. + /// @param submission_period A u64 representing the submission period. + /// @param winners_count A u8 representing the number of winners. + /// @param gated_type A GatedType representing the gated type. + /// @param entry_premium A Premium representing the entry premium. fn _create_tournament( ref self: ComponentState, ref store: Store, name: felt252, description: ByteArray, creator: ContractAddress, + registration_start_time: u64, + registration_end_time: u64, start_time: u64, end_time: u64, submission_period: u64, @@ -1546,11 +1572,13 @@ pub mod tournament_component { name, description, creator, - gated_type, + registration_start_time, + registration_end_time, start_time, end_time, submission_period, winners_count, + gated_type, entry_premium, } ); @@ -1578,14 +1606,14 @@ pub mod tournament_component { // Errors::TOKEN_ALREADY_REGISTERED // ); - // if (safe_mode) { - // assert( - // token.token == ETHEREUM_ADDRESS() - // || token.token == LORDS_ADDRESS() - // || token.token == SURVIVORS_ADDRESS(), - // Errors::INVALID_TOKEN_FOR_SAFE_MODE - // ); - // } + // if (safe_mode) { + // assert( + // token.token == ETHEREUM_ADDRESS() + // || token.token == LORDS_ADDRESS() + // || token.token == SURVIVORS_ADDRESS(), + // Errors::INVALID_TOKEN_FOR_SAFE_MODE + // ); + // } // let mut name = ""; // let mut symbol = ""; @@ -1667,6 +1695,185 @@ pub mod tournament_component { // } // } + fn _start_game( + ref self: ComponentState, + ref store: Store, + tournament_id: u64, + ref total_start_index: u64, + total_entries: u64, + address: ContractAddress, + ref golden_token_index: u32, + ref blobert_token_index: u32, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span, + ls_dispatcher: ILootSurvivorDispatcher, + client_reward_address: ContractAddress, + name: felt252 + ) { + let mut address_address_entries = store + .get_address_entries(tournament_id, address) + .entry_count; + let mut address_start_index = store + .get_tournament_starts(tournament_id, address) + .start_count; + let mut game_ids = ArrayTrait::::new(); + loop { + if (address_start_index == address_address_entries + || total_start_index == total_entries) { + break; + } + let mut game_id = 0; + if (golden_token_index != golden_token_free_game_token_ids.len()) { + let token_id = *golden_token_free_game_token_ids.at(golden_token_index); + // to avoid extra storage we are just providing defualt configs for the + // adventurers + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + name, + (token_id.low).try_into().unwrap(), + true, + contract_address_const::<0>(), + 0, + address + ); + golden_token_index += 1; + } else if (blobert_token_index != blobert_free_game_token_ids.len()) { + let token_id = *blobert_free_game_token_ids.at(blobert_token_index); + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + name, + 0, + true, + contract_address_const::<0>(), + token_id.low, + address + ); + blobert_token_index += 1; + } else { + game_id = ls_dispatcher + .new_game( + client_reward_address, + 12, // wand + name, + 0, + true, + contract_address_const::<0>(), + 0, + address + ); + } + game_ids.append(game_id.try_into().unwrap()); + let game = TournamentGameModel { + tournament_id, + game_id: game_id.try_into().unwrap(), + address: address, + status: EntryStatus::Started + }; + store.set_tournament_game(@game); + address_start_index += 1; + total_start_index += 1; + }; + let starts = TournamentStartIdsModel { tournament_id, address, game_ids }; + store.set_tournament_starts(@starts); + let address_starts = TournamentStartsAddressModel { + tournament_id, address, start_count: address_start_index + }; + store.set_address_starts(@address_starts); + } + + fn _flash_loan_free_game_tokens( + ref self: ComponentState, + tournament_config: TournamentConfig, + ls_dispatcher: ILootSurvivorDispatcher, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span + ) -> u32 { + let mut free_games = 0; + let mut golden_token_index = 0; + loop { + if golden_token_index == golden_token_free_game_token_ids.len() { + break; + } + let golden_token_id = *golden_token_free_game_token_ids.at(golden_token_index); + self + ._assert_token_owner( + tournament_config.golden_token, golden_token_id, get_caller_address() + ); + // check if free game token is available + let free_game_available = ls_dispatcher + .free_game_available(FreeGameTokenType::GoldenToken, golden_token_id.low); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan free game tokens + IERC721Dispatcher { contract_address: tournament_config.golden_token } + .transfer_from(get_caller_address(), get_contract_address(), golden_token_id); + free_games += 1; + golden_token_index += 1; + }; + + let mut blobert_token_index = 0; + loop { + if blobert_token_index == blobert_free_game_token_ids.len() { + break; + } + let blobert_token_id = *blobert_free_game_token_ids.at(blobert_token_index); + self + ._assert_token_owner( + tournament_config.blobert, blobert_token_id, get_caller_address() + ); + // check if caller has blobert + let free_game_available = ls_dispatcher + .free_game_available( + FreeGameTokenType::LaunchTournamentChampion, blobert_token_id.low + ); + assert(free_game_available, Errors::FREE_GAME_NOT_AVAILABLE); + // flash loan bloberts + IERC721Dispatcher { contract_address: tournament_config.blobert } + .transfer_from(get_caller_address(), get_contract_address(), blobert_token_id); + free_games += 1; + blobert_token_index += 1; + }; + free_games + } + + fn _return_free_game_tokens( + ref self: ComponentState, + ref tournament_config: TournamentConfig, + golden_token_free_game_token_ids: Span, + blobert_free_game_token_ids: Span + ) { + let mut golden_token_index = 0; + loop { + if golden_token_index == golden_token_free_game_token_ids.len() { + break; + } + IERC721Dispatcher { contract_address: tournament_config.golden_token } + .transfer_from( + get_contract_address(), + get_caller_address(), + *golden_token_free_game_token_ids.at(golden_token_index) + ); + golden_token_index += 1; + }; + + let mut blobert_token_index = 0; + loop { + if blobert_token_index == blobert_free_game_token_ids.len() { + break; + } + IERC721Dispatcher { contract_address: tournament_config.blobert } + .transfer_from( + get_contract_address(), + get_caller_address(), + *blobert_free_game_token_ids.at(blobert_token_index) + ); + blobert_token_index += 1; + }; + } + fn _format_premium_config_into_prize_keys( ref self: ComponentState, ref store: Store, tournament_id: u64 ) { @@ -1744,7 +1951,7 @@ pub mod tournament_component { } fn _update_tournament_scores( ref self: ComponentState, - ref store: Store, + store: Store, tournament_id: u64, game_id: felt252, score: u16, @@ -1766,15 +1973,14 @@ pub mod tournament_component { } else { if (game_index < num_scores) { let top_score_id = *top_score_ids.at(game_index); - let top_score = self - .get_score_from_id(ref store, top_score_id.try_into().unwrap()); + let top_score = self.get_score_from_id(store, top_score_id.try_into().unwrap()); if (score > top_score) { new_score_id = game_id.try_into().unwrap(); new_score = score; } else if (score == top_score) { // if scores are the same then use death date as the deciding factor let top_death_date = self - .get_death_date_from_id(ref store, top_score_id.try_into().unwrap()); + .get_death_date_from_id(store, top_score_id.try_into().unwrap()); if (death_date < top_death_date) { new_score_id = game_id.try_into().unwrap(); new_score = score; @@ -1814,8 +2020,20 @@ pub mod tournament_component { token_data_type: TokenDataType, position: u8 ) { - self._assert_prize_token_registered(ref store, token); - self._assert_prize_position_less_than_winners_count(ref store, tournament_id, position); + self._assert_prize_token_registered(store, token); + self._assert_prize_position_less_than_winners_count(store, tournament_id, position); + + let safe_mode = store.get_tournament_config(get_contract_address()).safe_mode; + if (safe_mode) { + assert( + token == ETHEREUM_ADDRESS() + || token == LORDS_ADDRESS() + || token == SURVIVORS_ADDRESS() + || token == BEASTS_ADDRESS(), + Errors::INVALID_TOKEN_FOR_SAFE_MODE + ); + } + match token_data_type { TokenDataType::erc20(token_data) => { let token_dispatcher = IERC20Dispatcher { contract_address: token }; @@ -1984,7 +2202,7 @@ pub mod tournament_component { fn _calculate_total_entries( self: @ComponentState, - ref store: Store, + store: Store, tournament_id: u64, addresses: Array ) -> u64 { @@ -1996,8 +2214,11 @@ pub mod tournament_component { break; } let address = *addresses.at(address_index); - let address_entries = store.get_address_entries(tournament_id, address); - entries += address_entries.entry_count; + let address_entries = store.get_address_entries(tournament_id, address).entry_count; + let address_starts = store + .get_tournament_starts(tournament_id, address) + .start_count; + entries += address_entries - address_starts; address_index += 1; }; entries diff --git a/contracts/src/presets/ls_tournament.cairo b/contracts/src/presets/ls_tournament.cairo index 06e69b6..bb9658d 100644 --- a/contracts/src/presets/ls_tournament.cairo +++ b/contracts/src/presets/ls_tournament.cairo @@ -175,5 +175,23 @@ pub mod LSTournament { "STARKNET BROTHER", "BROTHER" ); + self + .tournament + .initialize_erc721( + contract_address_const::< + 0x00539f522b29ae9251dbf7443c7a950cf260372e69efab3710a11bf17a9599f1 + >(), + "Blobert", + "BLOB" + ); + self + .tournament + .initialize_erc721( + contract_address_const::< + 0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd + >(), + "Beasts", + "BEASTS" + ); } } diff --git a/contracts/src/tests/constants.cairo b/contracts/src/tests/constants.cairo index a514be6..df440fb 100644 --- a/contracts/src/tests/constants.cairo +++ b/contracts/src/tests/constants.cairo @@ -1,6 +1,8 @@ use starknet::{ContractAddress, contract_address_const}; -use tournament::ls15_components::constants::{MIN_REGISTRATION_PERIOD, MIN_TOURNAMENT_LENGTH}; +use tournament::ls15_components::constants::{ + MIN_REGISTRATION_PERIOD, MIN_TOURNAMENT_LENGTH +}; pub fn ADMIN() -> ContractAddress { contract_address_const::<'ADMIN'>() @@ -72,8 +74,16 @@ pub fn TOURNAMENT_DESCRIPTION() -> ByteArray { pub const STARTING_BALANCE: u256 = 1000000000000000000000; +pub fn TEST_REGISTRATION_START_TIME() -> u32 { + 1 +} + +pub fn TEST_REGISTRATION_END_TIME() -> u32 { + TEST_REGISTRATION_START_TIME() + MIN_REGISTRATION_PERIOD +} + pub fn TEST_START_TIME() -> u32 { - 2 + MIN_REGISTRATION_PERIOD + 1 + MIN_REGISTRATION_PERIOD } pub fn TEST_END_TIME() -> u32 { diff --git a/tournament-ui/.env b/tournament-ui/.env index ccf7b78..3673ef7 100644 --- a/tournament-ui/.env +++ b/tournament-ui/.env @@ -1 +1 @@ -VITE_CHAIN_ID=SN_MAINNET \ No newline at end of file +VITE_CHAIN_ID=WP_LS_TOURNAMENTS_KATANA \ No newline at end of file diff --git a/tournament-ui/bun.lockb b/tournament-ui/bun.lockb index 8b0f0a372be39f8cd831e6ebed6c91348a7b087c..5344cfdb7762d3f795df19a26351dbd411880690 100755 GIT binary patch delta 36142 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 delta 35358 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 XwoSBb;a2UyVn4TQj;(`QxcU7bOR)Br diff --git a/tournament-ui/package.json b/tournament-ui/package.json index 36ec9bb..a34e6ae 100644 --- a/tournament-ui/package.json +++ b/tournament-ui/package.json @@ -14,6 +14,7 @@ "dependencies": { "@apollo/client": "^3.11.10", "@cartridge/connector": "0.5.0-alpha.8", + "@cartridge/controller": "^0.5.5", "@dojoengine/core": "1.0.2", "@dojoengine/create-burner": "1.0.2", "@dojoengine/sdk": "1.0.2", diff --git a/tournament-ui/public/blobert.png b/tournament-ui/public/blobert.png new file mode 100644 index 0000000000000000000000000000000000000000..13b9d7e880dc99373d06edd8e01960631617402b GIT binary patch literal 1189 zcmeAS@N?(olHy`uVBq!ia0vp^CxG}82OE&&7wu05Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFK(3Xii(^Oy2~*{hS}!Hxv8i>?}5Yn7?-e!+$RY0Tw1lRN~=aPt=sMXrA1y&jDB_(RV7U0w=~hYdQUDttnkLjSd3nM9bpSeY5qRgrA!mb8U-0_s=U|KF7tU>B;FV-hX?Y{<}N@4yHy0 z3}W^A8QJ@7Yj;hUZ&_PY@@|9flihi5x1aa-6|ccz%}3*xms|f_e{UbZ&7ju$=ar4Z zuk9Y&mA>u1x#`cvr{CS5*X{Ur`Nf^5>$m^h?Ym$9@_ED3uh-tpti)m5*?aHyecOEE zvTNC{9rt(ceph5|8hhgUzwgVq|J_>lUT8+KhJZoU8MJsTF+Gc|6wdHe45IGgKn z3*zg3AHKLVJifj-@^aYuFYkXnpZkx$qnmxZ`|~|_Wy3JN4%GY$sQF)PaYfFb-`@_a z)}G=;k2r92B>wUFRpPn7?&X_JtsgI)iktl}t0DbTA}hA=oc-7DSK{YO<@fb|Kj`;qS$}|E>AC4j5tie?C3m#Q#or^ZDKS-Mgh(nj923Fo^U^^Cwon-?QVxpTcI$ zs0Sto3H_Jl@BZ0Yl|TFPv9nHyoKG(O8C_VW7&9BWLZ|$D{_DlH%9ATY5?aS7+d(W)Ty*~Y4Z2XDkcd+LV zotM^Wt6Bf=U0?Slwt@+J7_+>7S)BFu=-o;GQgC?0UHsdu);}+CgwP+}H}P|;E7U#P P7=Xaj)z4*}Q$iB}n(4}g literal 0 HcmV?d00001 diff --git a/tournament-ui/public/golden-token.png b/tournament-ui/public/golden-token.png new file mode 100644 index 0000000000000000000000000000000000000000..7589d6fa980efbc977f6068c8eea9037922b8f5e GIT binary patch literal 26017 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 + const { tournament, eth, lords, goldenToken, blobert } = + useTournamentContracts(); + const { getBalanceGeneral } = useSystemCalls(); + const { inputDialog, setTokenBalance } = useUIStore(); - // const isMainnet = selectedChainConfig.chainId === "SN_MAINNET"; - const isMainnet = false; + const isMainnet = selectedChainConfig.chainId === "SN_MAINNET"; + // const isMainnet = false; // Getters useGetTournamentCountsQuery(tournament); @@ -40,8 +45,6 @@ function App() { // Subscriptions useSubscribeTournamentCountsQuery(tournament); - const { inputDialog } = useUIStore(); - const testMenuItems: Menu[] = useMemo( () => [ { @@ -142,21 +145,24 @@ function App() { return isMainnet ? mainMenuDisabled : testMenuDisabled; }, [isMainnet]); - // Memoize these functions to prevent recreating on every render const getBalances = useCallback(async () => { if (!account?.address) return; - const [ethBalance, lordsBalance] = await Promise.all([ - getERC20BalanceGeneral(eth), - getERC20BalanceGeneral(lords), - ]); + const [ethBalance, lordsBalance, goldenTokenBalance, blobertBalance] = + await Promise.all([ + getBalanceGeneral(eth), + getBalanceGeneral(lords), + getBalanceGeneral(goldenToken), + getBalanceGeneral(blobert), + ]); - setTokenBalance((prev) => ({ - ...prev, + setTokenBalance({ eth: ethBalance as bigint, lords: lordsBalance as bigint, - })); - }, [account?.address, getERC20BalanceGeneral]); + goldenToken: goldenTokenBalance as bigint, + blobert: blobertBalance as bigint, + }); + }, [account?.address, getBalanceGeneral]); useEffect(() => { if (account && eth && lords) { @@ -164,20 +170,6 @@ function App() { } }, [account, eth, lords]); - // const getTokenBalances = async () => { - // const balances = await sdk.getTokenBalances( - // [account?.address!], - // [addAddressPadding(eth_mock?.contractAddress!)] - // ); - // console.log(balances); - // }; - - // useEffect(() => { - // if (account) { - // getTokenBalances(); - // } - // }, [account]); - return (
-
+
diff --git a/tournament-ui/src/components/Countdown.tsx b/tournament-ui/src/components/Countdown.tsx index 12e001f..fe6b3f0 100644 --- a/tournament-ui/src/components/Countdown.tsx +++ b/tournament-ui/src/components/Countdown.tsx @@ -7,24 +7,30 @@ const formatTime = (totalSeconds: number) => { const seconds = totalSeconds % 60; return (
-
- - D - - {`${days.toString().padStart(2, "0")}:`} -
-
- - H - - {`${hours.toString().padStart(2, "0")}:`} -
-
- - M - - {`${minutes.toString().padStart(2, "0")}:`} -
+ {days > 0 && ( +
+ + D + + {`${days.toString().padStart(2, "0")}:`} +
+ )} + {(hours > 0 || days > 0) && ( +
+ + H + + {`${hours.toString().padStart(2, "0")}:`} +
+ )} + {(minutes > 0 || hours > 0 || days > 0) && ( +
+ + M + + {`${minutes.toString().padStart(2, "0")}:`} +
+ )}
S diff --git a/tournament-ui/src/components/Header.tsx b/tournament-ui/src/components/Header.tsx index bbcf77d..2ba9606 100644 --- a/tournament-ui/src/components/Header.tsx +++ b/tournament-ui/src/components/Header.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { Button } from "./buttons/Button"; import { CartridgeIcon, ETH, LORDS, LOGO } from "./Icons"; -import useUIStore from "../hooks/useUIStore"; import { displayAddress, formatNumber, @@ -12,22 +11,22 @@ import { useAccount, useConnect } from "@starknet-react/core"; import { checkCartridgeConnector } from "../lib/connectors"; import { useDojo } from "../DojoContext"; import { useConnectToSelectedChain } from "@/lib/dojo/hooks/useChain"; -import { useControllerMenu } from "@/hooks/useController"; - -export interface HeaderProps { - ethBalance: bigint; - lordsBalance: bigint; -} +import { + useControllerMenu, + useControllerUsername, +} from "@/hooks/useController"; +import useUIStore from "@/hooks/useUIStore"; -export default function Header({ ethBalance, lordsBalance }: HeaderProps) { +export default function Header() { const { account } = useAccount(); const { connector } = useConnect(); const { connect } = useConnectToSelectedChain(); const { openMenu } = useControllerMenu(); - const username = useUIStore((state: any) => state.username); + const { username } = useControllerUsername(); const { setup: { selectedChainConfig }, } = useDojo(); + const { tokenBalance } = useUIStore(); const isMainnet = selectedChainConfig.chainId === "SN_MAINNET"; @@ -61,7 +60,9 @@ export default function Header({ ethBalance, lordsBalance }: HeaderProps) {

- {formatEth(parseInt((ethBalance ?? 0).toString()) / 10 ** 18)} + {formatEth( + parseInt((tokenBalance.eth ?? 0).toString()) / 10 ** 18 + )}

@@ -92,7 +93,7 @@ export default function Header({ ethBalance, lordsBalance }: HeaderProps) {

{formatNumber( - parseInt((lordsBalance ?? 0).toString()) / 10 ** 18 + parseInt((tokenBalance.lords ?? 0).toString()) / 10 ** 18 )}

@@ -101,6 +102,20 @@ export default function Header({ ethBalance, lordsBalance }: HeaderProps) { )} +
diff --git a/tournament-ui/src/components/Icons.tsx b/tournament-ui/src/components/Icons.tsx index b03054f..cac60cf 100644 --- a/tournament-ui/src/components/Icons.tsx +++ b/tournament-ui/src/components/Icons.tsx @@ -200,6 +200,17 @@ export const InfoIcon: React.FC = () => ( ); +export const ClockIcon: React.FC = () => ( + + + + +); + export const ETH: React.FC = () => ( { + const { formData, setFormData } = useUIStore(); + + const sectionDisabled = + !formData.tournamentName || + !formData.startTime || + !formData.endTime || + !formData.submissionPeriod; + + return ( +
+

+ Top Scores +

+
+
+
+ + + + + {formData.scoreboardSize > 0 && ( + + + + +

{formData.scoreboardSize}

+
+ )} +
+
+
+
+ ); +}; + +export default TopScores; diff --git a/tournament-ui/src/components/create/TournamentDetails.tsx b/tournament-ui/src/components/create/TournamentDetails.tsx new file mode 100644 index 0000000..21c42a4 --- /dev/null +++ b/tournament-ui/src/components/create/TournamentDetails.tsx @@ -0,0 +1,64 @@ +import { ChangeEvent, useState } from "react"; +import useUIStore from "@/hooks/useUIStore"; + +const TournamentDetails = () => { + const { formData, setFormData } = useUIStore(); + const [isMaxLength, setIsMaxLength] = useState(false); + + const handleChangeName = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + if (name === "tournamentName" && value.length >= 31) { + setIsMaxLength(true); + } else { + setIsMaxLength(false); + } + }; + + const handleChangeDescription = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + return ( +
+

Tournament details

+
+
+
+

Name

+ + {isMaxLength &&

MAX LENGTH!

} +
+
+
+

+ Description +

+