Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Golden token #357

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/game/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ market = { path = "../market" }
obstacles = { path = "../obstacles" }
game_entropy = { path = "../game_entropy" }
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" }
goldenToken = { git = "https://github.com/BibliothecaDAO/golden-token" }
arcade_account = { git = "https://github.com/BibliothecaDAO/arcade-account" }

[[target.starknet-contract]]
allowed-libfuncs-list.name = "experimental"
Expand Down
2 changes: 1 addition & 1 deletion contracts/game/src/game/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use survivor::{
trait IGame<TContractState> {
// ------ Game Actions ------
fn new_game(
ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128
ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, golden_token_id: u256
);
fn explore(ref self: TContractState, adventurer_id: felt252, till_beast: bool);
fn attack(ref self: TContractState, adventurer_id: felt252, to_the_death: bool);
Expand Down
126 changes: 99 additions & 27 deletions contracts/game/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,30 @@ mod Game {
array::{SpanTrait, ArrayTrait}, integer::u256_try_as_non_zero, traits::{TryInto, Into},
clone::Clone, poseidon::poseidon_hash_span, option::OptionTrait, box::BoxTrait,
starknet::{
get_caller_address, ContractAddress, ContractAddressIntoFelt252, contract_address_const
get_caller_address, ContractAddress, ContractAddressIntoFelt252, contract_address_const,
get_block_timestamp
},
};

use openzeppelin::token::erc20::interface::{
IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait, IERC20CamelLibraryDispatcher
};
use openzeppelin::introspection::interface::{ISRC5Dispatcher, ISRC5DispatcherTrait};

use openzeppelin::token::erc721::interface::{
IERC721, IERC721Dispatcher, IERC721DispatcherTrait, IERC721LibraryDispatcher
};

use goldenToken::ERC721::{
GoldenToken, GoldenTokenDispatcher, GoldenTokenDispatcherTrait, GoldenTokenLibraryDispatcher
};

use arcade_account::{
account::interface::{
IMasterControl, IMasterControlDispatcher, IMasterControlDispatcherTrait
},
Account, ARCADE_ACCOUNT_ID
};

use super::game::{
interfaces::{IGame},
Expand Down Expand Up @@ -80,10 +97,12 @@ mod Game {
_game_counter: felt252,
_game_entropy: GameEntropy,
_genesis_block: u64,
_golden_token: ContractAddress,
_item_specials: LegacyMap::<(felt252, u8), ItemSpecialsStorage>,
_leaderboard: Leaderboard,
_lords: ContractAddress,
_owner: LegacyMap::<felt252, ContractAddress>,
_item_specials: LegacyMap::<(felt252, u8), ItemSpecialsStorage>,
_golden_token_last_use: LegacyMap::<felt252, felt252>,
}

#[event]
Expand Down Expand Up @@ -119,20 +138,24 @@ mod Game {
#[constructor]
fn constructor(
ref self: ContractState,
lords: ContractAddress,
dao: ContractAddress,
collectible_beasts: ContractAddress
lords_address: ContractAddress,
dao_address: ContractAddress,
beasts_address: ContractAddress,
golden_token_address: ContractAddress,
) {
// set the contract addresses
self._lords.write(lords);
self._dao.write(dao);
self._collectible_beasts.write(collectible_beasts);
self._lords.write(lords_address);
self._dao.write(dao_address);
self._collectible_beasts.write(beasts_address);

// set the genesis block number
let current_block_info = starknet::get_block_info().unbox();
self._genesis_block.write(current_block_info.block_number);

// set the genesis block
self._genesis_block.write(starknet::get_block_info().unbox().block_number.into());
// set golden token address
self._golden_token.write(golden_token_address);

// initialize game entropy
let current_block_info = starknet::get_block_info().unbox();
let new_game_entropy = ImplGameEntropy::new(
current_block_info.block_number,
current_block_info.block_timestamp,
Expand All @@ -158,13 +181,22 @@ mod Game {
/// @param weapon A u8 representing the weapon to start the game with. Valid options are: {wand: 12, book: 17, short sword: 46, club: 76}
/// @param name A u128 value representing the player's name.
fn new_game(
ref self: ContractState, client_reward_address: ContractAddress, weapon: u8, name: u128,
ref self: ContractState,
client_reward_address: ContractAddress,
weapon: u8,
name: u128,
golden_token_id: u256
) {
// assert provided weapon
_assert_valid_starter_weapon(weapon);

// process payment for game and distribute rewards
_process_payment_and_distribute_rewards(ref self, client_reward_address);
// if player has a golden token
// there is no 0 token
if (golden_token_id != 0) {
_play_with_token(ref self, golden_token_id);
} else {
_process_payment_and_distribute_rewards(ref self, client_reward_address);
}

// start the game
_start_game(ref self, weapon, name);
Expand Down Expand Up @@ -1139,13 +1171,19 @@ mod Game {
}
}

fn _golden_token_dispatcher(ref self: ContractState) -> IERC721Dispatcher {
IERC721Dispatcher { contract_address: self._golden_token.read() }
}

fn _lords_dispatcher(ref self: ContractState) -> IERC20CamelDispatcher {
IERC20CamelDispatcher { contract_address: self._lords.read() }
}
fn _process_payment_and_distribute_rewards(
ref self: ContractState, client_address: ContractAddress
) {
let caller = get_caller_address();
let block_number = starknet::get_block_info().unbox().block_number;

let lords = self._lords.read();
let genesis_block = self._genesis_block.read();
let dao_address = self._dao.read();

Expand All @@ -1160,8 +1198,7 @@ mod Game {
// the purpose of this is to let a decent set of top scores get set before payouts begin
// without this, there would be an incentive to start and die immediately after contract is deployed
// to capture the rewards from the launch hype
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, dao_address, COST_TO_PLAY.into());
_lords_dispatcher(ref self).transferFrom(caller, dao_address, COST_TO_PLAY.into());

__event_RewardDistribution(
ref self,
Expand Down Expand Up @@ -1217,28 +1254,24 @@ mod Game {

// DAO
if (week.DAO != 0) {
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, dao_address, week.DAO);
_lords_dispatcher(ref self).transferFrom(caller, dao_address, week.DAO);
}

// interface
if (week.INTERFACE != 0) {
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, client_address, week.INTERFACE);
_lords_dispatcher(ref self).transferFrom(caller, client_address, week.INTERFACE);
}

// first place
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, first_place_address, week.FIRST_PLACE);
_lords_dispatcher(ref self).transferFrom(caller, first_place_address, week.FIRST_PLACE);

// second place
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, second_place_address, week.SECOND_PLACE);
_lords_dispatcher(ref self).transferFrom(caller, second_place_address, week.SECOND_PLACE);

// third place
IERC20CamelDispatcher { contract_address: lords }
.transferFrom(caller, third_place_address, week.THIRD_PLACE);
_lords_dispatcher(ref self).transferFrom(caller, third_place_address, week.THIRD_PLACE);

// emit reward distribution event
__event_RewardDistribution(
ref self,
RewardDistribution {
Expand Down Expand Up @@ -2673,6 +2706,17 @@ mod Game {
// get current leaderboard which will be mutated as part of this function
let mut leaderboard = self._leaderboard.read();

// if the player scored a highscore using an Arcade account, we change the owner of the
// adventurer_id they played with to the master account since the arcade account is intended to be disposable.
// By doing this, when rewards are later distributed to that adventurer_id, we'll send them to primary account instead of arcade account
let caller = get_caller_address();
let account = ISRC5Dispatcher { contract_address: caller };
if account.supports_interface(ARCADE_ACCOUNT_ID) {
let primary_account = IMasterControlDispatcher { contract_address: caller }
.get_master_account();
self._owner.write(adventurer_id, primary_account);
};

// create a score struct for the players score
let player_score = Score {
adventurer_id: adventurer_id.try_into().unwrap(),
Expand Down Expand Up @@ -3303,4 +3347,32 @@ mod Game {
fn isMinted(self: @T, beast: u8, prefix: u8, suffix: u8) -> bool;
fn getMinter(self: @T) -> ContractAddress;
}

const DAY: felt252 = 86400;

fn _can_play(self: @ContractState, token_id: u256) -> bool {
_last_usage(self, token_id) + DAY.into() < get_block_timestamp().into()
}

fn _play_with_token(ref self: ContractState, token_id: u256) {
let golden_token = _golden_token_dispatcher(ref self);

let account = ISRC5Dispatcher { contract_address: get_caller_address() };
// let player = if account.supports_interface(ARCADE_ACCOUNT_ID) {
// IMasterControlDispatcher { contract_address: get_caller_address() }.get_master_account()
// } else {
// get_caller_address()
// };

assert(_can_play(@self, token_id), 'Cant play');
assert(golden_token.owner_of(token_id) == account, 'Not owner');

self
._golden_token_last_use
.write(token_id.try_into().unwrap(), get_block_timestamp().into());
}

fn _last_usage(self: @ContractState, token_id: u256) -> u256 {
self._golden_token_last_use.read(token_id.try_into().unwrap()).into()
}
}
77 changes: 66 additions & 11 deletions contracts/game/src/tests/test_game.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ mod tests {
use openzeppelin::token::erc20::interface::{
IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait, IERC20CamelLibraryDispatcher
};

use goldenToken::ERC721::{
GoldenToken, GoldenTokenDispatcher, GoldenTokenDispatcherTrait, GoldenTokenLibraryDispatcher
};

use openzeppelin::token::erc20::erc20::ERC20;
use market::market::{ImplMarket, LootWithPrice, ItemPurchase};
use lootitems::{loot::{Loot, ImplLoot, ILoot}, constants::{ItemId}};
Expand Down Expand Up @@ -50,6 +55,10 @@ mod tests {
contract_address_const::<1>()
}

fn GOLDEN_TOKEN_ADDRESS() -> ContractAddress {
contract_address_const::<1>()
}

const ADVENTURER_ID: felt252 = 1;

const MAX_LORDS: u256 = 1000000000000000000000;
Expand All @@ -61,45 +70,71 @@ mod tests {
contract_address_const::<10>()
}

fn deploy_lords() -> ContractAddress {
fn deploy_erc20(symbol: felt252) -> ContractAddress {
let mut calldata = array![];
calldata.append_serde(NAME);
calldata.append_serde(SYMBOL);
calldata.append_serde(symbol);
calldata.append_serde(MAX_LORDS);
calldata.append_serde(OWNER());

let lords0 = utils::deploy(CamelERC20Mock::TEST_CLASS_HASH, calldata);
utils::deploy(CamelERC20Mock::TEST_CLASS_HASH, calldata)
}

fn deploy_golden_token(eth: ContractAddress) -> (ContractAddress, GoldenTokenDispatcher) {
let mut calldata = ArrayTrait::new();
calldata.append(NAME);
calldata.append(SYMBOL);
calldata.append(OWNER().into());
calldata.append(DAO().into());
calldata.append(eth.into());

lords0
let (golden_token, _) = deploy_syscall(
goldenToken::ERC721::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false
)
.unwrap();

(golden_token, GoldenTokenDispatcher { contract_address: golden_token })
}

fn setup(starting_block: u64) -> IGameDispatcher {
testing::set_block_number(starting_block);
testing::set_block_timestamp(1696201757);

let lords = deploy_lords();
let lords = deploy_erc20('LORDS');
let eth = deploy_erc20('ETH');

let (golden_token_address, golden_token_dispatcher) = deploy_golden_token(eth);

let mut calldata = ArrayTrait::new();
calldata.append(lords.into());
calldata.append(DAO().into());
calldata.append(COLLECTIBLE_BEASTS().into());
calldata.append(golden_token_address.into());

let (address0, _) = deploy_syscall(
let (game_address, _) = deploy_syscall(
Game::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false
)
.unwrap();

testing::set_contract_address(OWNER());

let lordsContract = IERC20CamelDispatcher { contract_address: lords };
let lords_contract = IERC20CamelDispatcher { contract_address: lords };
lords_contract.approve(game_address, APPROVE.into());

lordsContract.approve(address0, APPROVE.into());
let eth_contract = IERC20CamelDispatcher { contract_address: eth };
eth_contract.approve(golden_token_address, APPROVE.into());

IGameDispatcher { contract_address: address0 }
golden_token_dispatcher.open();
golden_token_dispatcher.mint();
golden_token_dispatcher.mint();

IGameDispatcher { contract_address: game_address }
}

fn add_adventurer_to_game(ref game: IGameDispatcher) {
game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero');
let golden_token_id: u256 = 0;

game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero', golden_token_id);

let original_adventurer = game.get_adventurer(ADVENTURER_ID);
assert(original_adventurer.xp == 0, 'wrong starting xp');
Expand All @@ -115,9 +150,10 @@ mod tests {

let starting_weapon = ItemId::Wand;
let name = 'abcdefghijklmno';
let golden_token_id: u256 = 0;

// start new game
game.new_game(INTERFACE_ID(), starting_weapon, name);
game.new_game(INTERFACE_ID(), starting_weapon, name, golden_token_id);

// get adventurer state
let adventurer = game.get_adventurer(ADVENTURER_ID);
Expand Down Expand Up @@ -454,6 +490,12 @@ mod tests {
// let mut game = new_adventurer_lvl11_equipped(5);
// }

#[test]
#[available_gas(3000000000000)]
fn test_setup() {
setup(1000);
}

#[test]
#[available_gas(300000000000)]
fn test_start() {
Expand Down Expand Up @@ -1855,4 +1897,17 @@ mod tests {
game.attack(ADVENTURER_ID, false);
game.attack(ADVENTURER_ID, false);
}

#[test]
#[available_gas(944417814)]
fn test_golden_token_mint() {
let mut game = setup(1000);

let starting_weapon = ItemId::Wand;
let name = 'abcdefghijklmno';
let golden_token_id: u256 = 1;

// start new game
game.new_game(INTERFACE_ID(), starting_weapon, name, golden_token_id);
}
}
2 changes: 2 additions & 0 deletions ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ NEXT_PUBLIC_GOERLI_GAME_CONTRACT_ADDRESS=0x0229a6f42ae0bc258acf91b6e6d7f50e77608
NEXT_PUBLIC_MAINNET_GAME_CONTRACT_ADDRESS=0x0
NEXT_PUBLIC_GOERLI_LORDS_CONTRACT_ADDRESS=0x05e367ac160e5f90c5775089b582dfc987dd148a5a2f977c49def2a6644f724b
NEXT_PUBLIC_MAINNET_LORDS_CONTRACT_ADDRESS=0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49
NEXT_PUBLIC_GOERLI_GOLDEN_TOKEN_CONTRACT_ADDRESS=0x0
NEXT_PUBLIC_MAINNET_GOLDEN_TOKEN_CONTRACT_ADDRESS=0x0
NEXT_PUBLIC_RPC_GOERLI_ENDPOINT=https://starknet-goerli.infura.io/v3/6c536e8272f84d3ba63bf9f248c5e128
NEXT_PUBLIC_RPC_MAINNET_ENDPOINT=https://starknet-mainnet.infura.io/v3/6c536e8272f84d3ba63bf9f248c5e128
NEXT_PUBLIC_GOERLI_APP_URL=https://goerli-survivor.realms.world/
Expand Down
Binary file added ui/public/icons/golden-token.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading