diff --git a/listings/applications/coin_flip/src/contract.cairo b/listings/applications/coin_flip/src/contract.cairo index c2e356fc..4ee1d6e6 100644 --- a/listings/applications/coin_flip/src/contract.cairo +++ b/listings/applications/coin_flip/src/contract.cairo @@ -1,10 +1,6 @@ -use starknet::ContractAddress; - #[starknet::interface] pub trait ICoinFlip { fn flip(ref self: TContractState); - fn get_expected_deposit(self: @TContractState) -> u256; - fn refund(ref self: TContractState, flip_id: u64); } #[starknet::interface] @@ -28,25 +24,10 @@ pub mod CoinFlip { use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - #[derive(Drop, starknet::Store)] - struct RefundData { - flipper: ContractAddress, - amount: u256, - } - - #[derive(Drop, starknet::Store)] - struct LastRequestData { - flip_id: u64, - flipper: ContractAddress, - last_balance: u256, - } - #[storage] struct Storage { eth_dispatcher: IERC20Dispatcher, flips: LegacyMap, - last_received_request_id: Option, - refunds: LegacyMap, nonce: u64, randomness_contract_address: ContractAddress, } @@ -56,7 +37,6 @@ pub mod CoinFlip { pub enum Event { Flipped: Flipped, Landed: Landed, - Refunded: Refunded } #[derive(Drop, starknet::Event)] @@ -72,13 +52,6 @@ pub mod CoinFlip { pub side: Side } - #[derive(Drop, starknet::Event)] - pub struct Refunded { - pub flip_id: u64, - pub flipper: ContractAddress, - pub amount: u256 - } - #[derive(Drop, Serde)] pub enum Side { Heads, @@ -87,12 +60,9 @@ pub mod CoinFlip { } pub mod Errors { - pub const ALREADY_REFUNDED: felt252 = 'Already refunded'; pub const CALLER_NOT_RANDOMNESS: felt252 = 'Caller not randomness contract'; pub const INVALID_ADDRESS: felt252 = 'Invalid address'; pub const INVALID_FLIP_ID: felt252 = 'No flip with the given ID'; - pub const NOTHING_TO_REFUND: felt252 = 'Nothing to refund'; - pub const ONLY_FLIPPER_CAN_REFUND: felt252 = 'Only the flipper can refund'; pub const REQUESTOR_NOT_SELF: felt252 = 'Requestor is not self'; pub const TRANSFER_FAILED: felt252 = 'Transfer failed'; } @@ -100,6 +70,7 @@ pub mod CoinFlip { pub const PUBLISH_DELAY: u64 = 0; // return the random value asap pub const NUM_OF_WORDS: u64 = 1; // one random value is sufficient pub const CALLBACK_FEE_LIMIT: u128 = 100_000_000_000_000; // 0.0001 ETH + pub const CALLBACK_FEE_DEPOSIT: u256 = CALLBACK_FEE_LIMIT * 5; // needs to cover the Premium fee #[constructor] fn constructor( @@ -115,53 +86,14 @@ pub mod CoinFlip { #[abi(embed_v0)] impl CoinFlip of super::ICoinFlip { + /// The contract needs to be funded with some ETH in order for this function + /// to be callable. For simplicity, anyone can fund the contract. fn flip(ref self: ContractState) { - let flipper = get_caller_address(); - let this = get_contract_address(); - - // we pass the PragmaVRF fee to the flipper - // we take twice the callback fee amount just to make sure we - // can cover the fee + the premium - let deposit: u256 = self.get_expected_deposit(); - let eth_dispatcher = self.eth_dispatcher.read(); - let success = eth_dispatcher.transfer_from(flipper, this, deposit); - assert(success, Errors::TRANSFER_FAILED); - let flip_id = self._request_my_randomness(); - + let flipper = get_caller_address(); self.flips.write(flip_id, flipper); - self.emit(Event::Flipped(Flipped { flip_id, flipper })); } - - fn get_expected_deposit(self: @ContractState) -> u256 { - CALLBACK_FEE_LIMIT.into() * 5 - } - - fn refund(ref self: ContractState, flip_id: u64) { - let caller = get_caller_address(); - let flipper = self.flips.read(flip_id); - assert(flipper == caller, Errors::ONLY_FLIPPER_CAN_REFUND); - - let eth_dispatcher = self.eth_dispatcher.read(); - - if let Option::Some(data) = self.last_received_request_id.read() { - let to_refund = eth_dispatcher.balance_of(get_contract_address()) - - data.last_balance; - self.refunds.write(data.flip_id, RefundData { flipper, amount: to_refund }); - self.last_received_request_id.write(Option::None); - } - - let RefundData { flipper, amount } = self.refunds.read(flip_id); - assert(flipper.is_non_zero(), Errors::NOTHING_TO_REFUND); - - self.refunds.write(flip_id, RefundData { flipper: Zero::zero(), amount: 0 }); - - let success = eth_dispatcher.transfer(flipper, amount); - assert(success, Errors::TRANSFER_FAILED); - - self.emit(Event::Refunded(Refunded { flip_id, flipper, amount })); - } } #[abi(embed_v0)] @@ -197,7 +129,7 @@ pub mod CoinFlip { // Approve the randomness contract to transfer the callback deposit/fee let eth_dispatcher = self.eth_dispatcher.read(); - eth_dispatcher.approve(randomness_contract_address, self.get_expected_deposit().into()); + eth_dispatcher.approve(randomness_contract_address, CALLBACK_FEE_DEPOSIT); let nonce = self.nonce.read(); @@ -207,40 +139,23 @@ pub mod CoinFlip { nonce, this, CALLBACK_FEE_LIMIT, PUBLISH_DELAY, NUM_OF_WORDS, array![] ); - // remove approval once the randomness is paid for - eth_dispatcher.approve(randomness_contract_address, 0); - self.nonce.write(nonce + 1); request_id } + /// The chance of a flipped coin landing sideways is approximately 1 in 6000. + /// See paper: https://journals.aps.org/pre/abstract/10.1103/PhysRevE.48.2547 + /// + /// Since splitting the remainder (5999) equally between heads and tails is impossible, + /// we double the probability values: + /// - 2 in 12000 chance that it's sideways + /// - 5999 in 12000 chance that it's heads + /// - 5999 in 12000 chance that it's tails fn _process_coin_flip(ref self: ContractState, flip_id: u64, random_value: @felt252) { let flipper = self.flips.read(flip_id); assert(flipper.is_non_zero(), Errors::INVALID_FLIP_ID); - let eth_dispatcher = self.eth_dispatcher.read(); - let current_balance = eth_dispatcher.balance_of(get_contract_address()); - if let Option::Some(data) = self.last_received_request_id.read() { - self - .refunds - .write( - data.flip_id, - RefundData { flipper, amount: current_balance - data.last_balance } - ); - } - self - .last_received_request_id - .write( - Option::Some( - LastRequestData { flip_id, flipper, last_balance: current_balance } - ) - ); - - // The chance of a flipped coin landing sideways is approximately 1 in 6000. - // https://journals.aps.org/pre/abstract/10.1103/PhysRevE.48.2547 - // - // Since splitting the remainder (5999) equally is impossible, we double the values. let random_value: u256 = (*random_value).into() % 12000; let side = if random_value < 5999 { Side::Heads