From d3952d92ad22e24b80322e1b9f02cf84ea7c57ec Mon Sep 17 00:00:00 2001 From: Akinshola <114211385+Akshola00@users.noreply.github.com> Date: Tue, 6 May 2025 10:31:15 +0000 Subject: [PATCH 1/5] Add betting module and update dependencies in Scarab configuration --- .devcontainer/devcontainer.json | 1 + contracts/Scarb.lock | 16 ++++++++ contracts/Scarb.toml | 3 ++ contracts/src/Betting.cairo | 70 +++++++++++++++++++++++++++++++++ contracts/src/RoomManager.cairo | 44 +++++++++------------ contracts/src/lib.cairo | 28 +------------ 6 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 contracts/src/Betting.cairo diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d84cc1d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1 @@ +{"image":"mcr.microsoft.com/devcontainers/base:ubuntu-24.04"} \ No newline at end of file diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 83cca99..09f2d43 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -4,3 +4,19 @@ version = 1 [[package]] name = "contracts" version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" + +[[package]] +name = "snforge_std" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index d7c15bd..444ae31 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -8,5 +8,8 @@ edition = "2024_07" [dependencies] starknet = "2.11.4" + [dev-dependencies] cairo_test = "2.9.2" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.31.0" } + diff --git a/contracts/src/Betting.cairo b/contracts/src/Betting.cairo new file mode 100644 index 0000000..8531b25 --- /dev/null +++ b/contracts/src/Betting.cairo @@ -0,0 +1,70 @@ +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::{PoseidonTrait, poseidon_hash_span}; +use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, +}; +use starknet::{ContractAddress, get_caller_address}; + +#[starknet::interface] +pub trait IBetting { + fn place_bet(ref self: TContractState, room_id: u256, agent_id: u256, amount: u256) -> u256; + fn resolve_bet(ref self: TContractState, room_id: u256, winner: ContractAddress); + fn get_bet_amount(self: @TContractState, room_id: u256) -> u256; + fn withdraw_winnings(ref self: TContractState, room_id: u256, amount: u256); +} + +#[starknet::contract] +mod Betting { + use super::*; + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + BetPlaced: BetPlaced, + BetResolved: BetResolved, + } + + #[derive(Drop, starknet::Event)] + pub struct BetPlaced { + room_id: felt252, + player: ContractAddress, + amount: felt252, + } + + #[derive(Drop, starknet::Event)] + pub struct BetResolved { + room_id: felt252, + winner: ContractAddress, + amount: felt252, + } + + #[storage] + struct Storage { + bets_total_amount: Map<(u256, u256), u256> // room id, agent id, amount in the pool + } + + #[abi(embed_v0)] + impl BettingImpl of IBetting { + fn place_bet(ref self: ContractState, room_id: u256, agent_id: u256, amount: u256) -> u256 { + 1 + } + + fn resolve_bet(ref self: ContractState, room_id: u256, winner: ContractAddress) { + + } + + fn get_bet_amount(self: @ContractState, room_id: u256) -> u256 { + 1 + } + + fn withdraw_winnings(ref self: ContractState, room_id: u256, amount: u256) { + + } + + } + + #[external(v0)] + fn set_winner(ref self: ContractState, room_id: u256, agent_id: u256, winner: felt252) -> u256 { + 256 + } +} diff --git a/contracts/src/RoomManager.cairo b/contracts/src/RoomManager.cairo index 610f985..4731b7a 100644 --- a/contracts/src/RoomManager.cairo +++ b/contracts/src/RoomManager.cairo @@ -3,7 +3,7 @@ pub enum RoomStatus { #[default] BeforeBattle, BattleStarted, - BattleEnded + BattleEnded, } #[derive(Drop, Serde, starknet::Store)] @@ -11,7 +11,7 @@ struct Room { id: felt252, creator: starknet::ContractAddress, token: felt252, - status: RoomStatus + status: RoomStatus, } #[starknet::interface] @@ -24,42 +24,42 @@ pub trait IRoomManager { #[starknet::contract] pub mod RoomManager { - + use core::hash::{HashStateExTrait, HashStateTrait}; + use core::poseidon::{PoseidonTrait, poseidon_hash_span}; + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; use starknet::{ContractAddress, get_caller_address}; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map, StoragePathEntry}; - use core::poseidon::PoseidonTrait; - use core::poseidon::poseidon_hash_span; - use core::hash::{HashStateTrait, HashStateExTrait}; #[event] #[derive(Drop, starknet::Event)] pub enum Event { RoomCreated: RoomCreated, BattleStarted: BattleStarted, - BattleEnded: BattleEnded + BattleEnded: BattleEnded, } #[derive(Drop, starknet::Event)] pub struct RoomCreated { room_id: felt252, - created_by: ContractAddress + created_by: ContractAddress, } #[derive(Drop, starknet::Event)] pub struct BattleStarted { room_id: felt252, - started_by: ContractAddress + started_by: ContractAddress, } #[derive(Drop, starknet::Event)] pub struct BattleEnded { room_id: felt252, - ended_by: ContractAddress + ended_by: ContractAddress, } #[storage] struct Storage { - rooms: Map + rooms: Map, } #[constructor] @@ -70,7 +70,10 @@ pub mod RoomManager { fn create_room(ref self: ContractState, token: felt252) -> felt252 { let creator = get_caller_address(); let status = super::RoomStatus::BeforeBattle; - let mut room_id = PoseidonTrait::new().update(creator.try_into().unwrap()).update(token).finalize(); + let mut room_id = PoseidonTrait::new() + .update(creator.try_into().unwrap()) + .update(token) + .finalize(); let mut room = self.rooms.entry(room_id); room.id.write(room_id); @@ -78,10 +81,7 @@ pub mod RoomManager { room.token.write(token); room.status.write(status); - self.emit(RoomCreated { - room_id, - created_by: creator - }); + self.emit(RoomCreated { room_id, created_by: creator }); room_id } @@ -100,10 +100,7 @@ pub mod RoomManager { room.status.write(super::RoomStatus::BattleStarted); - self.emit(BattleStarted { - room_id, - started_by: caller - }) + self.emit(BattleStarted { room_id, started_by: caller }) } fn end_battle(ref self: ContractState, room_id: felt252) { @@ -114,10 +111,7 @@ pub mod RoomManager { room.status.write(super::RoomStatus::BattleEnded); - self.emit(BattleEnded { - room_id, - ended_by: caller - }) + self.emit(BattleEnded { room_id, ended_by: caller }) } } } diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index 00eeecd..e285d62 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,27 +1 @@ -fn main() -> u32 { - fib(16) -} - -fn fib(mut n: u32) -> u32 { - let mut a: u32 = 0; - let mut b: u32 = 1; - while n != 0 { - n = n - 1; - let temp = b; - b = a + b; - a = temp; - }; - a -} - -#[cfg(test)] -mod tests { - use super::fib; - - #[test] - fn it_works() { - assert(fib(16) == 987, 'it works!'); - } -} - -mod RoomManager; +pub mod Betting; From 38a736baeb4f823ee55b6ca2d801c1539adfcf08 Mon Sep 17 00:00:00 2001 From: Akinshola <114211385+Akshola00@users.noreply.github.com> Date: Tue, 6 May 2025 17:14:28 +0000 Subject: [PATCH 2/5] place bet implementation completed --- contracts/Scarb.lock | 107 +++++++++++++++++++++++ contracts/Scarb.toml | 2 + contracts/src/Betting.cairo | 135 +++++++++++++++++++++++++---- contracts/src/ierc20.cairo | 19 ++++ contracts/src/lib.cairo | 2 + contracts/src/strk.cairo | 82 ++++++++++++++++++ contracts/test/test_contract.cairo | 0 7 files changed, 332 insertions(+), 15 deletions(-) create mode 100644 contracts/src/ierc20.cairo create mode 100644 contracts/src/strk.cairo create mode 100644 contracts/test/test_contract.cairo diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 09f2d43..0e62537 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -5,9 +5,116 @@ version = 1 name = "contracts" version = "0.1.0" dependencies = [ + "openzeppelin", "snforge_std", ] +[[package]] +name = "openzeppelin" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_account" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_finance" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_access", + "openzeppelin_token", +] + +[[package]] +name = "openzeppelin_governance" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_introspection" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" + +[[package]] +name = "openzeppelin_presets" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_security" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" + +[[package]] +name = "openzeppelin_token" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" + +[[package]] +name = "openzeppelin_utils" +version = "2.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?branch=main#994bcb5eb5a3707f2fae02a9eb67e7e6ecfaf36f" + [[package]] name = "snforge_scarb_plugin" version = "0.31.0" diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 444ae31..08d24ca 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -7,6 +7,8 @@ edition = "2024_07" [dependencies] starknet = "2.11.4" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", branch = "main" } + [dev-dependencies] diff --git a/contracts/src/Betting.cairo b/contracts/src/Betting.cairo index 8531b25..8a0ebd7 100644 --- a/contracts/src/Betting.cairo +++ b/contracts/src/Betting.cairo @@ -1,27 +1,85 @@ use core::hash::{HashStateExTrait, HashStateTrait}; use core::poseidon::{PoseidonTrait, poseidon_hash_span}; +use openzeppelin::access::accesscontrol::AccessControlComponent; +use openzeppelin::access::ownable::OwnableComponent; +use openzeppelin::introspection::src5::SRC5Component; +use openzeppelin::token::erc20::interface::{ + ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Dispatcher, IERC20DispatcherTrait, + IERC20MetadataDispatcher, IERC20MetadataDispatcherTrait, +}; use starknet::storage::{ - Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + Map, MutableVecTrait, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + Vec, VecTrait, +}; +use starknet::{ + ContractAddress, contract_address_const, get_block_timestamp, get_caller_address, + get_contract_address, }; -use starknet::{ContractAddress, get_caller_address}; #[starknet::interface] pub trait IBetting { - fn place_bet(ref self: TContractState, room_id: u256, agent_id: u256, amount: u256) -> u256; + fn place_bet(ref self: TContractState, room_id: u256, agent_id: u256, amount: u256); fn resolve_bet(ref self: TContractState, room_id: u256, winner: ContractAddress); fn get_bet_amount(self: @TContractState, room_id: u256) -> u256; fn withdraw_winnings(ref self: TContractState, room_id: u256, amount: u256); + + fn create_pool(ref self: TContractState); } #[starknet::contract] mod Betting { use super::*; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + #[storage] + struct Storage { + bets_total_amount: Map<(u256, u256), u256>, // room id, agent id, amount in the pool + room: Map, // room if to battle details + token_addr: ContractAddress, + room_players: Map< + u256, Vec, + >, // a room id to the list of players in the room + player_stake: Map< + (ContractAddress, u256, u256), u256, + >, // (contract adddress of the payer that stakes and the room id with the agent he staekd on) to the amount the player stakes + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + } + #[event] #[derive(Drop, starknet::Event)] pub enum Event { BetPlaced: BetPlaced, BetResolved: BetResolved, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + FeesCollected: FeesCollected, } #[derive(Drop, starknet::Event)] @@ -38,29 +96,76 @@ mod Betting { amount: felt252, } - #[storage] - struct Storage { - bets_total_amount: Map<(u256, u256), u256> // room id, agent id, amount in the pool + #[derive(Drop, starknet::Event)] + struct FeesCollected { + fee_type: felt252, + agent_id: u256, + room_id: u256, + amount: u256, + } + + const ADMIN_ROLE: felt252 = selector!("ADMIN_ROLE"); + + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress, token_addr: ContractAddress) { + self.ownable.initializer(owner); + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(ADMIN_ROLE, owner); + self.token_addr.write(token_addr); + } + + #[derive(Copy, Drop, Serde, Hash, starknet::Store)] + pub struct Battle { + pub first_agent_id: u256, + pub second_agent_id: u256, } #[abi(embed_v0)] impl BettingImpl of IBetting { - fn place_bet(ref self: ContractState, room_id: u256, agent_id: u256, amount: u256) -> u256 { - 1 - } + fn place_bet(ref self: ContractState, room_id: u256, agent_id: u256, amount: u256) { + let caller = get_caller_address(); + assert(amount > 0, 'amount to stake cannot be 0'); + // assert room exists + let get_room = self.room.entry(room_id).read(); + assert(get_room.first_agent_id > 0, 'room doesnt exists'); + + let previous_amount: u256 = self.bets_total_amount.entry((room_id, agent_id)).read(); + + self.bets_total_amount.entry((room_id, agent_id)).write(previous_amount + amount); - fn resolve_bet(ref self: ContractState, room_id: u256, winner: ContractAddress) { - + // add the player to the room + self.room_players.entry(room_id).push(caller); + + // record what the player has staked + self.player_stake.entry((caller, room_id, agent_id)).write(amount); + + + let caller = get_caller_address(); + let dispatcher = IERC20Dispatcher { contract_address: self.token_addr.read() }; + + // Check balance and allowance + let user_balance = dispatcher.balance_of(caller); + assert(user_balance >= amount, 'Insufficient balance'); + + let contract_address = get_contract_address(); + + // approve the transfer + dispatcher.approve(contract_address, amount); + + // Transfer the tokens + dispatcher.transfer_from(caller, contract_address, amount); } + fn resolve_bet(ref self: ContractState, room_id: u256, winner: ContractAddress) {} + fn get_bet_amount(self: @ContractState, room_id: u256) -> u256 { 1 } - fn withdraw_winnings(ref self: ContractState, room_id: u256, amount: u256) { - - } - + fn withdraw_winnings(ref self: ContractState, room_id: u256, amount: u256) {} + + fn create_pool(ref self: ContractState) {} } #[external(v0)] diff --git a/contracts/src/ierc20.cairo b/contracts/src/ierc20.cairo new file mode 100644 index 0000000..842ec69 --- /dev/null +++ b/contracts/src/ierc20.cairo @@ -0,0 +1,19 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + fn decimals(self: @TContractState) -> u8; + + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index e285d62..48c5e39 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1 +1,3 @@ pub mod Betting; +pub mod ierc20; +pub mod strk; diff --git a/contracts/src/strk.cairo b/contracts/src/strk.cairo new file mode 100644 index 0000000..cab5f6f --- /dev/null +++ b/contracts/src/strk.cairo @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +use starknet::ContractAddress; + +#[starknet::interface] +trait IExternal { + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256); +} +#[starknet::contract] +pub mod STARKTOKEN { + use core::byte_array::ByteArray; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::token::erc20::interface::IERC20Metadata; + use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc20: ERC20Component::Storage, + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + custom_decimals: u8, + token_name: ByteArray, + token_symbol: ByteArray, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, recipient: ContractAddress, owner: ContractAddress, decimals: u8, + ) { + let name: ByteArray = "STRK"; + let symbol: ByteArray = "STRK"; + // Initialize the ERC20 component + self.erc20.initializer(name, symbol); + self.ownable.initializer(owner); + self.custom_decimals.write(decimals); + self.erc20.mint(recipient, 200_000_000_000_000_000_000_000); + } + + #[abi(embed_v0)] + impl CustomERC20MetadataImpl of IERC20Metadata { + fn name(self: @ContractState) -> ByteArray { + self.token_name.read() + } + + fn symbol(self: @ContractState) -> ByteArray { + self.token_symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.custom_decimals.read() // Return custom value + } + } + + // Keep existing implementations + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl InternalImpl = ERC20Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl ExternalImpl of super::IExternal { + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.erc20.mint(recipient, amount); + } + } +} diff --git a/contracts/test/test_contract.cairo b/contracts/test/test_contract.cairo new file mode 100644 index 0000000..e69de29 From 97eb5c6ea2366e35daae79c0c120fd8811070d24 Mon Sep 17 00:00:00 2001 From: Akinshola <114211385+Akshola00@users.noreply.github.com> Date: Tue, 6 May 2025 18:21:06 +0000 Subject: [PATCH 3/5] function successsfully implemeted --- .../.prev_tests_failed} | 0 contracts/src/Betting.cairo | 77 ++++++++++++++----- contracts/src/strk.cairo | 2 +- contracts/tests/lib.cairo | 1 + contracts/tests/test_contract.cairo | 52 +++++++++++++ 5 files changed, 112 insertions(+), 20 deletions(-) rename contracts/{test/test_contract.cairo => .snfoundry_cache/.prev_tests_failed} (100%) create mode 100644 contracts/tests/lib.cairo create mode 100644 contracts/tests/test_contract.cairo diff --git a/contracts/test/test_contract.cairo b/contracts/.snfoundry_cache/.prev_tests_failed similarity index 100% rename from contracts/test/test_contract.cairo rename to contracts/.snfoundry_cache/.prev_tests_failed diff --git a/contracts/src/Betting.cairo b/contracts/src/Betting.cairo index 8a0ebd7..cf7dcd1 100644 --- a/contracts/src/Betting.cairo +++ b/contracts/src/Betting.cairo @@ -19,11 +19,11 @@ use starknet::{ #[starknet::interface] pub trait IBetting { fn place_bet(ref self: TContractState, room_id: u256, agent_id: u256, amount: u256); - fn resolve_bet(ref self: TContractState, room_id: u256, winner: ContractAddress); + fn resolve_bet(ref self: TContractState, room_id: u256, winner: u256); fn get_bet_amount(self: @TContractState, room_id: u256) -> u256; - fn withdraw_winnings(ref self: TContractState, room_id: u256, amount: u256); + fn withdraw_winnings(ref self: TContractState, room_id: u256); - fn create_pool(ref self: TContractState); + fn create_game(ref self: TContractState) -> u256; } #[starknet::contract] @@ -52,14 +52,16 @@ mod Betting { #[storage] struct Storage { bets_total_amount: Map<(u256, u256), u256>, // room id, agent id, amount in the pool - room: Map, // room if to battle details + room: Map, // room id to battle details + rooms_len: u256, // total number of rooms token_addr: ContractAddress, room_players: Map< u256, Vec, >, // a room id to the list of players in the room player_stake: Map< - (ContractAddress, u256, u256), u256, - >, // (contract adddress of the payer that stakes and the room id with the agent he staekd on) to the amount the player stakes + (ContractAddress, u256), (u256, u256), + >, // (playerAddress, room id) to (agent, amount) + winner: Map, // room id to the winner agent id #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -84,16 +86,15 @@ mod Betting { #[derive(Drop, starknet::Event)] pub struct BetPlaced { - room_id: felt252, + room_id: u256, player: ContractAddress, - amount: felt252, + amount: u256, } #[derive(Drop, starknet::Event)] pub struct BetResolved { - room_id: felt252, - winner: ContractAddress, - amount: felt252, + room_id: u256, + winner: u256, } #[derive(Drop, starknet::Event)] @@ -138,8 +139,7 @@ mod Betting { self.room_players.entry(room_id).push(caller); // record what the player has staked - self.player_stake.entry((caller, room_id, agent_id)).write(amount); - + self.player_stake.entry((caller, room_id)).write((agent_id, amount)); let caller = get_caller_address(); let dispatcher = IERC20Dispatcher { contract_address: self.token_addr.read() }; @@ -155,21 +155,60 @@ mod Betting { // Transfer the tokens dispatcher.transfer_from(caller, contract_address, amount); + + self.emit(BetPlaced { room_id: room_id, player: caller, amount: amount }) } - fn resolve_bet(ref self: ContractState, room_id: u256, winner: ContractAddress) {} + fn resolve_bet(ref self: ContractState, room_id: u256, winner: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.winner.entry(room_id).write(winner); + } fn get_bet_amount(self: @ContractState, room_id: u256) -> u256 { - 1 + let caller = get_caller_address(); + let (_, bet_amount): (u256, u256) = self.player_stake.entry((caller, room_id)).read(); + assert(bet_amount > 0, 'no bets placed'); + bet_amount } - fn withdraw_winnings(ref self: ContractState, room_id: u256, amount: u256) {} + fn withdraw_winnings(ref self: ContractState, room_id: u256) { + // logic for calculating the winnings not yet explained so + // we are giving everyone their money and 10 strk back + let caller = get_caller_address(); + let (agent_id, bet_amount): (u256, u256) = self + .player_stake + .entry((caller, room_id)) + .read(); + assert(bet_amount > 0, 'no bets placed'); + let get_winner_of_the_room = self.winner.entry(room_id).read(); + assert(get_winner_of_the_room > 0, 'room not resolved yet'); + assert(get_winner_of_the_room == agent_id, 'you are not the winner'); + let dispatcher = IERC20Dispatcher { contract_address: self.token_addr.read() }; + let contract_balance = dispatcher.balance_of(get_contract_address()); + assert(contract_balance >= bet_amount + 10, 'not enough balance fr contract'); + + dispatcher.transfer(caller, bet_amount + 10); + } - fn create_pool(ref self: ContractState) {} + fn create_game(ref self: ContractState) -> u256 { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + let new_room: u256 = self.rooms_len.read() + 1; + let new_battle = Battle { + first_agent_id: 24, // random and default for now + second_agent_id: 66 // logic for creating of agents not yet implemendted + }; + self.room.entry(new_room).write(new_battle); + self.rooms_len.write(new_room); + new_room + } } #[external(v0)] - fn set_winner(ref self: ContractState, room_id: u256, agent_id: u256, winner: felt252) -> u256 { - 256 + fn set_winner(ref self: ContractState, room_id: u256, agent_id: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + let get_winner_of_the_room = self.winner.entry(room_id).read(); + assert(get_winner_of_the_room == 0, 'room already resolved'); + self.winner.entry(room_id).write(agent_id); + self.emit(BetResolved { room_id: room_id, winner: agent_id }); } } diff --git a/contracts/src/strk.cairo b/contracts/src/strk.cairo index cab5f6f..dc40ed8 100644 --- a/contracts/src/strk.cairo +++ b/contracts/src/strk.cairo @@ -6,7 +6,7 @@ trait IExternal { fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256); } #[starknet::contract] -pub mod STARKTOKEN { +pub mod STRK { use core::byte_array::ByteArray; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::token::erc20::interface::IERC20Metadata; diff --git a/contracts/tests/lib.cairo b/contracts/tests/lib.cairo new file mode 100644 index 0000000..4c719b5 --- /dev/null +++ b/contracts/tests/lib.cairo @@ -0,0 +1 @@ +pub mod test_contract; diff --git a/contracts/tests/test_contract.cairo b/contracts/tests/test_contract.cairo new file mode 100644 index 0000000..0fda2a2 --- /dev/null +++ b/contracts/tests/test_contract.cairo @@ -0,0 +1,52 @@ +// use contracts::base::types::{Category, Pool, PoolDetails, Status}; +// use contracts::interfaces::iUtils::{IUtilityDispatcher, IUtilityDispatcherTrait}; +use contracts::Betting::{IBetting, IBettingDispatcher, IBettingDispatcherTrait}; +// use contracts::predifi::Predifi; +// use contracts::utils::Utils; +// use contracts::utils::Utils::InternalFunctionsTrait; +// use core::array::ArrayTrait; +// use core::felt252; +// use core::serde::Serde; +// use core::traits::{Into, TryInto}; +// use openzeppelin::access::accesscontrol::AccessControlComponent::InternalTrait as +// AccessControlInternalTrait; +// use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, + start_cheat_block_timestamp, start_cheat_caller_address, stop_cheat_block_timestamp, + stop_cheat_caller_address, test_address, +}; +use starknet::storage::{MutableVecTrait, StoragePointerReadAccess, StoragePointerWriteAccess}; +use starknet::{ + ClassHash, ContractAddress, contract_address_const, get_block_timestamp, get_caller_address, + get_contract_address, +}; + +// // Validator role +const ADMIN_ROLE: felt252 = selector!("ADMIN_ROLE"); + +const OWNER: ContractAddress = 'owner'.try_into().unwrap(); + +fn deploy_betting_contract() -> (IBettingDispatcher, ContractAddress) { + // Deploy mock ERC20 + let erc20_class = declare("STRK").unwrap().contract_class(); + let mut calldata = array![OWNER.into(), OWNER.into(), 6]; + let (erc20_address, _) = erc20_class.deploy(@calldata).unwrap(); + + let contract_class = declare("Betting").unwrap().contract_class(); + + let (contract_address, _) = contract_class + .deploy(@array![OWNER.into(), erc20_address.into()]) + .unwrap(); + let dispatcher = IBettingDispatcher { contract_address }; + (dispatcher, erc20_address) +} + +#[test] +fn test_create_room() { + let (dispatcher, _) = deploy_betting_contract(); + + dispatcher.create_game(); +} + From 56dfc5bccf5347ee758f833cefa27434163af8fa Mon Sep 17 00:00:00 2001 From: Akinshola <114211385+Akshola00@users.noreply.github.com> Date: Wed, 7 May 2025 12:23:25 +0100 Subject: [PATCH 4/5] Update contracts.yml upgrade version of scarb and snforge --- .github/workflows/contracts.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index d25f88e..24b8573 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -21,11 +21,11 @@ jobs: - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "2.11.3" + scarb-version: "2.11.4" - uses: foundry-rs/setup-snfoundry@v3 with: - starknet-foundry-version: "0.39.0" + starknet-foundry-version: "0.31.0" - name: Check Versions @@ -42,4 +42,4 @@ jobs: run: snforge test - name: Build Project - run: scarb build \ No newline at end of file + run: scarb build From d00b9060a2f878ccf7b251f1c74fc1e262940dd9 Mon Sep 17 00:00:00 2001 From: Akshola00 Date: Wed, 7 May 2025 15:38:12 +0100 Subject: [PATCH 5/5] Refactor GitHub Actions workflow for contracts CI: streamline steps and improve build/test process --- .github/workflows/contracts.yml | 67 +++++++++++++++++---------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 24b8573..c4cc6a8 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -2,44 +2,45 @@ name: Contracts CI on: push: - paths: - - 'contracts/**' branches: [main, develop] pull_request: - paths: - - 'contracts/**' branches: [main, develop] - jobs: - test: + build-and-test: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./contracts steps: - - uses: actions/checkout@v4 - - - uses: software-mansion/setup-scarb@v1 - with: - scarb-version: "2.11.4" - - - uses: foundry-rs/setup-snfoundry@v3 + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - starknet-foundry-version: "0.31.0" - - - - name: Check Versions + toolchain: stable + override: true + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install asdf + uses: asdf-vm/actions/setup@v2 + + - name: Install plugins + run: | + asdf plugin add scarb + asdf install scarb 2.11.4 + asdf global scarb 2.11.4 + asdf plugin add starknet-foundry + asdf install starknet-foundry 0.31.0 + asdf global starknet-foundry 0.31.0 + + - name: Build contracts + run: | + cd contracts/ + scarb build + + - name: Run tests + run: | + cd contracts/ + snforge test + + - name: Check formatting run: | - scarb --version - snforge -V - - - - name: Format Check - run: scarb fmt --check - - - - name: Run Tests - run: snforge test - - - name: Build Project - run: scarb build + cd contracts/ + scarb fmt --check \ No newline at end of file