Skip to content
Merged
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
32 changes: 26 additions & 6 deletions packages/nextjs/contracts/deployedContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const deployedContracts = {
devnet: {
StarkPlayERC20: {
address:
"0x5183c0e5ae2579f5c541c6c5d69d89d7d71b80fe583a90bb1e41c383adadbc1",
"0x67c92804c52bf84d814b5ca866e3e6b9ed21634b15bc64234d2ffc1458c8d47",
abi: [
{
type: "impl",
Expand Down Expand Up @@ -234,7 +234,7 @@ const deployedContracts = {
items: [
{
type: "function",
name: "assign_prize_tokens",
name: "mark_as_prize",
inputs: [
{
name: "recipient",
Expand Down Expand Up @@ -1185,11 +1185,11 @@ const deployedContracts = {
},
],
classHash:
"0x1db26668046de7c00fed257f34a0dad314a19a1fc8cc6816381ab7defec22b0",
"0x6f7fbd824c9dcff98cf62eba37adcbb8698a9aef6e034181a91d5f0e508bb0f",
},
StarkPlayVault: {
address:
"0x621b858ef40bbc6a8716025f6be83e2334647069aaf10d98b567ea26414c691",
"0x5308b4f0dbca99d7692f951e4c30682a54acf1c2c49aad5d2f6853127bbbfc2",
abi: [
{
type: "impl",
Expand Down Expand Up @@ -2077,7 +2077,7 @@ const deployedContracts = {
},
Lottery: {
address:
"0x6e9f2cc499c9a1ce19be05a0373e1a4f7f6f44400d37bffb128a37cef58d08f",
"0x4fc81e6d7f3b7b05f40547a96312287104b173c12218468caab4bcdf64c601a",
abi: [
{
type: "impl",
Expand Down Expand Up @@ -2546,6 +2546,26 @@ const deployedContracts = {
],
state_mutability: "external",
},
{
type: "function",
name: "GetUserWinningTickets",
inputs: [
{
name: "drawId",
type: "core::integer::u64",
},
{
name: "player",
type: "core::starknet::contract_address::ContractAddress",
},
],
outputs: [
{
type: "core::array::Array::<contracts::Lottery::Ticket>",
},
],
state_mutability: "view",
},
{
type: "function",
name: "GetUserTicketsCount",
Expand Down Expand Up @@ -3479,7 +3499,7 @@ const deployedContracts = {
},
],
classHash:
"0x7dbe308ba32779b910b9ab4400caf084dc545c476eb36be6f65f98307a6c088",
"0x72bd355d5c5afa59f8372d324cee47f5e7e4ae679c9de99ab07973a07412b27",
},
},
} as const;
Expand Down
93 changes: 68 additions & 25 deletions packages/snfoundry/contracts/src/Lottery.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ pub trait ILottery<TContractState> {
fn GetUserTickets(
ref self: TContractState, drawId: u64, player: ContractAddress,
) -> Array<Ticket>;
fn GetUserWinningTickets(
self: @TContractState, drawId: u64, player: ContractAddress,
) -> Array<Ticket>;
fn GetUserTicketsCount(self: @TContractState, drawId: u64, player: ContractAddress) -> u32;
fn GetTicketInfo(
self: @TContractState, drawId: u64, ticketId: felt252, player: ContractAddress,
Expand Down Expand Up @@ -154,6 +157,7 @@ pub trait ILottery<TContractState> {
//=======================================================================================
#[starknet::contract]
pub mod Lottery {
use contracts::StarkPlayERC20::{IPrizeTokenDispatcher, IPrizeTokenDispatcherTrait};
use core::array::{Array, ArrayTrait};
use core::dict::{Felt252Dict, Felt252DictTrait};
use core::traits::TryInto;
Expand Down Expand Up @@ -657,41 +661,53 @@ pub mod Lottery {
}
//=======================================================================================
fn ClaimPrize(ref self: ContractState, drawId: u64, ticketId: felt252) {
// Validate that draw exists
// 1. Start reentrancy protection
self.reentrancy_guard.start();

// 2. Validate that draw exists
self.AssertDrawExists(drawId, 'ClaimPrize');

// 3. Get draw and validate state
let draw = self.draws.entry(drawId).read();
let ticket = self.tickets.entry((drawId, ticketId)).read();
assert(!ticket.claimed, 'Prize already claimed');
assert(!draw.isActive, 'Draw still active');
assert(draw.distribution_done, 'Distribution not done');

let matches = self
.CheckMatches(
drawId,
ticket.number1,
ticket.number2,
ticket.number3,
ticket.number4,
ticket.number5,
);
let prize = self.GetFixedPrize(drawId, matches);
// 4. Get ticket and validate ownership and prize
let mut ticket = self.tickets.entry((drawId, ticketId)).read();
let caller = get_caller_address();

let mut ticket = ticket;
assert(ticket.player == caller, 'Not ticket owner');
assert(!ticket.claimed, 'Prize already claimed');
assert(ticket.prize_assigned, 'No prize assigned');
assert(ticket.prize_amount > 0, 'No prize amount');

// 5. Get contract addresses
let vault_address = self.strkPlayVaultContractAddress.read();
let token_address = self.strkPlayContractAddress.read();

// 6. Transfer tokens from vault to player
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };

token_dispatcher.transfer_from(vault_address, caller, ticket.prize_amount);

// 7. Mark transferred tokens as prize tokens
let prize_dispatcher = IPrizeTokenDispatcher { contract_address: token_address };
prize_dispatcher.mark_as_prize(caller, ticket.prize_amount);

// 8. Mark ticket as claimed
ticket.claimed = true;
self.tickets.entry((drawId, ticketId)).write(ticket);

if prize > 0 {
//TODO: We need to process the payment of the prize
// 9. Emit event with correct prize amount
self
.emit(
PrizeClaimed {
drawId, player: caller, ticketId, prizeAmount: ticket.prize_amount,
},
);

self
.emit(
PrizeClaimed {
drawId, player: ticket.player, ticketId, prizeAmount: prize,
},
);
} else {
self.emit(PrizeClaimed { drawId, player: ticket.player, ticketId, prizeAmount: 0 });
}
// 10. Release reentrancy guard
self.reentrancy_guard.end();
}

//=======================================================================================
Expand Down Expand Up @@ -951,6 +967,33 @@ pub mod Lottery {
user_tickets_data
}

//=======================================================================================
fn GetUserWinningTickets(
self: @ContractState, drawId: u64, player: ContractAddress,
) -> Array<Ticket> {
// Validate that draw exists (need to create snapshot for immutable self)

let draw = self.draws.entry(drawId).read();
assert(draw.drawId > 0, 'Draw does not exist');

let ticket_ids = self.GetUserTicketIds(drawId, player);
let mut winning_tickets = ArrayTrait::new();
let mut i: usize = 0;

while i != ticket_ids.len() {
let ticket_id = *ticket_ids.at(i);
let ticket = self.tickets.entry((drawId, ticket_id)).read();

// Filter: prize_assigned=true AND prize_amount>0 AND NOT claimed
if ticket.prize_assigned && ticket.prize_amount > 0 && !ticket.claimed {
winning_tickets.append(ticket);
}
i += 1;
}

winning_tickets
}

//=======================================================================================
fn GetTicketInfo(
self: @ContractState, drawId: u64, ticketId: felt252, player: ContractAddress,
Expand Down
5 changes: 2 additions & 3 deletions packages/snfoundry/contracts/src/StarkPlayERC20.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait IBurnable<TContractState> {

#[starknet::interface]
pub trait IPrizeToken<TContractState> {
fn assign_prize_tokens(ref self: TContractState, recipient: ContractAddress, amount: u256);
fn mark_as_prize(ref self: TContractState, recipient: ContractAddress, amount: u256);
fn get_prize_balance(self: @TContractState, account: ContractAddress) -> u256;
fn grant_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
fn revoke_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
Expand Down Expand Up @@ -328,12 +328,11 @@ pub mod StarkPlayERC20 {

#[abi(embed_v0)]
impl PrizeTokenImpl of IPrizeToken<ContractState> {
fn assign_prize_tokens(ref self: ContractState, recipient: ContractAddress, amount: u256) {
fn mark_as_prize(ref self: ContractState, recipient: ContractAddress, amount: u256) {
self.pausable.assert_not_paused();
self.accesscontrol.assert_only_role(PRIZE_ASSIGNER_ROLE);
let current_prize_balance = self.prize_balances.entry(recipient).read();
self.prize_balances.entry(recipient).write(current_prize_balance + amount);
self.erc20.mint(recipient, amount);
self.emit(PrizeTokensAssigned { recipient, amount });
}

Expand Down
18 changes: 8 additions & 10 deletions packages/snfoundry/contracts/tests/test_CU02.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,26 @@ fn deploy_vault_contract() -> (IStarkPlayVaultDispatcher, IMintableDispatcher) {
starkplay_token.set_minter_allowance(vault_address, EXCEEDS_MINT_LIMIT().into() * 10);
starkplay_token_burn.grant_burner_role(vault_address);
stop_cheat_caller_address(starkplay_token.contract_address);
// βœ… VERIFICAR que el rol se asignΓ³ correctamente
// βœ… Verify that the role was assigned correctly
let starkplay_access = IAccessControlDispatcher {
contract_address: starkplay_token.contract_address,
};
let burner_role = selector!("BURNER_ROLE");
assert(starkplay_access.has_role(burner_role, vault_address), 'Vault should have BURNER_ROLE');

// πŸ† ASIGNAR PRIZE_ASSIGNER_ROLE al OWNER (no al vault)
// πŸ† Assign PRIZE_ASSIGNER_ROLE to OWNER (not to vault)
let prize_dispatcher = IPrizeTokenDispatcher { contract_address: starkplay_address };
start_cheat_caller_address(prize_dispatcher.contract_address, OWNER());
prize_dispatcher.grant_prize_assigner_role(vault_address);
stop_cheat_caller_address(prize_dispatcher.contract_address);
// πŸ† MINTEAR StarkPlay tokens a USER1
// πŸ† Mint StarkPlay tokens to USER1
start_cheat_caller_address(starkplay_token.contract_address, vault_address);
starkplay_token
.mint(USER1(), 1000_000_000_000_000_000_000_u256); // 1000 tokens with 18 decimals

// πŸ† REGISTRAR esos tokens como premios usando assign_prize_tokens
// πŸ† Mark those tokens as prizes using mark_as_prize (without additional minting)
prize_dispatcher
.assign_prize_tokens(
USER1(), 1000_000_000_000_000_000_000_u256,
); // 1000 tokens with 18 decimals
.mark_as_prize(USER1(), 1000_000_000_000_000_000_000_u256); // 1000 tokens with 18 decimals
stop_cheat_caller_address(starkplay_token.contract_address);
start_cheat_caller_address(starkplay_token.contract_address, OWNER());
// Set a large allowance for the vault to mint and burn tokens
Expand Down Expand Up @@ -168,7 +166,7 @@ fn setup_user_balance(
}

fn setup_vault_strk_balance(vault_address: ContractAddress, amount: u256) {
// Set up vault with STRK balance usando OWNER() como en test_CU01.cairo
// Set up vault with STRK balance using OWNER() as in test_CU01.cairo
let strk_token = IMintableDispatcher {
contract_address: 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d
.try_into()
Expand Down Expand Up @@ -221,7 +219,7 @@ fn test_convert_to_strk_burn_limit_validation() {
// Verify burn limit was set
assert(vault.get_burn_limit() == small_burn_limit, 'Burn limit should be set');

// Set up vault with STRK balance (para dar cambio)
// Set up vault with STRK balance (to provide change)
setup_vault_strk_balance(
vault.contract_address, 1000_000_000_000_000_000_000_u256,
); // 1000 tokens
Expand Down Expand Up @@ -278,7 +276,7 @@ fn test_convert_to_strk_exceeds_burn_limit() {
vault.setBurnLimit(burn_limit);
stop_cheat_caller_address(vault.contract_address);

// Set up vault with STRK balance (para dar cambio)
// Set up vault with STRK balance (to provide change)
setup_vault_strk_balance(
vault.contract_address, 1000_000_000_000_000_000_000_u256,
); // 1000 tokens
Expand Down
5 changes: 5 additions & 0 deletions packages/snfoundry/scripts-ts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ const deployScript = async (): Promise<void> => {
const burn_allowance = 1_000_000_000n * 1000000000000000000n; // 1B tokens with 18 decimals
await starkPlayTokenContract.set_burner_allowance(starkPlayVaultAddress, burn_allowance);

// Owner (deployer) assigns PRIZE_ASSIGNER_ROLE to Lottery contract
console.log("Granting PRIZE_ASSIGNER_ROLE to Lottery...");
await starkPlayTokenContract.grant_prize_assigner_role(lotteryAddress);

console.log("StarkPlayVault roles assigned successfully by owner");
console.log("Lottery PRIZE_ASSIGNER_ROLE assigned successfully");
} catch (error) {
console.error("Failed to assign vault roles:", error);
throw new Error(`Vault role assignment failed: ${error}`);
Expand Down
Loading