Skip to content

Commit

Permalink
Rewrite briq into Dojo
Browse files Browse the repository at this point in the history
  • Loading branch information
wraitii committed Aug 18, 2023
1 parent 2948f99 commit 8899b51
Show file tree
Hide file tree
Showing 31 changed files with 2,465 additions and 783 deletions.
12 changes: 12 additions & 0 deletions src/attributes.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
mod attributes;
mod collection;

use traits::{Into, TryInto};
use option::OptionTrait;

const COLLECTION_ID_MASK: felt252 = 0xffffffffffffffffffffffffffffffffffffffffffffffff; // 2**192 - 1;

fn get_collection_id(attribute_id: felt252) -> felt252 {
let collection_id = Into::<felt252, u256>::into(attribute_id) & COLLECTION_ID_MASK.into();
return collection_id.try_into().unwrap();
}
136 changes: 136 additions & 0 deletions src/attributes/attributes.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use core::serde::Serde;
use starknet::ContractAddress;
use traits::Into;
use traits::TryInto;
use array::ArrayTrait;
use array::SpanTrait;
use option::OptionTrait;
use zeroable::Zeroable;
use clone::Clone;

use briq_protocol::types::{FTSpec, ShapeItem};
use briq_protocol::felt_math::{feltBitAnd, feltOrd};
use briq_protocol::cumulative_balance::{CUM_BALANCE_TOKEN, CB_BRIQ, CB_ATTRIBUTES};

use dojo::world::{Context, IWorldDispatcherTrait};
use dojo_erc::erc1155::components::ERC1155Balance;
use briq_protocol::world_config::{SYSTEM_CONFIG_ID, WorldConfig};

use briq_protocol::attributes::get_collection_id;
use briq_protocol::attributes::collection::{Collection, CollectionTrait};

use briq_protocol::check_shape::{ShapeVerifier, CheckShapeTrait};

use debug::PrintTrait;


#[derive(Drop, starknet::Event)]
struct AttributeAssigned {
set_token_id: u256,
attribute_id: felt252
}

#[derive(Drop, starknet::Event)]
struct AttributeRemoved {
set_token_id: u256,
attribute_id: felt252
}

fn assign_attributes(
ctx: Context,
set_owner: ContractAddress,
set_token_id: felt252,
mut attributes: Array<felt252>,
shape: @Array<ShapeItem>,
fts: @Array<FTSpec>,
) {
loop
{
if (attributes.len() == 0) {
break ();
}
assign_attribute(ctx, set_owner, set_token_id, attributes.pop_front().unwrap(), shape, fts);
}
}

fn assign_attribute(
ctx: Context,
set_owner: ContractAddress,
set_token_id: felt252,
attribute_id: felt252,
shape: @Array<ShapeItem>,
fts: @Array<FTSpec>,
) {
assert(set_owner.is_non_zero(), 'Bad input');
assert(set_token_id != 0, 'Bad input');
assert(attribute_id != 0, 'Bad input');

let caller = ctx.origin;
let set_addr = get!(ctx.world, (SYSTEM_CONFIG_ID), WorldConfig).set;
// TODO: Set permissions on the collection (owner / set) ?
assert (caller == set_addr, 'Bad caller');

let collection_id = get_collection_id(attribute_id);
let (admin, system) = get!(ctx.world, (collection_id), Collection).get_admin_or_system();
if admin.is_some() {
//library_erc1155::transferability::Transferability::_transfer_burnable(0, set_token_id, attribute_id, 1);
assert(0 == 1, 'TODO');
} else {
let shape_verifier = get!(ctx.world, (attribute_id), ShapeVerifier);
shape_verifier.assign_attribute(ctx.world, set_owner, set_token_id, attribute_id, shape, fts);
}
emit!(ctx.world, AttributeAssigned { set_token_id: set_token_id.into(), attribute_id });

// Update the cumulative balance
let balance = get!(ctx.world, (CUM_BALANCE_TOKEN(), CB_ATTRIBUTES, set_token_id), ERC1155Balance).amount;
assert(balance < balance + 1, 'Balance overflow');
set!(ctx.world, ERC1155Balance { token: CUM_BALANCE_TOKEN(), token_id: CB_ATTRIBUTES, account: set_token_id.try_into().unwrap(), amount: balance + 1 });
}

fn remove_attributes(
ctx: Context,
set_owner: ContractAddress,
set_token_id: felt252,
mut attributes: Array<felt252>
) {
loop
{
if (attributes.len() == 0) {
break ();
}
remove_attribute(ctx, set_owner, set_token_id, attributes.pop_front().unwrap());
}
}

fn remove_attribute(
ctx: Context,
set_owner: ContractAddress,
set_token_id: felt252,
attribute_id: felt252,
) {
assert(set_owner.is_non_zero(), 'Bad input');
assert(set_token_id != 0, 'Bad input');
assert(attribute_id != 0, 'Bad input');

let caller = ctx.origin;
let set_addr = get!(ctx.world, (SYSTEM_CONFIG_ID), WorldConfig).set;
// TODO: Set permissions on the collection (owner / set) ?
assert (caller == set_addr, 'Bad caller');

let collection_id = get_collection_id(attribute_id);
let (admin, system) = get!(ctx.world, (collection_id), Collection).get_admin_or_system();
if admin.is_some() {
//library_erc1155::transferability::Transferability::_transfer_burnable(set_token_id, 0, attribute_id, 1);
assert(0 == 1, 'TODO');
} else {
let shape_verifier = get!(ctx.world, (attribute_id), ShapeVerifier);
shape_verifier.remove_attribute(ctx.world, set_owner, set_token_id, attribute_id);
}

emit!(ctx.world, AttributeRemoved { set_token_id: set_token_id.into(), attribute_id });

// Update the cumulative balance
let balance = get!(ctx.world, (CUM_BALANCE_TOKEN(), CB_ATTRIBUTES, set_token_id), ERC1155Balance).amount;
assert(balance > balance - 1, 'Balance underflow');
set!(ctx.world, ERC1155Balance { token: CUM_BALANCE_TOKEN(), token_id: CB_ATTRIBUTES, account: set_token_id.try_into().unwrap(), amount: balance - 1 });
}
112 changes: 112 additions & 0 deletions src/attributes/collection.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use starknet::ContractAddress;

const EXISTS_BIT: felt252 = 1; // This bit is always toggled for a collection that exists.
const CONTRACT_BIT: felt252 = 2;

use traits::{Into, TryInto};
use option::OptionTrait;
use clone::Clone;
use serde::Serde;

use briq_protocol::felt_math::{feltBitAnd, feltOrd};

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Collection {
#[key]
collection_id: u64,

parameters: felt252, // bitfield
admin_or_system: ContractAddress,
}

#[generate_trait]
impl CollectionImpl of CollectionTrait {
fn get_admin_or_system(self: @Collection) -> (Option<ContractAddress>, Option<ContractAddress>) {
let a_o_c = self.admin_or_system;
if *self.parameters & CONTRACT_BIT == 0 {
return (Option::Some(*a_o_c), Option::None);
} else {
return (Option::None, Option::Some(*a_o_c));
}
}
}

fn new_collection(collection_id: u64, params: felt252, admin_or_system: ContractAddress) -> Collection {
let existence_bit_toggled = params & EXISTS_BIT;
// Probably indicates an error, fail.
assert(existence_bit_toggled == 0, 'Invalid bits');
assert(params < 0x400000000000000000000000000000000000000000000000000000000000000, 'Invalid bits');

Collection {
collection_id: collection_id,
parameters: params + EXISTS_BIT,
admin_or_system: admin_or_system,
}
}

#[derive(Clone, Drop, Serde)]
struct CreateCollectionData
{
collection_id: u64,
params: felt252,
admin_or_system: ContractAddress
}

#[system]
mod create_collection {
use starknet::ContractAddress;
use traits::Into;
use traits::TryInto;
use array::ArrayTrait;
use array::SpanTrait;
use option::OptionTrait;
use zeroable::Zeroable;
use clone::Clone;
use serde::Serde;

use dojo::world::Context;
use dojo_erc::erc1155::components::ERC1155Balance;
use briq_protocol::world_config::AdminTrait;

use briq_protocol::types::{FTSpec, ShapeItem};

use briq_protocol::attributes::get_collection_id;
use briq_protocol::attributes::collection::{Collection, CollectionTrait, new_collection};

use debug::PrintTrait;

use super::CreateCollectionData;

#[derive(Drop, starknet::Event)]
struct CollectionCreated {
collection_id: u64,
system: ContractAddress,
admin: ContractAddress,
parameters: felt252
}

fn execute(
ctx: Context,
data: CreateCollectionData,
) {
let CreateCollectionData { collection_id, params, admin_or_system } = data;

assert(admin_or_system.is_non_zero(), 'Must have admin');

// TODO: check ctx.origin is actually the origin
ctx.world.only_admins(@ctx.origin);

assert(get!(ctx.world, (collection_id), Collection).parameters == 0, 'Collec already exists');

let collec = new_collection(collection_id, params, admin_or_system);

set!(ctx.world, (collec));

let (admin, system) = collec.get_admin_or_system();
if admin.is_some() {
emit!(ctx.world, CollectionCreated { collection_id, system: Zeroable::zero(), admin: admin.unwrap(), parameters:params });
} else {
emit!(ctx.world, CollectionCreated { collection_id, system: system.unwrap(), admin: Zeroable::zero(), parameters:params });
}
}
}
1 change: 1 addition & 0 deletions src/box_nft.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod unboxing;
111 changes: 111 additions & 0 deletions src/box_nft/unboxing.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use traits::Into;
use traits::TryInto;
use option::OptionTrait;

use array::ArrayTrait;

use starknet::ContractAddress;

use dojo::world::IWorldDispatcher;

use briq_protocol::world_config::{get_world_config, AdminTrait};

#[derive(Drop, Copy, Serde)]
struct BoxData {
briq_1: felt252, // nb of briqs of material 0x1
shape_class_hash: felt252, // Class hash of the matching shape contract
}

fn get_number_of_briqs_in_box(box_id: felt252) -> u128
{
// TODO: use match once that's supported
if box_id == 1 { return 434; }
if box_id == 2 { return 1252; }
if box_id == 3 { return 2636; }
if box_id == 4 { return 431; }
if box_id == 5 { return 1246; }
if box_id == 6 { return 2287; }
if box_id == 7 { return 431; }
if box_id == 8 { return 1286; }
if box_id == 9 { return 2392; }
if box_id == 10 { return 60; } // briqmas
assert(false, 'bad box id');
0
}

fn get_booklet_id_for_box(box_id: felt252) -> felt252
{
// TODO: use match once that's supported
if box_id == 1 { return 0x1000000000000000000000000000000000000000000000001; }
if box_id == 2 { return 0x2000000000000000000000000000000000000000000000001; }
if box_id == 3 { return 0x3000000000000000000000000000000000000000000000001; }
if box_id == 4 { return 0x4000000000000000000000000000000000000000000000001; }
if box_id == 5 { return 0x5000000000000000000000000000000000000000000000001; }
if box_id == 6 { return 0x6000000000000000000000000000000000000000000000001; }
if box_id == 7 { return 0x7000000000000000000000000000000000000000000000001; }
if box_id == 8 { return 0x8000000000000000000000000000000000000000000000001; }
if box_id == 9 { return 0x9000000000000000000000000000000000000000000000001; }
// TODO warning delta migration
if box_id == 10 { return 0x1000000000000000000000000000000000000000000000002; } // briqmas
assert(false, 'Invalid box id');
0
}

fn unbox(world: IWorldDispatcher, owner: ContractAddress, box_id: felt252)
{
// Burn the box
// TODO: use event-emitting variant
dojo_erc::erc1155::components::ERC1155BalanceTrait::transfer_tokens(
world,
get_world_config(world).box,
owner,
Zeroable::zero(),
array![box_id].span(),
array![1].span(),
);

// Mint a booklet
// TODO: use event-emitting variant
dojo_erc::erc1155::components::ERC1155BalanceTrait::transfer_tokens(
world,
get_world_config(world).booklet,
Zeroable::zero(),
owner,
array![get_booklet_id_for_box(box_id)].span(),
array![1].span(),
);
// TODO: register a specific shape verifier for this booklet ?
// Or will that be handled directly by a different system maybe...

// Mint briqs

briq_protocol::briq_token::systems::update_nocheck(
world,
owner,
get_world_config(world).briq,
from: Zeroable::zero(),
to: owner,
ids: array![1],
amounts: array![get_number_of_briqs_in_box(box_id)],
data: array![]
);
}

// Unbox burns the box NFT, and mints briqs & attributes_registry corresponding to the token URI.
#[system]
mod box_unboxing {
use traits::{Into, TryInto};
use option::OptionTrait;
use array::ArrayTrait;
use dojo::world::Context;
use zeroable::Zeroable;
use starknet::ContractAddress;

fn execute(
ctx: Context,
box_id: felt252,
) {
// Only the owner may unbox their box.
super::unbox(ctx.world, ctx.origin, box_id);
}
}
Loading

0 comments on commit 8899b51

Please sign in to comment.