From 1ac952a19ad672d5e92e1ff7eab31b3db56e9981 Mon Sep 17 00:00:00 2001 From: ooochoche Date: Fri, 23 Aug 2024 18:47:05 +0100 Subject: [PATCH 1/3] feat: implement strategy_private_sale contract --- marketplace/src/strategy_private_sale.cairo | 187 ++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/marketplace/src/strategy_private_sale.cairo b/marketplace/src/strategy_private_sale.cairo index 8b13789..6bdf344 100644 --- a/marketplace/src/strategy_private_sale.cairo +++ b/marketplace/src/strategy_private_sale.cairo @@ -1 +1,188 @@ +use starknet::ContractAddress; +use starknet::class_hash::ClassHash; +use marketplace::utils::order_types::{TakerOrder, MakerOrder}; +#[starknet::interface] +trait IStrategyPrivateSale { + fn initializer(ref self: TState, fee: u128, owner: ContractAddress); + fn update_protocol_fee(ref self: TState, fee: u128); + fn protocol_fee(self: @TState) -> u128; + fn add_address_to_whitelist(ref self: TState, address: ContractAddress); + fn remove_address_from_whitelist(ref self: TState, address: ContractAddress); + fn is_address_whitelisted(self: @TState, address: ContractAddress) -> bool; + fn whitelisted_addresses_count(self: @TState) -> usize; + fn whitelisted_address(self: @TState, index: usize) -> ContractAddress; + fn can_execute_taker_ask( + self: @TState, taker_ask: TakerOrder, maker_bid: MakerOrder, extra_params: Span + ) -> (bool, u256, u128); + fn can_execute_taker_bid( + self: @TState, taker_bid: TakerOrder, maker_ask: MakerOrder + ) -> (bool, u256, u128); + fn upgrade(ref self: TState, impl_hash: ClassHash); +} + +#[starknet::contract] +mod StrategyPrivateSale { + use marketplace::utils::order_types::{TakerOrder, MakerOrder}; + use starknet::{ + ContractAddress, contract_address_const, class_hash::ClassHash, get_block_timestamp, + get_caller_address + }; + use openzeppelin::{ + access::ownable::OwnableComponent, + upgrades::{upgradeable::UpgradeableComponent::InternalTrait, UpgradeableComponent} + }; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradable, event: UpgradeableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + protocol_fee: u128, + whitelisted_addresses: LegacyMap, + whitelisted_address_index: LegacyMap, + whitelisted_addresses_count: u32, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + AddressWhitelisted: AddressWhitelisted, + AddressRemoved: AddressRemoved + } + + #[derive(Drop, starknet::Event)] + struct AddressWhitelisted { + address: ContractAddress, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct AddressRemoved { + address: ContractAddress, + timestamp: u64, + } + + #[abi(embed_v0)] + impl StrategyPrivateSale of super::IStrategyPrivateSale { + fn initializer(ref self: ContractState, fee: u128, owner: ContractAddress,) { + self.ownable.initializer(owner); + self.protocol_fee.write(fee); + } + + fn update_protocol_fee(ref self: ContractState, fee: u128) { + self.ownable.assert_only_owner(); + self.protocol_fee.write(fee); + } + + fn protocol_fee(self: @ContractState) -> u128 { + self.protocol_fee.read() + } + + fn add_address_to_whitelist(ref self: ContractState, address: ContractAddress) { + self.ownable.assert_only_owner(); + let index = self.whitelisted_address_index.read(address); + assert!(index.is_zero(), "PrivateSaleStrategy: address already whitelisted"); + let new_count = self.whitelisted_addresses_count.read() + 1; + self.whitelisted_address_index.write(address, new_count); + self.whitelisted_addresses.write(new_count, address); + self.whitelisted_addresses_count.write(new_count); + let timestamp = get_block_timestamp(); + self.emit(AddressWhitelisted { address, timestamp }); + } + + fn remove_address_from_whitelist(ref self: ContractState, address: ContractAddress) { + self.ownable.assert_only_owner(); + let index = self.whitelisted_address_index.read(address); + assert!(!index.is_zero(), "PrivateSaleStrategy: address not whitelisted"); + let count = self.whitelisted_addresses_count.read(); + + let address_at_last_index = self.whitelisted_addresses.read(count); + self.whitelisted_addresses.write(index, address_at_last_index); + self.whitelisted_addresses.write(count, contract_address_const::<0>()); + self.whitelisted_address_index.write(address, 0); + + if (count != 1) { + self.whitelisted_address_index.write(address_at_last_index, index); + } + self.whitelisted_addresses_count.write(count - 1); + let timestamp = get_block_timestamp(); + self.emit(AddressRemoved { address, timestamp }); + } + + fn is_address_whitelisted(self: @ContractState, address: ContractAddress) -> bool { + let index = self.whitelisted_address_index.read(address); + if (index == 0) { + return false; + } + true + } + + fn whitelisted_addresses_count(self: @ContractState) -> usize { + self.whitelisted_addresses_count.read() + } + + fn whitelisted_address(self: @ContractState, index: usize) -> ContractAddress { + self.whitelisted_addresses.read(index) + } + + fn can_execute_taker_ask( + self: @ContractState, + taker_ask: TakerOrder, + maker_bid: MakerOrder, + extra_params: Span + ) -> (bool, u256, u128) { + let price_match: bool = maker_bid.price == taker_ask.price; + let token_id_match: bool = maker_bid.token_id == taker_ask.token_id; + let start_time_valid: bool = maker_bid.start_time < get_block_timestamp(); + let end_time_valid: bool = maker_bid.end_time > get_block_timestamp(); + let is_address_whitelisted: bool = self.is_address_whitelisted(get_caller_address()); + if (price_match + && token_id_match + && start_time_valid + && end_time_valid + && is_address_whitelisted) { + return (true, maker_bid.token_id, maker_bid.amount); + } else { + return (false, maker_bid.token_id, maker_bid.amount); + } + } + + fn can_execute_taker_bid( + self: @ContractState, taker_bid: TakerOrder, maker_ask: MakerOrder + ) -> (bool, u256, u128) { + let price_match: bool = maker_ask.price == taker_bid.price; + let token_id_match: bool = maker_ask.token_id == taker_bid.token_id; + let start_time_valid: bool = maker_ask.start_time < get_block_timestamp(); + let end_time_valid: bool = maker_ask.end_time > get_block_timestamp(); + let is_address_whitelisted: bool = self.is_address_whitelisted(get_caller_address()); + if (price_match + && token_id_match + && start_time_valid + && end_time_valid + && is_address_whitelisted) { + return (true, maker_ask.token_id, maker_ask.amount); + } else { + return (false, maker_ask.token_id, maker_ask.amount); + } + } + + fn upgrade(ref self: ContractState, impl_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradable._upgrade(impl_hash); + } + } +} From bf89150871fc35a8bf6131ba086dad463eecc5b7 Mon Sep 17 00:00:00 2001 From: Benedict Ejembi Date: Sat, 31 Aug 2024 23:16:27 +0000 Subject: [PATCH 2/3] feat: implement private sale on order basis --- marketplace/src/strategy_private_sale.cairo | 71 +++++++++++---------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/marketplace/src/strategy_private_sale.cairo b/marketplace/src/strategy_private_sale.cairo index 6bdf344..3481de3 100644 --- a/marketplace/src/strategy_private_sale.cairo +++ b/marketplace/src/strategy_private_sale.cairo @@ -7,11 +7,11 @@ trait IStrategyPrivateSale { fn initializer(ref self: TState, fee: u128, owner: ContractAddress); fn update_protocol_fee(ref self: TState, fee: u128); fn protocol_fee(self: @TState) -> u128; - fn add_address_to_whitelist(ref self: TState, address: ContractAddress); - fn remove_address_from_whitelist(ref self: TState, address: ContractAddress); - fn is_address_whitelisted(self: @TState, address: ContractAddress) -> bool; - fn whitelisted_addresses_count(self: @TState) -> usize; - fn whitelisted_address(self: @TState, index: usize) -> ContractAddress; + fn add_address_to_whitelist(ref self: TState, order_nonce: u128, address: ContractAddress); + fn remove_address_from_whitelist(ref self: TState, order_nonce: u128, address: ContractAddress); + fn is_address_whitelisted(self: @TState, order_nonce: u128, address: ContractAddress) -> bool; + fn order_whitelist_count(self: @TState, order_nonce: u128) -> u256; + fn whitelisted_address(self: @TState, order_nonce: u128, index: u256) -> ContractAddress; fn can_execute_taker_ask( self: @TState, taker_ask: TakerOrder, maker_bid: MakerOrder, extra_params: Span ) -> (bool, u256, u128); @@ -23,7 +23,8 @@ trait IStrategyPrivateSale { #[starknet::contract] mod StrategyPrivateSale { - use marketplace::utils::order_types::{TakerOrder, MakerOrder}; + use core::array::ArrayTrait; +use marketplace::utils::order_types::{TakerOrder, MakerOrder}; use starknet::{ ContractAddress, contract_address_const, class_hash::ClassHash, get_block_timestamp, get_caller_address @@ -44,9 +45,9 @@ mod StrategyPrivateSale { #[storage] struct Storage { protocol_fee: u128, - whitelisted_addresses: LegacyMap, - whitelisted_address_index: LegacyMap, - whitelisted_addresses_count: u32, + order_whitelist: LegacyMap<(u128,u256), ContractAddress>, // > + order_whitelist_index: LegacyMap<(u128, ContractAddress), u256>, // <(order_nonce, ContractAddress), u256> + order_whitelist_count: LegacyMap, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -92,51 +93,57 @@ mod StrategyPrivateSale { self.protocol_fee.read() } - fn add_address_to_whitelist(ref self: ContractState, address: ContractAddress) { + fn add_address_to_whitelist(ref self: ContractState, order_nonce: u128, address: ContractAddress) { self.ownable.assert_only_owner(); - let index = self.whitelisted_address_index.read(address); + let index = self.order_whitelist_index.read((order_nonce, address)); assert!(index.is_zero(), "PrivateSaleStrategy: address already whitelisted"); - let new_count = self.whitelisted_addresses_count.read() + 1; - self.whitelisted_address_index.write(address, new_count); - self.whitelisted_addresses.write(new_count, address); - self.whitelisted_addresses_count.write(new_count); + + let new_count = self.order_whitelist_count.read(order_nonce) + 1; + + self.order_whitelist_index.write((order_nonce, address), new_count); + + self.order_whitelist.write((order_nonce, new_count), address); + + self.order_whitelist_count.write(order_nonce, new_count); let timestamp = get_block_timestamp(); self.emit(AddressWhitelisted { address, timestamp }); } - fn remove_address_from_whitelist(ref self: ContractState, address: ContractAddress) { + fn remove_address_from_whitelist(ref self: ContractState, order_nonce: u128, address: ContractAddress) { self.ownable.assert_only_owner(); - let index = self.whitelisted_address_index.read(address); + let index = self.order_whitelist_index.read((order_nonce, address)); assert!(!index.is_zero(), "PrivateSaleStrategy: address not whitelisted"); - let count = self.whitelisted_addresses_count.read(); - let address_at_last_index = self.whitelisted_addresses.read(count); - self.whitelisted_addresses.write(index, address_at_last_index); - self.whitelisted_addresses.write(count, contract_address_const::<0>()); - self.whitelisted_address_index.write(address, 0); + let count = self.order_whitelist_count.read(order_nonce); + + let address_at_last_index = self.order_whitelist.read((order_nonce, count)); + self.order_whitelist.write((order_nonce, index), address_at_last_index); + self.order_whitelist.write((order_nonce, count), contract_address_const::<0>()); + self.order_whitelist_index.write((order_nonce, address), 0); if (count != 1) { - self.whitelisted_address_index.write(address_at_last_index, index); + self.order_whitelist_index.write((order_nonce, address_at_last_index), index); } - self.whitelisted_addresses_count.write(count - 1); + + self.order_whitelist_count.write(order_nonce, count - 1); let timestamp = get_block_timestamp(); self.emit(AddressRemoved { address, timestamp }); } - fn is_address_whitelisted(self: @ContractState, address: ContractAddress) -> bool { - let index = self.whitelisted_address_index.read(address); + fn is_address_whitelisted(self: @ContractState, order_nonce: u128, address: ContractAddress) -> bool { + let index = self.order_whitelist_index.read((order_nonce, address)); if (index == 0) { return false; } true } - fn whitelisted_addresses_count(self: @ContractState) -> usize { - self.whitelisted_addresses_count.read() + fn order_whitelist_count(self: @ContractState, order_nonce: u128) -> u256 { + self.order_whitelist_count.read(order_nonce) } - fn whitelisted_address(self: @ContractState, index: usize) -> ContractAddress { - self.whitelisted_addresses.read(index) + fn whitelisted_address(self: @ContractState, order_nonce: u128, index: u256) -> ContractAddress { + self.order_whitelist.read((order_nonce, index)) } fn can_execute_taker_ask( @@ -149,7 +156,7 @@ mod StrategyPrivateSale { let token_id_match: bool = maker_bid.token_id == taker_ask.token_id; let start_time_valid: bool = maker_bid.start_time < get_block_timestamp(); let end_time_valid: bool = maker_bid.end_time > get_block_timestamp(); - let is_address_whitelisted: bool = self.is_address_whitelisted(get_caller_address()); + let is_address_whitelisted: bool = self.is_address_whitelisted(maker_bid.salt_nonce, get_caller_address()); if (price_match && token_id_match && start_time_valid @@ -168,7 +175,7 @@ mod StrategyPrivateSale { let token_id_match: bool = maker_ask.token_id == taker_bid.token_id; let start_time_valid: bool = maker_ask.start_time < get_block_timestamp(); let end_time_valid: bool = maker_ask.end_time > get_block_timestamp(); - let is_address_whitelisted: bool = self.is_address_whitelisted(get_caller_address()); + let is_address_whitelisted: bool = self.is_address_whitelisted(maker_ask.salt_nonce, get_caller_address()); if (price_match && token_id_match && start_time_valid From 5c0fbf866c2bccd9c0b4f8ace0c0210f0e692e0f Mon Sep 17 00:00:00 2001 From: Benedict Ejembi Date: Wed, 4 Sep 2024 06:21:34 +0000 Subject: [PATCH 3/3] chore: add comments --- marketplace/src/strategy_private_sale.cairo | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/marketplace/src/strategy_private_sale.cairo b/marketplace/src/strategy_private_sale.cairo index 3481de3..8b5b370 100644 --- a/marketplace/src/strategy_private_sale.cairo +++ b/marketplace/src/strategy_private_sale.cairo @@ -93,43 +93,63 @@ use marketplace::utils::order_types::{TakerOrder, MakerOrder}; self.protocol_fee.read() } + // Add address to the whitelist of an order using the `order_nonce` fn add_address_to_whitelist(ref self: ContractState, order_nonce: u128, address: ContractAddress) { self.ownable.assert_only_owner(); + + // Verify address is not already whitelisted let index = self.order_whitelist_index.read((order_nonce, address)); assert!(index.is_zero(), "PrivateSaleStrategy: address already whitelisted"); + // Increment order whitelist count by 1 let new_count = self.order_whitelist_count.read(order_nonce) + 1; + // Set order whitelist index self.order_whitelist_index.write((order_nonce, address), new_count); + // Add address to whitelist self.order_whitelist.write((order_nonce, new_count), address); + // Set order whitelist count self.order_whitelist_count.write(order_nonce, new_count); let timestamp = get_block_timestamp(); self.emit(AddressWhitelisted { address, timestamp }); } + // Remove address from the whitelist of an order using the `order_nonce` fn remove_address_from_whitelist(ref self: ContractState, order_nonce: u128, address: ContractAddress) { self.ownable.assert_only_owner(); + + // Verify address is whitelisted let index = self.order_whitelist_index.read((order_nonce, address)); assert!(!index.is_zero(), "PrivateSaleStrategy: address not whitelisted"); + // Get current order whitelist count let count = self.order_whitelist_count.read(order_nonce); + // Get the last whitelisted address let address_at_last_index = self.order_whitelist.read((order_nonce, count)); + + // Replace p self.order_whitelist.write((order_nonce, index), address_at_last_index); + + // Remove whitelisted address at last index self.order_whitelist.write((order_nonce, count), contract_address_const::<0>()); + + // Remove index of last whitelisted address self.order_whitelist_index.write((order_nonce, address), 0); if (count != 1) { self.order_whitelist_index.write((order_nonce, address_at_last_index), index); } + // Decrement order whitelist count self.order_whitelist_count.write(order_nonce, count - 1); let timestamp = get_block_timestamp(); self.emit(AddressRemoved { address, timestamp }); } + // Function to check whitelisted status. Returns `true` is address is whitelisted else returns `false` fn is_address_whitelisted(self: @ContractState, order_nonce: u128, address: ContractAddress) -> bool { let index = self.order_whitelist_index.read((order_nonce, address)); if (index == 0) { @@ -156,7 +176,10 @@ use marketplace::utils::order_types::{TakerOrder, MakerOrder}; let token_id_match: bool = maker_bid.token_id == taker_ask.token_id; let start_time_valid: bool = maker_bid.start_time < get_block_timestamp(); let end_time_valid: bool = maker_bid.end_time > get_block_timestamp(); + + // Get the `caller` is whitelisted status let is_address_whitelisted: bool = self.is_address_whitelisted(maker_bid.salt_nonce, get_caller_address()); + if (price_match && token_id_match && start_time_valid @@ -175,7 +198,10 @@ use marketplace::utils::order_types::{TakerOrder, MakerOrder}; let token_id_match: bool = maker_ask.token_id == taker_bid.token_id; let start_time_valid: bool = maker_ask.start_time < get_block_timestamp(); let end_time_valid: bool = maker_ask.end_time > get_block_timestamp(); + + // Get the `caller` is whitelisted status let is_address_whitelisted: bool = self.is_address_whitelisted(maker_ask.salt_nonce, get_caller_address()); + if (price_match && token_id_match && start_time_valid