From 8c48d8900c2f3ffac3b60575f5b970699fde8e17 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Wed, 2 Oct 2024 18:39:53 +0100 Subject: [PATCH 01/18] init types and routes for erc20toerc20 orders --- .../ark_common/src/protocol/order_types.cairo | 16 ++++++ .../ark_common/src/protocol/order_v1.cairo | 21 ++++++-- .../src/orderbook/interface.cairo | 1 + .../src/orderbook/orderbook.cairo | 50 +++++++++++++++++++ contracts/ark_orderbook/src/orderbook.cairo | 14 ++++++ contracts/ark_starknet/src/executor.cairo | 19 ++++++- 6 files changed, 116 insertions(+), 5 deletions(-) diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index 34331abc1..f503d48f9 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -11,6 +11,8 @@ enum OrderType { Auction, Offer, CollectionOffer, + Limit, + Market } impl OrderTypeIntoFelt252 of Into { @@ -20,6 +22,8 @@ impl OrderTypeIntoFelt252 of Into { OrderType::Auction => 'AUCTION', OrderType::Offer => 'OFFER', OrderType::CollectionOffer => 'COLLECTION_OFFER', + OrderType::Limit => 'LIMIT', + OrderType::Market => 'MARKET' } } } @@ -34,6 +38,10 @@ impl Felt252TryIntoOrderType of TryInto { Option::Some(OrderType::Offer) } else if self == 'COLLECTION_OFFER' { Option::Some(OrderType::CollectionOffer) + } else if self == 'LIMIT'{ + Option::Some(OrderType::Limit) + } else if self == 'MARKET'{ + Option::Some(OrderType::Market) } else { Option::None } @@ -262,6 +270,8 @@ enum RouteType { #[default] Erc20ToErc721, Erc721ToErc20, + Erc20ToErc20Buy, + Erc20ToErc20Sell } impl RouteIntoFelt252 of Into { @@ -269,6 +279,8 @@ impl RouteIntoFelt252 of Into { match self { RouteType::Erc20ToErc721 => 'ERC20TOERC721', RouteType::Erc721ToErc20 => 'ERC721TOERC20', + RouteType::Erc20ToErc20Buy => 'ERC20TOERC20BUY', + RouteType::Erc20ToErc20Sell => 'ERC20TOERC20SELL' } } } @@ -279,6 +291,10 @@ impl Felt252TryIntoRoute of TryInto { 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 { Option::None } diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 4fd202216..241ae5bc3 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -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. @@ -31,7 +30,7 @@ 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, @@ -122,6 +121,20 @@ impl OrderTraitOrderV1 of OrderTrait { && (*self.route) == RouteType::Erc20ToErc721 { return Result::Ok(OrderType::CollectionOffer); } + + // Limit Order + if(*self.quantity) > 0 + && (*self.start_amount) > 0 // price is set + && (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { + return Result::Ok(OrderType::Limit); + } + + // Market Order + if(*self.quantity) > 0 + && (*self.start_amount) == 0 // no price + && (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { + return Result::Ok(OrderType::Limit); + } } // Other order types are not supported. diff --git a/contracts/ark_component/src/orderbook/interface.cairo b/contracts/ark_component/src/orderbook/interface.cairo index b8d6fa501..6a53f8843 100644 --- a/contracts/ark_component/src/orderbook/interface.cairo +++ b/contracts/ark_component/src/orderbook/interface.cairo @@ -33,6 +33,7 @@ pub mod orderbook_errors { const ORDER_NOT_AN_OFFER: felt252 = 'OB: order is not an offer'; 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'; diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 6e265aee2..cb9c3dded 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -275,6 +275,12 @@ pub mod OrderbookComponent { OrderType::CollectionOffer => { self._create_collection_offer(order, order_type, order_hash); }, + OrderType::Limit => { + self._create_limit_order(order, order_type, order_hash); + }, + OrderType::Market => { + self._create_market_order(order, order_type, order_hash); + } }; HooksCreateOrder::after_create_order(ref self, order); @@ -352,6 +358,7 @@ pub mod OrderbookComponent { OrderType::Auction => self._fulfill_auction_order(fulfill_info, order), OrderType::Offer => self._fulfill_offer(fulfill_info, order), OrderType::CollectionOffer => self._fulfill_offer(fulfill_info, order), + _ => panic_with_felt252(orderbook_errors::ORDER_NOT_SUPPORTED) }; self @@ -804,6 +811,49 @@ pub mod OrderbookComponent { } ); } + + /// Creates a limit order + fn _create_limit_order( + ref self: ComponentState, + order: OrderV1, + order_type: OrderType, + order_hash: felt252 + ) { + // todo add matching logic + order_write(order_hash, order_type, order); + self + .emit( + OrderPlaced { + order_hash: order_hash, + order_version: order.get_version(), + order_type: order_type, + cancelled_order_hash: Option::None, + order: order, + } + ); + } + + /// Creates a market order + fn _create_market_order( + ref self: ComponentState, + order: OrderV1, + order_type: + OrderType, + order_hash: felt252 + ) { + // todo add matching logic + order_write(order_hash, order_type, order); + self + .emit( + OrderPlaced { + order_hash: order_hash, + order_version: order.get_version(), + order_type: order_type, + cancelled_order_hash: Option::None, + order: order, + } + ); + } } } pub impl OrderbookHooksCreateOrderEmptyImpl< diff --git a/contracts/ark_orderbook/src/orderbook.cairo b/contracts/ark_orderbook/src/orderbook.cairo index de68329e5..83153d25a 100644 --- a/contracts/ark_orderbook/src/orderbook.cairo +++ b/contracts/ark_orderbook/src/orderbook.cairo @@ -333,5 +333,19 @@ mod orderbook { ) { self.orderbook._create_collection_offer(order, order_type, order_hash) } + + /// Creates a market order + fn _create_limit_order( + ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 + ) { + self.orderbook._create_limit_order(order, order_type, order_hash) + } + + /// Creates a limit order + fn _create_market_order( + ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 + ) { + self.orderbook._create_market_order(order, order_type, order_hash) + } } } diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index 614047a0b..fb812ea59 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -324,6 +324,22 @@ mod executor { Option::None => panic!("Invalid token id"), } }, + RouteType::Erc20ToErc20Buy => { + assert!( + _check_erc20_amount( + order.currency_address, *(order.start_amount), order.offerer + ), + "Oferrer does not own enough ERC20 tokens to buy" + ) + }, + RouteType::Erc20ToErc20Sell => { + assert!( + _check_erc20_amount( + order.token_address, *(order.quantity), order.offerer + ), + "Oferrer does not own enough ERC20 tokens to sell" + ) + } } } @@ -374,7 +390,8 @@ mod executor { _verify_fulfill_collection_offer_order( self, order_info, fulfill_info, contract_address ); - } + }, + _ => panic!("Order not supported") } } From 74e1c2171534b774e10206ec1b0063473363147c Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 10:51:35 +0100 Subject: [PATCH 02/18] impl tracking for orders and price levels --- .../ark_common/src/protocol/order_types.cairo | 47 +++++ .../ark_common/src/protocol/order_v1.cairo | 2 +- .../src/orderbook/erc20_helper.cairo | 171 ++++++++++++++++++ .../src/orderbook/orderbook.cairo | 26 +-- contracts/ark_starknet/src/executor.cairo | 13 +- 5 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 contracts/ark_component/src/orderbook/erc20_helper.cairo diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index f503d48f9..bb68362dc 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -300,3 +300,50 @@ impl Felt252TryIntoRoute of TryInto { } } } + + +/// A trait to describe order capability. +trait PriceLevelTrait, +Drop> { + /// get order version. + fn get_version(self: @T) -> felt252; + + /// returns route type i.e Erc20Buy or Erc20Sell + fn get_type(self: @T) -> RouteType; + + /// returns contract address of price level + fn get_token(self: @T) -> ContractAddress; + + /// Returns the hash of the pricelevel's data. + /// Every field of the order that must be signed + /// must be considered in the computation of this hash. + fn compute_price_level_hash(self: @T) -> felt252; +} + + +#[derive(Serde, Drop, Copy)] +struct PriceLevel { + route: RouteType, + token_address: ContractAddress, + token_chain_id: felt252, + price: u256 +} + +impl PriceLevelTraitV1 of PriceLevelTrait { + fn get_token(self: @PriceLevel) -> ContractAddress { + self.token_address + } + + fn get_route_type(self: @PriceLevel) -> RouteType { + self.route + } + + fn get_token_chain_id(self: @PriceLevel) -> felt252 { + self.token_chain_id + } + + fn compute_price_level_hash(self: @PriceLevel) -> felt252 { + let mut buf = array![]; + self.serialize(ref buf); + poseidon_hash_span(buf.span()) + } +} diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 241ae5bc3..3c8d74872 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -157,4 +157,4 @@ impl OrderTraitOrderV1 of OrderTrait { self.serialize(ref buf); poseidon_hash_span(buf.span()) } -} +} \ No newline at end of file diff --git a/contracts/ark_component/src/orderbook/erc20_helper.cairo b/contracts/ark_component/src/orderbook/erc20_helper.cairo new file mode 100644 index 000000000..87d4e79be --- /dev/null +++ b/contracts/ark_component/src/orderbook/erc20_helper.cairo @@ -0,0 +1,171 @@ +use ark_common::protocol::order_types::{ + RouteType, PriceLevel, PriceLevelTraitV1 +}; +use starknet::storage_access::{ + StoreFelt252, StorageBaseAddress, storage_base_address_from_felt252, storage_base_address_const +}; +use starknet::contract_address_to_felt252; + +use super::super::interface::{orderbook_errors}; + +mod Consts { + const EMPTY: felt252 = 0; + const DELETED: felt252 = + 0x800000000000011000000000000000000000000000000000000000000000000; // -1 +} + +// adds a buy or sell price level market for token address +fn add_new_token_market(token_address: ContractAddress, price_level_hash: felt252) { + let address = contract_address_to_felt252(token_address); + add_to_storage(address, price_level_hash); +} + +// removes a buy or sell market for token adddress +fn remove_token_market(token_address: ContractAddress, price_level_hash: felt252) { + let address = contract_address_to_felt252(token_address); + remove_value_from_storage(address, price_level_hash); +} + +/// Removes all price levels from a token market. If none exist then nothing changes. +fn remove_all_price_levels(token_address: ContractAddress) -> Array { + let address = contract_address_to_felt252(token_address); + remove_all_values_from_storage(address) +} + +/// Get all price levels of a given token address. Empty array is returned if none exist. +fn get_all_price_levels(token_address: ContractAddress) -> Array { + let address = contract_address_to_felt252(token_address); + get_all_values_from_storage(address) +} + +/// Add new order hash to price level +fn add_order_to_price_level(price_level_hash: felt252, order_hash: felt252) { + add_to_storage(price_level_hash, order_hash); +} + +/// Removes order from a price level +fn remove_order_from_price_level(price_level_hash: felt252, order_hash: felt252) { + remove_value_from_storage(price_level_hash, order_hash); +} + +// Removes all orders from a price level. If none exist then nothing changes. +fn remove_all_orders_from_price_level(price_level_hash: felt252) -> Array { + remove_all_values_from_storage(price_level_hash) +} + +/// Get all orders from a price levels. Empty array is returned if none exist. +fn get_all_orders_from_price_level(price_level_hash: felt252) -> Array { + get_all_values_from_storage(price_level_hash) +} + + +fn add_to_storage(k: felt252, v: felt252){ + if v == Consts::EMPTY|| v == Consts::DELETED { + panic_with_felt252('CANT USE PRESET VALUE') + } + + let mut offset = 0; + loop { + let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); + if thash == Consts::EMPTY { + StoreFelt252::write_at_offset(0, k, offset, v).is_ok(); + break; + } + if (thash == v) { + panic_with_felt252('VALUE EXISTS'); + } + offset = offset + 1; + } +} + +fn remove_value_from_storage(k: felt252, v: felt252) { + if v == Consts::EMPTY|| v == Consts::DELETED { + panic_with_felt252('CANT USE PRESET VALUE') + } + + let mut offset = 0; + loop { + let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); + if thash == Consts::EMPTY { + break; + } + if (thash == v) { + StoreFelt252::write_at_offset(0, k, offset, Consts::DELETED).is_ok(); + break; + } + offset = offset + 1; + } +} + +fn remove_all_values_from_storage(k: felt252) -> Array { + let mut result = array![]; + let mut offset = 0; + loop { + let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); + if thash == Consts::EMPTY { + break; + } + if (thash != Consts::DELETED) { + result.append(thash); + StoreFelt252::write_at_offset(0, k, offset, Consts::DELETED).is_ok(); + } + offset = offset + 1; + }; + return result; +} + +fn get_all_values_from_storage(k: felt252) -> Array{ + let mut result = array![]; + let mut offset = 0; + loop { + let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); + if thash == Consts::EMPTY { + break; + } + if (thash != Consts::DELETED) { + result.append(thash); + } + offset = offset + 1; + }; + return result; +} + +/// Returns true if given value exists in key +fn check_if_exists(k: felt252, v: felt252) -> bool { + if v == Consts::EMPTY || v == Consts::DELETED { + panic_with_felt252('CANT USE PRESET VALUE') + } + + let mut offset = 0; + let mut found: bool = false; + loop { + let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); + if thash == Consts::EMPTY { + break; + } + if thash == v { + found = true; + break; + } + offset = offset + 1; + }; + return found; +} + +/// Returns true if key has any value +fn any(k: felt252) -> bool { + let mut offset = 0; + let mut found: bool = false; + loop { + let thash = StoreFelt252::read_at_offset(0, address, offset).unwrap(); + if thash == Consts::EMPTY { + break; + } + if thash != Consts::DELETED { + found = true; + break; + } + offset = offset + 1; + }; + return found; +} \ No newline at end of file diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index cb9c3dded..2d42cbf34 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -5,7 +5,7 @@ pub mod OrderbookComponent { }; use ark_common::protocol::order_types::{ OrderStatus, OrderTrait, OrderType, CancelInfo, FulfillInfo, ExecutionValidationInfo, - ExecutionInfo, RouteType + ExecutionInfo, RouteType, PriceLevel }; use ark_common::protocol::order_v1::OrderV1; use core::debug::PrintTrait; @@ -33,6 +33,8 @@ pub mod OrderbookComponent { auctions: Map, /// Mapping of auction offer order_hash to auction listing order_hash. auction_offers: Map, + /// Mapping of price level hash to price level data. + price_levels: Map } // ************************************************************************* @@ -832,28 +834,6 @@ pub mod OrderbookComponent { } ); } - - /// Creates a market order - fn _create_market_order( - ref self: ComponentState, - order: OrderV1, - order_type: - OrderType, - order_hash: felt252 - ) { - // todo add matching logic - order_write(order_hash, order_type, order); - self - .emit( - OrderPlaced { - order_hash: order_hash, - order_version: order.get_version(), - order_type: order_type, - cancelled_order_hash: Option::None, - order: order, - } - ); - } } } pub impl OrderbookHooksCreateOrderEmptyImpl< diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index fb812ea59..5ae3b714b 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -4,6 +4,11 @@ use ark_common::protocol::order_types::OrderType; use starknet::ContractAddress; +#[derive(Drop, Copy, Debug)] +struct OptionU256 { + is_some: felt252, // 1 if Some, 0 if None + value: u256, // Valid only if is_some == 1 +} #[derive(Drop, Copy, Debug)] struct OrderInfo { @@ -13,12 +18,13 @@ struct OrderInfo { // The token contract address. token_address: ContractAddress, // The token id. - // TODO: how to store Option ? - token_id: u256, + token_id: OptionU256, // in wei. --> 10 | 10 | 10 | start_amount: u256, - // + // address making the order offerer: ContractAddress, + // number of tokens + quantity: u256 // 0 for ERC721 } @@ -779,6 +785,7 @@ mod executor { token_id, start_amount: order.start_amount, offerer: order.offerer, + quantity: order.quantity } } } From c6270a5ffc844d3032fa8134e869838baa8c826b Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 14:10:22 +0100 Subject: [PATCH 03/18] refactor code --- .../ark_common/src/protocol/order_types.cairo | 65 +--- .../ark_common/src/protocol/order_v1.cairo | 51 +-- .../src/orderbook/erc20_helper.cairo | 171 ---------- .../src/orderbook/interface.cairo | 5 + .../src/orderbook/orderbook.cairo | 304 ++++++++++++++++-- contracts/ark_orderbook/src/orderbook.cairo | 9 +- contracts/ark_starknet/src/executor.cairo | 18 +- 7 files changed, 333 insertions(+), 290 deletions(-) delete mode 100644 contracts/ark_component/src/orderbook/erc20_helper.cairo diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index bb68362dc..24a1f3a6d 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -12,7 +12,6 @@ enum OrderType { Offer, CollectionOffer, Limit, - Market } impl OrderTypeIntoFelt252 of Into { @@ -22,8 +21,7 @@ impl OrderTypeIntoFelt252 of Into { OrderType::Auction => 'AUCTION', OrderType::Offer => 'OFFER', OrderType::CollectionOffer => 'COLLECTION_OFFER', - OrderType::Limit => 'LIMIT', - OrderType::Market => 'MARKET' + OrderType::Limit => 'LIMIT' } } } @@ -40,8 +38,6 @@ impl Felt252TryIntoOrderType of TryInto { Option::Some(OrderType::CollectionOffer) } else if self == 'LIMIT'{ Option::Some(OrderType::Limit) - } else if self == 'MARKET'{ - Option::Some(OrderType::Market) } else { Option::None } @@ -224,10 +220,12 @@ struct FulfillInfo { #[derive(Drop, Serde, Copy)] struct ExecutionInfo { order_hash: felt252, - nft_address: ContractAddress, - nft_from: ContractAddress, - nft_to: ContractAddress, - nft_token_id: u256, + route: RouteType, + token_address: ContractAddress, + token_from: ContractAddress, + token_to: ContractAddress, + token_quantity: u256, + token_id: u256, payment_from: ContractAddress, payment_to: ContractAddress, payment_amount: u256, @@ -299,51 +297,4 @@ impl Felt252TryIntoRoute of TryInto { Option::None } } -} - - -/// A trait to describe order capability. -trait PriceLevelTrait, +Drop> { - /// get order version. - fn get_version(self: @T) -> felt252; - - /// returns route type i.e Erc20Buy or Erc20Sell - fn get_type(self: @T) -> RouteType; - - /// returns contract address of price level - fn get_token(self: @T) -> ContractAddress; - - /// Returns the hash of the pricelevel's data. - /// Every field of the order that must be signed - /// must be considered in the computation of this hash. - fn compute_price_level_hash(self: @T) -> felt252; -} - - -#[derive(Serde, Drop, Copy)] -struct PriceLevel { - route: RouteType, - token_address: ContractAddress, - token_chain_id: felt252, - price: u256 -} - -impl PriceLevelTraitV1 of PriceLevelTrait { - fn get_token(self: @PriceLevel) -> ContractAddress { - self.token_address - } - - fn get_route_type(self: @PriceLevel) -> RouteType { - self.route - } - - fn get_token_chain_id(self: @PriceLevel) -> felt252 { - self.token_chain_id - } - - fn compute_price_level_hash(self: @PriceLevel) -> felt252 { - let mut buf = array![]; - self.serialize(ref buf); - poseidon_hash_span(buf.span()) - } -} +} \ No newline at end of file diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 3c8d74872..8e5c8cd5e 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -63,16 +63,19 @@ impl OrderTraitOrderV1 of OrderTrait { 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); - } - - // 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); + 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); + } } // TODO: define a real value here. 20 is an example and @@ -128,13 +131,6 @@ impl OrderTraitOrderV1 of OrderTrait { && (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { return Result::Ok(OrderType::Limit); } - - // Market Order - if(*self.quantity) > 0 - && (*self.start_amount) == 0 // no price - && (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { - return Result::Ok(OrderType::Limit); - } } // Other order types are not supported. @@ -142,14 +138,21 @@ impl OrderTraitOrderV1 of OrderTrait { } 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 = 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) { + // 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 = 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 { diff --git a/contracts/ark_component/src/orderbook/erc20_helper.cairo b/contracts/ark_component/src/orderbook/erc20_helper.cairo deleted file mode 100644 index 87d4e79be..000000000 --- a/contracts/ark_component/src/orderbook/erc20_helper.cairo +++ /dev/null @@ -1,171 +0,0 @@ -use ark_common::protocol::order_types::{ - RouteType, PriceLevel, PriceLevelTraitV1 -}; -use starknet::storage_access::{ - StoreFelt252, StorageBaseAddress, storage_base_address_from_felt252, storage_base_address_const -}; -use starknet::contract_address_to_felt252; - -use super::super::interface::{orderbook_errors}; - -mod Consts { - const EMPTY: felt252 = 0; - const DELETED: felt252 = - 0x800000000000011000000000000000000000000000000000000000000000000; // -1 -} - -// adds a buy or sell price level market for token address -fn add_new_token_market(token_address: ContractAddress, price_level_hash: felt252) { - let address = contract_address_to_felt252(token_address); - add_to_storage(address, price_level_hash); -} - -// removes a buy or sell market for token adddress -fn remove_token_market(token_address: ContractAddress, price_level_hash: felt252) { - let address = contract_address_to_felt252(token_address); - remove_value_from_storage(address, price_level_hash); -} - -/// Removes all price levels from a token market. If none exist then nothing changes. -fn remove_all_price_levels(token_address: ContractAddress) -> Array { - let address = contract_address_to_felt252(token_address); - remove_all_values_from_storage(address) -} - -/// Get all price levels of a given token address. Empty array is returned if none exist. -fn get_all_price_levels(token_address: ContractAddress) -> Array { - let address = contract_address_to_felt252(token_address); - get_all_values_from_storage(address) -} - -/// Add new order hash to price level -fn add_order_to_price_level(price_level_hash: felt252, order_hash: felt252) { - add_to_storage(price_level_hash, order_hash); -} - -/// Removes order from a price level -fn remove_order_from_price_level(price_level_hash: felt252, order_hash: felt252) { - remove_value_from_storage(price_level_hash, order_hash); -} - -// Removes all orders from a price level. If none exist then nothing changes. -fn remove_all_orders_from_price_level(price_level_hash: felt252) -> Array { - remove_all_values_from_storage(price_level_hash) -} - -/// Get all orders from a price levels. Empty array is returned if none exist. -fn get_all_orders_from_price_level(price_level_hash: felt252) -> Array { - get_all_values_from_storage(price_level_hash) -} - - -fn add_to_storage(k: felt252, v: felt252){ - if v == Consts::EMPTY|| v == Consts::DELETED { - panic_with_felt252('CANT USE PRESET VALUE') - } - - let mut offset = 0; - loop { - let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); - if thash == Consts::EMPTY { - StoreFelt252::write_at_offset(0, k, offset, v).is_ok(); - break; - } - if (thash == v) { - panic_with_felt252('VALUE EXISTS'); - } - offset = offset + 1; - } -} - -fn remove_value_from_storage(k: felt252, v: felt252) { - if v == Consts::EMPTY|| v == Consts::DELETED { - panic_with_felt252('CANT USE PRESET VALUE') - } - - let mut offset = 0; - loop { - let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); - if thash == Consts::EMPTY { - break; - } - if (thash == v) { - StoreFelt252::write_at_offset(0, k, offset, Consts::DELETED).is_ok(); - break; - } - offset = offset + 1; - } -} - -fn remove_all_values_from_storage(k: felt252) -> Array { - let mut result = array![]; - let mut offset = 0; - loop { - let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); - if thash == Consts::EMPTY { - break; - } - if (thash != Consts::DELETED) { - result.append(thash); - StoreFelt252::write_at_offset(0, k, offset, Consts::DELETED).is_ok(); - } - offset = offset + 1; - }; - return result; -} - -fn get_all_values_from_storage(k: felt252) -> Array{ - let mut result = array![]; - let mut offset = 0; - loop { - let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); - if thash == Consts::EMPTY { - break; - } - if (thash != Consts::DELETED) { - result.append(thash); - } - offset = offset + 1; - }; - return result; -} - -/// Returns true if given value exists in key -fn check_if_exists(k: felt252, v: felt252) -> bool { - if v == Consts::EMPTY || v == Consts::DELETED { - panic_with_felt252('CANT USE PRESET VALUE') - } - - let mut offset = 0; - let mut found: bool = false; - loop { - let thash = StoreFelt252::read_at_offset(0, k, offset).unwrap(); - if thash == Consts::EMPTY { - break; - } - if thash == v { - found = true; - break; - } - offset = offset + 1; - }; - return found; -} - -/// Returns true if key has any value -fn any(k: felt252) -> bool { - let mut offset = 0; - let mut found: bool = false; - loop { - let thash = StoreFelt252::read_at_offset(0, address, offset).unwrap(); - if thash == Consts::EMPTY { - break; - } - if thash != Consts::DELETED { - found = true; - break; - } - offset = offset + 1; - }; - return found; -} \ No newline at end of file diff --git a/contracts/ark_component/src/orderbook/interface.cairo b/contracts/ark_component/src/orderbook/interface.cairo index 6a53f8843..53116cca5 100644 --- a/contracts/ark_component/src/orderbook/interface.cairo +++ b/contracts/ark_component/src/orderbook/interface.cairo @@ -31,9 +31,14 @@ 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 is not an erc 20 order'; + const ORDER_ROUTE_NOT_ERC20: felt252 = 'OB: order route is not an erc 20 route'; + const ORDER_ROUTE_NOT_VALID: felt252 = 'OB: order route is not a valid erc 20 route'; + const ORDER_PRICE_NOT_MATCH: felt252 = 'OB: erc 20 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 ORDER_IS_FILLED: felt252 = 'OB: order is filled'; 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'; diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 2d42cbf34..b2a5f2ad3 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -17,6 +17,7 @@ pub mod OrderbookComponent { use core::zeroable::Zeroable; use starknet::ContractAddress; use starknet::storage::Map; + use starknet::contract_address_to_felt252; use super::super::interface::{IOrderbook, IOrderbookAction, orderbook_errors}; const EXTENSION_TIME_IN_SECONDS: u64 = 600; @@ -33,8 +34,8 @@ pub mod OrderbookComponent { auctions: Map, /// Mapping of auction offer order_hash to auction listing order_hash. auction_offers: Map, - /// Mapping of price level hash to price level data. - price_levels: Map + /// Mapping of erc20s orderhash to the order (price, quantity) + erc20_orders: Map } // ************************************************************************* @@ -279,9 +280,6 @@ pub mod OrderbookComponent { }, OrderType::Limit => { self._create_limit_order(order, order_type, order_hash); - }, - OrderType::Market => { - self._create_market_order(order, order_type, order_hash); } }; @@ -310,6 +308,12 @@ pub mod OrderbookComponent { block_ts <= auction_end_date, orderbook_errors::ORDER_AUCTION_IS_EXPIRED ); self.auctions.write(auction_token_hash, (0, 0, 0)); + }else if order_type = OrderType::Limit { + let (_, quantity) = self.erc20_orders.read(order_hash); + assert( + quantity > 0, orderbook_errors::ORDER_IS_FILLED + ); + self.erc20_orders.write(order_hash, (0, 0, 0)) } else { assert(block_ts < order.end_date, orderbook_errors::ORDER_IS_EXPIRED); if order_type == OrderType::Listing { @@ -360,7 +364,7 @@ pub mod OrderbookComponent { OrderType::Auction => self._fulfill_auction_order(fulfill_info, order), OrderType::Offer => self._fulfill_offer(fulfill_info, order), OrderType::CollectionOffer => self._fulfill_offer(fulfill_info, order), - _ => panic_with_felt252(orderbook_errors::ORDER_NOT_SUPPORTED) + OrderType::Limit => self._fulfill_limit_order(fulfill_info, order), }; self @@ -459,10 +463,11 @@ pub mod OrderbookComponent { if order.token_id.is_some() { let execute_info = ExecutionInfo { order_hash: order.compute_order_hash(), - nft_address: order.token_address, - nft_from: order.offerer, - nft_to: related_order.offerer, - nft_token_id: order.token_id.unwrap(), + token_address: order.token_address, + token_from: order.offerer, + token_to: related_order.offerer, + token_id: order.token_id.unwrap(), + token_quantity: 1, payment_from: related_order.offerer, payment_to: fulfill_info.fulfiller, payment_amount: related_order.start_amount, @@ -505,10 +510,11 @@ pub mod OrderbookComponent { let execute_info = ExecutionInfo { order_hash: order.compute_order_hash(), - nft_address: order.token_address, - nft_from: fulfill_info.fulfiller, - nft_to: order.offerer, - nft_token_id: fulfill_info.token_id.unwrap(), + token_address: order.token_address, + token_from: fulfill_info.fulfiller, + token_to: order.offerer, + token_id: fulfill_info.token_id.unwrap(), + token_quantity: 1, payment_from: order.offerer, payment_to: fulfill_info.fulfiller, payment_amount: order.start_amount, @@ -538,10 +544,11 @@ pub mod OrderbookComponent { if order.token_id.is_some() { let execute_info = ExecutionInfo { order_hash: order.compute_order_hash(), - nft_address: order.token_address, - nft_from: order.offerer, - nft_to: fulfill_info.fulfiller, - nft_token_id: order.token_id.unwrap(), + token_address: order.token_address, + token_from: order.offerer, + token_to: fulfill_info.fulfiller, + token_id: order.token_id.unwrap(), + token_quantity: 1, payment_from: fulfill_info.fulfiller, payment_to: order.offerer, payment_amount: order.start_amount, @@ -821,18 +828,273 @@ pub mod OrderbookComponent { order_type: OrderType, order_hash: felt252 ) { - // todo add matching logic + let token_hash = order.compute_token_hash(); + // revert if order is fulfilled or Open + let current_order_hash = self.erc20_orders.read(token_hash); + if (current_order_hash.is_non_zero()) { + assert( + order_status_read(current_order_hash) != Option::Some(OrderStatus::Fulfilled), + orderbook_errors::ORDER_FULFILLED + ); + } + let current_order: Option = order_read(current_order_hash); + let cancelled_order_hash = self._process_previous_order(token_hash, order.offerer); order_write(order_hash, order_type, order); + self.erc20_orders.write(order_hash, (order.start_amount, order.quantity)); self .emit( OrderPlaced { order_hash: order_hash, order_version: order.get_version(), order_type: order_type, - cancelled_order_hash: Option::None, - order: order, + version: ORDER_PLACED_EVENT_VERSION, + cancelled_order_hash, + order: order } ); + cancelled_order_hash + } + + /// Fulfill limit order + fn _fulfill_limit_order( + ref self: ComponentState, + fulfill_info: FulfillInfo, + order: OrderV1 + ) { + assert(order.offerer != fulfill_info.fulfiller, orderbook_errors::ORDER_SAME_OFFERER); + let order_hash = order.compute_order_hash(); + + assert( + order_hash == fulfill_info.order_hash, + orderbook_errors::ORDER_HASH_DOES_NOT_MATCH + ); + + let related_order_hash = fulfill_info + .related_order_hash + .expect(orderbook_errors::ORDER_MISSING_RELATED_ORDER); + + match order_type_read(related_order_hash) { + Option::Some(order_type) => { + assert( + order_type == OrderType::Limit, + orderbook_errors::ORDER_NOT_AN_ERC20_ORDER + ); + }, + Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), + } + + match order_status_read(related_order_hash) { + Option::Some(s) => { + assert(s == OrderStatus::Open, orderbook_errors::ORDER_NOT_OPEN); + s + }, + Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), + }; + + let related_order = match order_read::(related_order_hash) { + Option::Some(o) => o, + Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), + }; + + let related_order_token_hash = related_order.compute_token_hash(); + + // check that they are both the same token + assert( + related_order_token_hash == order.compute_token_hash(), + orderbook_errors::ORDER_TOKEN_HASH_DOES_NOT_MATCH + ); + + // check that the price is the same + assert( + related_order.start_amount == order.start_amount, + orderbook_errors::ORDER_PRICE_NOT_MATCH + ) + + let (_, related_order_quantity) = self.erc20_orders.read(related_order_hash); + let (_, order_quantity) = self.erc20_orders.read(order_hash); + + match order.route{ + // fulfilling a sell order with a buy order (related-order) + RouteType::Erc20ToErc20Sell => { + assert( + related_order.route == Erc20ToErc20Buy, + orderbook_errors::ORDER_ROUTE_NOT_VALID + ); + if order_quantity > related_order_quantity { + // reduce sell quantity order and execute buy order + self + .erc20_orders + .write( + order_hash, + ( + order.start_amount, + order_quantity - related_order_quantity + ) + ); + + // set buy order as fufilled + order_status_write(related_order_hash, OrderStatus::Fulfilled); + + let execute_info = ExecutionInfo { + order_hash: related_order_hash, + token_address: order.token_address, + token_from: order.offerer, + token_to: related_order.offerer, + token_id: 0, + token_quantity: related_order_quantity, + payment_from: related_order.offerer, + payment_to: fulfill_info.fulfiller, + payment_amount: related_order.start_amount * related_order_quantity, + payment_currency_address: related_order.currency_address, + payment_currency_chain_id: related_order.currency_chain_id, + listing_broker_address: related_order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + }else if related_order_quantity > order_quantity { + // reduce buy quantity, and execute sell quantity + self + .erc20_orders + .write( + related_order_hash, + ( + order.start_amount, + related_order_quantity - order_quantity + ) + ); + // set sell order as fulfilled + order_status_write(order_hash, OrderStatus::Fulfilled); + + let execute_info = ExecutionInfo { + order_hash: order_hash, + token_address: order.token_address, + token_from: order.offerer, + token_to: related_order.offerer, + token_id: 0, + token_quantity: order_quantity, + payment_from: related_order.offerer, + payment_to: fulfill_info.fulfiller, + payment_amount: related_order.start_amount * order.quantity, + payment_currency_address: order.currency_address, + payment_currency_chain_id: order.currency_chain_id, + listing_broker_address: order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + }else{ + // execute both orders + order_status_write(order_hash, OrderStatus::Fulfilled); + order_status_write(related_order_hash, OrderStatus::Fulfilled); + + // passing any of them as the order hash will fulfill both orders, + // so just one executioninfo will be sent. + let execute_info = ExecutionInfo { + order_hash: order_hash, + token_address: order.token_address, + token_from: order.offerer, + token_to: related_order.offerer, + token_id: 0, + token_quantity: order_quantity, + payment_from: related_order.offerer, + payment_to: fulfill_info.fulfiller, + payment_amount: related_order.start_amount * order.quantity, + payment_currency_address: order.currency_address, + payment_currency_chain_id: order.currency_chain_id, + listing_broker_address: order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + } + }, + // fulfilling a buy order with a sell order (related-order) + RouteType::Erc20ToErc20Buy => { + assert( + related_order.route == Erc20ToErc20Sell, + orderbook_errors::ORDER_ROUTE_NOT_VALID + ); + + if order_quantity > related_order_quantity { + // reduce buy quantity order and execute sell order + self + .erc20_orders + .write( + order_hash, + ( + order.start_amount, + order_quantity - related_order_quantity + ) + ); + + // set sell order as fufilled + order_status_write(related_order_hash, OrderStatus::Fulfilled); + + let execute_info = ExecutionInfo { + order_hash: related_order_hash, + address: order.token_address, + token_from: related_order.offerer, + token_to: order.offerer, + token_id: 0, + token_quantity: related_order_quantity, + payment_from: order.offerer, + payment_to: related_order.offerer, + payment_amount: related_order.start_amount * related_order_quantity, + payment_currency_address: related_order.currency_address, + payment_currency_chain_id: related_order.currency_chain_id, + listing_broker_address: related_order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + }else if related_order_quantity > order_quantity { + // reduce sell quantity, and execute buy order + self + .erc20_orders + .write( + related_order_hash, + ( + order.start_amount, + related_order_quantity - order_quantity + ) + ); + // set buy order as fulfilled + order_status_write(order_hash, OrderStatus::Fulfilled); + + let execute_info = ExecutionInfo { + order_hash: order_hash, + token_address: order.token_address, + token_from: related_order.offerer, + token_to: order.offerer, + token_id: 0, + token_quantity: order_quantity, + payment_from: order.offerer, + payment_to: related_order.offerer, + payment_amount: related_order.start_amount * order.quantity, + payment_currency_address: order.currency_address, + payment_currency_chain_id: order.currency_chain_id, + listing_broker_address: order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + }else{ + // execute both orders + order_status_write(order_hash, OrderStatus::Fulfilled); + order_status_write(related_order_hash, OrderStatus::Fulfilled); + + // passing any of them as the order hash will fulfill both orders, + // so just one executioninfo will be sent. + let execute_info = ExecutionInfo { + order_hash: order_hash, + token_address: order.token_address, + token_from: related_order.offerer, + token_to: order.offerer, + token_id: 0, + token_quantity: order_quantity, + payment_from: order.offerer, + payment_to: related_order.offerer, + payment_amount: related_order.start_amount * order.quantity, + payment_currency_address: related_order.currency_address, + payment_currency_chain_id: related_order.currency_chain_id, + listing_broker_address: order.broker_id, + fulfill_broker_address: fulfill_info.fulfill_broker_address + }; + } + }, + _ => orderbook_errors::ORDER_ROUTE_NOT_ERC20 + } } } } diff --git a/contracts/ark_orderbook/src/orderbook.cairo b/contracts/ark_orderbook/src/orderbook.cairo index 83153d25a..1a937c0f4 100644 --- a/contracts/ark_orderbook/src/orderbook.cairo +++ b/contracts/ark_orderbook/src/orderbook.cairo @@ -334,18 +334,11 @@ mod orderbook { self.orderbook._create_collection_offer(order, order_type, order_hash) } - /// Creates a market order + /// Creates a limit erc20 order fn _create_limit_order( ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 ) { self.orderbook._create_limit_order(order, order_type, order_hash) } - - /// Creates a limit order - fn _create_market_order( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 - ) { - self.orderbook._create_market_order(order, order_type, order_hash) - } } } diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index 5ae3b714b..0389f8103 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -580,17 +580,17 @@ mod executor { let (creator_address, creator_fees_amount) = _compute_creator_fees_amount( @self, - @execution_info.nft_address, + @execution_info.token_address, execution_info.payment_amount, - execution_info.nft_token_id + execution_info.token_id ); let (fulfill_broker_fees_amount, listing_broker_fees_amount, ark_fees_amount, _) = _compute_fees_amount( @self, execution_info.fulfill_broker_address, execution_info.listing_broker_address, - execution_info.nft_address, - execution_info.nft_token_id, + execution_info.token_address, + execution_info.token_id, execution_info.payment_amount ); assert!( @@ -633,7 +633,7 @@ mod executor { self .emit( CollectionFallbackFees { - collection: execution_info.nft_address, + collection: execution_info.token_address, amount: creator_fees_amount, currency_contract: currency_contract.contract_address, receiver: default_receiver_creator, @@ -658,10 +658,10 @@ mod executor { ); } - let nft_contract = IERC721Dispatcher { contract_address: execution_info.nft_address }; + let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; nft_contract .transfer_from( - execution_info.nft_from, execution_info.nft_to, execution_info.nft_token_id + execution_info.token_from, execution_info.token_to, execution_info.token_id ); let tx_info = starknet::get_tx_info().unbox(); @@ -672,8 +672,8 @@ mod executor { order_hash: execution_info.order_hash, transaction_hash, starknet_block_timestamp: block_timestamp, - from: execution_info.nft_from, - to: execution_info.nft_to, + from: execution_info.token_from, + to: execution_info.token_to, }; self.orderbook.validate_order_execution(vinfo); From 542d82c6e965f20b49566103e2d285e982ff7025 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 14:27:38 +0100 Subject: [PATCH 04/18] clean up --- contracts/ark_orderbook/src/orderbook.cairo | 344 -------------------- 1 file changed, 344 deletions(-) delete mode 100644 contracts/ark_orderbook/src/orderbook.cairo diff --git a/contracts/ark_orderbook/src/orderbook.cairo b/contracts/ark_orderbook/src/orderbook.cairo deleted file mode 100644 index 1a937c0f4..000000000 --- a/contracts/ark_orderbook/src/orderbook.cairo +++ /dev/null @@ -1,344 +0,0 @@ -use ark_common::crypto::signer::{SignInfo, Signer, SignerValidator}; -//! # 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 (creation, cancellation, fulfillment), and order queries. - -use ark_common::protocol::order_types::{FulfillInfo, OrderType, CancelInfo, OrderStatus}; -use ark_common::protocol::order_v1::OrderV1; -use starknet::ContractAddress; - -#[starknet::interface] -trait OrderbookAdmin { - /// Upgrades the contract to a new version. - /// - /// # Arguments - /// * `class_hash` - The class hash of the new contract version. - fn upgrade(ref self: T, class_hash: starknet::ClassHash); - - fn update_starknet_executor_address(ref self: T, value: starknet::ContractAddress); -} - - -/// StarkNet smart contract module for an order book. -#[starknet::contract] -mod orderbook { - use ark_common::crypto::hash::{serialized_hash}; - use ark_common::crypto::signer::{SignInfo, Signer, SignerTrait, SignerValidator}; - use ark_common::crypto::typed_data::{OrderSign, TypedDataTrait}; - use ark_common::protocol::order_database::{ - order_read, order_status_read, order_write, order_status_write, order_type_read - }; - use ark_common::protocol::order_types::{ - OrderStatus, OrderTrait, OrderType, CancelInfo, FulfillInfo, ExecutionValidationInfo, - ExecutionInfo, RouteType - }; - use ark_common::protocol::order_v1::OrderV1; - use ark_component::orderbook::OrderbookComponent; - use ark_component::orderbook::{ - OrderbookHooksCreateOrderEmptyImpl, OrderbookHooksCancelOrderEmptyImpl, - OrderbookHooksFulfillOrderEmptyImpl, OrderbookHooksValidateOrderExecutionEmptyImpl, - }; - use core::debug::PrintTrait; - use core::option::OptionTrait; - use core::result::ResultTrait; - use core::traits::Into; - use core::traits::TryInto; - use core::zeroable::Zeroable; - - use starknet::ContractAddress; - use starknet::storage::Map; - use super::OrderbookAdmin; - const EXTENSION_TIME_IN_SECONDS: u64 = 600; - const AUCTION_ACCEPTING_TIME_SECS: u64 = 172800; - - component!(path: OrderbookComponent, storage: orderbook, event: OrderbookEvent); - - /// Storage struct for the Orderbook contract. - #[storage] - struct Storage { - /// Order database. - /// Mapping of token hashes to order data. - /// Order database [token_hash, order_data] - /// see ark_orderbook:order::database for more details. - chain_id: felt252, - /// Administrator address of the order book. - admin: ContractAddress, - /// The address of the StarkNet executor contract. - starknet_executor_address: ContractAddress, - #[substorage(v0)] - orderbook: OrderbookComponent::Storage, - } - - // ************************************************************************* - // EVENTS - // ************************************************************************* - - /// Events emitted by the Orderbook contract. - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OrderbookEvent: OrderbookComponent::Event, - Upgraded: Upgraded, - } - - #[derive(Drop, starknet::Event)] - struct Upgraded { - class_hash: starknet::ClassHash, - } - - #[abi(embed_v0)] - impl OrderbookImpl = OrderbookComponent::OrderbookImpl; - impl OrderbookActionImpl = OrderbookComponent::OrderbookActionImpl; - impl OrderbookInternalImpl = OrderbookComponent::InternalImpl; - - // ************************************************************************* - // CONSTRUCTOR - // ************************************************************************* - #[constructor] - fn constructor(ref self: ContractState, admin: ContractAddress, chain_id: felt252) { - self.admin.write(admin); - self.chain_id.write(chain_id); - } - - // ************************************************************************* - // L1 HANDLERS - // Only the sequencer can call this function with L1HandlerTransaction. - // ************************************************************************* - - /// L1 handler for placing an order. - /// # TODO - /// * Verify it comes from Arkchain executor contract. - /// * Check data + cancel / execute the order. - #[l1_handler] - fn validate_order_execution( - ref self: ContractState, _from_address: felt252, info: ExecutionValidationInfo - ) { - // Solis already checks that ALL the messages are coming from the executor contract. - // TODO: anyway, it can be useful to have an extra check here. - self.orderbook.validate_order_execution(info); - } - - /// Update status : only from solis. - // #[l1_handler] - // fn rollback_status_order( - // ref self: ContractState, _from_address: felt252, order_hash: felt252, reason: felt252 - // ) { - // order_status_write(order_hash, OrderStatus::Open); - // self.emit(RollbackStatus { order_hash, reason: reason.into() }); - // } - - #[l1_handler] - fn create_order_from_l2(ref self: ContractState, _from_address: felt252, order: OrderV1) { - self.orderbook.create_order(order); - } - - #[l1_handler] - fn cancel_order_from_l2( - ref self: ContractState, _from_address: felt252, cancelInfo: CancelInfo - ) { - self.orderbook.cancel_order(cancelInfo); - } - - #[l1_handler] - fn fulfill_order_from_l2( - ref self: ContractState, _from_address: felt252, fulfillInfo: FulfillInfo - ) { - let _ = self.orderbook.fulfill_order(fulfillInfo); - } - - #[abi(embed_v0)] - impl OrderbookAdminImpl of OrderbookAdmin { - fn upgrade(ref self: ContractState, class_hash: starknet::ClassHash) { - assert( - starknet::get_caller_address() == self.admin.read(), 'Unauthorized replace class' - ); - - match starknet::replace_class_syscall(class_hash) { - Result::Ok(_) => self.emit(Upgraded { class_hash }), - Result::Err(revert_reason) => panic(revert_reason), - }; - } - - fn update_starknet_executor_address(ref self: ContractState, value: ContractAddress) { - assert(starknet::get_caller_address() == self.admin.read(), 'Unauthorized update'); - self.starknet_executor_address.write(value); - } - } - - // ************************************************************************* - // INTERNAL FUNCTIONS FOR TESTING PURPOSE - // ************************************************************************* - #[generate_trait] - impl InternalFunctions of InternalFunctionsTrait { - fn _cancel_order(ref self: ContractState, cancel_info: CancelInfo) { - self.orderbook.cancel_order(cancel_info); - } - - /// Submits and places an order to the orderbook if the order is valid. - fn _create_order(ref self: ContractState, order: OrderV1) { - self.orderbook.create_order(order); - } - - fn _fulfill_order(ref self: ContractState, fulfill_info: FulfillInfo) { - let _ = self.orderbook.fulfill_order(fulfill_info); - } - - /// Fulfill auction order - /// - /// # Arguments - /// * `fulfill_info` - The execution info of the order. - /// * `order_type` - The type of the order. - /// - fn _fulfill_auction_order( - ref self: ContractState, fulfill_info: FulfillInfo, order: OrderV1 - ) { - let (execute_info, _) = self.orderbook._fulfill_auction_order(fulfill_info, order); - match execute_info { - Option::Some(execute_info) => { - let execute_order_selector = selector!("execute_order"); - let starknet_executor_address: ContractAddress = self - .starknet_executor_address - .read(); - - let mut buf: Array = array![ - starknet_executor_address.into(), execute_order_selector - ]; - execute_info.serialize(ref buf); - starknet::send_message_to_l1_syscall('EXE', buf.span()).unwrap(); - }, - Option::None => () - } - } - - /// Fulfill offer order - /// - /// # Arguments - /// * `fulfill_info` - The execution info of the order. - /// * `order` - The order. - /// - fn _fulfill_offer(ref self: ContractState, fulfill_info: FulfillInfo, order: OrderV1) { - let (execute_info, _) = self.orderbook._fulfill_offer(fulfill_info, order); - match execute_info { - Option::Some(execute_info) => { - let execute_order_selector = selector!("execute_order"); - let starknet_executor_address: ContractAddress = self - .starknet_executor_address - .read(); - - let mut buf: Array = array![ - starknet_executor_address.into(), execute_order_selector - ]; - execute_info.serialize(ref buf); - starknet::send_message_to_l1_syscall('EXE', buf.span()).unwrap(); - }, - Option::None => () - } - } - - /// Fulfill listing order - /// - /// # Arguments - /// * `fulfill_info` - The execution info of the order. - /// * `order_type` - The type of the order. - /// - fn _fulfill_listing_order( - ref self: ContractState, fulfill_info: FulfillInfo, order: OrderV1 - ) { - let (execute_info, _) = self.orderbook._fulfill_listing_order(fulfill_info, order); - match execute_info { - Option::Some(execute_info) => { - let execute_order_selector = selector!("execute_order"); - let starknet_executor_address: ContractAddress = self - .starknet_executor_address - .read(); - - let mut buf: Array = array![ - starknet_executor_address.into(), execute_order_selector - ]; - - execute_info.serialize(ref buf); - starknet::send_message_to_l1_syscall('EXE', buf.span()).unwrap(); - }, - Option::None => {} - } - } - - /// Get order hash from token hash - /// - /// # Arguments - /// * `token_hash` - The token hash of the order. - /// - fn _get_order_hash_from_token_hash(self: @ContractState, token_hash: felt252) -> felt252 { - self.orderbook._get_order_hash_from_token_hash(token_hash) - } - - /// get previous order - /// - /// # Arguments - /// * `token_hash` - The token hash of the order. - /// - /// # Return option of (order hash: felt252, is_order_expired: bool, order: OrderV1) - /// * order_hash - /// * is_order_expired - /// * order - fn _get_previous_order( - self: @ContractState, token_hash: felt252 - ) -> Option<(felt252, bool, OrderV1)> { - self.orderbook._get_previous_order(token_hash) - } - - /// Process previous order - /// - /// # Arguments - /// * `token_hash` - The token hash of the order. - /// - fn _process_previous_order( - ref self: ContractState, token_hash: felt252, offerer: ContractAddress - ) -> Option { - self.orderbook._process_previous_order(token_hash, offerer) - } - - /// Creates a listing order. - fn _create_listing_order( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252, - ) -> Option { - self.orderbook._create_listing_order(order, order_type, order_hash) - } - - /// Creates an auction order. - fn _create_auction( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 - ) { - self.orderbook._create_auction(order, order_type, order_hash) - } - - fn _manage_auction_offer(ref self: ContractState, order: OrderV1, order_hash: felt252) { - self.orderbook._manage_auction_offer(order, order_hash) - } - - /// Creates an offer order. - fn _create_offer( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 - ) { - self.orderbook._create_offer(order, order_type, order_hash) - } - - /// Creates a collection offer order. - fn _create_collection_offer( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 - ) { - self.orderbook._create_collection_offer(order, order_type, order_hash) - } - - /// Creates a limit erc20 order - fn _create_limit_order( - ref self: ContractState, order: OrderV1, order_type: OrderType, order_hash: felt252 - ) { - self.orderbook._create_limit_order(order, order_type, order_hash) - } - } -} From 1533fe6b63657f96be07d74ac3852dbf1a0964c3 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 14:58:31 +0100 Subject: [PATCH 05/18] clean up --- .../src/orderbook/orderbook.cairo | 182 +++++++++--------- 1 file changed, 86 insertions(+), 96 deletions(-) diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index b2a5f2ad3..43a8da3bc 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -855,6 +855,31 @@ pub mod OrderbookComponent { cancelled_order_hash } + fn _create_execution_info( + order_hash: felt252, + buy_order: OrderV1, + sell_order: OrderV1, + fulfill_info: FulfillInfo, + token_quantity: u64, + listing_broker_address: ContractAddress, + ) -> ExecutionInfo { + ExecutionInfo { + order_hash, + token_address: buy_order.token_address, + token_from: sell_order.offerer, + token_to: buy_order.offerer, + token_id: 0, + token_quantity, + payment_from: buy_order.offerer, + payment_to: sell_order.offerer, + payment_amount: buy_order.start_amount * token_quantity, + payment_currency_address: buy_order.currency_address, + payment_currency_chain_id: buy_order.currency_chain_id, + listing_broker_address: listing_broker_address, + fulfill_broker_address: fulfill_info.fulfill_broker_address, + } + } + /// Fulfill limit order fn _fulfill_limit_order( ref self: ComponentState, @@ -931,25 +956,18 @@ pub mod OrderbookComponent { order_quantity - related_order_quantity ) ); - // set buy order as fufilled order_status_write(related_order_hash, OrderStatus::Fulfilled); - - let execute_info = ExecutionInfo { - order_hash: related_order_hash, - token_address: order.token_address, - token_from: order.offerer, - token_to: related_order.offerer, - token_id: 0, - token_quantity: related_order_quantity, - payment_from: related_order.offerer, - payment_to: fulfill_info.fulfiller, - payment_amount: related_order.start_amount * related_order_quantity, - payment_currency_address: related_order.currency_address, - payment_currency_chain_id: related_order.currency_chain_id, - listing_broker_address: related_order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + // set execute info + let execute_info = self._create_execution_info( + related_order_hash, + related_order, + order, + fulfill_info, + related_order_quantity, + related_order_hash.broker_id + ); + (Option::Some(execute_info), Option::Some(related_order_hash)) }else if related_order_quantity > order_quantity { // reduce buy quantity, and execute sell quantity self @@ -963,22 +981,16 @@ pub mod OrderbookComponent { ); // set sell order as fulfilled order_status_write(order_hash, OrderStatus::Fulfilled); - - let execute_info = ExecutionInfo { - order_hash: order_hash, - token_address: order.token_address, - token_from: order.offerer, - token_to: related_order.offerer, - token_id: 0, - token_quantity: order_quantity, - payment_from: related_order.offerer, - payment_to: fulfill_info.fulfiller, - payment_amount: related_order.start_amount * order.quantity, - payment_currency_address: order.currency_address, - payment_currency_chain_id: order.currency_chain_id, - listing_broker_address: order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + // generate execution info + let execute_info = self._create_execution_info( + order_hash, + related_order, + order, + fulfill_info, + order_quantity, + order.broker_id + ); + (Option::Some(execute_info), Option::Some(related_order_hash)) }else{ // execute both orders order_status_write(order_hash, OrderStatus::Fulfilled); @@ -986,21 +998,16 @@ pub mod OrderbookComponent { // passing any of them as the order hash will fulfill both orders, // so just one executioninfo will be sent. - let execute_info = ExecutionInfo { - order_hash: order_hash, - token_address: order.token_address, - token_from: order.offerer, - token_to: related_order.offerer, - token_id: 0, - token_quantity: order_quantity, - payment_from: related_order.offerer, - payment_to: fulfill_info.fulfiller, - payment_amount: related_order.start_amount * order.quantity, - payment_currency_address: order.currency_address, - payment_currency_chain_id: order.currency_chain_id, - listing_broker_address: order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + let execute_info = self._create_execution_info( + order_hash, + related_order, + order, + fulfill_info, + order_quantity, + order.broker_id + ); + // return + (Option::Some(execute_info), Option::Some(related_order_hash)) } }, // fulfilling a buy order with a sell order (related-order) @@ -1021,25 +1028,18 @@ pub mod OrderbookComponent { order_quantity - related_order_quantity ) ); - // set sell order as fufilled order_status_write(related_order_hash, OrderStatus::Fulfilled); - - let execute_info = ExecutionInfo { - order_hash: related_order_hash, - address: order.token_address, - token_from: related_order.offerer, - token_to: order.offerer, - token_id: 0, - token_quantity: related_order_quantity, - payment_from: order.offerer, - payment_to: related_order.offerer, - payment_amount: related_order.start_amount * related_order_quantity, - payment_currency_address: related_order.currency_address, - payment_currency_chain_id: related_order.currency_chain_id, - listing_broker_address: related_order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + let execute_info = self._create_execution_info( + related_order_hash, + order, + related_order, + fulfill_info, + related_order_quantity, + related_order.broker_id + ); + // return + (Option::Some(execute_info), Option::Some(related_order_hash)) }else if related_order_quantity > order_quantity { // reduce sell quantity, and execute buy order self @@ -1054,43 +1054,33 @@ pub mod OrderbookComponent { // set buy order as fulfilled order_status_write(order_hash, OrderStatus::Fulfilled); - let execute_info = ExecutionInfo { - order_hash: order_hash, - token_address: order.token_address, - token_from: related_order.offerer, - token_to: order.offerer, - token_id: 0, - token_quantity: order_quantity, - payment_from: order.offerer, - payment_to: related_order.offerer, - payment_amount: related_order.start_amount * order.quantity, - payment_currency_address: order.currency_address, - payment_currency_chain_id: order.currency_chain_id, - listing_broker_address: order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + let execute_info = self._create_execution_info( + order_hash, + order, + related_order, + fulfill_info, + order_quantity, + order.broker_id + ); + // return + (Option::Some(execute_info), Option::Some(related_order_hash)) }else{ // execute both orders order_status_write(order_hash, OrderStatus::Fulfilled); order_status_write(related_order_hash, OrderStatus::Fulfilled); // passing any of them as the order hash will fulfill both orders, - // so just one executioninfo will be sent. - let execute_info = ExecutionInfo { - order_hash: order_hash, - token_address: order.token_address, - token_from: related_order.offerer, - token_to: order.offerer, - token_id: 0, - token_quantity: order_quantity, - payment_from: order.offerer, - payment_to: related_order.offerer, - payment_amount: related_order.start_amount * order.quantity, - payment_currency_address: related_order.currency_address, - payment_currency_chain_id: related_order.currency_chain_id, - listing_broker_address: order.broker_id, - fulfill_broker_address: fulfill_info.fulfill_broker_address - }; + // so just one executioninfo will be sent + let execute_info = self._create_execution_info( + order_hash, + order, + related_order, + fulfill_info, + order_quantity, + order.broker_id + ); + + (Option::Some(execute_info), Option::Some(related_order_hash)) } }, _ => orderbook_errors::ORDER_ROUTE_NOT_ERC20 From 36ce0b49690099b0b7e022ee4a0fccc4f64a738f Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 16:29:47 +0100 Subject: [PATCH 06/18] finalize erc20 order supports --- .../ark_common/src/protocol/order_types.cairo | 28 +++- .../ark_common/src/protocol/order_v1.cairo | 2 +- .../src/orderbook/orderbook.cairo | 57 ++++--- contracts/ark_starknet/src/executor.cairo | 157 ++++++++++++++---- turbo.json | 101 ----------- 5 files changed, 189 insertions(+), 156 deletions(-) delete mode 100644 turbo.json diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index 24a1f3a6d..6435774c7 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -225,7 +225,7 @@ struct ExecutionInfo { token_from: ContractAddress, token_to: ContractAddress, token_quantity: u256, - token_id: u256, + token_id: OptionU256, payment_from: ContractAddress, payment_to: ContractAddress, payment_amount: u256, @@ -297,4 +297,28 @@ impl Felt252TryIntoRoute of TryInto { Option::None } } -} \ No newline at end of file +} + +#[derive(Drop, Copy)] +struct OptionU256 { + is_some: felt252, + value: u256, +} + +trait OptionU256Trait, +Drop> { + fn get_some(self: @T, value: u256) -> (felt252, u256); + fn is_some(self: @T) -> bool; +} + +impl OptionU256Impl of OptionU256Trait { + 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 + } + } +} diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 8e5c8cd5e..749a6402c 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -37,7 +37,7 @@ struct OrderV1 { // 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. // in wei. --> 0 | 10 | 20 | end_amount: u256, // Start validity date of the offer, seconds since unix epoch. diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 43a8da3bc..d7383740d 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -5,7 +5,7 @@ pub mod OrderbookComponent { }; use ark_common::protocol::order_types::{ OrderStatus, OrderTrait, OrderType, CancelInfo, FulfillInfo, ExecutionValidationInfo, - ExecutionInfo, RouteType, PriceLevel + ExecutionInfo, RouteType, OptionU256 }; use ark_common::protocol::order_v1::OrderV1; use core::debug::PrintTrait; @@ -17,7 +17,7 @@ pub mod OrderbookComponent { use core::zeroable::Zeroable; use starknet::ContractAddress; use starknet::storage::Map; - use starknet::contract_address_to_felt252; + use super::super::interface::{IOrderbook, IOrderbookAction, orderbook_errors}; const EXTENSION_TIME_IN_SECONDS: u64 = 600; @@ -466,7 +466,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: order.offerer, token_to: related_order.offerer, - token_id: order.token_id.unwrap(), + token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap()}, token_quantity: 1, payment_from: related_order.offerer, payment_to: fulfill_info.fulfiller, @@ -513,7 +513,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: fulfill_info.fulfiller, token_to: order.offerer, - token_id: fulfill_info.token_id.unwrap(), + token_id: OptionU256 { is_some: 1, value: fulfill_info.token_id.unwrap()}, token_quantity: 1, payment_from: order.offerer, payment_to: fulfill_info.fulfiller, @@ -547,7 +547,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: order.offerer, token_to: fulfill_info.fulfiller, - token_id: order.token_id.unwrap(), + token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap()}, token_quantity: 1, payment_from: fulfill_info.fulfiller, payment_to: order.offerer, @@ -855,24 +855,25 @@ pub mod OrderbookComponent { cancelled_order_hash } - fn _create_execution_info( + fn _create_listing_execution_info( order_hash: felt252, buy_order: OrderV1, sell_order: OrderV1, fulfill_info: FulfillInfo, - token_quantity: u64, + token_quantity: u256, listing_broker_address: ContractAddress, + price: u256 ) -> ExecutionInfo { ExecutionInfo { order_hash, token_address: buy_order.token_address, token_from: sell_order.offerer, token_to: buy_order.offerer, - token_id: 0, + token_id: OptionU256 { is_some: 0, value: 0}, token_quantity, payment_from: buy_order.offerer, payment_to: sell_order.offerer, - payment_amount: buy_order.start_amount * token_quantity, + payment_amount: price * token_quantity, payment_currency_address: buy_order.currency_address, payment_currency_chain_id: buy_order.currency_chain_id, listing_broker_address: listing_broker_address, @@ -930,8 +931,10 @@ pub mod OrderbookComponent { ); // check that the price is the same + let order_price = order.start_amount / order.quantity; + let related_order_price = related_order.start_amount / related_order.quantity; assert( - related_order.start_amount == order.start_amount, + order_price == related_order_price, orderbook_errors::ORDER_PRICE_NOT_MATCH ) @@ -942,7 +945,7 @@ pub mod OrderbookComponent { // fulfilling a sell order with a buy order (related-order) RouteType::Erc20ToErc20Sell => { assert( - related_order.route == Erc20ToErc20Buy, + related_order.route == RouteType::Erc20ToErc20Buy, orderbook_errors::ORDER_ROUTE_NOT_VALID ); if order_quantity > related_order_quantity { @@ -959,13 +962,14 @@ pub mod OrderbookComponent { // set buy order as fufilled order_status_write(related_order_hash, OrderStatus::Fulfilled); // set execute info - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( related_order_hash, related_order, order, fulfill_info, related_order_quantity, - related_order_hash.broker_id + related_order_hash.broker_id, + order_price ); (Option::Some(execute_info), Option::Some(related_order_hash)) }else if related_order_quantity > order_quantity { @@ -982,13 +986,14 @@ pub mod OrderbookComponent { // set sell order as fulfilled order_status_write(order_hash, OrderStatus::Fulfilled); // generate execution info - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( order_hash, related_order, order, fulfill_info, order_quantity, - order.broker_id + order.broker_id, + order_price ); (Option::Some(execute_info), Option::Some(related_order_hash)) }else{ @@ -998,13 +1003,14 @@ pub mod OrderbookComponent { // passing any of them as the order hash will fulfill both orders, // so just one executioninfo will be sent. - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( order_hash, related_order, order, fulfill_info, order_quantity, - order.broker_id + order.broker_id, + order_price ); // return (Option::Some(execute_info), Option::Some(related_order_hash)) @@ -1013,7 +1019,7 @@ pub mod OrderbookComponent { // fulfilling a buy order with a sell order (related-order) RouteType::Erc20ToErc20Buy => { assert( - related_order.route == Erc20ToErc20Sell, + related_order.route == RouteType::Erc20ToErc20Sell, orderbook_errors::ORDER_ROUTE_NOT_VALID ); @@ -1030,13 +1036,14 @@ pub mod OrderbookComponent { ); // set sell order as fufilled order_status_write(related_order_hash, OrderStatus::Fulfilled); - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( related_order_hash, order, related_order, fulfill_info, related_order_quantity, - related_order.broker_id + related_order.broker_id, + order_price ); // return (Option::Some(execute_info), Option::Some(related_order_hash)) @@ -1054,13 +1061,14 @@ pub mod OrderbookComponent { // set buy order as fulfilled order_status_write(order_hash, OrderStatus::Fulfilled); - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( order_hash, order, related_order, fulfill_info, order_quantity, - order.broker_id + order.broker_id, + order_price ); // return (Option::Some(execute_info), Option::Some(related_order_hash)) @@ -1071,13 +1079,14 @@ pub mod OrderbookComponent { // passing any of them as the order hash will fulfill both orders, // so just one executioninfo will be sent - let execute_info = self._create_execution_info( + let execute_info = self._create_listing_execution_info( order_hash, order, related_order, fulfill_info, order_quantity, - order.broker_id + order.broker_id, + order_price ); (Option::Some(execute_info), Option::Some(related_order_hash)) diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index 0389f8103..df0d41a90 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -1,15 +1,8 @@ use ark_common::protocol::order_types::OrderTrait; -use ark_common::protocol::order_types::OrderType; - +use ark_common::protocol::order_types::{OrderType, RouteType, OptionU256}; use starknet::ContractAddress; -#[derive(Drop, Copy, Debug)] -struct OptionU256 { - is_some: felt252, // 1 if Some, 0 if None - value: u256, // Valid only if is_some == 1 -} - #[derive(Drop, Copy, Debug)] struct OrderInfo { order_type: OrderType, @@ -18,16 +11,17 @@ struct OrderInfo { // The token contract address. token_address: ContractAddress, // The token id. - token_id: OptionU256, + token_id: u256, // in wei. --> 10 | 10 | 10 | - start_amount: u256, + start_amount: u256, // amount to pay // address making the order offerer: ContractAddress, // number of tokens - quantity: u256 // 0 for ERC721 + quantity: u256,// 0 for ERC721, + // route type + route: RouteType } - //! Executor contract on Starknet //! //! This contract is responsible of executing the orders @@ -397,6 +391,11 @@ mod executor { self, order_info, fulfill_info, contract_address ); }, + OrderType::Limit => { + _verify_limit_order( + self, order_info, fulfill_info, contract_address + ) + }, _ => panic!("Order not supported") } } @@ -526,6 +525,83 @@ mod executor { ); } + + fn _verify_limit_order( + self: @ContractState, + order_info: OrderInfo, + fulfill_info: @FulfillInfo, + contract_address: ContractAddress + ) { + let related_order_info = match *(fulfill_info.related_order_hash) { + Option::None => panic!("Fulfill limit order require a related order"), + Option::Some(related_order_hash) => _get_order_info(self, related_order_hash), + }; + assert!( + @order_info.currency_address == @related_order_info.currency_address, + "Order and related order use different currency" + ); + + let (buyer_order, seller_order) = match order.route { + RouteType::Erc20ToErc20Sell => { + assert( + related_order_info.route == RouteType::Erc20ToErc20Buy, + 'Order route not valid' + ); + (related_order_info, order_info) + }, + + RouteType::Erc20ToErc20Buy => { + assert( + related_order_info.route == RouteType::Erc20ToErc20Sell, + 'Order route not valid' + ); + (order_info, related_order_info) + }, + _ => panic!('route not supported') + }; + + let buyer = buyer_order.offerer; + + // checks for buyer + assert!( + _check_erc20_amount( + @buyer_order.currency_address, buyer_order.start_amount, @buyer + ), + "Buyer does not own enough ERC20 tokens" + ); + + assert!( + _check_erc20_allowance( + @buyer_order.currency_address, + buyer_order.start_amount, + @buyer, + @get_contract_address() + ), + "Buyer's allowance of executor is not enough" + ); + + let seller = seller_order.offerer; + + // checks for seller + assert!( + _check_erc20_amount( + @seller_order.token_address, seller_order.quantity, @seller + ), + "Seller does not own enough ERC20 tokens" + ); + + assert!( + _check_erc20_allowance( + @seller_order.token_address, + seller_order.quantity, + @seller, + @get_contract_address() + ), + "Seller's allowance of executor is not enough" + ); + } + + fn _verify_fulfill_collection_offer_order( self: @ContractState, order_info: OrderInfo, @@ -658,23 +734,47 @@ mod executor { ); } - let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; - nft_contract - .transfer_from( - execution_info.token_from, execution_info.token_to, execution_info.token_id - ); + let (is_some, token_id) = execution_info.token_id.get_some(); - let tx_info = starknet::get_tx_info().unbox(); - let transaction_hash = tx_info.transaction_hash; - let block_timestamp = starknet::info::get_block_timestamp(); + if is_some == 1 { + let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; + nft_contract + .transfer_from( + execution_info.token_from, execution_info.token_to, token_id + ); + + let tx_info = starknet::get_tx_info().unbox(); + let transaction_hash = tx_info.transaction_hash; + let block_timestamp = starknet::info::get_block_timestamp(); + + let vinfo = ExecutionValidationInfo { + order_hash: execution_info.order_hash, + transaction_hash, + starknet_block_timestamp: block_timestamp, + from: execution_info.token_from, + to: execution_info.token_to, + }; - let vinfo = ExecutionValidationInfo { - order_hash: execution_info.order_hash, - transaction_hash, - starknet_block_timestamp: block_timestamp, - from: execution_info.token_from, - to: execution_info.token_to, - }; + } else { + let erc20_contract = IERC20Dispatcher { contract_address: execution_info.token_address }; + erc20_contract + .transfer_from( + execution_info.token_from, execution_info.token_to, execution_info.token_quantity + ); + + let tx_info = starknet::get_tx_info().unbox(); + let transaction_hash = tx_info.transaction_hash; + let block_timestamp = starknet::info::get_block_timestamp(); + + let vinfo = ExecutionValidationInfo { + order_hash: execution_info.order_hash, + transaction_hash, + starknet_block_timestamp: block_timestamp, + from: execution_info.token_from, + to: execution_info.token_to, + }; + + } self.orderbook.validate_order_execution(vinfo); } @@ -785,7 +885,8 @@ mod executor { token_id, start_amount: order.start_amount, offerer: order.offerer, - quantity: order.quantity + quantity: order.quantity, + route: order.route, } } } diff --git a/turbo.json b/turbo.json deleted file mode 100644 index 822314520..000000000 --- a/turbo.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "globalDependencies": ["**/.env.*local"], - "tasks": { - "build:packages": { - "dependsOn": ["^build"], - "outputs": [ - ".next/**", - "!.next/cache/**", - ".vercel/output/**", - ".dist/**" - ] - }, - "build": { - "cache": false, - "dependsOn": ["^build"], - "outputs": [ - ".next/**", - "!.next/cache/**", - ".vercel/output/**", - ".dist/**" - ] - }, - "@ark-project/demo#build": { - "dependsOn": ["^build"], - "env": [ - "NEXT_PUBLIC_NFT_API_KEY", - "NEXT_PUBLIC_ORDERBOOK_API_URL", - "NEXT_PUBLIC_NFT_API_URL" - ], - "outputs": [".next/**", "!.next/cache/**", ".vercel/output/**"] - }, - "dev": { - "dependsOn": ["^dev"], - "outputs": [".dist/**"] - }, - "lint": { - "dependsOn": ["^lint"] - }, - "test": { - "dependsOn": ["^test"] - }, - "lint:fix": { - "dependsOn": ["^lint:fix"] - }, - "clean": { - "cache": false - }, - "//#clean": { - "cache": false - }, - "deploy:solis": { - "cache": false, - "persistent": true - }, - "deploy:solis:local": { - "cache": false - }, - "deploy:starknet": { - "cache": false, - "persistent": true - }, - "deploy:starknet:local": { - "cache": false - }, - "deploy:starknet:tokens": { - "cache": false - }, - "accounts:new": { - "cache": false, - "persistent": true - }, - "accounts:deploy": { - "cache": false, - "persistent": true - } - }, - "globalEnv": [ - "ACCOUNT_CLASS_HASH", - "CI", - "SOLIS_ACCOUNT_CLASS_HASH", - "SOLIS_ADMIN_ADDRESS", - "SOLIS_ADMIN_ADDRESS_DEV", - "SOLIS_ADMIN_PRIVATE_KEY", - "SOLIS_NODE_URL", - "STARKNET_ACCOUNT1_ADDRESS", - "STARKNET_ACCOUNT1_PRIVATE_KEY", - "STARKNET_ACCOUNT2_ADDRESS", - "STARKNET_ACCOUNT2_PRIVATE_KEY", - "STARKNET_ADMIN_ADDRESS_DEV", - "STARKNET_ADMIN_PRIVATE_KEY_DEV", - "STARKNET_CURRENCY_ADDRESS", - "STARKNET_EXECUTOR_ADDRESS_DEV", - "STARKNET_NETWORK", - "STARKNET_NETWORK_ID", - "STARKNET_NFT_ADDRESS_DEV", - "STARKNET_RPC_URL", - "STARKNET_ADMIN_PRIVATE_KEY", - "STARKNET_ADMIN_ADDRESS" - ] -} From b1b5d4f3a48a27188b736ead73701024ca4c002d Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 16:36:01 +0100 Subject: [PATCH 07/18] add missing files --- turbo.json | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 turbo.json diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..f16e72e19 --- /dev/null +++ b/turbo.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build:packages": { + "dependsOn": ["^build"], + "outputs": [ + ".next/**", + "!.next/cache/**", + ".vercel/output/**", + ".dist/**" + ] + }, + "build": { + "cache": false, + "dependsOn": ["^build"], + "outputs": [ + ".next/**", + "!.next/cache/**", + ".vercel/output/**", + ".dist/**" + ] + }, + "@ark-project/demo#build": { + "dependsOn": ["^build"], + "env": [ + "NEXT_PUBLIC_NFT_API_KEY", + "NEXT_PUBLIC_ORDERBOOK_API_URL", + "NEXT_PUBLIC_NFT_API_URL" + ], + "outputs": [".next/**", "!.next/cache/**", ".vercel/output/**"] + }, + "dev": { + "dependsOn": ["^dev"], + "outputs": [".dist/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "test": { + "dependsOn": ["^test"] + }, + "lint:fix": { + "dependsOn": ["^lint:fix"] + }, + "clean": { + "cache": false + }, + "//#clean": { + "cache": false + }, + "deploy:solis": { + "cache": false, + "persistent": true + }, + "deploy:solis:local": { + "cache": false + }, + "deploy:starknet": { + "cache": false, + "persistent": true + }, + "deploy:starknet:local": { + "cache": false + }, + "deploy:starknet:tokens": { + "cache": false + }, + "accounts:new": { + "cache": false, + "persistent": true + }, + "accounts:deploy": { + "cache": false, + "persistent": true + } + }, + "globalEnv": [ + "ACCOUNT_CLASS_HASH", + "CI", + "SOLIS_ACCOUNT_CLASS_HASH", + "SOLIS_ADMIN_ADDRESS", + "SOLIS_ADMIN_ADDRESS_DEV", + "SOLIS_ADMIN_PRIVATE_KEY", + "SOLIS_NODE_URL", + "STARKNET_ACCOUNT1_ADDRESS", + "STARKNET_ACCOUNT1_PRIVATE_KEY", + "STARKNET_ACCOUNT2_ADDRESS", + "STARKNET_ACCOUNT2_PRIVATE_KEY", + "STARKNET_ADMIN_ADDRESS_DEV", + "STARKNET_ADMIN_PRIVATE_KEY_DEV", + "STARKNET_CURRENCY_ADDRESS", + "STARKNET_EXECUTOR_ADDRESS_DEV", + "STARKNET_NETWORK", + "STARKNET_NETWORK_ID", + "STARKNET_NFT_ADDRESS_DEV", + "STARKNET_RPC_URL", + "STARKNET_ADMIN_PRIVATE_KEY", + "STARKNET_ADMIN_ADDRESS" + ] + } \ No newline at end of file From 76453ac769eece46ff620351031e103b4526c314 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 18:07:24 +0100 Subject: [PATCH 08/18] fix build errors --- .../ark_common/src/protocol/order_types.cairo | 5 +-- .../ark_common/src/protocol/order_v1.cairo | 3 +- .../src/orderbook/interface.cairo | 9 ++-- .../src/orderbook/orderbook.cairo | 44 +++++++++---------- contracts/ark_starknet/src/executor.cairo | 29 ++++++------ 5 files changed, 42 insertions(+), 48 deletions(-) diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index 6435774c7..4a618d81a 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -220,7 +220,6 @@ struct FulfillInfo { #[derive(Drop, Serde, Copy)] struct ExecutionInfo { order_hash: felt252, - route: RouteType, token_address: ContractAddress, token_from: ContractAddress, token_to: ContractAddress, @@ -299,14 +298,14 @@ impl Felt252TryIntoRoute of TryInto { } } -#[derive(Drop, Copy)] +#[derive(starknet::Store, Serde, Drop, PartialEq, Copy, Debug)] struct OptionU256 { is_some: felt252, value: u256, } trait OptionU256Trait, +Drop> { - fn get_some(self: @T, value: u256) -> (felt252, u256); + fn get_some(self: @T) -> (felt252, u256); fn is_some(self: @T) -> bool; } diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 749a6402c..ef010d1be 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -138,7 +138,8 @@ impl OrderTraitOrderV1 of OrderTrait { } fn compute_token_hash(self: @OrderV1) -> felt252 { - if (self.route == RouteType::Erc20ToErc20Buy || self.route == RouteType::Erc20ToErc20Sell) { + if (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { + let mut buf: Array = array![]; // used quantity, start_date and the offerer as the identifiers buf.append((*self.token_address).into()); buf.append(*self.token_chain_id); diff --git a/contracts/ark_component/src/orderbook/interface.cairo b/contracts/ark_component/src/orderbook/interface.cairo index 53116cca5..492545ef1 100644 --- a/contracts/ark_component/src/orderbook/interface.cairo +++ b/contracts/ark_component/src/orderbook/interface.cairo @@ -31,14 +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 is not an erc 20 order'; - const ORDER_ROUTE_NOT_ERC20: felt252 = 'OB: order route is not an erc 20 route'; - const ORDER_ROUTE_NOT_VALID: felt252 = 'OB: order route is not a valid erc 20 route'; - const ORDER_PRICE_NOT_MATCH: felt252 = 'OB: erc 20 order price not match'; + 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 ORDER_IS_FILLED: felt252 = 'OB: order is filled'; 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'; diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index d7383740d..3f7d61b2c 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -288,41 +288,38 @@ pub mod OrderbookComponent { fn cancel_order(ref self: ComponentState, cancel_info: CancelInfo) { HooksCancelOrder::before_cancel_order(ref self, cancel_info); - let order_hash = cancel_info.order_hash; let order_option = order_read::(order_hash); assert(order_option.is_some(), orderbook_errors::ORDER_NOT_FOUND); let order = order_option.unwrap(); assert(order.offerer == cancel_info.canceller, 'not the same offerrer'); match order_status_read(order_hash) { - Option::Some(s) => s, + Option::Some(s) => assert(s == OrderStatus::Open, orderbook_errors::ORDER_FULFILLED), Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), }; let block_ts = starknet::get_block_timestamp(); let order_type = match order_type_read(order_hash) { Option::Some(order_type) => { - if order_type == OrderType::Auction { + if order_type == OrderType::Auction { let auction_token_hash = order.compute_token_hash(); let (_, auction_end_date, _) = self.auctions.read(auction_token_hash); assert( block_ts <= auction_end_date, orderbook_errors::ORDER_AUCTION_IS_EXPIRED ); self.auctions.write(auction_token_hash, (0, 0, 0)); - }else if order_type = OrderType::Limit { - let (_, quantity) = self.erc20_orders.read(order_hash); - assert( - quantity > 0, orderbook_errors::ORDER_IS_FILLED - ); - self.erc20_orders.write(order_hash, (0, 0, 0)) + } else if order_type == OrderType::Limit { + self.erc20_orders.write(order_hash, (0, 0)); } else { assert(block_ts < order.end_date, orderbook_errors::ORDER_IS_EXPIRED); if order_type == OrderType::Listing { - self.token_listings.write(order.compute_token_hash(), 0); + let order_hash = order.compute_token_hash(); + self.token_listings.write(order_hash, 0); } - } + }; + order_type }, - Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), + Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND) }; // Cancel order @@ -828,17 +825,15 @@ pub mod OrderbookComponent { order_type: OrderType, order_hash: felt252 ) { - let token_hash = order.compute_token_hash(); // revert if order is fulfilled or Open - let current_order_hash = self.erc20_orders.read(token_hash); - if (current_order_hash.is_non_zero()) { + let (price, _) = self.erc20_orders.read(order_hash); + if (price.is_non_zero()) { assert( - order_status_read(current_order_hash) != Option::Some(OrderStatus::Fulfilled), - orderbook_errors::ORDER_FULFILLED + order_status_read(order_hash) != Option::Some(OrderStatus::Fulfilled), + panic_with_felt252(orderbook_errors::ORDER_FULFILLED) ); } - let current_order: Option = order_read(current_order_hash); - let cancelled_order_hash = self._process_previous_order(token_hash, order.offerer); + let cancelled_order_hash = self._process_previous_order(order_hash, order.offerer); order_write(order_hash, order_type, order); self.erc20_orders.write(order_hash, (order.start_amount, order.quantity)); self @@ -852,10 +847,10 @@ pub mod OrderbookComponent { order: order } ); - cancelled_order_hash } fn _create_listing_execution_info( + ref self: ComponentState, order_hash: felt252, buy_order: OrderV1, sell_order: OrderV1, @@ -886,7 +881,7 @@ pub mod OrderbookComponent { ref self: ComponentState, fulfill_info: FulfillInfo, order: OrderV1 - ) { + ) -> (Option, Option) { assert(order.offerer != fulfill_info.fulfiller, orderbook_errors::ORDER_SAME_OFFERER); let order_hash = order.compute_order_hash(); @@ -933,10 +928,11 @@ pub mod OrderbookComponent { // check that the price is the same let order_price = order.start_amount / order.quantity; let related_order_price = related_order.start_amount / related_order.quantity; + assert( order_price == related_order_price, orderbook_errors::ORDER_PRICE_NOT_MATCH - ) + ); let (_, related_order_quantity) = self.erc20_orders.read(related_order_hash); let (_, order_quantity) = self.erc20_orders.read(order_hash); @@ -968,7 +964,7 @@ pub mod OrderbookComponent { order, fulfill_info, related_order_quantity, - related_order_hash.broker_id, + related_order.broker_id, order_price ); (Option::Some(execute_info), Option::Some(related_order_hash)) @@ -1092,7 +1088,7 @@ pub mod OrderbookComponent { (Option::Some(execute_info), Option::Some(related_order_hash)) } }, - _ => orderbook_errors::ORDER_ROUTE_NOT_ERC20 + _ => panic_with_felt252(orderbook_errors::ORDER_ROUTE_NOT_ERC20) } } } diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index df0d41a90..ff12c8b29 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -1,5 +1,5 @@ use ark_common::protocol::order_types::OrderTrait; -use ark_common::protocol::order_types::{OrderType, RouteType, OptionU256}; +use ark_common::protocol::order_types::{OrderType, RouteType}; use starknet::ContractAddress; @@ -34,7 +34,7 @@ struct OrderInfo { mod executor { use ark_common::protocol::order_types::{ RouteType, ExecutionInfo, ExecutionValidationInfo, FulfillInfo, CreateOrderInfo, - FulfillOrderInfo, CancelOrderInfo, CancelInfo, OrderType, + FulfillOrderInfo, CancelOrderInfo, CancelInfo, OrderType, OptionU256, OptionU256Trait }; use ark_common::protocol::order_v1::{OrderV1, OrderTraitOrderV1}; @@ -541,7 +541,7 @@ mod executor { "Order and related order use different currency" ); - let (buyer_order, seller_order) = match order.route { + let (buyer_order, seller_order) = match order_info.route { RouteType::Erc20ToErc20Sell => { assert( related_order_info.route == RouteType::Erc20ToErc20Buy, @@ -557,7 +557,7 @@ mod executor { ); (order_info, related_order_info) }, - _ => panic!('route not supported') + _ => panic!("route not supported") }; let buyer = buyer_order.offerer; @@ -650,6 +650,8 @@ mod executor { 'Chain ID is not SN_MAIN' ); + let (is_some, token_id) = execution_info.token_id.get_some(); + let currency_contract = IERC20Dispatcher { contract_address: execution_info.payment_currency_address.try_into().unwrap() }; @@ -658,7 +660,7 @@ mod executor { @self, @execution_info.token_address, execution_info.payment_amount, - execution_info.token_id + token_id ); let (fulfill_broker_fees_amount, listing_broker_fees_amount, ark_fees_amount, _) = _compute_fees_amount( @@ -666,7 +668,7 @@ mod executor { execution_info.fulfill_broker_address, execution_info.listing_broker_address, execution_info.token_address, - execution_info.token_id, + token_id, execution_info.payment_amount ); assert!( @@ -734,9 +736,7 @@ mod executor { ); } - let (is_some, token_id) = execution_info.token_id.get_some(); - - if is_some == 1 { + let vinfo = if is_some == 1 { let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; nft_contract .transfer_from( @@ -747,13 +747,13 @@ mod executor { let transaction_hash = tx_info.transaction_hash; let block_timestamp = starknet::info::get_block_timestamp(); - let vinfo = ExecutionValidationInfo { + ExecutionValidationInfo { order_hash: execution_info.order_hash, transaction_hash, starknet_block_timestamp: block_timestamp, from: execution_info.token_from, to: execution_info.token_to, - }; + } } else { let erc20_contract = IERC20Dispatcher { contract_address: execution_info.token_address }; @@ -766,15 +766,14 @@ mod executor { let transaction_hash = tx_info.transaction_hash; let block_timestamp = starknet::info::get_block_timestamp(); - let vinfo = ExecutionValidationInfo { + ExecutionValidationInfo { order_hash: execution_info.order_hash, transaction_hash, starknet_block_timestamp: block_timestamp, from: execution_info.token_from, to: execution_info.token_to, - }; - - } + } + }; self.orderbook.validate_order_execution(vinfo); } From 67403270b14e7a1eacef15d7f13952317ec3014f Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 19:26:04 +0100 Subject: [PATCH 09/18] handle token id in fee payments --- contracts/ark_starknet/src/executor.cairo | 54 ++++++++++++--------- contracts/ark_starknet/src/interfaces.cairo | 10 ++-- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index ff12c8b29..71df3a432 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -183,9 +183,9 @@ mod executor { } fn get_collection_creator_fees( - self: @ContractState, nft_address: ContractAddress + self: @ContractState, token_address: ContractAddress ) -> (ContractAddress, FeesRatio) { - let (receiver, fees_ratio) = self.creator_fees.read(nft_address); + let (receiver, fees_ratio) = self.creator_fees.read(token_address); if fees_ratio.denominator.is_zero() { self.get_default_creator_fees() } else { @@ -195,21 +195,21 @@ mod executor { fn set_collection_creator_fees( ref self: ContractState, - nft_address: ContractAddress, + token_address: ContractAddress, receiver: ContractAddress, fees_ratio: FeesRatio ) { _ensure_admin(@self); assert(fees_ratio.is_valid(), Errors::FEES_RATIO_INVALID); - self.creator_fees.write(nft_address, (receiver, fees_ratio)); + self.creator_fees.write(token_address, (receiver, fees_ratio)); } fn get_fees_amount( self: @ContractState, fulfill_broker: ContractAddress, listing_broker: ContractAddress, - nft_address: ContractAddress, - nft_token_id: u256, + token_address: ContractAddress, + token_id: OptionU256, payment_amount: u256 ) -> FeesAmount { let ( @@ -219,7 +219,7 @@ mod executor { creator_fees_amount ) = _compute_fees_amount( - self, fulfill_broker, listing_broker, nft_address, nft_token_id, payment_amount + self, fulfill_broker, listing_broker, token_address, token_id, payment_amount ); FeesAmount { @@ -650,7 +650,6 @@ mod executor { 'Chain ID is not SN_MAIN' ); - let (is_some, token_id) = execution_info.token_id.get_some(); let currency_contract = IERC20Dispatcher { contract_address: execution_info.payment_currency_address.try_into().unwrap() @@ -660,7 +659,7 @@ mod executor { @self, @execution_info.token_address, execution_info.payment_amount, - token_id + execution_info.token_id, ); let (fulfill_broker_fees_amount, listing_broker_fees_amount, ark_fees_amount, _) = _compute_fees_amount( @@ -668,7 +667,7 @@ mod executor { execution_info.fulfill_broker_address, execution_info.listing_broker_address, execution_info.token_address, - token_id, + execution_info.token_id, execution_info.payment_amount ); assert!( @@ -736,6 +735,8 @@ mod executor { ); } + let (is_some, token_id) = execution_info.token_id.get_some(); + let vinfo = if is_some == 1 { let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; nft_contract @@ -814,22 +815,27 @@ mod executor { } fn _compute_creator_fees_amount( - self: @ContractState, nft_address: @ContractAddress, payment_amount: u256, token_id: u256 + self: @ContractState, token_address: @ContractAddress, payment_amount: u256, token_id: OptionU256 ) -> (ContractAddress, u256) { - // check if nft support 2981 interface - let dispatcher = ISRC5Dispatcher { contract_address: *nft_address }; - if dispatcher.supports_interface(IERC2981_ID) { - IERC2981Dispatcher { contract_address: *nft_address } - .royalty_info(token_id, payment_amount) - } else { - _fallback_compute_creator_fees_amount(self, nft_address, payment_amount) - } + let (is_some, token_id) = token_id.get_some(); + if is_some == 0 { + _fallback_compute_creator_fees_amount(self, token_address, payment_amount) + }else{ + // check if nft support 2981 interface + let dispatcher = ISRC5Dispatcher { contract_address: *token_address }; + if dispatcher.supports_interface(IERC2981_ID) { + IERC2981Dispatcher { contract_address: *token_address } + .royalty_info(token_id, payment_amount) + } else { + _fallback_compute_creator_fees_amount(self, token_address, payment_amount) + } + } } fn _fallback_compute_creator_fees_amount( - self: @ContractState, nft_address: @ContractAddress, payment_amount: u256 + self: @ContractState, token_address: @ContractAddress, payment_amount: u256 ) -> (ContractAddress, u256) { - let (receiver, fees_ratio) = self.get_collection_creator_fees(*nft_address); + let (receiver, fees_ratio) = self.get_collection_creator_fees(*token_address); let amount = fees_ratio.compute_amount(payment_amount); (receiver, amount) } @@ -848,8 +854,8 @@ mod executor { self: @ContractState, fulfill_broker_address: ContractAddress, listing_broker_address: ContractAddress, - nft_address: ContractAddress, - nft_token_id: u256, + token_address: ContractAddress, + token_id: OptionU256, payment_amount: u256 ) -> (u256, u256, u256, u256) { let fulfill_broker_fees = self.get_broker_fees(fulfill_broker_address); @@ -859,7 +865,7 @@ mod executor { let fulfill_broker_fees_amount = fulfill_broker_fees.compute_amount(payment_amount); let listing_broker_fees_amount = listing_broker_fees.compute_amount(payment_amount); let (_, creator_fees_amount) = _compute_creator_fees_amount( - self, @nft_address, payment_amount, nft_token_id + self, @token_address, payment_amount, token_id, ); let ark_fees_amount = ark_fees.compute_amount(payment_amount); ( diff --git a/contracts/ark_starknet/src/interfaces.cairo b/contracts/ark_starknet/src/interfaces.cairo index f283272b5..9a5b887f6 100644 --- a/contracts/ark_starknet/src/interfaces.cairo +++ b/contracts/ark_starknet/src/interfaces.cairo @@ -1,5 +1,5 @@ use ark_common::protocol::order_types::ExecutionInfo; -use ark_common::protocol::order_types::OrderV1; +use ark_common::protocol::order_types::{OrderV1, OptionU256, OptionU256Trait}; use ark_common::protocol::order_types::{FulfillInfo, CancelInfo}; use ark_oz::erc2981::FeesRatio; //! Interfaces for arkchain operator. @@ -29,18 +29,18 @@ trait IExecutor { fn get_default_creator_fees(self: @T) -> (ContractAddress, FeesRatio); fn set_default_creator_fees(ref self: T, receiver: ContractAddress, fees_ratio: FeesRatio); fn get_collection_creator_fees( - self: @T, nft_address: ContractAddress + self: @T, token_address: ContractAddress ) -> (ContractAddress, FeesRatio); fn set_collection_creator_fees( - ref self: T, nft_address: ContractAddress, receiver: ContractAddress, fees_ratio: FeesRatio + ref self: T, token_address: ContractAddress, receiver: ContractAddress, fees_ratio: FeesRatio ); fn get_fees_amount( self: @T, fulfill_broker: ContractAddress, listing_broker: ContractAddress, - nft_address: ContractAddress, - nft_token_id: u256, + token_address: ContractAddress, + token_id: OptionU256, payment_amount: u256 ) -> FeesAmount; } From cf9783e1703444ab61fdaebc1bfdf22750b7ee20 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 20:44:35 +0100 Subject: [PATCH 10/18] code refactor --- .../ark_common/src/protocol/order_types.cairo | 12 +- .../ark_common/src/protocol/order_v1.cairo | 21 +- .../src/orderbook/orderbook.cairo | 281 ++++++++---------- contracts/ark_starknet/src/executor.cairo | 12 +- .../ark_starknet/tests/common/setup.cairo | 103 ++++++- .../tests/integration/create_order.cairo | 101 ++++++- 6 files changed, 347 insertions(+), 183 deletions(-) diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index 4a618d81a..2673dc08a 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -11,7 +11,8 @@ enum OrderType { Auction, Offer, CollectionOffer, - Limit, + LimitBuy, + LimitSell } impl OrderTypeIntoFelt252 of Into { @@ -21,7 +22,8 @@ impl OrderTypeIntoFelt252 of Into { OrderType::Auction => 'AUCTION', OrderType::Offer => 'OFFER', OrderType::CollectionOffer => 'COLLECTION_OFFER', - OrderType::Limit => 'LIMIT' + OrderType::LimitBuy => 'LIMIT_BUY', + OrderType::LimitSell => 'LIMIT_SELL' } } } @@ -36,8 +38,10 @@ impl Felt252TryIntoOrderType of TryInto { Option::Some(OrderType::Offer) } else if self == 'COLLECTION_OFFER' { Option::Some(OrderType::CollectionOffer) - } else if self == 'LIMIT'{ - Option::Some(OrderType::Limit) + } else if self == 'LIMIT_BUY'{ + Option::Some(OrderType::LimitBuy) + }else if self == 'LIMIT_SELL'{ + Option::Some(OrderType::LimitSell) } else { Option::None } diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index ef010d1be..ab98a207c 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -37,9 +37,9 @@ struct OrderV1 { // The quantity of the token_id to be offerred (1 for NFTs). quantity: u256, // in wei. --> 10 | 10 | 10 | - start_amount: u256, // amount to pay. + 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. @@ -125,11 +125,20 @@ impl OrderTraitOrderV1 of OrderTrait { return Result::Ok(OrderType::CollectionOffer); } - // Limit Order + // Limit Buy Order if(*self.quantity) > 0 - && (*self.start_amount) > 0 // price is set - && (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { - return Result::Ok(OrderType::Limit); + && (*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); } } diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 3f7d61b2c..623aee755 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -34,8 +34,10 @@ pub mod OrderbookComponent { auctions: Map, /// Mapping of auction offer order_hash to auction listing order_hash. auction_offers: Map, - /// Mapping of erc20s orderhash to the order (price, quantity) - erc20_orders: Map + /// Mapping of erc20s buy orderhash to the order (price, quantity) + buy_orders: Map, + /// Mapping of erc20s sell orderhash to the order (price, quantity) + sell_orders: Map } // ************************************************************************* @@ -278,7 +280,10 @@ pub mod OrderbookComponent { OrderType::CollectionOffer => { self._create_collection_offer(order, order_type, order_hash); }, - OrderType::Limit => { + OrderType::LimitBuy => { + self._create_limit_order(order, order_type, order_hash); + }, + OrderType::LimitSell => { self._create_limit_order(order, order_type, order_hash); } }; @@ -307,8 +312,10 @@ pub mod OrderbookComponent { block_ts <= auction_end_date, orderbook_errors::ORDER_AUCTION_IS_EXPIRED ); self.auctions.write(auction_token_hash, (0, 0, 0)); - } else if order_type == OrderType::Limit { - self.erc20_orders.write(order_hash, (0, 0)); + } else if order_type == OrderType::LimitBuy { + self.buy_orders.write(order_hash, (0, 0)); + }else if order_type == OrderType::LimitSell { + self.sell_orders.write(order_hash, (0, 0)); } else { assert(block_ts < order.end_date, orderbook_errors::ORDER_IS_EXPIRED); if order_type == OrderType::Listing { @@ -361,7 +368,8 @@ pub mod OrderbookComponent { OrderType::Auction => self._fulfill_auction_order(fulfill_info, order), OrderType::Offer => self._fulfill_offer(fulfill_info, order), OrderType::CollectionOffer => self._fulfill_offer(fulfill_info, order), - OrderType::Limit => self._fulfill_limit_order(fulfill_info, order), + OrderType::LimitBuy => self._fulfill_limit_order(fulfill_info, order), + OrderType::LimitSell => self._fulfill_limit_order(fulfill_info, order), }; self @@ -818,7 +826,7 @@ pub mod OrderbookComponent { ); } - /// Creates a limit order + /// Creates a limit buy order fn _create_limit_order( ref self: ComponentState, order: OrderV1, @@ -826,7 +834,7 @@ pub mod OrderbookComponent { order_hash: felt252 ) { // revert if order is fulfilled or Open - let (price, _) = self.erc20_orders.read(order_hash); + let (price, _) = self.buy_orders.read(order_hash); if (price.is_non_zero()) { assert( order_status_read(order_hash) != Option::Some(OrderStatus::Fulfilled), @@ -834,8 +842,21 @@ pub mod OrderbookComponent { ); } let cancelled_order_hash = self._process_previous_order(order_hash, order.offerer); + order_write(order_hash, order_type, order); - self.erc20_orders.write(order_hash, (order.start_amount, order.quantity)); + + match order_type { + OrderType::LimitBuy => { + let price = order.start_amount / order.quantity; + self.buy_orders.write(order_hash, (price, order.quantity)); + }, + OrderType::LimitSell => { + let price = order.end_amount / order.quantity; + self.sell_orders.write(order_hash, (price, order.quantity)); + }, + _ => () + } + self .emit( OrderPlaced { @@ -897,7 +918,7 @@ pub mod OrderbookComponent { match order_type_read(related_order_hash) { Option::Some(order_type) => { assert( - order_type == OrderType::Limit, + order_type == OrderType::LimitBuy || order_type == OrderType::LimitSell, orderbook_errors::ORDER_NOT_AN_ERC20_ORDER ); }, @@ -925,170 +946,106 @@ pub mod OrderbookComponent { orderbook_errors::ORDER_TOKEN_HASH_DOES_NOT_MATCH ); - // check that the price is the same - let order_price = order.start_amount / order.quantity; - let related_order_price = related_order.start_amount / related_order.quantity; - - assert( - order_price == related_order_price, - orderbook_errors::ORDER_PRICE_NOT_MATCH - ); - - let (_, related_order_quantity) = self.erc20_orders.read(related_order_hash); - let (_, order_quantity) = self.erc20_orders.read(order_hash); - - match order.route{ - // fulfilling a sell order with a buy order (related-order) + let (buy_order, sell_order) = match order.route { RouteType::Erc20ToErc20Sell => { assert( related_order.route == RouteType::Erc20ToErc20Buy, orderbook_errors::ORDER_ROUTE_NOT_VALID ); - if order_quantity > related_order_quantity { - // reduce sell quantity order and execute buy order - self - .erc20_orders - .write( - order_hash, - ( - order.start_amount, - order_quantity - related_order_quantity - ) - ); - // set buy order as fufilled - order_status_write(related_order_hash, OrderStatus::Fulfilled); - // set execute info - let execute_info = self._create_listing_execution_info( - related_order_hash, - related_order, - order, - fulfill_info, - related_order_quantity, - related_order.broker_id, - order_price - ); - (Option::Some(execute_info), Option::Some(related_order_hash)) - }else if related_order_quantity > order_quantity { - // reduce buy quantity, and execute sell quantity - self - .erc20_orders - .write( - related_order_hash, - ( - order.start_amount, - related_order_quantity - order_quantity - ) - ); - // set sell order as fulfilled - order_status_write(order_hash, OrderStatus::Fulfilled); - // generate execution info - let execute_info = self._create_listing_execution_info( - order_hash, - related_order, - order, - fulfill_info, - order_quantity, - order.broker_id, - order_price - ); - (Option::Some(execute_info), Option::Some(related_order_hash)) - }else{ - // execute both orders - order_status_write(order_hash, OrderStatus::Fulfilled); - order_status_write(related_order_hash, OrderStatus::Fulfilled); - - // passing any of them as the order hash will fulfill both orders, - // so just one executioninfo will be sent. - let execute_info = self._create_listing_execution_info( - order_hash, - related_order, - order, - fulfill_info, - order_quantity, - order.broker_id, - order_price - ); - // return - (Option::Some(execute_info), Option::Some(related_order_hash)) - } + (related_order, order) }, - // fulfilling a buy order with a sell order (related-order) + RouteType::Erc20ToErc20Buy => { assert( related_order.route == RouteType::Erc20ToErc20Sell, orderbook_errors::ORDER_ROUTE_NOT_VALID ); - - if order_quantity > related_order_quantity { - // reduce buy quantity order and execute sell order - self - .erc20_orders - .write( - order_hash, - ( - order.start_amount, - order_quantity - related_order_quantity - ) - ); - // set sell order as fufilled - order_status_write(related_order_hash, OrderStatus::Fulfilled); - let execute_info = self._create_listing_execution_info( - related_order_hash, - order, - related_order, - fulfill_info, - related_order_quantity, - related_order.broker_id, - order_price - ); - // return - (Option::Some(execute_info), Option::Some(related_order_hash)) - }else if related_order_quantity > order_quantity { - // reduce sell quantity, and execute buy order - self - .erc20_orders - .write( - related_order_hash, - ( - order.start_amount, - related_order_quantity - order_quantity - ) - ); - // set buy order as fulfilled - order_status_write(order_hash, OrderStatus::Fulfilled); - - let execute_info = self._create_listing_execution_info( - order_hash, - order, - related_order, - fulfill_info, - order_quantity, - order.broker_id, - order_price - ); - // return - (Option::Some(execute_info), Option::Some(related_order_hash)) - }else{ - // execute both orders - order_status_write(order_hash, OrderStatus::Fulfilled); - order_status_write(related_order_hash, OrderStatus::Fulfilled); - - // passing any of them as the order hash will fulfill both orders, - // so just one executioninfo will be sent - let execute_info = self._create_listing_execution_info( - order_hash, - order, - related_order, - fulfill_info, - order_quantity, - order.broker_id, - order_price - ); - - (Option::Some(execute_info), Option::Some(related_order_hash)) - } + (order, related_order) }, - _ => panic_with_felt252(orderbook_errors::ORDER_ROUTE_NOT_ERC20) + _ => panic!("route not supported") + }; + + // check that the price is the same + let buy_price = buy_order.start_amount / buy_order.quantity; + let sell_price = sell_order.end_amount / sell_order.quantity; + + let buy_order_hash = buy_order.compute_order_hash(); + let sell_order_hash = sell_order.compute_order_hash(); + + assert( + buy_price == sell_price, + orderbook_errors::ORDER_PRICE_NOT_MATCH + ); + + let (_, buy_order_quantity) = self.buy_orders.read(buy_order_hash); + let (_, sell_order_quantity) = self.sell_orders.read(sell_order_hash); + + if buy_order_quantity > sell_order_quantity { + // reduce buy quantity order and execute sell order + self + .buy_orders + .write( + buy_order_hash, + ( + buy_price, + buy_order_quantity - sell_order_quantity + ) + ); + // set buy order as fufilled + order_status_write(sell_order_hash, OrderStatus::Fulfilled); + // set execute info + let execute_info = self._create_listing_execution_info( + sell_order_hash, + buy_order, + sell_order, + fulfill_info, + sell_order_quantity, + related_order.broker_id, + buy_price + ); + (Option::Some(execute_info), Option::Some(related_order_hash)) + }else if sell_order_quantity > buy_order_quantity { + // reduce sell quantity, and execute buy order + self + .sell_orders + .write( + sell_order_hash, + ( + sell_price, + sell_order_quantity - buy_order_quantity + ) + ); + // set sell order as fulfilled + order_status_write(buy_order_hash, OrderStatus::Fulfilled); + // generate execution info + let execute_info = self._create_listing_execution_info( + buy_order_hash, + buy_order, + sell_order, + fulfill_info, + buy_order_quantity, + order.broker_id, + buy_price + ); + (Option::Some(execute_info), Option::Some(related_order_hash)) + }else{ + // execute both orders + order_status_write(buy_order_hash, OrderStatus::Fulfilled); + order_status_write(sell_order_hash, OrderStatus::Fulfilled); + + // passing any of them as the order hash will fulfill both orders, + // so just one executioninfo will be sent. + let execute_info = self._create_listing_execution_info( + buy_order_hash, + buy_order, + sell_order, + fulfill_info, + buy_order_quantity, + order.broker_id, + buy_price + ); + // return + (Option::Some(execute_info), Option::Some(related_order_hash)) } } } diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index 71df3a432..697dde27f 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -391,7 +391,12 @@ mod executor { self, order_info, fulfill_info, contract_address ); }, - OrderType::Limit => { + OrderType::LimitBuy => { + _verify_limit_order( + self, order_info, fulfill_info, contract_address + ) + }, + OrderType::LimitSell => { _verify_limit_order( self, order_info, fulfill_info, contract_address ) @@ -543,10 +548,7 @@ mod executor { let (buyer_order, seller_order) = match order_info.route { RouteType::Erc20ToErc20Sell => { - assert( - related_order_info.route == RouteType::Erc20ToErc20Buy, - 'Order route not valid' - ); + (related_order_info, order_info) }, diff --git a/contracts/ark_starknet/tests/common/setup.cairo b/contracts/ark_starknet/tests/common/setup.cairo index bab0b3281..dae575c71 100644 --- a/contracts/ark_starknet/tests/common/setup.cairo +++ b/contracts/ark_starknet/tests/common/setup.cairo @@ -69,6 +69,13 @@ fn setup() -> (ContractAddress, ContractAddress, ContractAddress) { (executor_address, erc20_address, nft_address) } +fn setup_erc20_order() -> (ContractAddress, ContractAddress, ContractAddress) { + let erc20_address = deploy_erc20(); + let token_address = deploy_erc20(); + let executor_address = deploy_executor(); + (executor_address, erc20_address, token_address) +} + fn setup_royalty() -> (ContractAddress, ContractAddress, ContractAddress) { let erc20_address = deploy_erc20(); let nft_address = deploy_nft(true); @@ -84,6 +91,7 @@ fn setup_order( token_id: Option, start_amount: u256, end_amount: u256, + quantity: u256, ) -> OrderV1 { let chain_id = 'SN_MAIN'; let block_timestamp = starknet::get_block_timestamp(); @@ -99,7 +107,7 @@ fn setup_order( token_chain_id: chain_id, token_address: nft_address, token_id, - quantity: 1, + quantity: quantity, start_amount, end_amount, start_date: block_timestamp, @@ -123,7 +131,8 @@ fn setup_offer_order( offerer, Option::Some(token_id), start_amount, - 0 + 0, + 1 ) } @@ -141,7 +150,8 @@ fn setup_listing_order( offerer, Option::Some(token_id), start_amount, - 0 + 0, + 1 ) } @@ -160,7 +170,8 @@ fn setup_auction_order( offerer, Option::Some(token_id), start_amount, - end_amount + end_amount, + 1 ) } @@ -177,7 +188,46 @@ fn setup_collection_offer_order( offerer, Option::None, start_amount, - 0 + 0, + 1 + ) +} + +fn setup_buy_limit_order( + currency_address: ContractAddress, + token_address: ContractAddress, + offerer: ContractAddress, + start_amount: u256, + quantity: u256 +) -> OrderV1 { + setup_order( + currency_address, + token_address, + RouteType::Erc20ToErc20Buy, + offerer, + Option::None, + start_amount, + 0, + quantity + ) +} + +fn setup_sell_limit_order( + currency_address: ContractAddress, + token_address: ContractAddress, + offerer: ContractAddress, + start_amount: u256, + quantity: u256 +) -> OrderV1 { + setup_order( + currency_address, + nft_address, + RouteType::Erc20ToErc20Sell, + offerer, + Option::None, + start_amount, + 0, + quantity ) } @@ -285,3 +335,46 @@ fn create_collection_offer_order( (order.compute_order_hash(), offerer, start_amount) } + + +fn create_buy_limit_order( + executor_address: ContractAddress, + erc20_address: ContractAddress, + token_address: ContractAddress, + start_amount: u256, + quantity: u256 +) -> (felt252, ContractAddress, u256) { + let offerer = contract_address_const::<'offerer'>(); + + IFreeMintDispatcher { contract_address: erc20_address }.mint(offerer, start_amount); + + let order = setup_buy_limit_order( + erc20_address, token_address, offerer, start_amount, quantity + ); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + + (order.compute_order_hash(), offerer, start_amount) +} + +fn create_sell_limit_order( + executor_address: ContractAddress, + erc20_address: ContractAddress, + token_address: ContractAddress, + start_amount: u256, + quantity: u256 +) -> (felt252, ContractAddress, u256) { + let offerer = contract_address_const::<'offerer'>(); + + IFreeMintDispatcher { contract_address: token_address }.mint(offerer, quantity); + + let order = setup_sell_limit_order( + erc20_address, token_address, offerer, start_amount, quantity + ); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + + (order.compute_order_hash(), offerer, quantity) +} \ No newline at end of file diff --git a/contracts/ark_starknet/tests/integration/create_order.cairo b/contracts/ark_starknet/tests/integration/create_order.cairo index d3f421788..965cf77a6 100644 --- a/contracts/ark_starknet/tests/integration/create_order.cairo +++ b/contracts/ark_starknet/tests/integration/create_order.cairo @@ -23,7 +23,8 @@ use snforge_std::{ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ - setup, setup_auction_order, setup_collection_offer_order, setup_listing_order, setup_offer_order + setup, setup_erc20_order, setup_auction_order, setup_collection_offer_order, setup_listing_order, setup_offer_order, + setup_sell_limit_order, setup_buy_limit_order }; @@ -229,6 +230,104 @@ fn test_create_collection_offer_order_ok() { ); } +#[test] +fn test_create_buy_limit_order_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let start_amount = 10_000_000; + let quantity = 5_000_000; + Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount); + + let order = setup_buy_limit_order(erc20_address, token_address, offerer, start_amount, quantity); + let order_hash = order.compute_order_hash(); + let order_version = order.get_version(); + + let mut spy = spy_events(); + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + + spy + .assert_emitted( + @array![ + ( + executor_address, + executor::Event::OrderbookEvent( + OrderbookComponent::Event::OrderPlaced( + OrderbookComponent::OrderPlaced { + order_hash, + order_version, + order_type: OrderType::LimitBuy, + version: OrderbookComponent::ORDER_PLACED_EVENT_VERSION, + cancelled_order_hash: Option::None, + order, + } + ) + ) + ) + ] + ); + + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_type(order_hash), + OrderType::LimitBuy, + "Wrong order type" + ); + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_status(order_hash), + OrderStatus::Open, + "Wrong order status" + ); +} + +#[test] +fn test_create_sell_limit_order_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let start_amount = 10_000_000; + let quantity = 5_000_000; + Erc20Dispatcher { contract_address: token_address }.mint(offerer, quantity); + + let order = setup_sell_limit_order(erc20_address, token_address, offerer, start_amount, quantity); + let order_hash = order.compute_order_hash(); + let order_version = order.get_version(); + + let mut spy = spy_events(); + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + + spy + .assert_emitted( + @array![ + ( + executor_address, + executor::Event::OrderbookEvent( + OrderbookComponent::Event::OrderPlaced( + OrderbookComponent::OrderPlaced { + order_hash, + order_version, + order_type: OrderType::LimitSell, + version: OrderbookComponent::ORDER_PLACED_EVENT_VERSION, + cancelled_order_hash: Option::None, + order, + } + ) + ) + ) + ] + ); + + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_type(order_hash), + OrderType::LimitSell, + "Wrong order type" + ); + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_status(order_hash), + OrderStatus::Open, + "Wrong order status" + ); +} + #[test] #[should_panic(expected: "Caller is not the offerer")] fn test_create_offer_order_offerer_shall_be_caller() { From 886017deca8003d5df27d5b481a58b786723385b Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Fri, 4 Oct 2024 21:40:42 +0100 Subject: [PATCH 11/18] add tests --happy path --- .../ark_starknet/tests/common/setup.cairo | 36 ++++-- .../tests/integration/cancel_order.cairo | 110 ++++++++++++++++- .../tests/integration/create_order.cairo | 12 +- .../tests/integration/fulfill_order.cairo | 111 +++++++++++++++--- 4 files changed, 232 insertions(+), 37 deletions(-) diff --git a/contracts/ark_starknet/tests/common/setup.cairo b/contracts/ark_starknet/tests/common/setup.cairo index dae575c71..10ad4ee2f 100644 --- a/contracts/ark_starknet/tests/common/setup.cairo +++ b/contracts/ark_starknet/tests/common/setup.cairo @@ -28,6 +28,20 @@ fn deploy_erc20() -> ContractAddress { erc20_address } +fn deploy_erc20_2() -> ContractAddress { + let contract = declare("FreeMintERC202").unwrap().contract_class(); + let initial_supply: u256 = 10_000_000_000_u256; + let name: ByteArray = "DummyERC202"; + let symbol: ByteArray = "DUMMY2"; + + let mut calldata: Array = array![]; + initial_supply.serialize(ref calldata); + name.serialize(ref calldata); + symbol.serialize(ref calldata); + let (erc20_address, _) = contract.deploy(@calldata).unwrap(); + erc20_address +} + fn deploy_nft(royalty: bool) -> ContractAddress { let name: ByteArray = "DummyNFT"; let symbol: ByteArray = "DUMNFT"; @@ -71,7 +85,7 @@ fn setup() -> (ContractAddress, ContractAddress, ContractAddress) { fn setup_erc20_order() -> (ContractAddress, ContractAddress, ContractAddress) { let erc20_address = deploy_erc20(); - let token_address = deploy_erc20(); + let token_address = deploy_erc20_2(); let executor_address = deploy_executor(); (executor_address, erc20_address, token_address) } @@ -193,7 +207,7 @@ fn setup_collection_offer_order( ) } -fn setup_buy_limit_order( +fn setup_limit_buy_order( currency_address: ContractAddress, token_address: ContractAddress, offerer: ContractAddress, @@ -212,11 +226,11 @@ fn setup_buy_limit_order( ) } -fn setup_sell_limit_order( +fn setup_limit_sell_order( currency_address: ContractAddress, token_address: ContractAddress, offerer: ContractAddress, - start_amount: u256, + end_amount: u256, quantity: u256 ) -> OrderV1 { setup_order( @@ -225,8 +239,8 @@ fn setup_sell_limit_order( RouteType::Erc20ToErc20Sell, offerer, Option::None, - start_amount, 0, + end_amount, quantity ) } @@ -337,7 +351,7 @@ fn create_collection_offer_order( } -fn create_buy_limit_order( +fn create_limit_buy_order( executor_address: ContractAddress, erc20_address: ContractAddress, token_address: ContractAddress, @@ -348,7 +362,7 @@ fn create_buy_limit_order( IFreeMintDispatcher { contract_address: erc20_address }.mint(offerer, start_amount); - let order = setup_buy_limit_order( + let order = setup_limit_buy_order( erc20_address, token_address, offerer, start_amount, quantity ); @@ -358,19 +372,19 @@ fn create_buy_limit_order( (order.compute_order_hash(), offerer, start_amount) } -fn create_sell_limit_order( +fn create_limit_sell_order( executor_address: ContractAddress, erc20_address: ContractAddress, token_address: ContractAddress, - start_amount: u256, + end_amount: u256, quantity: u256 ) -> (felt252, ContractAddress, u256) { let offerer = contract_address_const::<'offerer'>(); IFreeMintDispatcher { contract_address: token_address }.mint(offerer, quantity); - let order = setup_sell_limit_order( - erc20_address, token_address, offerer, start_amount, quantity + let order = setup_limit_sell_order( + erc20_address, token_address, offerer, end_amount, quantity ); cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); diff --git a/contracts/ark_starknet/tests/integration/cancel_order.cairo b/contracts/ark_starknet/tests/integration/cancel_order.cairo index 8e71daf9f..d4eb10647 100644 --- a/contracts/ark_starknet/tests/integration/cancel_order.cairo +++ b/contracts/ark_starknet/tests/integration/cancel_order.cairo @@ -15,8 +15,8 @@ use snforge_std::{cheat_caller_address, CheatSpan, spy_events, EventSpyAssertion use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, - setup, setup_default_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order + setup, setup_erc20_order, setup_default_order, setup_auction_order, setup_collection_offer_order, + setup_listing_order, setup_offer_order, create_limit_buy_order, create_limit_sell_order }; #[test] @@ -225,6 +225,112 @@ fn test_cancel_collection_offer_order() { ); } +#[test] +fn test_cancel_limit_buy_order() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let start_amount = 10_000_000; + let quantity = 5000; + + let (order_hash, offerer, start_amount) = create_limit_buy_order( + executor_address, erc20_address, token_address, start_amount, quantity + ); + + let cancel_info = CancelInfo { + order_hash, + canceller: offerer, + token_chain_id: 'SN_MAIN', + token_address: nft_address, + token_id: Option::None, + }; + + let mut spy = spy_events(); + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.cancel_order(cancel_info); + + spy + .assert_emitted( + @array![ + ( + executor_address, + executor::Event::OrderbookEvent( + OrderbookComponent::Event::OrderCancelled( + OrderbookComponent::OrderCancelled { + order_hash, + reason: OrderStatus::CancelledUser.into(), + order_type: OrderType::LimitBuy, + version: OrderbookComponent::ORDER_CANCELLED_EVENT_VERSION, + } + ) + ) + ) + ] + ); + + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_type(order_hash), + OrderType::LimitBuy, + "Wrong order type" + ); + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_status(order_hash), + OrderStatus::CancelledUser, + "Wrong order status" + ); +} + +#[test] +fn test_cancel_limit_sell_order() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let end_amount = 10_000_000; + let quantity = 5000; + + let (order_hash, offerer, start_amount) = create_limit_sell_order( + executor_address, erc20_address, token_address, end_amount, quantity + ); + + let cancel_info = CancelInfo { + order_hash, + canceller: offerer, + token_chain_id: 'SN_MAIN', + token_address: nft_address, + token_id: Option::None, + }; + + let mut spy = spy_events(); + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.cancel_order(cancel_info); + + spy + .assert_emitted( + @array![ + ( + executor_address, + executor::Event::OrderbookEvent( + OrderbookComponent::Event::OrderCancelled( + OrderbookComponent::OrderCancelled { + order_hash, + reason: OrderStatus::CancelledUser.into(), + order_type: OrderType::LimitSell, + version: OrderbookComponent::ORDER_CANCELLED_EVENT_VERSION, + } + ) + ) + ) + ] + ); + + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_type(order_hash), + OrderType::LimitSell, + "Wrong order type" + ); + assert_eq!( + IOrderbookDispatcher { contract_address: executor_address }.get_order_status(order_hash), + OrderStatus::CancelledUser, + "Wrong order status" + ); +} + #[test] // #[should_panic] fn test_cancel_offer_order_already_cancelled() { diff --git a/contracts/ark_starknet/tests/integration/create_order.cairo b/contracts/ark_starknet/tests/integration/create_order.cairo index 965cf77a6..081b9f8fc 100644 --- a/contracts/ark_starknet/tests/integration/create_order.cairo +++ b/contracts/ark_starknet/tests/integration/create_order.cairo @@ -24,7 +24,7 @@ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ setup, setup_erc20_order, setup_auction_order, setup_collection_offer_order, setup_listing_order, setup_offer_order, - setup_sell_limit_order, setup_buy_limit_order + setup_limit_sell_order, setup_limit_buy_order }; @@ -231,14 +231,14 @@ fn test_create_collection_offer_order_ok() { } #[test] -fn test_create_buy_limit_order_ok() { +fn test_create_limit_buy_order_ok() { let (executor_address, erc20_address, token_address) = setup_erc20_order(); let offerer = contract_address_const::<'offerer'>(); let start_amount = 10_000_000; let quantity = 5_000_000; Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount); - let order = setup_buy_limit_order(erc20_address, token_address, offerer, start_amount, quantity); + let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); let order_hash = order.compute_order_hash(); let order_version = order.get_version(); @@ -280,14 +280,14 @@ fn test_create_buy_limit_order_ok() { } #[test] -fn test_create_sell_limit_order_ok() { +fn test_create_limit_sell_order_ok() { let (executor_address, erc20_address, token_address) = setup_erc20_order(); let offerer = contract_address_const::<'offerer'>(); - let start_amount = 10_000_000; + let end_amount = 10_000_000; let quantity = 5_000_000; Erc20Dispatcher { contract_address: token_address }.mint(offerer, quantity); - let order = setup_sell_limit_order(erc20_address, token_address, offerer, start_amount, quantity); + let order = setup_limit_sell_order(erc20_address, token_address, offerer, end_amount, quantity); let order_hash = order.compute_order_hash(); let order_version = order.get_version(); diff --git a/contracts/ark_starknet/tests/integration/fulfill_order.cairo b/contracts/ark_starknet/tests/integration/fulfill_order.cairo index c23d362dd..70c374f57 100644 --- a/contracts/ark_starknet/tests/integration/fulfill_order.cairo +++ b/contracts/ark_starknet/tests/integration/fulfill_order.cairo @@ -20,12 +20,12 @@ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, setup, setup_default_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order + setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, create_limit_sell_order }; fn create_fulfill_info( - order_hash: felt252, fulfiller: ContractAddress, token_address: ContractAddress, token_id: u256 + order_hash: felt252, fulfiller: ContractAddress, token_address: ContractAddress, token_id: Option ) -> FulfillInfo { FulfillInfo { order_hash: order_hash, @@ -33,7 +33,7 @@ fn create_fulfill_info( fulfiller: fulfiller, token_chain_id: 'SN_MAIN', token_address: token_address, - token_id: Option::Some(token_id), + token_id: token_id, fulfill_broker_address: contract_address_const::<'broker'>() } } @@ -54,7 +54,7 @@ fn test_fulfill_offer_order_ok() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -80,7 +80,7 @@ fn test_fulfill_listing_order_ok() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -96,7 +96,7 @@ fn test_fulfill_order_fulfiller_shall_be_caller() { let caller = contract_address_const::<'caller'>(); let fulfiller = contract_address_const::<'fulfiller'>(); - let fulfill_info = create_fulfill_info(0x123, fulfiller, nft_address, 1); + let fulfill_info = create_fulfill_info(0x123, fulfiller, nft_address, Option::Some(1)); cheat_caller_address(executor_address, caller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -115,7 +115,7 @@ fn test_fulfill_listing_order_fulfiller_not_enough_erc20_token() { IFreeMintDispatcher { contract_address: erc20_address }.mint(fulfiller, start_amount - 100); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -136,7 +136,7 @@ fn test_fulfill_offer_order_fulfiller_not_owner() { executor_address, erc20_address, nft_address, token_id ); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -148,7 +148,7 @@ fn test_fulfill_order_not_found() { let (executor_address, _erc20_address, nft_address) = setup(); let fulfiller = contract_address_const::<'fulfiller'>(); - let fulfill_info = create_fulfill_info(0x1234, fulfiller, nft_address, 1); + let fulfill_info = create_fulfill_info(0x1234, fulfiller, nft_address, Option::Some(1)); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -172,7 +172,7 @@ fn test_fulfill_offer_order_offerer_not_enough_allowance() { IERC20Dispatcher { contract_address: erc20_address } .approve(executor_address, start_amount - 10); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -199,7 +199,7 @@ fn test_fulfill_listing_order_fulfiller_not_enough_allowance() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address } @@ -222,7 +222,7 @@ fn test_fulfill_listing_order_offerer_not_approved() { IFreeMintDispatcher { contract_address: erc20_address }.mint(fulfiller, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -248,7 +248,7 @@ fn test_fulfill_offer_order_fulfiller_not_approved() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -272,7 +272,7 @@ fn test_fulfill_offer_order_fulfiller_same_as_offerer() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -299,7 +299,7 @@ fn test_fulfill_listing_order_fulfiller_same_as_offerer() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -331,7 +331,7 @@ fn test_fulfill_auction_order_ok() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); @@ -341,6 +341,81 @@ fn test_fulfill_auction_order_ok() { IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); } + +#[test] +fn test_fulfill_limit_buy_order_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let start_amount = 10_000_000; + let quantity = 20_000_000; + + let (order_hash, buyer, _) = create_limit_buy_order( + executor_address, erc20_address, token_address, start_amount, quantity + ); + + let fulfiller = buyer; + + let seller = contract_address_const::<'seller'>(); + + IFreeMintDispatcher { contract_address: token_address }.mint(seller, quantity); + + let sell_order = setup_limit_sell_order(erc20_address, token_address, seller, start_amount, quantity); + + cheat_caller_address(executor_address, seller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(sell_order); + + // approve executor + cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); + + // approve executor + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); + fulfill_info.related_order_hash = Option::Some(seller_order.compute_order_hash()); + + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); +} + +#[test] +fn test_fulfill_limit_sell_order_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let end_amount = 50_000_000; + let start_amount = end_amount; + let quantity = 20_000_000; + + let (order_hash, seller, _) = create_limit_sell_order( + executor_address, erc20_address, token_address, end_amount, quantity + ); + + let fulfiller = seller; + + let buyer = contract_address_const::<'buyer'>(); + + IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, end_amount); + + let buy_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); + + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(buy_order); + + // approve executor + cheat_caller_address(token_address, buyer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + // approve executor + cheat_caller_address(erc20_address, seller, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); + fulfill_info.related_order_hash = Option::Some(buy_order.compute_order_hash()); + + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); +} + #[test] fn test_fulfill_auction_order_fulfiller_same_as_offerer() { let (executor_address, erc20_address, nft_address) = setup(); @@ -364,7 +439,7 @@ fn test_fulfill_auction_order_fulfiller_same_as_offerer() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); @@ -392,7 +467,7 @@ fn test_fulfill_order_not_enabled() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id); + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } From 6ca06fefef23c35cecc014768c2cd38cf9de0243 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Sat, 5 Oct 2024 08:48:55 +0100 Subject: [PATCH 12/18] fix happy path errors --- .../ark_common/src/protocol/order_v1.cairo | 17 ++++-- .../src/orderbook/orderbook.cairo | 17 +++--- contracts/ark_starknet/Scarb.toml | 1 + contracts/ark_starknet/src/executor.cairo | 14 +++-- .../ark_starknet/tests/common/setup.cairo | 8 +-- .../tests/integration/cancel_order.cairo | 10 ++-- .../tests/integration/fees_amount.cairo | 5 +- .../tests/integration/fulfill_order.cairo | 24 ++++----- contracts/ark_tokens/src/erc20_trade.cairo | 54 +++++++++++++++++++ contracts/ark_tokens/src/lib.cairo | 2 +- 10 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 contracts/ark_tokens/src/erc20_trade.cairo diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index ab98a207c..316fd9001 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -64,7 +64,7 @@ impl OrderTraitOrderV1 of OrderTrait { } // check for expiry only if not erc20 buys or sells - if (*self.route != RouteType::Erc20ToErc20Buy || *self.route != RouteType::Erc20ToErc20Sell) { + if (*self.route != RouteType::Erc20ToErc20Buy && *self.route != RouteType::Erc20ToErc20Sell) { let end_date = *self.end_date; if end_date < block_timestamp { @@ -76,6 +76,10 @@ impl OrderTraitOrderV1 of OrderTrait { if end_date > max_end_date { return Result::Err(OrderValidationError::EndDateTooFar); } + + if (*self.start_amount).is_zero(){ + return Result::Err(OrderValidationError::InvalidContent); + } } // TODO: define a real value here. 20 is an example and @@ -87,10 +91,9 @@ impl OrderTraitOrderV1 of OrderTrait { } if (*self.offerer).is_zero() - || (*self.token_address).is_zero() - || (*self.start_amount).is_zero() - || (*self.salt).is_zero() - || (*self.quantity).is_zero() { + || (*self.token_address).is_zero() + || (*self.salt).is_zero() + || (*self.quantity).is_zero() { return Result::Err(OrderValidationError::InvalidContent); } @@ -140,6 +143,10 @@ impl OrderTraitOrderV1 of OrderTrait { && (*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. diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 623aee755..3fc19b043 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -55,6 +55,9 @@ pub mod OrderbookComponent { OrderFulfilled: OrderFulfilled, } + // precision for erc20 price division + const PRECISION : u256 = 1000000000000000000; + // must be increased when `OrderPlaced` content change pub const ORDER_PLACED_EVENT_VERSION: u8 = 1; /// Event for when an order is placed. @@ -847,11 +850,11 @@ pub mod OrderbookComponent { match order_type { OrderType::LimitBuy => { - let price = order.start_amount / order.quantity; + let price = order.start_amount / order.quantity * PRECISION; self.buy_orders.write(order_hash, (price, order.quantity)); }, OrderType::LimitSell => { - let price = order.end_amount / order.quantity; + let price = order.end_amount / order.quantity * PRECISION; self.sell_orders.write(order_hash, (price, order.quantity)); }, _ => () @@ -889,7 +892,7 @@ pub mod OrderbookComponent { token_quantity, payment_from: buy_order.offerer, payment_to: sell_order.offerer, - payment_amount: price * token_quantity, + payment_amount: price * token_quantity / PRECISION, payment_currency_address: buy_order.currency_address, payment_currency_chain_id: buy_order.currency_chain_id, listing_broker_address: listing_broker_address, @@ -903,7 +906,6 @@ pub mod OrderbookComponent { fulfill_info: FulfillInfo, order: OrderV1 ) -> (Option, Option) { - assert(order.offerer != fulfill_info.fulfiller, orderbook_errors::ORDER_SAME_OFFERER); let order_hash = order.compute_order_hash(); assert( @@ -965,9 +967,11 @@ pub mod OrderbookComponent { _ => panic!("route not supported") }; + // add 1e18 to the multiplication; + // check that the price is the same - let buy_price = buy_order.start_amount / buy_order.quantity; - let sell_price = sell_order.end_amount / sell_order.quantity; + let buy_price = buy_order.start_amount / buy_order.quantity * PRECISION; + let sell_price = sell_order.end_amount / sell_order.quantity * PRECISION; let buy_order_hash = buy_order.compute_order_hash(); let sell_order_hash = sell_order.compute_order_hash(); @@ -1032,7 +1036,6 @@ pub mod OrderbookComponent { // execute both orders order_status_write(buy_order_hash, OrderStatus::Fulfilled); order_status_write(sell_order_hash, OrderStatus::Fulfilled); - // passing any of them as the order hash will fulfill both orders, // so just one executioninfo will be sent. let execute_info = self._create_listing_execution_info( diff --git a/contracts/ark_starknet/Scarb.toml b/contracts/ark_starknet/Scarb.toml index f23da1e15..edb5d78d7 100644 --- a/contracts/ark_starknet/Scarb.toml +++ b/contracts/ark_starknet/Scarb.toml @@ -30,6 +30,7 @@ casm = true allowed-libfuncs-list.name = "experimental" build-external-contracts = [ "ark_tokens::erc20::FreeMintERC20", + "ark_tokens::erc20_trade::TradeERC20", "ark_tokens::erc721::FreeMintNFT", "ark_tokens::erc721_royalty::FreeMintNFTRoyalty", ] diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index 697dde27f..ae4140641 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -371,22 +371,21 @@ mod executor { panic!("Order not found"); } - if order_info.order_type != OrderType::Auction { - assert!(order_info.offerer != fulfiller, "Offerer and fulfiller must be different"); - } - let contract_address = get_contract_address(); match order_info.order_type { OrderType::Listing => { + assert!(order_info.offerer != fulfiller, "Offerer and fulfiller must be different"); _verify_fulfill_listing_order(self, order_info, fulfill_info, contract_address); }, OrderType::Offer => { + assert!(order_info.offerer != fulfiller, "Offerer and fulfiller must be different"); _verify_fulfill_offer_order(self, order_info, fulfill_info, contract_address); }, OrderType::Auction => { _verify_fulfill_auction_order(self, order_info, fulfill_info, contract_address); }, OrderType::CollectionOffer => { + assert!(order_info.offerer != fulfiller, "Offerer and fulfiller must be different"); _verify_fulfill_collection_offer_order( self, order_info, fulfill_info, contract_address ); @@ -548,6 +547,10 @@ mod executor { let (buyer_order, seller_order) = match order_info.route { RouteType::Erc20ToErc20Sell => { + assert( + related_order_info.route == RouteType::Erc20ToErc20Buy, + 'Order route not valid' + ); (related_order_info, order_info) }, @@ -564,7 +567,7 @@ mod executor { let buyer = buyer_order.offerer; - // checks for buyer + //checks for buyer assert!( _check_erc20_amount( @buyer_order.currency_address, buyer_order.start_amount, @buyer @@ -672,6 +675,7 @@ mod executor { execution_info.token_id, execution_info.payment_amount ); + assert!( execution_info .payment_amount > (fulfill_broker_fees_amount diff --git a/contracts/ark_starknet/tests/common/setup.cairo b/contracts/ark_starknet/tests/common/setup.cairo index 10ad4ee2f..112e0aa85 100644 --- a/contracts/ark_starknet/tests/common/setup.cairo +++ b/contracts/ark_starknet/tests/common/setup.cairo @@ -29,10 +29,10 @@ fn deploy_erc20() -> ContractAddress { } fn deploy_erc20_2() -> ContractAddress { - let contract = declare("FreeMintERC202").unwrap().contract_class(); + let contract = declare("TradeERC20").unwrap().contract_class(); let initial_supply: u256 = 10_000_000_000_u256; - let name: ByteArray = "DummyERC202"; - let symbol: ByteArray = "DUMMY2"; + let name: ByteArray = "TradeERC20"; + let symbol: ByteArray = "TRADE"; let mut calldata: Array = array![]; initial_supply.serialize(ref calldata); @@ -235,7 +235,7 @@ fn setup_limit_sell_order( ) -> OrderV1 { setup_order( currency_address, - nft_address, + token_address, RouteType::Erc20ToErc20Sell, offerer, Option::None, diff --git a/contracts/ark_starknet/tests/integration/cancel_order.cairo b/contracts/ark_starknet/tests/integration/cancel_order.cairo index d4eb10647..02d97257a 100644 --- a/contracts/ark_starknet/tests/integration/cancel_order.cairo +++ b/contracts/ark_starknet/tests/integration/cancel_order.cairo @@ -231,7 +231,7 @@ fn test_cancel_limit_buy_order() { let start_amount = 10_000_000; let quantity = 5000; - let (order_hash, offerer, start_amount) = create_limit_buy_order( + let (order_hash, offerer, _) = create_limit_buy_order( executor_address, erc20_address, token_address, start_amount, quantity ); @@ -239,7 +239,7 @@ fn test_cancel_limit_buy_order() { order_hash, canceller: offerer, token_chain_id: 'SN_MAIN', - token_address: nft_address, + token_address: token_address, token_id: Option::None, }; @@ -284,7 +284,7 @@ fn test_cancel_limit_sell_order() { let end_amount = 10_000_000; let quantity = 5000; - let (order_hash, offerer, start_amount) = create_limit_sell_order( + let (order_hash, offerer, _) = create_limit_sell_order( executor_address, erc20_address, token_address, end_amount, quantity ); @@ -292,7 +292,7 @@ fn test_cancel_limit_sell_order() { order_hash, canceller: offerer, token_chain_id: 'SN_MAIN', - token_address: nft_address, + token_address: token_address, token_id: Option::None, }; @@ -332,7 +332,7 @@ fn test_cancel_limit_sell_order() { } #[test] -// #[should_panic] +#[should_panic(expected: 'OB: order fulfilled')] fn test_cancel_offer_order_already_cancelled() { let (executor_address, erc20_address, nft_address) = setup(); let token_id = 10; diff --git a/contracts/ark_starknet/tests/integration/fees_amount.cairo b/contracts/ark_starknet/tests/integration/fees_amount.cairo index 1000e32ba..83726ed09 100644 --- a/contracts/ark_starknet/tests/integration/fees_amount.cairo +++ b/contracts/ark_starknet/tests/integration/fees_amount.cairo @@ -1,6 +1,7 @@ use ark_starknet::interfaces::{ IExecutorDispatcher, IExecutorDispatcherTrait, FeesAmount, FeesRatio }; +use ark_common::protocol::order_types::{OptionU256, OptionU256Trait}; use snforge_std::{cheat_caller_address, CheatSpan}; use starknet::{ContractAddress, contract_address_const}; @@ -34,7 +35,7 @@ fn test_get_fees_amount_default_creator() { executor.set_default_creator_fees(creator, default_creator_fees_ratio); let fees_amount = executor - .get_fees_amount(fulfill_broker, listing_broker, nft_address, 1, amount); + .get_fees_amount(fulfill_broker, listing_broker, nft_address, OptionU256 {is_some: 0, value: 1}, amount); assert_eq!(fees_amount.fulfill_broker, 1_000_000, "Wrong amount for fulfill broker"); assert_eq!(fees_amount.listing_broker, 500_000, "Wrong amount for listing broker"); @@ -71,7 +72,7 @@ fn test_get_fees_amount_collection_creator() { executor.set_collection_creator_fees(nft_address, creator, collection_creator_fees_ratio); let fees_amount = executor - .get_fees_amount(fulfill_broker, listing_broker, nft_address, 1, amount); + .get_fees_amount(fulfill_broker, listing_broker, nft_address, OptionU256 {is_some: 0, value: 1}, amount); assert_eq!(fees_amount.fulfill_broker, 1_000_000, "Wrong amount for fulfill broker"); assert_eq!(fees_amount.listing_broker, 500_000, "Wrong amount for listing broker"); diff --git a/contracts/ark_starknet/tests/integration/fulfill_order.cairo b/contracts/ark_starknet/tests/integration/fulfill_order.cairo index 70c374f57..c3d5ed8c0 100644 --- a/contracts/ark_starknet/tests/integration/fulfill_order.cairo +++ b/contracts/ark_starknet/tests/integration/fulfill_order.cairo @@ -1,7 +1,6 @@ use ark_common::protocol::order_types::{FulfillInfo, OrderTrait, RouteType}; use ark_common::protocol::order_v1::OrderV1; - use ark_starknet::interfaces::{ IExecutorDispatcher, IExecutorDispatcherTrait, IMaintenanceDispatcher, IMaintenanceDispatcherTrait @@ -20,7 +19,8 @@ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, setup, setup_default_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, create_limit_sell_order + setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, create_limit_sell_order, setup_limit_sell_order, + setup_limit_buy_order }; @@ -346,7 +346,7 @@ fn test_fulfill_auction_order_ok() { fn test_fulfill_limit_buy_order_ok() { let (executor_address, erc20_address, token_address) = setup_erc20_order(); let start_amount = 10_000_000; - let quantity = 20_000_000; + let quantity = 20_000; let (order_hash, buyer, _) = create_limit_buy_order( executor_address, erc20_address, token_address, start_amount, quantity @@ -358,10 +358,10 @@ fn test_fulfill_limit_buy_order_ok() { IFreeMintDispatcher { contract_address: token_address }.mint(seller, quantity); - let sell_order = setup_limit_sell_order(erc20_address, token_address, seller, start_amount, quantity); + let seller_order = setup_limit_sell_order(erc20_address, token_address, seller, start_amount, quantity); cheat_caller_address(executor_address, seller, CheatSpan::TargetCalls(1)); - IExecutorDispatcher { contract_address: executor_address }.create_order(sell_order); + IExecutorDispatcher { contract_address: executor_address }.create_order(seller_order); // approve executor cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); @@ -394,23 +394,23 @@ fn test_fulfill_limit_sell_order_ok() { let buyer = contract_address_const::<'buyer'>(); - IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, end_amount); - - let buy_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); + IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); + let buyer_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); - IExecutorDispatcher { contract_address: executor_address }.create_order(buy_order); + IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); // approve executor - cheat_caller_address(token_address, buyer, CheatSpan::TargetCalls(1)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); // approve executor - cheat_caller_address(erc20_address, seller, CheatSpan::TargetCalls(1)); + cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); - fulfill_info.related_order_hash = Option::Some(buy_order.compute_order_hash()); + fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); diff --git a/contracts/ark_tokens/src/erc20_trade.cairo b/contracts/ark_tokens/src/erc20_trade.cairo new file mode 100644 index 000000000..e5603b166 --- /dev/null +++ b/contracts/ark_tokens/src/erc20_trade.cairo @@ -0,0 +1,54 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IFreeMint { + fn mint(ref self: T, recipient: ContractAddress, amount: u256); +} + +#[starknet::contract] +mod TradeERC20 { + use openzeppelin::token::erc20::ERC20Component; + use openzeppelin::token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + use super::IFreeMint; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + name: core::byte_array::ByteArray, + symbol: core::byte_array::ByteArray + ) { + self.erc20.initializer(name, symbol); + } + + #[abi(embed_v0)] + impl ImplFreeMint of IFreeMint { + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.erc20.mint(recipient, amount); + } + } +} diff --git a/contracts/ark_tokens/src/lib.cairo b/contracts/ark_tokens/src/lib.cairo index 1af6a81a3..72904dafc 100644 --- a/contracts/ark_tokens/src/lib.cairo +++ b/contracts/ark_tokens/src/lib.cairo @@ -1,4 +1,4 @@ mod erc20; mod erc721; mod erc721_royalty; - +mod erc20_trade; \ No newline at end of file From 8e4c140c2440166aca417fa5f85b1943c8edfcdd Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Mon, 7 Oct 2024 08:58:19 +0100 Subject: [PATCH 13/18] add more tests --bad paths --- .../ark_common/src/protocol/order_v1.cairo | 7 + .../tests/integration/create_order.cairo | 66 +++++++ .../tests/integration/fulfill_order.cairo | 185 ++++++++++++++---- 3 files changed, 223 insertions(+), 35 deletions(-) diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 316fd9001..7dcf86c36 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -76,10 +76,17 @@ impl OrderTraitOrderV1 of OrderTrait { 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 diff --git a/contracts/ark_starknet/tests/integration/create_order.cairo b/contracts/ark_starknet/tests/integration/create_order.cairo index 081b9f8fc..010a289df 100644 --- a/contracts/ark_starknet/tests/integration/create_order.cairo +++ b/contracts/ark_starknet/tests/integration/create_order.cairo @@ -571,3 +571,69 @@ fn test_create_collection_offer_order_expired() { IExecutorDispatcher { contract_address: executor_address }.create_order(order); stop_cheat_block_timestamp_global(); } + +#[test] +#[should_panic(expected: "Oferrer does not own enough ERC20 tokens to buy")] +fn test_create_limit_buy_order_offerer_not_enough_erc20_tokens() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let start_amount = 10_000_000; + let minted = 10_000; + let quantity = 100_000; + + Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, minted); + + let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); +} + +#[test] +#[should_panic(expected: "Oferrer does not own enough ERC20 tokens to sell")] +fn test_create_limit_sell_order_offerer_not_enough_erc20_tokens() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let end_amount = 10_000_000; + let minted = 10_000; + let quantity = 100_000; + + Erc20Dispatcher { contract_address: token_address }.mint(offerer, minted); + + let order = setup_limit_sell_order(erc20_address, token_address, offerer, end_amount, quantity); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); +} + +#[test] +#[should_panic(expected: 'OB: order already exists')] +fn test_create_limit_buy_order_twice() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let start_amount = 10_000_000; + let quantity = 5_000_000; + Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount); + + let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(2)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); +} + +#[test] +#[should_panic(expected: 'OB: order already exists')] +fn test_create_limit_sell_order_twice() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let offerer = contract_address_const::<'offerer'>(); + let end_amount = 10_000_000; + let quantity = 5_000_000; + Erc20Dispatcher { contract_address: token_address }.mint(offerer, quantity); + + let order = setup_limit_sell_order(erc20_address, token_address, offerer, end_amount, quantity); + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(2)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); +} \ No newline at end of file diff --git a/contracts/ark_starknet/tests/integration/fulfill_order.cairo b/contracts/ark_starknet/tests/integration/fulfill_order.cairo index c3d5ed8c0..9c9a767d5 100644 --- a/contracts/ark_starknet/tests/integration/fulfill_order.cairo +++ b/contracts/ark_starknet/tests/integration/fulfill_order.cairo @@ -342,6 +342,71 @@ fn test_fulfill_auction_order_ok() { } +#[test] +fn test_fulfill_auction_order_fulfiller_same_as_offerer() { + let (executor_address, erc20_address, nft_address) = setup(); + let start_amount = 10_000_000; + let end_amount = 20_000_000; + let buyer = contract_address_const::<'buyer'>(); + + let (order_hash, offerer, token_id) = create_auction_order( + executor_address, erc20_address, nft_address, start_amount, end_amount + ); + let fulfiller = offerer; + + IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); + + let buyer_order = setup_offer_order(erc20_address, nft_address, buyer, token_id, start_amount); + + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); + + cheat_caller_address(nft_address, offerer, CheatSpan::TargetCalls(1)); + IERC721Dispatcher { contract_address: nft_address } + .set_approval_for_all(executor_address, true); + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); + + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); +} + +#[test] +#[should_panic(expected: 'Executor not enabled')] +fn test_fulfill_order_not_enabled() { + let (executor_address, erc20_address, nft_address) = setup(); + let admin = contract_address_const::<'admin'>(); + let fulfiller = contract_address_const::<'fulfiller'>(); + + let token_id: u256 = Erc721Dispatcher { contract_address: nft_address } + .get_current_token_id() + .into(); + Erc721Dispatcher { contract_address: nft_address }.mint(fulfiller, 'base_uri'); + let (order_hash, offerer, start_amount) = create_offer_order( + executor_address, erc20_address, nft_address, token_id + ); + + cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + + cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); + IERC721Dispatcher { contract_address: nft_address } + .set_approval_for_all(executor_address, true); + + cheat_caller_address(executor_address, admin, CheatSpan::TargetCalls(1)); + IMaintenanceDispatcher { contract_address: executor_address }.set_maintenance_mode(true); + + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); +} + + #[test] fn test_fulfill_limit_buy_order_ok() { let (executor_address, erc20_address, token_address) = setup_erc20_order(); @@ -417,64 +482,114 @@ fn test_fulfill_limit_sell_order_ok() { } #[test] -fn test_fulfill_auction_order_fulfiller_same_as_offerer() { - let (executor_address, erc20_address, nft_address) = setup(); +#[should_panic(expected: 'Order route not valid')] +fn test_fulfill_limit_buy_order_with_buy_order() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); let start_amount = 10_000_000; - let end_amount = 20_000_000; - let buyer = contract_address_const::<'buyer'>(); + let quantity = 20_000; - let (order_hash, offerer, token_id) = create_auction_order( - executor_address, erc20_address, nft_address, start_amount, end_amount + let (order_hash, buyer, _) = create_limit_buy_order( + executor_address, erc20_address, token_address, start_amount, quantity ); - let fulfiller = offerer; - IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); + let fulfiller = buyer; - let buyer_order = setup_offer_order(erc20_address, nft_address, buyer, token_id, start_amount); + let wrong_seller = contract_address_const::<'seller'>(); - cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); - IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); + IFreeMintDispatcher { contract_address: erc20_address }.mint(wrong_seller, start_amount); - cheat_caller_address(nft_address, offerer, CheatSpan::TargetCalls(1)); - IERC721Dispatcher { contract_address: nft_address } - .set_approval_for_all(executor_address, true); + let wrong_order = setup_limit_buy_order(erc20_address, token_address, wrong_seller, start_amount, quantity); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); - fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); + cheat_caller_address(executor_address, wrong_seller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(wrong_order); + // approve executor + cheat_caller_address(erc20_address, wrong_seller, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + // approve executor cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); + fulfill_info.related_order_hash = Option::Some(wrong_order.compute_order_hash()); + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); } + #[test] -#[should_panic(expected: 'Executor not enabled')] -fn test_fulfill_order_not_enabled() { - let (executor_address, erc20_address, nft_address) = setup(); - let admin = contract_address_const::<'admin'>(); - let fulfiller = contract_address_const::<'fulfiller'>(); +#[should_panic(expected: 'Order route not valid')] +fn test_fulfill_limit_sell_order_with_sell_order_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let end_amount = 50_000_000; + let quantity = 20_000_000; - let token_id: u256 = Erc721Dispatcher { contract_address: nft_address } - .get_current_token_id() - .into(); - Erc721Dispatcher { contract_address: nft_address }.mint(fulfiller, 'base_uri'); - let (order_hash, offerer, start_amount) = create_offer_order( - executor_address, erc20_address, nft_address, token_id + let (order_hash, seller, _) = create_limit_sell_order( + executor_address, erc20_address, token_address, end_amount, quantity ); - cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); - IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + let fulfiller = seller; - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let wrong_buyer = contract_address_const::<'buyer'>(); - cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); - IERC721Dispatcher { contract_address: nft_address } - .set_approval_for_all(executor_address, true); + IFreeMintDispatcher { contract_address: token_address }.mint(wrong_buyer, quantity); - cheat_caller_address(executor_address, admin, CheatSpan::TargetCalls(1)); - IMaintenanceDispatcher { contract_address: executor_address }.set_maintenance_mode(true); + let wrong_order = setup_limit_sell_order(erc20_address, token_address, wrong_buyer, end_amount, quantity); + + cheat_caller_address(executor_address, wrong_buyer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(wrong_order); + + // approve executor + cheat_caller_address(token_address, wrong_buyer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); + + // approve executor + cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); + fulfill_info.related_order_hash = Option::Some(wrong_order.compute_order_hash()); + + cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); +} + +#[test] +#[should_panic(expected: 'OB: order price not match')] +fn test_fulfill_limit_order_without_matching_price_ok() { + let (executor_address, erc20_address, token_address) = setup_erc20_order(); + let end_amount = 50_000_000; + let start_amount = 100_000_000; + let quantity = 20_000_000; + + let (order_hash, seller, _) = create_limit_sell_order( + executor_address, erc20_address, token_address, end_amount, quantity + ); + + let fulfiller = seller; + + let buyer = contract_address_const::<'buyer'>(); + + IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); + + let buyer_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); + + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); + + // approve executor + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); + + // approve executor + cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); + IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); + + let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); + fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); From 75080de8c6b41aa37f977fa62ebcdb279b75b151 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Mon, 7 Oct 2024 08:58:53 +0100 Subject: [PATCH 14/18] clean ups --- packages/deployer/src/contracts/erc20trade.ts | 40 ++++ turbo.json | 198 +++++++++--------- 2 files changed, 139 insertions(+), 99 deletions(-) create mode 100644 packages/deployer/src/contracts/erc20trade.ts diff --git a/packages/deployer/src/contracts/erc20trade.ts b/packages/deployer/src/contracts/erc20trade.ts new file mode 100644 index 000000000..68b110821 --- /dev/null +++ b/packages/deployer/src/contracts/erc20trade.ts @@ -0,0 +1,40 @@ +import * as sn from "starknet"; + +import { loadArtifacts } from "./common"; + +/** + * Declare and deploys orderbook contract. + * Returns the contract object. + */ +export async function deployERC20( + artifactsPath: string, + account: sn.Account, + provider: sn.RpcProvider, + name: string, + symbol: string +): Promise { + const artifacts = loadArtifacts(artifactsPath, "ark_tokens_TradeERC20"); + const contractCallData = new sn.CallData(artifacts.sierra.abi); + const contractConstructor = contractCallData.compile("constructor", { + initial_supply: sn.cairo.uint256(0), + name, + symbol + }); + + const deployR = await account.declareAndDeploy({ + contract: artifacts.sierra, + casm: artifacts.casm, + constructorCalldata: contractConstructor, + salt: "1337" + }); + + if (deployR.declare.transaction_hash) { + await provider.waitForTransaction(deployR.declare.transaction_hash); + } + + return new sn.Contract( + artifacts.sierra.abi, + deployR.deploy.contract_address, + provider + ); +} diff --git a/turbo.json b/turbo.json index f16e72e19..822314520 100644 --- a/turbo.json +++ b/turbo.json @@ -1,101 +1,101 @@ { - "$schema": "https://turbo.build/schema.json", - "globalDependencies": ["**/.env.*local"], - "tasks": { - "build:packages": { - "dependsOn": ["^build"], - "outputs": [ - ".next/**", - "!.next/cache/**", - ".vercel/output/**", - ".dist/**" - ] - }, - "build": { - "cache": false, - "dependsOn": ["^build"], - "outputs": [ - ".next/**", - "!.next/cache/**", - ".vercel/output/**", - ".dist/**" - ] - }, - "@ark-project/demo#build": { - "dependsOn": ["^build"], - "env": [ - "NEXT_PUBLIC_NFT_API_KEY", - "NEXT_PUBLIC_ORDERBOOK_API_URL", - "NEXT_PUBLIC_NFT_API_URL" - ], - "outputs": [".next/**", "!.next/cache/**", ".vercel/output/**"] - }, - "dev": { - "dependsOn": ["^dev"], - "outputs": [".dist/**"] - }, - "lint": { - "dependsOn": ["^lint"] - }, - "test": { - "dependsOn": ["^test"] - }, - "lint:fix": { - "dependsOn": ["^lint:fix"] - }, - "clean": { - "cache": false - }, - "//#clean": { - "cache": false - }, - "deploy:solis": { - "cache": false, - "persistent": true - }, - "deploy:solis:local": { - "cache": false - }, - "deploy:starknet": { - "cache": false, - "persistent": true - }, - "deploy:starknet:local": { - "cache": false - }, - "deploy:starknet:tokens": { - "cache": false - }, - "accounts:new": { - "cache": false, - "persistent": true - }, - "accounts:deploy": { - "cache": false, - "persistent": true - } + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build:packages": { + "dependsOn": ["^build"], + "outputs": [ + ".next/**", + "!.next/cache/**", + ".vercel/output/**", + ".dist/**" + ] }, - "globalEnv": [ - "ACCOUNT_CLASS_HASH", - "CI", - "SOLIS_ACCOUNT_CLASS_HASH", - "SOLIS_ADMIN_ADDRESS", - "SOLIS_ADMIN_ADDRESS_DEV", - "SOLIS_ADMIN_PRIVATE_KEY", - "SOLIS_NODE_URL", - "STARKNET_ACCOUNT1_ADDRESS", - "STARKNET_ACCOUNT1_PRIVATE_KEY", - "STARKNET_ACCOUNT2_ADDRESS", - "STARKNET_ACCOUNT2_PRIVATE_KEY", - "STARKNET_ADMIN_ADDRESS_DEV", - "STARKNET_ADMIN_PRIVATE_KEY_DEV", - "STARKNET_CURRENCY_ADDRESS", - "STARKNET_EXECUTOR_ADDRESS_DEV", - "STARKNET_NETWORK", - "STARKNET_NETWORK_ID", - "STARKNET_NFT_ADDRESS_DEV", - "STARKNET_RPC_URL", - "STARKNET_ADMIN_PRIVATE_KEY", - "STARKNET_ADMIN_ADDRESS" - ] - } \ No newline at end of file + "build": { + "cache": false, + "dependsOn": ["^build"], + "outputs": [ + ".next/**", + "!.next/cache/**", + ".vercel/output/**", + ".dist/**" + ] + }, + "@ark-project/demo#build": { + "dependsOn": ["^build"], + "env": [ + "NEXT_PUBLIC_NFT_API_KEY", + "NEXT_PUBLIC_ORDERBOOK_API_URL", + "NEXT_PUBLIC_NFT_API_URL" + ], + "outputs": [".next/**", "!.next/cache/**", ".vercel/output/**"] + }, + "dev": { + "dependsOn": ["^dev"], + "outputs": [".dist/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "test": { + "dependsOn": ["^test"] + }, + "lint:fix": { + "dependsOn": ["^lint:fix"] + }, + "clean": { + "cache": false + }, + "//#clean": { + "cache": false + }, + "deploy:solis": { + "cache": false, + "persistent": true + }, + "deploy:solis:local": { + "cache": false + }, + "deploy:starknet": { + "cache": false, + "persistent": true + }, + "deploy:starknet:local": { + "cache": false + }, + "deploy:starknet:tokens": { + "cache": false + }, + "accounts:new": { + "cache": false, + "persistent": true + }, + "accounts:deploy": { + "cache": false, + "persistent": true + } + }, + "globalEnv": [ + "ACCOUNT_CLASS_HASH", + "CI", + "SOLIS_ACCOUNT_CLASS_HASH", + "SOLIS_ADMIN_ADDRESS", + "SOLIS_ADMIN_ADDRESS_DEV", + "SOLIS_ADMIN_PRIVATE_KEY", + "SOLIS_NODE_URL", + "STARKNET_ACCOUNT1_ADDRESS", + "STARKNET_ACCOUNT1_PRIVATE_KEY", + "STARKNET_ACCOUNT2_ADDRESS", + "STARKNET_ACCOUNT2_PRIVATE_KEY", + "STARKNET_ADMIN_ADDRESS_DEV", + "STARKNET_ADMIN_PRIVATE_KEY_DEV", + "STARKNET_CURRENCY_ADDRESS", + "STARKNET_EXECUTOR_ADDRESS_DEV", + "STARKNET_NETWORK", + "STARKNET_NETWORK_ID", + "STARKNET_NFT_ADDRESS_DEV", + "STARKNET_RPC_URL", + "STARKNET_ADMIN_PRIVATE_KEY", + "STARKNET_ADMIN_ADDRESS" + ] +} From 67f86f3773e4e2bd62d084905cfdb44cf33d4d57 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Tue, 8 Oct 2024 09:33:06 +0100 Subject: [PATCH 15/18] add deploy erc20 trade token --- packages/deployer/scripts/deploy.ts | 12 +++++++++++- packages/deployer/src/contracts/erc20trade.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/deployer/scripts/deploy.ts b/packages/deployer/scripts/deploy.ts index 0617d0429..3a7d9e566 100644 --- a/packages/deployer/scripts/deploy.ts +++ b/packages/deployer/scripts/deploy.ts @@ -5,6 +5,7 @@ import { Account, RpcProvider } from "starknet"; import { ARTIFACTS_PATH } from "../src/constants"; import { deployERC20 } from "../src/contracts/erc20"; +import { deployERC20Trade } from "../src/contracts/erc20trade"; import { deployERC721 } from "../src/contracts/erc721"; import { deployERC721Royalties } from "../src/contracts/erc721royalties"; import { deployExecutor } from "../src/contracts/executor"; @@ -44,6 +45,14 @@ async function run() { "ETH" ); + const ethTradeContract = await deployERC20Trade( + ARTIFACTS_PATH, + starknetAdminAccount, + provider, + "ETH", + "ETH" + ); + const nftContract = await deployERC721( ARTIFACTS_PATH, starknetAdminAccount, @@ -73,7 +82,8 @@ async function run() { nftContract: nftContract.address, nftContractFixedFees: nftContractFixedFees.address, nftContractRoyalties: nftContractRoyalties.address, - eth: ethContract.address + eth: ethContract.address, + ethTrade: ethTradeContract.address }); await fs.writeFile(contractsFilePath, contractsContent); } diff --git a/packages/deployer/src/contracts/erc20trade.ts b/packages/deployer/src/contracts/erc20trade.ts index 68b110821..f1c3f2fe0 100644 --- a/packages/deployer/src/contracts/erc20trade.ts +++ b/packages/deployer/src/contracts/erc20trade.ts @@ -6,7 +6,7 @@ import { loadArtifacts } from "./common"; * Declare and deploys orderbook contract. * Returns the contract object. */ -export async function deployERC20( +export async function deployERC20Trade( artifactsPath: string, account: sn.Account, provider: sn.RpcProvider, From b4a5fd77f097bf42c6de5ced9c1b17d1b8c3b4a9 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Tue, 8 Oct 2024 09:56:32 +0100 Subject: [PATCH 16/18] fix fmt --- .../ark_common/src/protocol/order_types.cairo | 14 +- .../ark_common/src/protocol/order_v1.cairo | 34 +++-- .../src/orderbook/orderbook.cairo | 144 ++++++++---------- contracts/ark_starknet/src/executor.cairo | 75 ++++----- contracts/ark_starknet/src/interfaces.cairo | 7 +- .../ark_starknet/tests/common/setup.cairo | 6 +- .../tests/integration/cancel_order.cairo | 5 +- .../tests/integration/create_order.cairo | 18 ++- .../tests/integration/fees_amount.cairo | 10 +- .../tests/integration/fulfill_order.cairo | 89 +++++++---- contracts/ark_tokens/src/lib.cairo | 2 +- 11 files changed, 212 insertions(+), 192 deletions(-) diff --git a/contracts/ark_common/src/protocol/order_types.cairo b/contracts/ark_common/src/protocol/order_types.cairo index 2673dc08a..68e8f561a 100644 --- a/contracts/ark_common/src/protocol/order_types.cairo +++ b/contracts/ark_common/src/protocol/order_types.cairo @@ -38,9 +38,9 @@ impl Felt252TryIntoOrderType of TryInto { Option::Some(OrderType::Offer) } else if self == 'COLLECTION_OFFER' { Option::Some(OrderType::CollectionOffer) - } else if self == 'LIMIT_BUY'{ + } else if self == 'LIMIT_BUY' { Option::Some(OrderType::LimitBuy) - }else if self == 'LIMIT_SELL'{ + } else if self == 'LIMIT_SELL' { Option::Some(OrderType::LimitSell) } else { Option::None @@ -292,9 +292,9 @@ impl Felt252TryIntoRoute of TryInto { Option::Some(RouteType::Erc20ToErc721) } else if self == 'ERC721TOERC20' { Option::Some(RouteType::Erc721ToErc20) - } else if self == 'ERC20TOERC20BUY'{ + } else if self == 'ERC20TOERC20BUY' { Option::Some(RouteType::Erc20ToErc20Buy) - } else if self == 'ERC20TOERC20SELL'{ + } else if self == 'ERC20TOERC20SELL' { Option::Some(RouteType::Erc20ToErc20Sell) } else { Option::None @@ -304,8 +304,8 @@ impl Felt252TryIntoRoute of TryInto { #[derive(starknet::Store, Serde, Drop, PartialEq, Copy, Debug)] struct OptionU256 { - is_some: felt252, - value: u256, + is_some: felt252, + value: u256, } trait OptionU256Trait, +Drop> { @@ -319,7 +319,7 @@ impl OptionU256Impl of OptionU256Trait { } fn is_some(self: @OptionU256) -> bool { if *self.is_some == 1 { - true + true } else { false } diff --git a/contracts/ark_common/src/protocol/order_v1.cairo b/contracts/ark_common/src/protocol/order_v1.cairo index 7dcf86c36..7c2b34c4c 100644 --- a/contracts/ark_common/src/protocol/order_v1.cairo +++ b/contracts/ark_common/src/protocol/order_v1.cairo @@ -64,13 +64,14 @@ impl OrderTraitOrderV1 of OrderTrait { } // check for expiry only if not erc20 buys or sells - if (*self.route != RouteType::Erc20ToErc20Buy && *self.route != RouteType::Erc20ToErc20Sell) { + 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); } - + // End date -> block_timestamp + 30 days. let max_end_date = block_timestamp + (30 * 24 * 60 * 60); if end_date > max_end_date { @@ -79,12 +80,12 @@ impl OrderTraitOrderV1 of OrderTrait { } // check that the start amount is not zero for sell erc20 orders - if(*self.route != RouteType::Erc20ToErc20Sell){ - if (*self.start_amount).is_zero(){ + if (*self.route != RouteType::Erc20ToErc20Sell) { + if (*self.start_amount).is_zero() { return Result::Err(OrderValidationError::InvalidContent); } - }else{ - if (*self.end_amount).is_zero(){ + } else { + if (*self.end_amount).is_zero() { return Result::Err(OrderValidationError::InvalidContent); } } @@ -98,9 +99,9 @@ impl OrderTraitOrderV1 of OrderTrait { } if (*self.offerer).is_zero() - || (*self.token_address).is_zero() - || (*self.salt).is_zero() - || (*self.quantity).is_zero() { + || (*self.token_address).is_zero() + || (*self.salt).is_zero() + || (*self.quantity).is_zero() { return Result::Err(OrderValidationError::InvalidContent); } @@ -135,16 +136,16 @@ impl OrderTraitOrderV1 of OrderTrait { return Result::Ok(OrderType::CollectionOffer); } - // Limit Buy Order - if(*self.quantity) > 0 + // 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 + // Limit Sell Order + if (*self.quantity) > 0 && (*self.start_amount).is_zero() && (*self.end_amount) > 0 // amount to receive && (*self.route == RouteType::Erc20ToErc20Sell) { @@ -161,13 +162,14 @@ impl OrderTraitOrderV1 of OrderTrait { } fn compute_token_hash(self: @OrderV1) -> felt252 { - if (*self.route == RouteType::Erc20ToErc20Buy || *self.route == RouteType::Erc20ToErc20Sell) { + if (*self.route == RouteType::Erc20ToErc20Buy + || *self.route == RouteType::Erc20ToErc20Sell) { let mut buf: Array = 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{ + } else { assert(OptionTrait::is_some(self.token_id), 'Token ID expected'); let token_id = (*self.token_id).unwrap(); let mut buf: Array = array![]; @@ -184,4 +186,4 @@ impl OrderTraitOrderV1 of OrderTrait { self.serialize(ref buf); poseidon_hash_span(buf.span()) } -} \ No newline at end of file +} diff --git a/contracts/ark_component/src/orderbook/orderbook.cairo b/contracts/ark_component/src/orderbook/orderbook.cairo index 3fc19b043..34038f653 100644 --- a/contracts/ark_component/src/orderbook/orderbook.cairo +++ b/contracts/ark_component/src/orderbook/orderbook.cairo @@ -56,7 +56,7 @@ pub mod OrderbookComponent { } // precision for erc20 price division - const PRECISION : u256 = 1000000000000000000; + const PRECISION: u256 = 1000000000000000000; // must be increased when `OrderPlaced` content change pub const ORDER_PLACED_EVENT_VERSION: u8 = 1; @@ -283,12 +283,8 @@ pub mod OrderbookComponent { OrderType::CollectionOffer => { self._create_collection_offer(order, order_type, order_hash); }, - OrderType::LimitBuy => { - self._create_limit_order(order, order_type, order_hash); - }, - OrderType::LimitSell => { - self._create_limit_order(order, order_type, order_hash); - } + OrderType::LimitBuy => { self._create_limit_order(order, order_type, order_hash); }, + OrderType::LimitSell => { self._create_limit_order(order, order_type, order_hash); } }; HooksCreateOrder::after_create_order(ref self, order); @@ -302,13 +298,15 @@ pub mod OrderbookComponent { let order = order_option.unwrap(); assert(order.offerer == cancel_info.canceller, 'not the same offerrer'); match order_status_read(order_hash) { - Option::Some(s) => assert(s == OrderStatus::Open, orderbook_errors::ORDER_FULFILLED), + Option::Some(s) => assert( + s == OrderStatus::Open, orderbook_errors::ORDER_FULFILLED + ), Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND), }; let block_ts = starknet::get_block_timestamp(); let order_type = match order_type_read(order_hash) { Option::Some(order_type) => { - if order_type == OrderType::Auction { + if order_type == OrderType::Auction { let auction_token_hash = order.compute_token_hash(); let (_, auction_end_date, _) = self.auctions.read(auction_token_hash); assert( @@ -317,7 +315,7 @@ pub mod OrderbookComponent { self.auctions.write(auction_token_hash, (0, 0, 0)); } else if order_type == OrderType::LimitBuy { self.buy_orders.write(order_hash, (0, 0)); - }else if order_type == OrderType::LimitSell { + } else if order_type == OrderType::LimitSell { self.sell_orders.write(order_hash, (0, 0)); } else { assert(block_ts < order.end_date, orderbook_errors::ORDER_IS_EXPIRED); @@ -474,7 +472,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: order.offerer, token_to: related_order.offerer, - token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap()}, + token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap() }, token_quantity: 1, payment_from: related_order.offerer, payment_to: fulfill_info.fulfiller, @@ -521,7 +519,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: fulfill_info.fulfiller, token_to: order.offerer, - token_id: OptionU256 { is_some: 1, value: fulfill_info.token_id.unwrap()}, + token_id: OptionU256 { is_some: 1, value: fulfill_info.token_id.unwrap() }, token_quantity: 1, payment_from: order.offerer, payment_to: fulfill_info.fulfiller, @@ -555,7 +553,7 @@ pub mod OrderbookComponent { token_address: order.token_address, token_from: order.offerer, token_to: fulfill_info.fulfiller, - token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap()}, + token_id: OptionU256 { is_some: 1, value: order.token_id.unwrap() }, token_quantity: 1, payment_from: fulfill_info.fulfiller, payment_to: order.offerer, @@ -831,9 +829,9 @@ pub mod OrderbookComponent { /// Creates a limit buy order fn _create_limit_order( - ref self: ComponentState, - order: OrderV1, - order_type: OrderType, + ref self: ComponentState, + order: OrderV1, + order_type: OrderType, order_hash: felt252 ) { // revert if order is fulfilled or Open @@ -845,9 +843,9 @@ pub mod OrderbookComponent { ); } let cancelled_order_hash = self._process_previous_order(order_hash, order.offerer); - + order_write(order_hash, order_type, order); - + match order_type { OrderType::LimitBuy => { let price = order.start_amount / order.quantity * PRECISION; @@ -859,7 +857,7 @@ pub mod OrderbookComponent { }, _ => () } - + self .emit( OrderPlaced { @@ -888,7 +886,7 @@ pub mod OrderbookComponent { token_address: buy_order.token_address, token_from: sell_order.offerer, token_to: buy_order.offerer, - token_id: OptionU256 { is_some: 0, value: 0}, + token_id: OptionU256 { is_some: 0, value: 0 }, token_quantity, payment_from: buy_order.offerer, payment_to: sell_order.offerer, @@ -902,15 +900,12 @@ pub mod OrderbookComponent { /// Fulfill limit order fn _fulfill_limit_order( - ref self: ComponentState, - fulfill_info: FulfillInfo, - order: OrderV1 + ref self: ComponentState, fulfill_info: FulfillInfo, order: OrderV1 ) -> (Option, Option) { let order_hash = order.compute_order_hash(); - + assert( - order_hash == fulfill_info.order_hash, - orderbook_errors::ORDER_HASH_DOES_NOT_MATCH + order_hash == fulfill_info.order_hash, orderbook_errors::ORDER_HASH_DOES_NOT_MATCH ); let related_order_hash = fulfill_info @@ -948,18 +943,17 @@ pub mod OrderbookComponent { orderbook_errors::ORDER_TOKEN_HASH_DOES_NOT_MATCH ); - let (buy_order, sell_order) = match order.route { + let (buy_order, sell_order) = match order.route { RouteType::Erc20ToErc20Sell => { assert( - related_order.route == RouteType::Erc20ToErc20Buy, + related_order.route == RouteType::Erc20ToErc20Buy, orderbook_errors::ORDER_ROUTE_NOT_VALID ); (related_order, order) }, - RouteType::Erc20ToErc20Buy => { assert( - related_order.route == RouteType::Erc20ToErc20Sell, + related_order.route == RouteType::Erc20ToErc20Sell, orderbook_errors::ORDER_ROUTE_NOT_VALID ); (order, related_order) @@ -975,11 +969,8 @@ pub mod OrderbookComponent { let buy_order_hash = buy_order.compute_order_hash(); let sell_order_hash = sell_order.compute_order_hash(); - - assert( - buy_price == sell_price, - orderbook_errors::ORDER_PRICE_NOT_MATCH - ); + + assert(buy_price == sell_price, orderbook_errors::ORDER_PRICE_NOT_MATCH); let (_, buy_order_quantity) = self.buy_orders.read(buy_order_hash); let (_, sell_order_quantity) = self.sell_orders.read(sell_order_hash); @@ -987,67 +978,58 @@ pub mod OrderbookComponent { if buy_order_quantity > sell_order_quantity { // reduce buy quantity order and execute sell order self - .buy_orders - .write( - buy_order_hash, - ( - buy_price, - buy_order_quantity - sell_order_quantity - ) - ); + .buy_orders + .write(buy_order_hash, (buy_price, buy_order_quantity - sell_order_quantity)); // set buy order as fufilled order_status_write(sell_order_hash, OrderStatus::Fulfilled); // set execute info - let execute_info = self._create_listing_execution_info( - sell_order_hash, - buy_order, - sell_order, - fulfill_info, - sell_order_quantity, - related_order.broker_id, - buy_price - ); + let execute_info = self + ._create_listing_execution_info( + sell_order_hash, + buy_order, + sell_order, + fulfill_info, + sell_order_quantity, + related_order.broker_id, + buy_price + ); (Option::Some(execute_info), Option::Some(related_order_hash)) - }else if sell_order_quantity > buy_order_quantity { + } else if sell_order_quantity > buy_order_quantity { // reduce sell quantity, and execute buy order self - .sell_orders - .write( - sell_order_hash, - ( - sell_price, - sell_order_quantity - buy_order_quantity - ) - ); + .sell_orders + .write(sell_order_hash, (sell_price, sell_order_quantity - buy_order_quantity)); // set sell order as fulfilled order_status_write(buy_order_hash, OrderStatus::Fulfilled); // generate execution info - let execute_info = self._create_listing_execution_info( - buy_order_hash, - buy_order, - sell_order, - fulfill_info, - buy_order_quantity, - order.broker_id, - buy_price - ); + let execute_info = self + ._create_listing_execution_info( + buy_order_hash, + buy_order, + sell_order, + fulfill_info, + buy_order_quantity, + order.broker_id, + buy_price + ); (Option::Some(execute_info), Option::Some(related_order_hash)) - }else{ + } else { // execute both orders order_status_write(buy_order_hash, OrderStatus::Fulfilled); order_status_write(sell_order_hash, OrderStatus::Fulfilled); // passing any of them as the order hash will fulfill both orders, // so just one executioninfo will be sent. - let execute_info = self._create_listing_execution_info( - buy_order_hash, - buy_order, - sell_order, - fulfill_info, - buy_order_quantity, - order.broker_id, - buy_price - ); - // return + let execute_info = self + ._create_listing_execution_info( + buy_order_hash, + buy_order, + sell_order, + fulfill_info, + buy_order_quantity, + order.broker_id, + buy_price + ); + // return (Option::Some(execute_info), Option::Some(related_order_hash)) } } diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index ae4140641..96c814633 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -17,7 +17,7 @@ struct OrderInfo { // address making the order offerer: ContractAddress, // number of tokens - quantity: u256,// 0 for ERC721, + quantity: u256, // 0 for ERC721, // route type route: RouteType } @@ -334,9 +334,7 @@ mod executor { }, RouteType::Erc20ToErc20Sell => { assert!( - _check_erc20_amount( - order.token_address, *(order.quantity), order.offerer - ), + _check_erc20_amount(order.token_address, *(order.quantity), order.offerer), "Oferrer does not own enough ERC20 tokens to sell" ) } @@ -391,14 +389,10 @@ mod executor { ); }, OrderType::LimitBuy => { - _verify_limit_order( - self, order_info, fulfill_info, contract_address - ) + _verify_limit_order(self, order_info, fulfill_info, contract_address) }, OrderType::LimitSell => { - _verify_limit_order( - self, order_info, fulfill_info, contract_address - ) + _verify_limit_order(self, order_info, fulfill_info, contract_address) }, _ => panic!("Order not supported") } @@ -545,20 +539,17 @@ mod executor { "Order and related order use different currency" ); - let (buyer_order, seller_order) = match order_info.route { + let (buyer_order, seller_order) = match order_info.route { RouteType::Erc20ToErc20Sell => { assert( - related_order_info.route == RouteType::Erc20ToErc20Buy, - 'Order route not valid' + related_order_info.route == RouteType::Erc20ToErc20Buy, 'Order route not valid' ); - + (related_order_info, order_info) }, - RouteType::Erc20ToErc20Buy => { assert( - related_order_info.route == RouteType::Erc20ToErc20Sell, - 'Order route not valid' + related_order_info.route == RouteType::Erc20ToErc20Sell, 'Order route not valid' ); (order_info, related_order_info) }, @@ -569,9 +560,7 @@ mod executor { //checks for buyer assert!( - _check_erc20_amount( - @buyer_order.currency_address, buyer_order.start_amount, @buyer - ), + _check_erc20_amount(@buyer_order.currency_address, buyer_order.start_amount, @buyer), "Buyer does not own enough ERC20 tokens" ); @@ -589,18 +578,13 @@ mod executor { // checks for seller assert!( - _check_erc20_amount( - @seller_order.token_address, seller_order.quantity, @seller - ), + _check_erc20_amount(@seller_order.token_address, seller_order.quantity, @seller), "Seller does not own enough ERC20 tokens" ); assert!( _check_erc20_allowance( - @seller_order.token_address, - seller_order.quantity, - @seller, - @get_contract_address() + @seller_order.token_address, seller_order.quantity, @seller, @get_contract_address() ), "Seller's allowance of executor is not enough" ); @@ -655,7 +639,6 @@ mod executor { 'Chain ID is not SN_MAIN' ); - let currency_contract = IERC20Dispatcher { contract_address: execution_info.payment_currency_address.try_into().unwrap() }; @@ -675,7 +658,7 @@ mod executor { execution_info.token_id, execution_info.payment_amount ); - + assert!( execution_info .payment_amount > (fulfill_broker_fees_amount @@ -746,14 +729,12 @@ mod executor { let vinfo = if is_some == 1 { let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; nft_contract - .transfer_from( - execution_info.token_from, execution_info.token_to, token_id - ); - + .transfer_from(execution_info.token_from, execution_info.token_to, token_id); + let tx_info = starknet::get_tx_info().unbox(); let transaction_hash = tx_info.transaction_hash; let block_timestamp = starknet::info::get_block_timestamp(); - + ExecutionValidationInfo { order_hash: execution_info.order_hash, transaction_hash, @@ -761,18 +742,21 @@ mod executor { from: execution_info.token_from, to: execution_info.token_to, } - } else { - let erc20_contract = IERC20Dispatcher { contract_address: execution_info.token_address }; + let erc20_contract = IERC20Dispatcher { + contract_address: execution_info.token_address + }; erc20_contract .transfer_from( - execution_info.token_from, execution_info.token_to, execution_info.token_quantity + execution_info.token_from, + execution_info.token_to, + execution_info.token_quantity ); - + let tx_info = starknet::get_tx_info().unbox(); let transaction_hash = tx_info.transaction_hash; let block_timestamp = starknet::info::get_block_timestamp(); - + ExecutionValidationInfo { order_hash: execution_info.order_hash, transaction_hash, @@ -821,13 +805,16 @@ mod executor { } fn _compute_creator_fees_amount( - self: @ContractState, token_address: @ContractAddress, payment_amount: u256, token_id: OptionU256 + self: @ContractState, + token_address: @ContractAddress, + payment_amount: u256, + token_id: OptionU256 ) -> (ContractAddress, u256) { let (is_some, token_id) = token_id.get_some(); if is_some == 0 { _fallback_compute_creator_fees_amount(self, token_address, payment_amount) - }else{ - // check if nft support 2981 interface + } else { + // check if nft support 2981 interface let dispatcher = ISRC5Dispatcher { contract_address: *token_address }; if dispatcher.supports_interface(IERC2981_ID) { IERC2981Dispatcher { contract_address: *token_address } @@ -835,7 +822,7 @@ mod executor { } else { _fallback_compute_creator_fees_amount(self, token_address, payment_amount) } - } + } } fn _fallback_compute_creator_fees_amount( @@ -871,7 +858,7 @@ mod executor { let fulfill_broker_fees_amount = fulfill_broker_fees.compute_amount(payment_amount); let listing_broker_fees_amount = listing_broker_fees.compute_amount(payment_amount); let (_, creator_fees_amount) = _compute_creator_fees_amount( - self, @token_address, payment_amount, token_id, + self, @token_address, payment_amount, token_id, ); let ark_fees_amount = ark_fees.compute_amount(payment_amount); ( diff --git a/contracts/ark_starknet/src/interfaces.cairo b/contracts/ark_starknet/src/interfaces.cairo index 9a5b887f6..b4bc5c4d2 100644 --- a/contracts/ark_starknet/src/interfaces.cairo +++ b/contracts/ark_starknet/src/interfaces.cairo @@ -1,6 +1,6 @@ use ark_common::protocol::order_types::ExecutionInfo; -use ark_common::protocol::order_types::{OrderV1, OptionU256, OptionU256Trait}; use ark_common::protocol::order_types::{FulfillInfo, CancelInfo}; +use ark_common::protocol::order_types::{OrderV1, OptionU256, OptionU256Trait}; use ark_oz::erc2981::FeesRatio; //! Interfaces for arkchain operator. use starknet::{ClassHash, ContractAddress}; @@ -32,7 +32,10 @@ trait IExecutor { self: @T, token_address: ContractAddress ) -> (ContractAddress, FeesRatio); fn set_collection_creator_fees( - ref self: T, token_address: ContractAddress, receiver: ContractAddress, fees_ratio: FeesRatio + ref self: T, + token_address: ContractAddress, + receiver: ContractAddress, + fees_ratio: FeesRatio ); fn get_fees_amount( diff --git a/contracts/ark_starknet/tests/common/setup.cairo b/contracts/ark_starknet/tests/common/setup.cairo index 112e0aa85..dd87c5c49 100644 --- a/contracts/ark_starknet/tests/common/setup.cairo +++ b/contracts/ark_starknet/tests/common/setup.cairo @@ -383,12 +383,10 @@ fn create_limit_sell_order( IFreeMintDispatcher { contract_address: token_address }.mint(offerer, quantity); - let order = setup_limit_sell_order( - erc20_address, token_address, offerer, end_amount, quantity - ); + let order = setup_limit_sell_order(erc20_address, token_address, offerer, end_amount, quantity); cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(order); (order.compute_order_hash(), offerer, quantity) -} \ No newline at end of file +} diff --git a/contracts/ark_starknet/tests/integration/cancel_order.cairo b/contracts/ark_starknet/tests/integration/cancel_order.cairo index 02d97257a..d2252e0e7 100644 --- a/contracts/ark_starknet/tests/integration/cancel_order.cairo +++ b/contracts/ark_starknet/tests/integration/cancel_order.cairo @@ -15,8 +15,9 @@ use snforge_std::{cheat_caller_address, CheatSpan, spy_events, EventSpyAssertion use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, - setup, setup_erc20_order, setup_default_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order, create_limit_buy_order, create_limit_sell_order + setup, setup_erc20_order, setup_default_order, setup_auction_order, + setup_collection_offer_order, setup_listing_order, setup_offer_order, create_limit_buy_order, + create_limit_sell_order }; #[test] diff --git a/contracts/ark_starknet/tests/integration/create_order.cairo b/contracts/ark_starknet/tests/integration/create_order.cairo index 010a289df..dfdf04e7d 100644 --- a/contracts/ark_starknet/tests/integration/create_order.cairo +++ b/contracts/ark_starknet/tests/integration/create_order.cairo @@ -23,8 +23,8 @@ use snforge_std::{ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ - setup, setup_erc20_order, setup_auction_order, setup_collection_offer_order, setup_listing_order, setup_offer_order, - setup_limit_sell_order, setup_limit_buy_order + setup, setup_erc20_order, setup_auction_order, setup_collection_offer_order, + setup_listing_order, setup_offer_order, setup_limit_sell_order, setup_limit_buy_order }; @@ -238,7 +238,9 @@ fn test_create_limit_buy_order_ok() { let quantity = 5_000_000; Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount); - let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); + let order = setup_limit_buy_order( + erc20_address, token_address, offerer, start_amount, quantity + ); let order_hash = order.compute_order_hash(); let order_version = order.get_version(); @@ -583,7 +585,9 @@ fn test_create_limit_buy_order_offerer_not_enough_erc20_tokens() { Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, minted); - let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); + let order = setup_limit_buy_order( + erc20_address, token_address, offerer, start_amount, quantity + ); cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(order); @@ -615,7 +619,9 @@ fn test_create_limit_buy_order_twice() { let quantity = 5_000_000; Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount); - let order = setup_limit_buy_order(erc20_address, token_address, offerer, start_amount, quantity); + let order = setup_limit_buy_order( + erc20_address, token_address, offerer, start_amount, quantity + ); cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(2)); IExecutorDispatcher { contract_address: executor_address }.create_order(order); @@ -636,4 +642,4 @@ fn test_create_limit_sell_order_twice() { cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(2)); IExecutorDispatcher { contract_address: executor_address }.create_order(order); IExecutorDispatcher { contract_address: executor_address }.create_order(order); -} \ No newline at end of file +} diff --git a/contracts/ark_starknet/tests/integration/fees_amount.cairo b/contracts/ark_starknet/tests/integration/fees_amount.cairo index 83726ed09..740d5dbbf 100644 --- a/contracts/ark_starknet/tests/integration/fees_amount.cairo +++ b/contracts/ark_starknet/tests/integration/fees_amount.cairo @@ -1,7 +1,7 @@ +use ark_common::protocol::order_types::{OptionU256, OptionU256Trait}; use ark_starknet::interfaces::{ IExecutorDispatcher, IExecutorDispatcherTrait, FeesAmount, FeesRatio }; -use ark_common::protocol::order_types::{OptionU256, OptionU256Trait}; use snforge_std::{cheat_caller_address, CheatSpan}; use starknet::{ContractAddress, contract_address_const}; @@ -35,7 +35,9 @@ fn test_get_fees_amount_default_creator() { executor.set_default_creator_fees(creator, default_creator_fees_ratio); let fees_amount = executor - .get_fees_amount(fulfill_broker, listing_broker, nft_address, OptionU256 {is_some: 0, value: 1}, amount); + .get_fees_amount( + fulfill_broker, listing_broker, nft_address, OptionU256 { is_some: 0, value: 1 }, amount + ); assert_eq!(fees_amount.fulfill_broker, 1_000_000, "Wrong amount for fulfill broker"); assert_eq!(fees_amount.listing_broker, 500_000, "Wrong amount for listing broker"); @@ -72,7 +74,9 @@ fn test_get_fees_amount_collection_creator() { executor.set_collection_creator_fees(nft_address, creator, collection_creator_fees_ratio); let fees_amount = executor - .get_fees_amount(fulfill_broker, listing_broker, nft_address, OptionU256 {is_some: 0, value: 1}, amount); + .get_fees_amount( + fulfill_broker, listing_broker, nft_address, OptionU256 { is_some: 0, value: 1 }, amount + ); assert_eq!(fees_amount.fulfill_broker, 1_000_000, "Wrong amount for fulfill broker"); assert_eq!(fees_amount.listing_broker, 500_000, "Wrong amount for listing broker"); diff --git a/contracts/ark_starknet/tests/integration/fulfill_order.cairo b/contracts/ark_starknet/tests/integration/fulfill_order.cairo index 9c9a767d5..8b22d1c44 100644 --- a/contracts/ark_starknet/tests/integration/fulfill_order.cairo +++ b/contracts/ark_starknet/tests/integration/fulfill_order.cairo @@ -19,13 +19,16 @@ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, setup, setup_default_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, create_limit_sell_order, setup_limit_sell_order, - setup_limit_buy_order + setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, + create_limit_sell_order, setup_limit_sell_order, setup_limit_buy_order }; fn create_fulfill_info( - order_hash: felt252, fulfiller: ContractAddress, token_address: ContractAddress, token_id: Option + order_hash: felt252, + fulfiller: ContractAddress, + token_address: ContractAddress, + token_id: Option ) -> FulfillInfo { FulfillInfo { order_hash: order_hash, @@ -54,7 +57,9 @@ fn test_fulfill_offer_order_ok() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -80,7 +85,9 @@ fn test_fulfill_listing_order_ok() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -115,7 +122,9 @@ fn test_fulfill_listing_order_fulfiller_not_enough_erc20_token() { IFreeMintDispatcher { contract_address: erc20_address }.mint(fulfiller, start_amount - 100); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -136,7 +145,9 @@ fn test_fulfill_offer_order_fulfiller_not_owner() { executor_address, erc20_address, nft_address, token_id ); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -172,7 +183,9 @@ fn test_fulfill_offer_order_offerer_not_enough_allowance() { IERC20Dispatcher { contract_address: erc20_address } .approve(executor_address, start_amount - 10); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -199,7 +212,9 @@ fn test_fulfill_listing_order_fulfiller_not_enough_allowance() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address } @@ -222,7 +237,9 @@ fn test_fulfill_listing_order_offerer_not_approved() { IFreeMintDispatcher { contract_address: erc20_address }.mint(fulfiller, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -248,7 +265,9 @@ fn test_fulfill_offer_order_fulfiller_not_approved() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info); @@ -272,7 +291,9 @@ fn test_fulfill_offer_order_fulfiller_same_as_offerer() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -299,7 +320,9 @@ fn test_fulfill_listing_order_fulfiller_same_as_offerer() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(erc20_address, fulfiller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); @@ -331,7 +354,9 @@ fn test_fulfill_auction_order_ok() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); @@ -365,7 +390,9 @@ fn test_fulfill_auction_order_fulfiller_same_as_offerer() { IERC721Dispatcher { contract_address: nft_address } .set_approval_for_all(executor_address, true); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); @@ -393,7 +420,9 @@ fn test_fulfill_order_not_enabled() { cheat_caller_address(erc20_address, offerer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, Option::Some(token_id)); + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, nft_address, Option::Some(token_id) + ); cheat_caller_address(nft_address, fulfiller, CheatSpan::TargetCalls(1)); IERC721Dispatcher { contract_address: nft_address } @@ -423,7 +452,9 @@ fn test_fulfill_limit_buy_order_ok() { IFreeMintDispatcher { contract_address: token_address }.mint(seller, quantity); - let seller_order = setup_limit_sell_order(erc20_address, token_address, seller, start_amount, quantity); + let seller_order = setup_limit_sell_order( + erc20_address, token_address, seller, start_amount, quantity + ); cheat_caller_address(executor_address, seller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(seller_order); @@ -436,7 +467,6 @@ fn test_fulfill_limit_buy_order_ok() { cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); fulfill_info.related_order_hash = Option::Some(seller_order.compute_order_hash()); @@ -461,8 +491,10 @@ fn test_fulfill_limit_sell_order_ok() { IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); - let buyer_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); - + let buyer_order = setup_limit_buy_order( + erc20_address, token_address, buyer, start_amount, quantity + ); + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); @@ -498,7 +530,9 @@ fn test_fulfill_limit_buy_order_with_buy_order() { IFreeMintDispatcher { contract_address: erc20_address }.mint(wrong_seller, start_amount); - let wrong_order = setup_limit_buy_order(erc20_address, token_address, wrong_seller, start_amount, quantity); + let wrong_order = setup_limit_buy_order( + erc20_address, token_address, wrong_seller, start_amount, quantity + ); cheat_caller_address(executor_address, wrong_seller, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(wrong_order); @@ -511,7 +545,6 @@ fn test_fulfill_limit_buy_order_with_buy_order() { cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None); fulfill_info.related_order_hash = Option::Some(wrong_order.compute_order_hash()); @@ -537,8 +570,10 @@ fn test_fulfill_limit_sell_order_with_sell_order_ok() { IFreeMintDispatcher { contract_address: token_address }.mint(wrong_buyer, quantity); - let wrong_order = setup_limit_sell_order(erc20_address, token_address, wrong_buyer, end_amount, quantity); - + let wrong_order = setup_limit_sell_order( + erc20_address, token_address, wrong_buyer, end_amount, quantity + ); + cheat_caller_address(executor_address, wrong_buyer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(wrong_order); @@ -575,8 +610,10 @@ fn test_fulfill_limit_order_without_matching_price_ok() { IFreeMintDispatcher { contract_address: erc20_address }.mint(buyer, start_amount); - let buyer_order = setup_limit_buy_order(erc20_address, token_address, buyer, start_amount, quantity); - + let buyer_order = setup_limit_buy_order( + erc20_address, token_address, buyer, start_amount, quantity + ); + cheat_caller_address(executor_address, buyer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(buyer_order); diff --git a/contracts/ark_tokens/src/lib.cairo b/contracts/ark_tokens/src/lib.cairo index 72904dafc..bd79f450e 100644 --- a/contracts/ark_tokens/src/lib.cairo +++ b/contracts/ark_tokens/src/lib.cairo @@ -1,4 +1,4 @@ mod erc20; +mod erc20_trade; mod erc721; mod erc721_royalty; -mod erc20_trade; \ No newline at end of file From a0241d5b03ff442dcf3467b1f37abb46ecc3f9d6 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Sat, 12 Oct 2024 02:16:51 +0100 Subject: [PATCH 17/18] clean up --- contracts/ark_starknet/src/executor.cairo | 17 ++++------ .../ark_starknet/tests/common/setup.cairo | 16 +++++----- .../tests/integration/create_order.cairo | 3 +- .../tests/integration/fulfill_order.cairo | 31 +++++++++++++------ 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/contracts/ark_starknet/src/executor.cairo b/contracts/ark_starknet/src/executor.cairo index ebae33032..a48106378 100644 --- a/contracts/ark_starknet/src/executor.cairo +++ b/contracts/ark_starknet/src/executor.cairo @@ -815,18 +815,14 @@ mod executor { let (is_some, token_id) = execution_info.token_id.get_some(); let vinfo = if is_some == 1 { - let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; - nft_contract - .transfer_from(execution_info.token_from, execution_info.token_to, token_id); - if _is_erc721(execution_info.token_address) { - let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; + let nft_contract = IERC721Dispatcher { + contract_address: execution_info.token_address + }; nft_contract - .transfer_from( - execution_info.token_from, execution_info.token_to, execution_info.token_id - ); + .transfer_from(execution_info.token_from, execution_info.token_to, token_id); } - + if _is_erc1155(execution_info.token_address) { let erc1155_contract = IERC1155Dispatcher { contract_address: execution_info.token_address @@ -835,7 +831,7 @@ mod executor { .safe_transfer_from( execution_info.token_from, execution_info.token_to, - execution_info.token_id, + token_id, execution_info.token_quantity, array![].span() ); @@ -1018,7 +1014,6 @@ mod executor { quantity: order.quantity, start_amount: order.start_amount, offerer: order.offerer, - quantity: order.quantity, route: order.route, } } diff --git a/contracts/ark_starknet/tests/common/setup.cairo b/contracts/ark_starknet/tests/common/setup.cairo index 49fe21f84..f129de3b4 100644 --- a/contracts/ark_starknet/tests/common/setup.cairo +++ b/contracts/ark_starknet/tests/common/setup.cairo @@ -94,15 +94,12 @@ fn setup() -> (ContractAddress, ContractAddress, ContractAddress) { (executor_address, erc20_address, nft_address) } -<<<<<<< HEAD fn setup_erc20_order() -> (ContractAddress, ContractAddress, ContractAddress) { let erc20_address = deploy_erc20(); let token_address = deploy_erc20_2(); let executor_address = deploy_executor(); (executor_address, erc20_address, token_address) } -======= ->>>>>>> ArkProjectNFTs-feat/contract-v2 fn setup_royalty() -> (ContractAddress, ContractAddress, ContractAddress) { let erc20_address = deploy_erc20(); @@ -371,7 +368,6 @@ fn create_collection_offer_order( (order.compute_order_hash(), offerer, start_amount) } -<<<<<<< HEAD fn create_limit_buy_order( executor_address: ContractAddress, @@ -387,7 +383,13 @@ fn create_limit_buy_order( let order = setup_limit_buy_order( erc20_address, token_address, offerer, start_amount, quantity ); -======= + + cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); + IExecutorDispatcher { contract_address: executor_address }.create_order(order); + + (order.compute_order_hash(), offerer, start_amount) +} + fn setup_order_erc1155( erc20_address: ContractAddress, erc1155_address: ContractAddress, quantity: u256 ) -> OrderV1 { @@ -432,7 +434,6 @@ fn create_offer_order_erc1155( order.offerer = offerer; order.start_amount = start_amount; order.token_id = Option::Some(token_id); ->>>>>>> ArkProjectNFTs-feat/contract-v2 cheat_caller_address(executor_address, offerer, CheatSpan::TargetCalls(1)); IExecutorDispatcher { contract_address: executor_address }.create_order(order); @@ -440,7 +441,6 @@ fn create_offer_order_erc1155( (order.compute_order_hash(), offerer, start_amount) } -<<<<<<< HEAD fn create_limit_sell_order( executor_address: ContractAddress, erc20_address: ContractAddress, @@ -459,5 +459,3 @@ fn create_limit_sell_order( (order.compute_order_hash(), offerer, quantity) } -======= ->>>>>>> ArkProjectNFTs-feat/contract-v2 diff --git a/contracts/ark_starknet/tests/integration/create_order.cairo b/contracts/ark_starknet/tests/integration/create_order.cairo index 8b728767b..08bb6740b 100644 --- a/contracts/ark_starknet/tests/integration/create_order.cairo +++ b/contracts/ark_starknet/tests/integration/create_order.cairo @@ -26,7 +26,7 @@ use starknet::{ContractAddress, contract_address_const}; use super::super::common::setup::{ setup, setup_erc20_order, setup_auction_order, setup_collection_offer_order, - setup_listing_order, setup_offer_order, setup_limit_sell_order, setup_limit_buy_order, + setup_listing_order, setup_offer_order, setup_limit_sell_order, setup_limit_buy_order, setup_erc1155, setup_order_erc1155 }; @@ -694,7 +694,6 @@ fn test_create_order_erc1155_to_erc20_ok() { fn test_create_order_offerer_not_own_enough_erc1155_token() { let (executor_address, erc20_address, erc1155_address) = setup_erc1155(); let offerer = erc1155_address; - let other = contract_address_const::<'other'>(); let quantity = 50_u256; let token_id = Erc1155Dispatcher { contract_address: erc1155_address }.mint(offerer, 2_u256); diff --git a/contracts/ark_starknet/tests/integration/fulfill_order.cairo b/contracts/ark_starknet/tests/integration/fulfill_order.cairo index cba9863c8..6a74ecb83 100644 --- a/contracts/ark_starknet/tests/integration/fulfill_order.cairo +++ b/contracts/ark_starknet/tests/integration/fulfill_order.cairo @@ -22,7 +22,8 @@ use super::super::common::setup::{ create_auction_order, create_collection_offer_order, create_listing_order, create_offer_order, setup, setup_default_order, setup_auction_order, setup_collection_offer_order, setup_listing_order, setup_offer_order, setup_erc20_order, create_limit_buy_order, - create_limit_sell_order, setup_limit_sell_order, setup_limit_buy_order, create_offer_order_erc1155, setup_erc1155 + create_limit_sell_order, setup_limit_sell_order, setup_limit_buy_order, + create_offer_order_erc1155, setup_erc1155 }; @@ -31,7 +32,7 @@ fn create_fulfill_info( fulfiller: ContractAddress, token_address: ContractAddress, token_id: Option, - quantity: u256 + quantity: u256, ) -> FulfillInfo { FulfillInfo { order_hash: order_hash, @@ -41,9 +42,11 @@ fn create_fulfill_info( token_address: token_address, fulfill_broker_address: contract_address_const::<'broker'>(), quantity: quantity, + token_id: token_id } } + #[test] fn test_fulfill_offer_order_ok() { let (executor_address, erc20_address, nft_address) = setup(); @@ -170,8 +173,8 @@ fn test_fulfill_offer_order_fulfiller_not_owner_for_erc1155() { executor_address, erc20_address, erc1155_address, token_id, quantity ); - let fulfill_info = create_fulfill_info_erc1155( - order_hash, fulfiller, erc1155_address, token_id, quantity, 1_u256 + let fulfill_info = create_fulfill_info( + order_hash, fulfiller, erc1155_address, Option::Some(token_id), quantity ); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); @@ -492,7 +495,9 @@ fn test_fulfill_limit_buy_order_ok() { cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None, quantity); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, token_address, Option::None, quantity + ); fulfill_info.related_order_hash = Option::Some(seller_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); @@ -531,7 +536,9 @@ fn test_fulfill_limit_sell_order_ok() { cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None, quantity); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, token_address, Option::None, quantity + ); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); @@ -570,7 +577,9 @@ fn test_fulfill_limit_buy_order_with_buy_order() { cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None, quantity); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, token_address, Option::None, quantity + ); fulfill_info.related_order_hash = Option::Some(wrong_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); @@ -610,7 +619,9 @@ fn test_fulfill_limit_sell_order_with_sell_order_ok() { cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None, quantity); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, token_address, Option::None, quantity + ); fulfill_info.related_order_hash = Option::Some(wrong_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); @@ -650,7 +661,9 @@ fn test_fulfill_limit_order_without_matching_price_ok() { cheat_caller_address(token_address, seller, CheatSpan::TargetCalls(1)); IERC20Dispatcher { contract_address: token_address }.approve(executor_address, quantity); - let mut fulfill_info = create_fulfill_info(order_hash, fulfiller, token_address, Option::None, quantity); + let mut fulfill_info = create_fulfill_info( + order_hash, fulfiller, token_address, Option::None, quantity + ); fulfill_info.related_order_hash = Option::Some(buyer_order.compute_order_hash()); cheat_caller_address(executor_address, fulfiller, CheatSpan::TargetCalls(1)); From 26340cb9a4ce41f6586821390530bdb4bd571066 Mon Sep 17 00:00:00 2001 From: JoE11-y Date: Sat, 12 Oct 2024 02:17:19 +0100 Subject: [PATCH 18/18] clean up --- packages/deployer/scripts/deploy.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/deployer/scripts/deploy.ts b/packages/deployer/scripts/deploy.ts index c32b99dd8..d35a4c1a7 100644 --- a/packages/deployer/scripts/deploy.ts +++ b/packages/deployer/scripts/deploy.ts @@ -8,9 +8,9 @@ import { deployERC20 } from "../src/contracts/erc20"; import { deployERC20Trade } from "../src/contracts/erc20trade"; import { deployERC721 } from "../src/contracts/erc721"; import { deployERC721Royalties } from "../src/contracts/erc721royalties"; +import { deployERC1155 } from "../src/contracts/erc1155"; import { deployExecutor } from "../src/contracts/executor"; import { getFeeAddress } from "../src/providers"; -import { deployERC1155 } from "../src/contracts/erc1155"; async function run() { if ( @@ -90,11 +90,8 @@ async function run() { nftContractFixedFees: nftContractFixedFees.address, nftContractRoyalties: nftContractRoyalties.address, eth: ethContract.address, -<<<<<<< HEAD - ethTrade: ethTradeContract.address -======= - erc1155: erc1155Contract.address, ->>>>>>> ArkProjectNFTs-feat/contract-v2 + ethTrade: ethTradeContract.address, + erc1155: erc1155Contract.address }); await fs.writeFile(contractsFilePath, contractsContent); }