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

Add source #15

Merged
merged 2 commits into from
Oct 11, 2024
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
25 changes: 20 additions & 5 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
# VRF for Cartridge Controller with paymaster

Randomness is requested by calling `vrf_provider.request_random` with:
- caller : the contract that will call consume_random
- source : one of this 2 options :

**Source::Nonce(address)** :
- each request_random will generate a unique seed using a nonce by address (nonce is increased after each call)
- (its recommanded to use wallet address to avoid nonce collisions)

**Source::Salt(salt)** :
- you have to provider you own salt
- using same salt will generate same seed = same randomness

# VRF for Cartridge Controller with paymaster

## How it works

caller send multicall :

```
[
vrf_provider.request_random(),
vrf_provider.request_random(game_contract.address, Source::Nonce(wallet_address)),
game_contract.you_function_consuming_randomness(...params)
]
```

Cartridge backend receive the tx,
retrieve seed using vrf_provider.get_next_seed( caller ),
compute proof for seed
and inject calls to sandwitch caller in a multicall :

```
[
vrf_provider.submit_random( seed, proof),
controller.outside_execution([
vrf_provider.request_random(),
vrf_provider.request_random(game_contract.address, Source::Nonce(wallet_address)),
game_contract.you_function_consuming_randomness(...params)
])
vrf_provider.assert_consumed( seed ),
Expand All @@ -31,6 +46,6 @@ and inject calls to sandwitch caller in a multicall :
- Randomness must be consume
- Randomness can only be consumed once
- Tx (submit_random / user calls / assert_consumed) is executed atomically by Cartridge backend
- Sumbitted randomness only last for the tx duration
- Sumbitted randomness only last for the tx duration
- It's not possible to request_random in a tx and consume_random in another tx
- User cannot probe randomness
- User cannot probe randomness
3 changes: 2 additions & 1 deletion contracts/dojo/vrf_consumer_template.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod consumer_template {
use starknet::get_caller_address;

use vrf_contracts::vrf_consumer::vrf_consumer_component::VrfConsumerComponent;
use vrf_contracts::vrf_provider::vrf_provider_component::Source;

component!(path: VrfConsumerComponent, storage: vrf_consumer, event: VrfConsumerEvent);

Expand Down Expand Up @@ -39,7 +40,7 @@ mod consumer_template {
impl VrfConsumerTemplateImpl of super::IVrfConsumerTemplate<ContractState> {
fn dice(ref self: ContractState) {
let player_id = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(player_id).into();
let random: u256 = self.vrf_consumer.consume_random(Source::Nonce(player_id)).into();
let value: u8 = (random % 6).try_into().unwrap() + 1;
// do the right things
}
Expand Down
8 changes: 7 additions & 1 deletion contracts/src/tests/common.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use openzeppelin_testing as utils;
use openzeppelin_utils::serde::SerializedAppend;
use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address};
use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address, cheat_max_fee_global };
use starknet::{ContractAddress, contract_address_const};
use stark_vrf::ecvrf::Proof;
use utils::constants::{AUTHORIZED, OWNER};
Expand All @@ -27,6 +27,10 @@ pub fn CONSUMER2() -> ContractAddress {
contract_address_const::<'CONSUMER2'>()
}

pub fn PLAYER1() -> ContractAddress {
contract_address_const::<'PLAYER1'>()
}

#[derive(Drop, Copy, Clone)]
pub struct SetupResult {
provider: IVrfProviderDispatcher,
Expand Down Expand Up @@ -57,6 +61,8 @@ pub fn setup() -> SetupResult {
utils::declare_and_deploy_at("VrfConsumer", CONSUMER1(), consumer_calldata.clone());
utils::deploy_another_at(CONSUMER1(), CONSUMER2(), consumer_calldata);

cheat_max_fee_global(10000000000000000);

SetupResult {
provider: IVrfProviderDispatcher { contract_address: PROVIDER() },
consumer1: IVrfConsumerExampleDispatcher { contract_address: CONSUMER1() },
Expand Down
138 changes: 87 additions & 51 deletions contracts/src/tests/test_dice.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,64 @@ use openzeppelin_utils::serde::SerializedAppend;

use vrf_contracts::vrf_provider::vrf_provider::VrfProvider;
use vrf_contracts::vrf_provider::vrf_provider_component::{
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey,
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey, Source
};

use vrf_contracts::vrf_consumer::vrf_consumer_example::{
VrfConsumer, IVrfConsumerExample, IVrfConsumerExampleDispatcher,
IVrfConsumerExampleDispatcherTrait
};

use super::common::{setup, submit_random, SetupResult, CONSUMER1, CONSUMER2};
use super::common::{setup, submit_random, SetupResult, CONSUMER1, CONSUMER2, PLAYER1};

// private key: 420
// {"public_key_x":"0x66da5d53168d591c55d4c05f3681663ac51bcdccd5ca09e366b71b0c40ccff4","public_key_y":"0x6d3eb29920bf55195e5ec76f69e247c0942c7ef85f6640896c058ec75ca2232"}
// seed: 0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09
// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09"]}' http://0.0.0.0:3000/stark_vrf
// {
// "result": {
// "gamma_x": "0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d",
// "gamma_y": "0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8",
// "c": "0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302",
// "s": "0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6",
// "sqrt_ratio": "0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01",
// "rnd": "0x735e9c275caf267880ec4e5967fde13ca084244384c03c739fcc54ac23789a4"
// }
// }


const SEED: felt252 = 0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09;

// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09"]}' http://0.0.0.0:3000/stark_vrf
pub fn proof() -> Proof {
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
}
}

const SEED_FROM_SALT: felt252 = 0x767EBFD1241683397A6CB06FDE012811BB27FD6E768D7A4BB8670ED10DF95C0;

// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x767EBFD1241683397A6CB06FDE012811BB27FD6E768D7A4BB8670ED10DF95C0"]}' http://0.0.0.0:3000/stark_vrf
pub fn proof_from_salt() -> Proof {
Proof {
gamma: Point {
x: 0x28473f4cad1406e83a766a1137281340b93600661af4eda228d5f73ae4e0fe9,
y: 0x36bf0d2884aa739d5e419f1eb9fdf4af61c489169830d748ae0bbc7707b95ae
},
c: 0x137ed85cd1ae3b25d6f9c2e3e23912cde001696fb4c0caba403967ecdab4bb4,
s: 0x74b0e76d8cfffc8a4755f4bcdefd3b1339d68e2c06b1072b36cfb2661fd4a27,
sqrt_ratio_hint: 0x29523a3636251c7085188108d8912577076ab6337892a3aa492dea4015d3a0c,
}
}

#[test]
fn test_dice() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
// PLAYER1 call dice, CONSUMER1 is caller of consume_random
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let dice1 = setup.consumer1.dice();
assert(dice1 == 3, 'dice1 should be 3');
stop_cheat_caller_address(setup.consumer1.contract_address);
Expand All @@ -71,24 +80,17 @@ fn test_dice() {
fn test_not_consuming__must_consume() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
// noop just here for example
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER dont consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
// PLAYER1 dont consume
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
setup.consumer1.not_consuming();

stop_cheat_caller_address(setup.consumer1.contract_address);
Expand All @@ -100,27 +102,61 @@ fn test_not_consuming__must_consume() {
fn test_dice__cannot_consume_twice() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
// noop just here for example
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

// provider submit_random
submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
start_cheat_caller_address(setup.consumer2.contract_address, CALLER());
// PLAYER1 consume twice
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
start_cheat_caller_address(setup.consumer2.contract_address, PLAYER1());

let _dice1 = setup.consumer1.dice();
let _dice2 = setup.consumer1.dice();
}

#[test]
fn test_dice_with_salt() {
let setup = setup();

// noop just here for example
setup.provider.request_random(CONSUMER1(),Source::Salt('salt'));

submit_random(
setup.provider,
SEED_FROM_SALT,
proof_from_salt(),
);

// PLAYER1 call dice_with_salt, CONSUMER1 is caller of consume_random
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let dice1 = setup.consumer1.dice_with_salt();
assert(dice1 == 2, 'dice1 should be 2');
stop_cheat_caller_address(setup.consumer1.contract_address);

setup.provider.assert_consumed(SEED);
}

#[test]
#[should_panic(expected: 'VrfProvider: not fulfilled')]
fn test_dice_with_salt__wrong_proof() {
let setup = setup();

// noop just here for example
setup.provider.request_random(CONSUMER1(),Source::Salt('salt'));

submit_random(
setup.provider,
SEED,
proof(),
);

// PLAYER1 consume
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let _dice1 = setup.consumer1.dice_with_salt();
}
8 changes: 3 additions & 5 deletions contracts/src/vrf_consumer/vrf_consumer_component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod VrfConsumerComponent {

use vrf_contracts::vrf_provider::vrf_provider_component::{
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey,
PublicKeyIntoPoint
PublicKeyIntoPoint, Source
};

#[storage]
Expand Down Expand Up @@ -67,10 +67,8 @@ pub mod VrfConsumerComponent {
self.set_vrf_provider(vrf_provider);
}

fn consume_random(
self: @ComponentState<TContractState>, caller: ContractAddress
) -> felt252 {
self.vrf_provider_disp().consume_random(Option::None)
fn consume_random(self: @ComponentState<TContractState>, source: Source) -> felt252 {
self.vrf_provider_disp().consume_random(source)
}

fn vrf_provider_disp(self: @ComponentState<TContractState>,) -> IVrfProviderDispatcher {
Expand Down
15 changes: 12 additions & 3 deletions contracts/src/vrf_consumer/vrf_consumer_example.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#[starknet::interface]
trait IVrfConsumerExample<TContractState> {
fn dice(ref self: TContractState) -> u8;
fn dice_with_salt(ref self: TContractState) -> u8;

fn not_consuming(ref self: TContractState);

// admin
Expand All @@ -22,6 +24,7 @@ mod VrfConsumer {
use stark_vrf::ecvrf::{Point, Proof, ECVRF, ECVRFImpl};

use vrf_contracts::vrf_consumer::vrf_consumer_component::{VrfConsumerComponent};
use vrf_contracts::vrf_provider::vrf_provider_component::Source;

component!(path: VrfConsumerComponent, storage: vrf_consumer, event: VrfConsumerEvent);

Expand Down Expand Up @@ -52,15 +55,21 @@ mod VrfConsumer {
impl ConsumerImpl of super::IVrfConsumerExample<ContractState> {
// throw dice
fn dice(ref self: ContractState) -> u8 {
let caller = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(caller).into();
let player_id = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(Source::Nonce(player_id)).into();

((random % 6) + 1).try_into().unwrap()
}

fn dice_with_salt(ref self: ContractState) -> u8 {
let random: u256 = self.vrf_consumer.consume_random(Source::Salt('salt')).into();

((random % 6) + 1).try_into().unwrap()
}

fn not_consuming(ref self: ContractState) {
let _player_id = get_caller_address();
// do the nothing
// do the nothing
}

fn set_vrf_provider(ref self: ContractState, new_vrf_provider: ContractAddress) {
Expand Down
Loading