Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random Number Generator #238

Merged
merged 50 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
33085ea
feat: dice game vrf application
okhaimie-dev May 14, 2024
a6aa097
feat: add summary nav
okhaimie-dev May 14, 2024
ef37fa6
fix: ran scarb fmt
okhaimie-dev May 14, 2024
c2e1553
fix: ran scarb fmt
okhaimie-dev May 14, 2024
f594f76
Fix new lines
Jul 16, 2024
3db1038
Add more info on randomness sources
Jul 16, 2024
24fc67f
Rename dice_game_vrf.md->random_number_generator.md and update titles
Jul 16, 2024
3dc1a26
minor rewording of 1 entropy source
Jul 16, 2024
272c1f8
remove anchors
Jul 16, 2024
60ecfbd
Minor changes to fn names
Jul 16, 2024
416fefb
Implement dice game scaffold
Jul 25, 2024
a19d4dc
Implement Pragma randomness
Jul 25, 2024
5b78f83
minor refactor in randomness request
Jul 26, 2024
fb75473
Implement powerball scaffold
Jul 26, 2024
7e47ed3
Turn Dice Game into CoinFlip
Jul 26, 2024
104bd22
Implement coin_flip test
Jul 26, 2024
c16a65c
Add more tests
Jul 26, 2024
f080d47
Update titles
Jul 26, 2024
c0e0ff6
Remove redundant blank line
Jul 26, 2024
2f6000d
Add premium fee calculation into tests
Jul 30, 2024
dca7512
Assert leftover balance
Jul 30, 2024
06f9dc3
Remove comment about fees
Jul 30, 2024
f6f7dca
Increase the expected callback fee, update mock to expose fee calc fn
Jul 30, 2024
fe21adc
Unfinished: refunded
Jul 30, 2024
bf9de2d
Store and use is_refunded flag
Jul 31, 2024
d8ce40b
Implement logic necessary to successfully perform & test refund
Jul 31, 2024
c835463
Update callback fee limit based on manual testing + update term to de…
Jul 31, 2024
2089633
Format
Jul 31, 2024
468ff98
Use a FlipData struct instead of tuple
Jul 31, 2024
3ad1b6f
Fix refund
Jul 31, 2024
d037d57
Simplify CoinFlip to pay the flips itself
Aug 1, 2024
d141d5e
CALLBACK_FEE_DEPOSIT->MAX_CALLBACK_FEE_DEPOSIT
Aug 1, 2024
9e4cda0
Update tests to test the new CoinFlip contract
Aug 1, 2024
a81ecad
Fix compile errors
Aug 1, 2024
437b15c
Increase publish_delay to 1 & remove unused imports
Aug 1, 2024
9f3f790
Remove starkli-wallet dir
Aug 1, 2024
a2ed992
Generate 3 random words for the 1st test
Aug 1, 2024
73a97a9
refactor tests
Aug 1, 2024
49b42d3
Add missng newline to scarb.toml
Aug 1, 2024
6022c02
fix typo in md
Aug 9, 2024
d1174bd
reword 'manipulation' def
Aug 9, 2024
fb33c55
Chainlink->Pragma
Aug 9, 2024
fcce3d3
link to Commit-reveal chapter issue
Aug 9, 2024
9a0ed91
list 'shut down' as possible centr. issue with ext. oracles
Aug 9, 2024
a3b6e3b
Turn point 5 into a note
Aug 9, 2024
b75676d
Remove Sideways enum
Aug 9, 2024
642054e
fix conflicts with upstream
Aug 9, 2024
5c68e77
add contract description
Aug 9, 2024
6b3c539
Merge with upstream + update to Cairo 2.8.2
0xNeshi Sep 27, 2024
3f99abf
Remove ResultTrait from crowdfunding tests.cairo
0xNeshi Sep 27, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ output
# Others
.snfoundry_cache
.vscode/settings.json
**/starkli-wallet
14 changes: 14 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ version = "0.1.0"
name = "calling_other_contracts"
version = "0.1.0"

[[package]]
name = "coin_flip"
version = "0.1.0"
dependencies = [
"openzeppelin",
"pragma_lib",
"snforge_std",
]

[[package]]
name = "components"
version = "0.1.0"
Expand Down Expand Up @@ -120,6 +129,11 @@ name = "openzeppelin"
version = "0.14.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.14.0#f091c4f51ddeb10297db984acae965328c5a4e5b"

[[package]]
name = "pragma_lib"
version = "1.0.0"
source = "git+https://github.com/astraly-labs/pragma-lib#2eca9b70dc505788423da1eedbf2d65563cc3102"

[[package]]
name = "scarb"
version = "0.1.0"
Expand Down
1 change: 1 addition & 0 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ components = { path = "listings/applications/components" }
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" }
# The latest Alexandria release supports only Cairo v2.6.0, so using explicit rev that supports Cairo v2.6.3
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="800f5ad" }
pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" }

[workspace.package]
description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet."
Expand Down
2 changes: 0 additions & 2 deletions listings/applications/advanced_factory/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use core::clone::Clone;
use core::result::ResultTrait;
use advanced_factory::contract::{
CampaignFactory, ICampaignFactoryDispatcher, ICampaignFactoryDispatcherTrait
};
Expand Down
2 changes: 2 additions & 0 deletions listings/applications/coin_flip/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
.snfoundry_cache/
17 changes: 17 additions & 0 deletions listings/applications/coin_flip/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "coin_flip"
version = "0.1.0"
edition = "2023_11"

[dependencies]
starknet.workspace = true
openzeppelin.workspace = true
pragma_lib.workspace = true
snforge_std.workspace = true

[scripts]
test.workspace = true

[[target.starknet-contract]]
casm = true
build-external-contracts = ["openzeppelin::presets::erc20::ERC20Upgradeable"]
169 changes: 169 additions & 0 deletions listings/applications/coin_flip/src/contract.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use starknet::ContractAddress;

#[starknet::interface]
pub trait ICoinFlip<TContractState> {
fn flip(ref self: TContractState);
}

// declares just the pragma_lib::abi::IRandomness.receive_random_words function
#[starknet::interface]
pub trait IPragmaVRF<TContractState> {
fn receive_random_words(
ref self: TContractState,
requestor_address: ContractAddress,
request_id: u64,
random_words: Span<felt252>,
calldata: Array<felt252>
);
}

#[starknet::contract]
pub mod CoinFlip {
use core::num::traits::zero::Zero;
use starknet::{ContractAddress, get_caller_address, get_contract_address,};
use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};

#[storage]
struct Storage {
eth_dispatcher: IERC20Dispatcher,
julio4 marked this conversation as resolved.
Show resolved Hide resolved
flips: LegacyMap<u64, ContractAddress>,
nonce: u64,
randomness_contract_address: ContractAddress,
}

#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
Flipped: Flipped,
Landed: Landed,
}

#[derive(Drop, starknet::Event)]
pub struct Flipped {
pub flip_id: u64,
pub flipper: ContractAddress,
}

#[derive(Drop, starknet::Event)]
pub struct Landed {
pub flip_id: u64,
pub flipper: ContractAddress,
pub side: Side
}

#[derive(Drop, Debug, PartialEq, Serde)]
pub enum Side {
Heads,
Tails,
}

pub mod Errors {
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 REQUESTOR_NOT_SELF: felt252 = 'Requestor is not self';
pub const TRANSFER_FAILED: felt252 = 'Transfer failed';
}

pub const PUBLISH_DELAY: u64 = 1; // 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 MAX_CALLBACK_FEE_DEPOSIT: u256 =
500_000_000_000_000; // CALLBACK_FEE_LIMIT * 5; needs to cover the Premium fee

#[constructor]
fn constructor(
ref self: ContractState,
randomness_contract_address: ContractAddress,
eth_address: ContractAddress
) {
assert(randomness_contract_address.is_non_zero(), Errors::INVALID_ADDRESS);
assert(eth_address.is_non_zero(), Errors::INVALID_ADDRESS);
self.randomness_contract_address.write(randomness_contract_address);
self.eth_dispatcher.write(IERC20Dispatcher { contract_address: eth_address });
}

#[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 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 }));
}
}

#[abi(embed_v0)]
impl PragmaVRF of super::IPragmaVRF<ContractState> {
fn receive_random_words(
ref self: ContractState,
requestor_address: ContractAddress,
request_id: u64,
random_words: Span<felt252>,
calldata: Array<felt252>
) {
let caller = get_caller_address();
assert(
caller == self.randomness_contract_address.read(), Errors::CALLER_NOT_RANDOMNESS
);

let this = get_contract_address();
assert(requestor_address == this, Errors::REQUESTOR_NOT_SELF);

self._process_coin_flip(request_id, random_words.at(0));
}
}

#[generate_trait]
impl Private of PrivateTrait {
fn _request_my_randomness(ref self: ContractState) -> u64 {
let randomness_contract_address = self.randomness_contract_address.read();
let randomness_dispatcher = IRandomnessDispatcher {
contract_address: randomness_contract_address
};

let this = get_contract_address();

// Approve the randomness contract to transfer the callback deposit/fee
let eth_dispatcher = self.eth_dispatcher.read();
eth_dispatcher.approve(randomness_contract_address, MAX_CALLBACK_FEE_DEPOSIT);

let nonce = self.nonce.read();

// Request the randomness to be used to construct the winning combination
let request_id = randomness_dispatcher
.request_random(
nonce, this, CALLBACK_FEE_LIMIT, PUBLISH_DELAY, NUM_OF_WORDS, array![]
);

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 random_value: u256 = (*random_value).into();
let side = if random_value % 2 == 0 {
Side::Heads
} else {
Side::Tails
};

self.emit(Event::Landed(Landed { flip_id, flipper, side }));
}
}
}
5 changes: 5 additions & 0 deletions listings/applications/coin_flip/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod contract;
mod mock_randomness;

#[cfg(test)]
mod tests;
Loading