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

feat(ark-orderbook): whitelist brokers #292

Merged
merged 3 commits into from
Feb 22, 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
6 changes: 5 additions & 1 deletion contracts/ark_common/src/protocol/order_types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ enum OrderValidationError {
EndDateTooFar,
AdditionalDataTooLong,
InvalidContent,
InvalidSalt
InvalidSalt,
InvalidBroker,
}

impl OrderValidationErrorIntoFelt252 of Into<OrderValidationError, felt252> {
Expand All @@ -62,6 +63,7 @@ impl OrderValidationErrorIntoFelt252 of Into<OrderValidationError, felt252> {
OrderValidationError::AdditionalDataTooLong => 'ADDITIONAL_DATA_TOO_LONG',
OrderValidationError::InvalidContent => 'INVALID_CONTENT',
OrderValidationError::InvalidSalt => 'INVALID_SALT',
OrderValidationError::InvalidBroker => 'INVALID_BROKER',
}
}
}
Expand All @@ -82,6 +84,8 @@ impl Felt252TryIntoOrderValidationError of TryInto<felt252, OrderValidationError
Option::Some(OrderValidationError::InvalidContent)
} else if self == 'INVALID_SALT' {
Option::Some(OrderValidationError::InvalidSalt)
} else if self == 'INVALID_BROKER' {
Option::Some(OrderValidationError::InvalidBroker)
} else {
Option::None
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/ark_orderbook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ To build:

# Testing

For testing, you must have [starknet forge](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html) installed (at least `0.6.0`). Then you can run:
For testing, you must have [starknet forge](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html) installed (at least `0.9.1`). Then you can run:

`snforge`
67 changes: 67 additions & 0 deletions contracts/ark_orderbook/src/broker/database.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Orders database.
//!
//! The order database uses the `storage_read` and `storage_write`
//! syscalls directly to optimize how data are stored.
//!
//! The status is always stored independently of the seriliazed
//! broker. This allows a quick and cheap storage/retrieval of the status
//! without having to write/read the whole order.
//!
//! The only assumption for now is that,
//! a single order serialized buffer must not exceed
//! 256 felts.
//!
//! The storage layout is the following:
//!
//! 1. Compute the storage base address with [BROKER_DB_BASE_KEY, broker_id]
//! 2. At base address => The broker whitelisting status.
use starknet::SyscallResultTrait;


/// Must remain equal to 0 for now.
const ADDRESS_DOMAIN: u32 = 0;
/// A constant value used in the base key hash.
const BROKER_DB_BASE_KEY: felt252 = 'broker whitelist';


/// Reads whitelist status of broker.
///
/// # Arguments
///
/// * `broker_id` - ID of the broker.
fn broker_whitelist_read(broker_id: felt252) -> bool {
let key = array![BROKER_DB_BASE_KEY, broker_id];

let base = starknet::storage_base_address_from_felt252(
poseidon::poseidon_hash_span(key.span())
);

// First offset is the status.
let whitelisted: felt252 = starknet::storage_read_syscall(
ADDRESS_DOMAIN, starknet::storage_address_from_base(base)
)
.unwrap_syscall();

whitelisted == 1
}

/// Writes only the whitelisted brokers.
/// It can be whitelisted or blacklisted.
///
/// # Arguments
///
/// * `broker_id` - ID of the broker.
/// * `status` - 1 if whitelisted, 0 if not.
fn broker_whitelist_write(broker_id: felt252, status: felt252) -> bool {
let key = array![BROKER_DB_BASE_KEY, broker_id];

let base = starknet::storage_base_address_from_felt252(
poseidon::poseidon_hash_span(key.span())
);

starknet::storage_write_syscall(
ADDRESS_DOMAIN, starknet::storage_address_from_base(base), status
);

true
}
4 changes: 4 additions & 0 deletions contracts/ark_orderbook/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ mod order {
mod order_v1;
}

mod broker {
mod database;
}

mod orderbook;
mod orderbook_event_mock;
13 changes: 13 additions & 0 deletions contracts/ark_orderbook/src/order/order_v1.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ use core::array::ArrayTrait;
use core::traits::Into;
use core::traits::TryInto;
use core::option::OptionTrait;

use ark_orderbook::broker::database::{broker_whitelist_read};

//! Order v1 supported by the Orderbook.
//!
use starknet::ContractAddress;
use starknet::contract_address_to_felt252;
use ark_common::protocol::order_types::{OrderTrait, OrderValidationError, OrderType, RouteType};
use ark_common::protocol::order_types::FulfillInfo;
use poseidon::poseidon_hash_span;
use starknet::SyscallResultTrait;

/// Must remain equal to 0 for now.
const ADDRESS_DOMAIN: u32 = 0;
const ORDER_VERSION_V1: felt252 = 'v1';
// Auction -> end_amount (reserve price) > start_amount (starting price).
// Auction -> ERC721_ERC20.
Expand Down Expand Up @@ -103,6 +109,13 @@ impl OrderTraitOrderV1 of OrderTrait<OrderV1> {
return Result::Err(OrderValidationError::InvalidContent);
}

// check if the broker is whitelisted.
let whitelisted = broker_whitelist_read(*self.broker_id);

if whitelisted == false {
return Result::Err(OrderValidationError::InvalidBroker);
}

Result::Ok(())
}

Expand Down
36 changes: 23 additions & 13 deletions contracts/ark_orderbook/src/orderbook.cairo
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! # Orderbook Contract
//!
//!
//! This module defines the structure and functionalities of an orderbook contract. It includes
//! trait definitions, error handling, contract storage, events, constructors, L1 handlers, external functions,
//! and internal functions. The primary functionalities include broker whitelisting, order management
//! trait definitions, error handling, contract storage, events, constructors, L1 handlers, external functions,
//! and internal functions. The primary functionalities include broker whitelisting, order management
//! (creation, cancellation, fulfillment), and order queries.

use ark_common::protocol::order_types::{FulfillInfo, OrderType, CancelInfo, OrderStatus};
Expand All @@ -13,13 +13,19 @@ use ark_orderbook::order::order_v1::OrderV1;
#[starknet::interface]
trait Orderbook<T> {
/// Whitelists a broker.
/// TODO: good first exercise to use a components for broker management.
///
/// # Arguments
///
/// * `broker_id` - ID of the broker.
fn whitelist_broker(ref self: T, broker_id: felt252);

/// Remove a broker from the whitelist.
///
/// # Arguments
///
/// * `broker_id` - ID of the broker.
fn unwhitelist_broker(ref self: T, broker_id: felt252);

/// Submits and places an order to the orderbook if the order is valid.
///
/// # Arguments
Expand Down Expand Up @@ -74,7 +80,7 @@ trait Orderbook<T> {
/// * `order_hash` - The order hash of order.
fn get_order_signer(self: @T, order_hash: felt252) -> felt252;

/// Retrieves the order hash using its token hash.
/// Retrieves the order hash using its token hash.
///
/// # Arguments
/// * `token_hash` - The token hash of the order.
Expand Down Expand Up @@ -119,6 +125,7 @@ mod orderbook_errors {
const ORDER_OPEN: felt252 = 'OB: order is not open';
const USE_FULFILL_AUCTION: felt252 = 'OB: must use fulfill auction';
const OFFER_NOT_STARTED: felt252 = 'OB: offer is not started';
const INVALID_BROKER: felt252 = 'OB: broker is not whitelisted';
}

/// StarkNet smart contract module for an order book.
Expand All @@ -145,6 +152,8 @@ mod orderbook {
order_read, order_status_read, order_write, order_status_write, order_type_read
};

use ark_orderbook::broker::database::{broker_whitelist_write};

const EXTENSION_TIME_IN_SECONDS: u64 = 600;
const AUCTION_ACCEPTING_TIME_SECS: u64 = 172800;
/// Storage struct for the Orderbook contract.
Expand Down Expand Up @@ -351,13 +360,14 @@ mod orderbook {

/// Whitelists a broker.
fn whitelist_broker(ref self: ContractState, broker_id: felt252) {
// TODO: check components with OZ when ready for ownable.
assert(
self.admin.read() == starknet::get_caller_address(),
orderbook_errors::BROKER_UNREGISTERED
);
assert(starknet::get_caller_address() == self.admin.read(), 'Unauthorized update');
broker_whitelist_write(broker_id, 1);
}

self.brokers.write(broker_id, 1);
/// Remove a broker from whitelist.
fn unwhitelist_broker(ref self: ContractState, broker_id: felt252) {
assert(starknet::get_caller_address() == self.admin.read(), 'Unauthorized update');
broker_whitelist_write(broker_id, 0);
}

/// Submits and places an order to the orderbook if the order is valid.
Expand Down Expand Up @@ -853,12 +863,12 @@ mod orderbook {
let auction_is_pending = current_block_timestamp < auction_end_date;

if auction_is_pending {
// If the auction is still pending, record the new offer by linking it to the
// If the auction is still pending, record the new offer by linking it to the
// auction order hash in the 'auction_offers' mapping.
self.auction_offers.write(order_hash, auction_order_hash);

if auction_end_date - current_block_timestamp < EXTENSION_TIME_IN_SECONDS {
// Increment the number of offers for this auction and extend the auction
// Increment the number of offers for this auction and extend the auction
// end date by the predefined extension time to allow for additional offers.
self
.auctions
Expand Down
18 changes: 17 additions & 1 deletion contracts/ark_orderbook/tests/common/setup.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ use core::traits::Into;
use ark_common::protocol::order_types::{RouteType, FulfillInfo, OrderTrait, OrderType, OrderStatus};
use ark_common::crypto::signer::{Signer, SignInfo};
use ark_orderbook::order::order_v1::OrderV1;
use ark_orderbook::orderbook::{OrderbookDispatcher, OrderbookDispatcherTrait};

use snforge_std::signature::{
StarkCurveKeyPair, StarkCurveKeyPairTrait, Signer as SNSigner, Verifier
};

use snforge_std::{start_prank, stop_prank};
use starknet::ContractAddress;

use super::super::common::signer::sign_mock;
/// Utility function to setup orders for test environment.
///
Expand Down Expand Up @@ -215,7 +220,7 @@ fn setup_listing_order(price: felt252) -> (OrderV1, felt252, felt252) {
/// Utility function to setup a listing order for test environment.
///
/// # Returns a tuple of the different orders data
///
///
/// * order_listing
/// * sign_info
/// * order_hash
Expand Down Expand Up @@ -366,3 +371,14 @@ fn get_offer_order() -> OrderV1 {
additional_data: data_span,
}
}

fn whitelist_creator_broker(
contract_address: ContractAddress, broker_id: felt252, dispatcher: OrderbookDispatcher
) {
start_prank(
contract_address,
0x00E4769a4d2F7F69C70951A003eBA5c32707Cef3CdfB6B27cA63567f51cdd078.try_into().unwrap()
);
dispatcher.whitelist_broker(broker_id);
stop_prank(contract_address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use snforge_std::{
};

use super::super::common::setup::{
setup_auction_order, setup_listing, sign_mock, setup_orders, setup_offer
setup_auction_order, setup_listing, sign_mock, setup_orders, setup_offer,
whitelist_creator_broker
};

#[test]
Expand All @@ -34,6 +35,7 @@ fn test_cancel_auction() {
let contract_address = contract.deploy(@contract_data).unwrap();

let dispatcher = OrderbookDispatcher { contract_address };
whitelist_creator_broker(contract_address, auction_listing_order.broker_id, dispatcher);
dispatcher.create_order(order: auction_listing_order, signer: signer);

let cancel_info = CancelInfo {
Expand Down Expand Up @@ -100,6 +102,7 @@ fn test_invalid_cancel_auction_order() {
let contract_address = contract.deploy(@contract_data).unwrap();

let dispatcher = OrderbookDispatcher { contract_address };
whitelist_creator_broker(contract_address, auction_listing_order.broker_id, dispatcher);
dispatcher.create_order(order: auction_listing_order, signer: signer);

let order_type = dispatcher.get_order_type(order_hash);
Expand Down Expand Up @@ -138,6 +141,7 @@ fn test_cancel_auction_during_the_extended_time() {
];
let contract_address = contract.deploy(@contract_data).unwrap();
let dispatcher = OrderbookDispatcher { contract_address };
whitelist_creator_broker(contract_address, auction_listing_order.broker_id, dispatcher);
dispatcher.create_order(order: auction_listing_order, signer: auction_listing_signer);
let order_type = dispatcher.get_order_type(order_hash);
assert(order_type == OrderType::Auction.into(), 'order is not auction');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use snforge_std::{
start_warp, declare, ContractClassTrait, spy_events, EventSpy, EventFetcher, EventAssertions,
Event, SpyOn, test_address, signature::{StarkCurveKeyPair, StarkCurveKeyPairTrait, Verifier}
};
use super::super::common::setup::{setup_auction_order, sign_mock, setup_orders, setup_offer};
use super::super::common::setup::{
setup_auction_order, sign_mock, setup_orders, setup_offer, whitelist_creator_broker
};

#[test]
fn test_create_valid_auction_offer() {
Expand All @@ -29,6 +31,7 @@ fn test_create_valid_auction_offer() {
let contract_address = contract.deploy(@contract_data).unwrap();

let dispatcher = OrderbookDispatcher { contract_address };
whitelist_creator_broker(contract_address, auction_listing_order.broker_id, dispatcher);
dispatcher.create_order(order: auction_listing_order, signer: signer);

let (auction_offer, signer, order_hash, token_hash) = setup_offer(
Expand All @@ -53,6 +56,7 @@ fn test_accept_auction_after_expiration() {
let contract_address = contract.deploy(@contract_data).unwrap();

let dispatcher = OrderbookDispatcher { contract_address };
whitelist_creator_broker(contract_address, auction_listing_order.broker_id, dispatcher);
dispatcher.create_order(order: auction_listing_order, signer: signer);

let (auction_offer, signer, auction_offer_order_hash, token_hash) = setup_offer(
Expand Down
Loading
Loading