Skip to content

Commit

Permalink
Merge pull request #420 from BibliothecaDAO/feat/interfaces
Browse files Browse the repository at this point in the history
Handle multiple account interfaces
  • Loading branch information
starknetdev authored Oct 23, 2023
2 parents 15e7a2a + be4fcb4 commit f73cedc
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 96 deletions.
2 changes: 1 addition & 1 deletion contracts/game/src/game/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use game_snapshot::GamesPlayedSnapshot;
trait IGame<TContractState> {
// ------ Game Actions ------
fn new_game(
ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, golden_token_id: u256
ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, golden_token_id: u256, interface_camel: bool
);
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
46 changes: 31 additions & 15 deletions contracts/game/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ mod Game {
IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait, IERC20CamelLibraryDispatcher
};

use openzeppelin::introspection::interface::{ISRC5Dispatcher, ISRC5DispatcherTrait};
use openzeppelin::introspection::interface::{ISRC5Dispatcher, ISRC5DispatcherTrait, ISRC5CamelDispatcher, ISRC5CamelDispatcherTrait};

use openzeppelin::token::erc721::interface::{
IERC721, IERC721Dispatcher, IERC721DispatcherTrait, IERC721LibraryDispatcher
Expand Down Expand Up @@ -199,14 +199,15 @@ mod Game {
client_reward_address: ContractAddress,
weapon: u8,
name: u128,
golden_token_id: u256
golden_token_id: u256,
interface_camel: bool
) {
// assert provided weapon
_assert_valid_starter_weapon(weapon);

// process payment for game and distribute rewards
if (golden_token_id != 0) {
_play_with_token(ref self, golden_token_id);
_play_with_token(ref self, golden_token_id, interface_camel);
} else {
_process_payment_and_distribute_rewards(ref self, client_reward_address);
}
Expand Down Expand Up @@ -3541,24 +3542,39 @@ mod Game {
_last_usage(self, token_id) + SECONDS_IN_DAY.into() <= get_block_timestamp().into()
}

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

let caller = get_caller_address();

let account = ISRC5Dispatcher { contract_address: caller };
let player = if account.supports_interface(ARCADE_ACCOUNT_ID) {
IMasterControlDispatcher { contract_address: caller }.get_master_account()
} else {
caller
};
let account_snake = ISRC5Dispatcher { contract_address: caller };
let account_camel = ISRC5CamelDispatcher { contract_address: caller };

assert(_can_play(@self, token_id), messages::CANNOT_PLAY_WITH_TOKEN);
assert(golden_token.owner_of(token_id) == player, messages::NOT_OWNER_OF_TOKEN);
if interface_camel {
let player = if account_camel.supportsInterface(ARCADE_ACCOUNT_ID) {
IMasterControlDispatcher { contract_address: caller }.get_master_account()
} else {
caller
};
assert(_can_play(@self, token_id), messages::CANNOT_PLAY_WITH_TOKEN);
assert(golden_token.owner_of(token_id) == player, messages::NOT_OWNER_OF_TOKEN);

self
._golden_token_last_use
.write(token_id.try_into().unwrap(), get_block_timestamp().into());
self
._golden_token_last_use
.write(token_id.try_into().unwrap(), get_block_timestamp().into());
} else {
let player = if account_snake.supports_interface(ARCADE_ACCOUNT_ID) {
IMasterControlDispatcher { contract_address: caller }.get_master_account()
} else {
caller
};
assert(_can_play(@self, token_id), messages::CANNOT_PLAY_WITH_TOKEN);
assert(golden_token.owner_of(token_id) == player, messages::NOT_OWNER_OF_TOKEN);

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

fn _last_usage(self: @ContractState, token_id: u256) -> u256 {
Expand Down
14 changes: 7 additions & 7 deletions contracts/game/src/tests/test_game.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mod tests {
}

fn add_adventurer_to_game(ref game: IGameDispatcher) {
game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero', DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero', DEFAULT_NO_GOLDEN_TOKEN.into(), false);

let original_adventurer = game.get_adventurer(ADVENTURER_ID);
assert(original_adventurer.xp == 0, 'wrong starting xp');
Expand All @@ -131,7 +131,7 @@ mod tests {
let name = 'abcdefghijklmno';

// start new game
game.new_game(INTERFACE_ID(), starting_weapon, name, DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), starting_weapon, name, DEFAULT_NO_GOLDEN_TOKEN.into(), false);

// get adventurer state
let adventurer = game.get_adventurer(ADVENTURER_ID);
Expand Down Expand Up @@ -470,7 +470,7 @@ mod tests {
let name = 'abcdefghijklmno';

// start new game
game.new_game(INTERFACE_ID(), starting_weapon, name, DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), starting_weapon, name, DEFAULT_NO_GOLDEN_TOKEN.into(), false);

// get adventurer state
let adventurer = game.get_adventurer(ADVENTURER_ID);
Expand Down Expand Up @@ -1954,7 +1954,7 @@ mod tests {
if i == 10 {
break ();
}
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into(), false);
i += 1;
};
testing::set_block_timestamp(starknet::get_block_timestamp() + (DAY * 7));
Expand All @@ -1968,7 +1968,7 @@ mod tests {
if i == 10 {
break ();
}
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into(), false);
i += 1;
};
testing::set_block_timestamp(starknet::get_block_timestamp() + (DAY * 7));
Expand All @@ -1988,7 +1988,7 @@ mod tests {
if i == 20 {
break ();
}
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into(), false);
i += 1;
};

Expand All @@ -2014,7 +2014,7 @@ mod tests {
if i == 10 {
break ();
}
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into());
game.new_game(INTERFACE_ID(), ItemId::Wand, 'phase1', DEFAULT_NO_GOLDEN_TOKEN.into(), false);
i += 1;
};
testing::set_block_timestamp(starknet::get_block_timestamp() + (DAY * 14));
Expand Down
2 changes: 1 addition & 1 deletion indexer/env-goerli
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
GAME="0x043f3c52dc1d27362ddfd058a9e8083e47763a906eba9f8e8d07a2d22a11de5f"
GAME="0x04cfd4feff2185113e07bee4b354a82480dd6c79189baf18e4bc93093149f2ab"
START=873000
MONGO_CONNECTION_STRING="mongodb://mongo:[email protected]:27017"
14 changes: 12 additions & 2 deletions ui/src/app/components/ArcadeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ interface ArcadeDialogProps {
gameContract: Contract;
lordsContract: Contract;
ethContract: Contract;
updateConnectors: () => void;
}

export const ArcadeDialog = ({
gameContract,
lordsContract,
ethContract,
updateConnectors,
}: ArcadeDialogProps) => {
const [fetchedBalances, setFetchedBalances] = useState(false);
const { account: walletAccount, address, connector } = useAccount();
const showArcadeDialog = useUIStore((state) => state.showArcadeDialog);
const arcadeDialog = useUIStore((state) => state.arcadeDialog);
const isWrongNetwork = useUIStore((state) => state.isWrongNetwork);
const { connectors } = useConnect();
const { connect, connectors } = useConnect();
const { disconnect } = useDisconnect();
const {
getMasterAccount,
Expand All @@ -47,6 +49,7 @@ export const ArcadeDialog = ({
isToppingUpLords,
withdraw,
isWithdrawing,
listConnectors,
} = useBurner(walletAccount, gameContract, lordsContract, ethContract);
const [arcadebalances, setArcadeBalances] = useState<
Record<string, { eth: bigint; lords: bigint; lordsGameAllowance: bigint }>
Expand Down Expand Up @@ -125,7 +128,14 @@ export const ArcadeDialog = ({
connected wallet to the arcade account to cover your transaction
costs from normal gameplay.
</p>
<Button onClick={() => create()} disabled={isWrongNetwork}>
<Button
onClick={async () => {
await create(connector);
connect({ connector: listConnectors()[0] });
updateConnectors();
}}
disabled={isWrongNetwork}
>
create arcade account
</Button>
</div>
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/components/intro/ArcadeIntro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ArcadeIntro = ({
ethContract,
updateConnectors,
}: ArcadeIntroProps) => {
const { account, address } = useAccount();
const { account, address, connector } = useAccount();
const { connect, connectors } = useConnect();
const [step, setStep] = useState(1);
const [readDisclaimer, setReadDisclaimer] = useState(false);
Expand Down Expand Up @@ -226,7 +226,7 @@ export const ArcadeIntro = ({
if (checkNotEnoughPrefundEth) {
window.open("https://faucet.goerli.starknet.io/", "_blank");
} else {
await create();
await create(connector!);
connect({ connector: listConnectors()[0] });
updateConnectors();
showArcadeIntro(false);
Expand Down
139 changes: 72 additions & 67 deletions ui/src/app/lib/burner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getArcadeClassHash,
getContracts,
} from "@/app/lib/constants";
import { Connector } from "@starknet-react/core";

export const ETH_PREFUND_AMOUNT = "0x38D7EA4C68000"; // 0.001ETH
export const LORDS_PREFUND_AMOUNT = "0x0d8d726b7177a80000"; // 250LORDS
Expand Down Expand Up @@ -170,84 +171,88 @@ export const useBurner = (
}
};

const create = useCallback(async () => {
setIsDeploying(true);
const privateKey = stark.randomAddress();
const publicKey = ec.starkCurve.getStarkKey(privateKey);
const create = useCallback(
async (connector: Connector) => {
setIsDeploying(true);
const privateKey = stark.randomAddress();
const publicKey = ec.starkCurve.getStarkKey(privateKey);

if (!walletAccount) {
throw new Error("wallet account not found");
}

const constructorAACalldata = CallData.compile({
_public_key: publicKey,
_master_account: walletAccount.address,
});
if (!walletAccount) {
throw new Error("wallet account not found");
}

const address = hash.calculateContractAddressFromHash(
publicKey,
arcadeClassHash!,
constructorAACalldata,
0
);
const constructorAACalldata = CallData.compile({
_public_key: publicKey,
_master_account: walletAccount.address,
});

try {
await prefundAccount(address, walletAccount);
} catch (e) {
setIsDeploying(false);
}
const address = hash.calculateContractAddressFromHash(
publicKey,
arcadeClassHash!,
constructorAACalldata,
0
);

// deploy burner
const burner = new Account(provider, address, privateKey, "1");

const {
transaction_hash: deployTx,
contract_address: accountAAFinalAddress,
} = await burner.deployAccount(
{
classHash: arcadeClassHash!,
constructorCalldata: constructorAACalldata,
contractAddress: address,
addressSalt: publicKey,
},
{
maxFee: "100000000000000", // currently setting to 0.0001ETH
try {
await prefundAccount(address, walletAccount);
} catch (e) {
setIsDeploying(false);
}
);

await provider.waitForTransaction(deployTx);
// deploy burner
const burner = new Account(provider, address, privateKey, "1");

const {
transaction_hash: deployTx,
contract_address: accountAAFinalAddress,
} = await burner.deployAccount(
{
classHash: arcadeClassHash!,
constructorCalldata: constructorAACalldata,
contractAddress: address,
addressSalt: publicKey,
},
{
maxFee: "100000000000000", // currently setting to 0.0001ETH
}
);

await provider.waitForTransaction(deployTx);

setIsSettingPermissions(true);
setIsSettingPermissions(true);

const setPermissionsAndApprovalTx = await setPermissionsAndApproval(
accountAAFinalAddress,
walletAccount
);
const setPermissionsAndApprovalTx = await setPermissionsAndApproval(
accountAAFinalAddress,
walletAccount
);

await provider.waitForTransaction(setPermissionsAndApprovalTx);
await provider.waitForTransaction(setPermissionsAndApprovalTx);

// save burner
let storage = Storage.get("burners") || {};
for (let address in storage) {
storage[address].active = false;
}
// save burner
let storage = Storage.get("burners") || {};
for (let address in storage) {
storage[address].active = false;
}

storage[address] = {
privateKey,
publicKey,
deployTx,
setPermissionsAndApprovalTx,
masterAccount: walletAccount.address,
gameContract: gameContract?.address,
active: true,
};

setAccount(burner);
Storage.set("burners", storage);
setIsSettingPermissions(false);
setIsDeploying(false);
return burner;
}, [walletAccount]);
storage[address] = {
privateKey,
publicKey,
deployTx,
setPermissionsAndApprovalTx,
masterAccount: walletAccount.address,
masterAccountProvider: connector.id,
gameContract: gameContract?.address,
active: true,
};

setAccount(burner);
Storage.set("burners", storage);
setIsSettingPermissions(false);
setIsDeploying(false);
return burner;
},
[walletAccount]
);

const setPermissionsAndApproval = useCallback(
async (accountAAFinalAdress: any, walletAccount: any) => {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function getContracts() {
case "goerli":
return {
eth: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
game: "0x043f3c52dc1d27362ddfd058a9e8083e47763a906eba9f8e8d07a2d22a11de5f",
game: "0x04cfd4feff2185113e07bee4b354a82480dd6c79189baf18e4bc93093149f2ab",
lords:
"0x05e367ac160e5f90c5775089b582dfc987dd148a5a2f977c49def2a6644f724b",
};
Expand Down
Loading

1 comment on commit f73cedc

@vercel
Copy link

@vercel vercel bot commented on f73cedc Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.