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: add erc-20 support to orderbook #476

Open
wants to merge 22 commits into
base: feat/contract-v2
Choose a base branch
from
Open
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
42 changes: 41 additions & 1 deletion contracts/ark_common/src/protocol/order_types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ enum OrderType {
Auction,
Offer,
CollectionOffer,
LimitBuy,
LimitSell
}

impl OrderTypeIntoFelt252 of Into<OrderType, felt252> {
Expand All @@ -20,6 +22,8 @@ impl OrderTypeIntoFelt252 of Into<OrderType, felt252> {
OrderType::Auction => 'AUCTION',
OrderType::Offer => 'OFFER',
OrderType::CollectionOffer => 'COLLECTION_OFFER',
OrderType::LimitBuy => 'LIMIT_BUY',
OrderType::LimitSell => 'LIMIT_SELL'
}
}
}
Expand All @@ -34,6 +38,10 @@ impl Felt252TryIntoOrderType of TryInto<felt252, OrderType> {
Option::Some(OrderType::Offer)
} else if self == 'COLLECTION_OFFER' {
Option::Some(OrderType::CollectionOffer)
} else if self == 'LIMIT_BUY' {
Option::Some(OrderType::LimitBuy)
} else if self == 'LIMIT_SELL' {
Option::Some(OrderType::LimitSell)
} else {
Option::None
}
Expand Down Expand Up @@ -221,8 +229,8 @@ struct ExecutionInfo {
token_address: ContractAddress,
token_from: ContractAddress,
token_to: ContractAddress,
token_id: u256,
token_quantity: u256,
token_id: OptionU256,
payment_from: ContractAddress,
payment_to: ContractAddress,
payment_amount: u256,
Expand Down Expand Up @@ -265,6 +273,8 @@ enum RouteType {
#[default]
Erc20ToErc721,
Erc721ToErc20,
Erc20ToErc20Buy,
Erc20ToErc20Sell,
Erc20ToErc1155,
Erc1155ToErc20,
}
Expand All @@ -274,6 +284,8 @@ impl RouteIntoFelt252 of Into<RouteType, felt252> {
match self {
RouteType::Erc20ToErc721 => 'ERC20TOERC721',
RouteType::Erc721ToErc20 => 'ERC721TOERC20',
RouteType::Erc20ToErc20Buy => 'ERC20TOERC20BUY',
RouteType::Erc20ToErc20Sell => 'ERC20TOERC20SELL',
RouteType::Erc20ToErc1155 => 'ERC20TOERC1155',
RouteType::Erc1155ToErc20 => 'ERC1155TOERC20',
}
Expand All @@ -286,6 +298,10 @@ impl Felt252TryIntoRoute of TryInto<felt252, RouteType> {
Option::Some(RouteType::Erc20ToErc721)
} else if self == 'ERC721TOERC20' {
Option::Some(RouteType::Erc721ToErc20)
} else if self == 'ERC20TOERC20BUY' {
Option::Some(RouteType::Erc20ToErc20Buy)
} else if self == 'ERC20TOERC20SELL' {
Option::Some(RouteType::Erc20ToErc20Sell)
} else if self == 'ERC1155TOERC20' {
Option::Some(RouteType::Erc1155ToErc20)
} else if self == 'ERC20TOERC1155' {
Expand All @@ -295,3 +311,27 @@ impl Felt252TryIntoRoute of TryInto<felt252, RouteType> {
}
}
}

#[derive(starknet::Store, Serde, Drop, PartialEq, Copy, Debug)]
struct OptionU256 {
is_some: felt252,
value: u256,
}

trait OptionU256Trait<T, +Serde<T>, +Drop<T>> {
fn get_some(self: @T) -> (felt252, u256);
fn is_some(self: @T) -> bool;
}

impl OptionU256Impl of OptionU256Trait<OptionU256> {
fn get_some(self: @OptionU256) -> (felt252, u256) {
(*self.is_some, *self.value)
}
fn is_some(self: @OptionU256) -> bool {
if *self.is_some == 1 {
true
} else {
false
}
}
}
86 changes: 64 additions & 22 deletions contracts/ark_common/src/protocol/order_v1.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ const ORDER_VERSION_V1: felt252 = 'v1';

#[derive(Serde, Drop, Copy)]
struct OrderV1 {
// Route ERC20->ERC721 (buy) ERC721->ERC20 (sell)
// Route ERC20->ERC721 (buy) ERC721->ERC20 (sell) ERC20BUY ERC20SELL
route: RouteType,
// Contract address of the currency used on Starknet for the transfer.
// For now ERC20 -> ETH Starkgate.
// Contract address of the payment currency used on Starknet for the transfer.
currency_address: ContractAddress,
currency_chain_id: felt252,
// Salt.
Expand All @@ -31,16 +30,16 @@ struct OrderV1 {
offerer: ContractAddress,
// Chain id.
token_chain_id: felt252,
// The token contract address.
// The token contract address. // exchange token
token_address: ContractAddress,
// The token id.
token_id: Option<u256>,
// The quantity of the token_id to be offerred (1 for NFTs).
quantity: u256,
// in wei. --> 10 | 10 | 10 |
start_amount: u256,
start_amount: u256, // amount to pay for buy order.
// in wei. --> 0 | 10 | 20 |
end_amount: u256,
end_amount: u256, // amount to receive for sell order
// Start validity date of the offer, seconds since unix epoch.
start_date: u64,
// Expiry date of the order, seconds since unix epoch.
Expand All @@ -64,16 +63,31 @@ impl OrderTraitOrderV1 of OrderTrait<OrderV1> {
return Result::Err(OrderValidationError::InvalidSalt);
}

let end_date = *self.end_date;
// check for expiry only if not erc20 buys or sells
if (*self.route != RouteType::Erc20ToErc20Buy
&& *self.route != RouteType::Erc20ToErc20Sell) {
let end_date = *self.end_date;

if end_date < block_timestamp {
return Result::Err(OrderValidationError::EndDateInThePast);
if end_date < block_timestamp {
return Result::Err(OrderValidationError::EndDateInThePast);
}

// End date -> block_timestamp + 30 days.
let max_end_date = block_timestamp + (30 * 24 * 60 * 60);
if end_date > max_end_date {
return Result::Err(OrderValidationError::EndDateTooFar);
}
}

// End date -> block_timestamp + 30 days.
let max_end_date = block_timestamp + (30 * 24 * 60 * 60);
if end_date > max_end_date {
return Result::Err(OrderValidationError::EndDateTooFar);
// check that the start amount is not zero for sell erc20 orders
if (*self.route != RouteType::Erc20ToErc20Sell) {
if (*self.start_amount).is_zero() {
return Result::Err(OrderValidationError::InvalidContent);
}
} else {
if (*self.end_amount).is_zero() {
return Result::Err(OrderValidationError::InvalidContent);
}
}

// TODO: define a real value here. 20 is an example and
Expand All @@ -86,7 +100,6 @@ impl OrderTraitOrderV1 of OrderTrait<OrderV1> {

if (*self.offerer).is_zero()
|| (*self.token_address).is_zero()
|| (*self.start_amount).is_zero()
|| (*self.salt).is_zero()
|| (*self.quantity).is_zero() {
return Result::Err(OrderValidationError::InvalidContent);
Expand Down Expand Up @@ -126,21 +139,50 @@ impl OrderTraitOrderV1 of OrderTrait<OrderV1> {
|| (*self.route) == RouteType::Erc20ToErc1155) {
return Result::Ok(OrderType::CollectionOffer);
}

// Limit Buy Order
if (*self.quantity) > 0
&& (*self.start_amount) > 0 // amount to pay
&& (*self.end_amount).is_zero()
&& (*self.route == RouteType::Erc20ToErc20Buy) {
return Result::Ok(OrderType::LimitBuy);
}

// Limit Sell Order
if (*self.quantity) > 0
&& (*self.start_amount).is_zero()
&& (*self.end_amount) > 0 // amount to receive
&& (*self.route == RouteType::Erc20ToErc20Sell) {
return Result::Ok(OrderType::LimitSell);
}

if (*self.route == RouteType::Erc20ToErc20Sell) {
return Result::Ok(OrderType::LimitSell);
}
}

// Other order types are not supported.
Result::Err(OrderValidationError::InvalidContent)
}

fn compute_token_hash(self: @OrderV1) -> felt252 {
assert(OptionTrait::is_some(self.token_id), 'Token ID expected');
let token_id = (*self.token_id).unwrap();
let mut buf: Array<felt252> = array![];
buf.append((token_id.low).into());
buf.append((token_id.high).into());
buf.append((*self.token_address).into());
buf.append(*self.token_chain_id);
poseidon_hash_span(buf.span())
if (*self.route == RouteType::Erc20ToErc20Buy
|| *self.route == RouteType::Erc20ToErc20Sell) {
let mut buf: Array<felt252> = array![];
// used quantity, start_date and the offerer as the identifiers
buf.append((*self.token_address).into());
buf.append(*self.token_chain_id);
poseidon_hash_span(buf.span())
} else {
assert(OptionTrait::is_some(self.token_id), 'Token ID expected');
let token_id = (*self.token_id).unwrap();
let mut buf: Array<felt252> = array![];
buf.append((token_id.low).into());
buf.append((token_id.high).into());
buf.append((*self.token_address).into());
buf.append(*self.token_chain_id);
poseidon_hash_span(buf.span())
}
}

fn compute_order_hash(self: @OrderV1) -> felt252 {
Expand Down
5 changes: 5 additions & 0 deletions contracts/ark_component/src/orderbook/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ pub mod orderbook_errors {
const ORDER_TOKEN_ID_IS_MISSING: felt252 = 'OB: token id is missing';
const ORDER_TOKEN_HASH_DOES_NOT_MATCH: felt252 = 'OB: token hash does not match';
const ORDER_NOT_AN_OFFER: felt252 = 'OB: order is not an offer';
const ORDER_NOT_AN_ERC20_ORDER: felt252 = 'OB: order not erc 20';
const ORDER_ROUTE_NOT_ERC20: felt252 = 'OB: order route not erc 20';
const ORDER_ROUTE_NOT_VALID: felt252 = 'OB: order not valid erc 20';
const ORDER_PRICE_NOT_MATCH: felt252 = 'OB: order price not match';
const ORDER_NOT_OPEN: felt252 = 'OB: order is not open';
const ORDER_OPEN: felt252 = 'OB: order is not open';
const ORDER_NOT_SUPPORTED: felt252 = 'OB: order not supported';
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';
Expand Down
Loading
Loading