Skip to content

Commit

Permalink
Simplify CoinFlip to pay the flips itself
Browse files Browse the repository at this point in the history
  • Loading branch information
Nenad Misić authored Aug 1, 2024
1 parent 3ad1b6f commit d037d57
Showing 1 changed file with 13 additions and 98 deletions.
111 changes: 13 additions & 98 deletions listings/applications/coin_flip/src/contract.cairo
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use starknet::ContractAddress;

#[starknet::interface]
pub trait ICoinFlip<TContractState> {
fn flip(ref self: TContractState);
fn get_expected_deposit(self: @TContractState) -> u256;
fn refund(ref self: TContractState, flip_id: u64);
}

#[starknet::interface]
Expand All @@ -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<u64, ContractAddress>,
last_received_request_id: Option<LastRequestData>,
refunds: LegacyMap<u64, RefundData>,
nonce: u64,
randomness_contract_address: ContractAddress,
}
Expand All @@ -56,7 +37,6 @@ pub mod CoinFlip {
pub enum Event {
Flipped: Flipped,
Landed: Landed,
Refunded: Refunded
}

#[derive(Drop, starknet::Event)]
Expand All @@ -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,
Expand All @@ -87,19 +60,17 @@ 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';
}

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(
Expand All @@ -115,53 +86,14 @@ pub mod CoinFlip {

#[abi(embed_v0)]
impl CoinFlip of super::ICoinFlip<ContractState> {
/// 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)]
Expand Down Expand Up @@ -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();

Expand All @@ -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
Expand Down

0 comments on commit d037d57

Please sign in to comment.