diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbc15a1..675ccd0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: [push, pull_request] env: - SCARB_VERSION: 2.4.0 + SCARB_VERSION: 2.4.3 jobs: check: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 187ccf0..6027a80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: [push, pull_request] env: SCARB_VERSION: 2.4.3 - SNFORGE_VERSION: 0.13.1 + SNFORGE_VERSION: 0.14.0 jobs: check: diff --git a/flex_marketplace/Scarb.lock b/flex_marketplace/Scarb.lock index 6ae0a09..d6edad7 100644 --- a/flex_marketplace/Scarb.lock +++ b/flex_marketplace/Scarb.lock @@ -16,5 +16,5 @@ source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.8.0#c23e8e9 [[package]] name = "snforge_std" -version = "0.1.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.13.0#99c2f9d33159988efd339bd969c78d82b0b4b6f7" +version = "0.14.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.14.0#e8cbecee4e31ed428c76d5173eaa90c8df796fe3" diff --git a/flex_marketplace/Scarb.toml b/flex_marketplace/Scarb.toml index 38f7564..d3f8580 100644 --- a/flex_marketplace/Scarb.toml +++ b/flex_marketplace/Scarb.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2023_01" [dependencies] -starknet = "2.4.0" -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.13.0" } -openzeppelin = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.8.0" } +starknet = "2.4.3" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.14.0" } +openzeppelin = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.8.0" } [scripts] test = "snforge test" diff --git a/flex_marketplace/src/lib.cairo b/flex_marketplace/src/lib.cairo index 4d8a78f..2ed09e9 100644 --- a/flex_marketplace/src/lib.cairo +++ b/flex_marketplace/src/lib.cairo @@ -1,5 +1,5 @@ use core::fmt::{Display, Error, Formatter, Debug}; -use starknet::contract_address_to_felt252; +use starknet::{contract_address_to_felt252, ContractAddress, contract_address_const}; impl DisplayContractAddress of Display { fn fmt(self: @starknet::ContractAddress, ref f: Formatter) -> Result<(), Error> { @@ -58,6 +58,9 @@ mod marketplace { } mod mocks { + mod account; + mod erc20; mod erc1155; mod erc721; + mod strategy; } diff --git a/flex_marketplace/src/marketplace/marketplace.cairo b/flex_marketplace/src/marketplace/marketplace.cairo index 7ea12bf..b26fae4 100644 --- a/flex_marketplace/src/marketplace/marketplace.cairo +++ b/flex_marketplace/src/marketplace/marketplace.cairo @@ -61,13 +61,11 @@ trait IMarketPlace { #[starknet::interface] trait IExecutionStrategy { - fn protocol_fee(self: @TState) -> u128; - - fn can_execute_taker_ask( + fn protocolFee(self: @TState) -> u128; + fn canExecuteTakerAsk( self: @TState, taker_ask: TakerOrder, maker_bid: MakerOrder, extra_params: Array ) -> (bool, u256, u128); - - fn can_execute_taker_bid( + fn canExecuteTakerBid( self: @TState, taker_bid: TakerOrder, maker_ask: MakerOrder ) -> (bool, u256, u128); } @@ -125,6 +123,7 @@ mod MarketPlace { impl ReentrancyGuardInternalImpl = ReentrancyGuardComponent::InternalImpl; + use snforge_std::PrintTrait; #[storage] struct Storage { hash_domain: felt252, @@ -340,13 +339,11 @@ mod MarketPlace { assert!(!caller.is_zero(), "MarketPlace: invalid caller address {:?}", caller); assert!(maker_ask.is_order_ask, "MarketPlace: maker ask is not an ask order"); assert!(!taker_bid.is_order_ask, "MarketPlace: taker bid is an ask order"); - self.validate_order(@maker_ask, maker_ask_signature); - let (can_execute, token_id, amount) = IExecutionStrategyDispatcher { contract_address: maker_ask.strategy } - .can_execute_taker_bid(taker_bid, maker_ask); + .canExecuteTakerBid(taker_bid, maker_ask); assert!(can_execute, "Marketplace: order cannot be executed"); @@ -365,26 +362,20 @@ mod MarketPlace { taker_bid.price, maker_ask.min_percentage_to_ask ); - let mut non_fungible_token_recipient = contract_address_const::<0>(); if custom_non_fungible_token_recepient.is_zero() { non_fungible_token_recipient = taker_bid.taker; } else { non_fungible_token_recipient = custom_non_fungible_token_recepient; }; - self - .transfer_fees_and_funds( - maker_ask.strategy, + .transfer_non_fungible_token( maker_ask.collection, - token_id, - maker_ask.currency, - taker_bid.taker, maker_ask.signer, - taker_bid.price, - maker_ask.min_percentage_to_ask + non_fungible_token_recipient, + token_id, + amount ); - let order_hash = self .signature_checker .read() @@ -436,19 +427,17 @@ mod MarketPlace { let (can_execute, token_id, amount) = IExecutionStrategyDispatcher { contract_address: maker_bid.strategy } - .can_execute_taker_ask(taker_ask, maker_bid, extra_params); + .canExecuteTakerAsk(taker_ask, maker_bid, extra_params); assert!(can_execute, "Marketplace: taker ask cannot be executed"); self .is_user_order_nonce_executed_or_cancelled .write((maker_bid.signer, maker_bid.nonce), true); - self .transfer_non_fungible_token( maker_bid.collection, taker_ask.taker, maker_bid.signer, token_id, amount ); - self .transfer_fees_and_funds( maker_bid.strategy, @@ -460,7 +449,6 @@ mod MarketPlace { taker_ask.price, taker_ask.min_percentage_to_ask ); - let order_hash = self .signature_checker .read() @@ -504,7 +492,7 @@ mod MarketPlace { contract_address: maker_ask.strategy }; let relayer = auction_strategy.auctionRelayer(); - assert!(caller == relayer, "MarketPlace: caller {} is not relayer {}", caller, relayer); + assert!(caller == relayer, "MarketPlace: caller is not relayer"); self.validate_order(@maker_ask, maker_ask_signature); self.validate_order(@maker_bid, maker_bid_signature); @@ -531,7 +519,6 @@ mod MarketPlace { maker_bid.price, maker_ask.min_percentage_to_ask ); - self .transfer_non_fungible_token( maker_ask.collection, maker_ask.signer, maker_bid.signer, token_id, amount @@ -667,21 +654,16 @@ mod MarketPlace { let protocol_fee_amount = self.calculate_protocol_fee(strategy, amount); let recipient = self.get_protocol_fee_recipient(); - let currency_erc20 = IERC20CamelDispatcher { contract_address: currency }; - if !protocol_fee_amount.is_zero() && !recipient.is_zero() { currency_erc20.transferFrom(from, recipient, protocol_fee_amount.into()); } - let (recipient, royalty_amount) = self .royalty_fee_manager .read() .calculate_royalty_fee_and_get_recipient(collection, token_id, amount); - if !royalty_amount.is_zero() && !recipient.is_zero() { currency_erc20.transferFrom(from, recipient, royalty_amount.into()); - self .emit( RoyaltyPayment { @@ -724,7 +706,7 @@ mod MarketPlace { self: @ContractState, execution_strategy: ContractAddress, amount: u128 ) -> u128 { let fee = IExecutionStrategyDispatcher { contract_address: execution_strategy } - .protocol_fee(); + .protocolFee(); amount * fee / 10_000 } @@ -733,7 +715,6 @@ mod MarketPlace { ) { let executed_order_cancelled = self .get_is_user_order_nonce_executed_or_cancelled(*order.signer, *order.nonce); - let min_nonce = self.get_user_min_order_nonce(*order.signer); assert!(!executed_order_cancelled, "MarketPlace: executed order is cancelled"); assert!( @@ -742,19 +723,16 @@ mod MarketPlace { min_nonce, *order.nonce ); - assert!( !(*order.signer).is_zero(), "MarketPlace: invalid order signer {}", *order.signer ); assert!( !(*order.amount).is_zero(), "MarketPlace: invalid order amount {}", *order.amount ); - self .signature_checker .read() .verify_maker_order_signature(self.get_hash_domain(), *order, order_signature); - let currency_whitelisted = self .currency_manager .read() @@ -762,12 +740,15 @@ mod MarketPlace { assert!( currency_whitelisted, "MarketPlace: currency {} is not whitelisted", *order.currency ); - let strategy_whitelisted = self .execution_manager .read() .is_strategy_whitelisted(*order.strategy); - assert!(strategy_whitelisted, "MarketPlace: strategy is not whitelisted"); + assert!( + strategy_whitelisted, + "MarketPlace: strategy {} is not whitelisted", + (*order.strategy) + ); } } } diff --git a/flex_marketplace/src/marketplace/transfer_selector_NFT.cairo b/flex_marketplace/src/marketplace/transfer_selector_NFT.cairo index cdb4130..bb13974 100644 --- a/flex_marketplace/src/marketplace/transfer_selector_NFT.cairo +++ b/flex_marketplace/src/marketplace/transfer_selector_NFT.cairo @@ -45,6 +45,8 @@ mod TransferSelectorNFT { impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; + use snforge_std::PrintTrait; + #[storage] struct Storage { initialized: bool, diff --git a/flex_marketplace/src/mocks/account.cairo b/flex_marketplace/src/mocks/account.cairo new file mode 100644 index 0000000..e00d15e --- /dev/null +++ b/flex_marketplace/src/mocks/account.cairo @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.7.0 (account/account.cairo) + +trait PublicKeyTrait { + fn set_public_key(ref self: TState, new_public_key: felt252); + fn get_public_key(self: @TState) -> felt252; +} + +trait PublicKeyCamelTrait { + fn setPublicKey(ref self: TState, newPublicKey: felt252); + fn getPublicKey(self: @TState) -> felt252; +} + +#[starknet::contract] +mod Account { + use ecdsa::check_ecdsa_signature; + + use openzeppelin::account::interface; + use openzeppelin::introspection::interface::ISRC5; + use openzeppelin::introspection::interface::ISRC5Camel; + use openzeppelin::account::account::AccountComponent; + use starknet::account::Call; + use starknet::get_caller_address; + use starknet::get_contract_address; + use starknet::get_tx_info; + use openzeppelin::introspection::src5::SRC5Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + #[abi(embed_v0)] + impl SRC5CamelImpl = SRC5Component::SRC5CamelImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + #[abi(embed_v0)] + impl SRC6Impl = AccountComponent::SRC6Impl; + #[abi(embed_v0)] + impl DeployableImpl = AccountComponent::DeployableImpl; + #[abi(embed_v0)] + impl DeclarerImpl = AccountComponent::DeclarerImpl; + #[abi(embed_v0)] + impl PublicKeyImpl = AccountComponent::PublicKeyImpl; + #[abi(embed_v0)] + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; + #[abi(embed_v0)] + impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; + impl InternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + account: AccountComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + AccountEvent: AccountComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, _public_key: felt252) { + self.account.initializer(_public_key); + } +} + diff --git a/flex_marketplace/src/mocks/erc20.cairo b/flex_marketplace/src/mocks/erc20.cairo new file mode 100644 index 0000000..3720a18 --- /dev/null +++ b/flex_marketplace/src/mocks/erc20.cairo @@ -0,0 +1,351 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IERC20Camel { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +//! SPDX-License-Identifier: MIT +//! OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/erc20.cairo) +//! +//! # ERC20 Contract and Implementation +//! +//! This ERC20 contract includes both a library and a basic preset implementation. +//! The library is agnostic regarding how tokens are created; however, +//! the preset implementation sets the initial supply in the constructor. +//! A derived contract can use [_mint](_mint) to create a different supply mechanism. +#[starknet::contract] +mod ERC20 { + use integer::BoundedInt; + use starknet::ContractAddress; + use starknet::get_caller_address; + use snforge_std::PrintTrait; + + #[storage] + struct Storage { + ERC20_name: felt252, + ERC20_symbol: felt252, + ERC20_total_supply: u256, + ERC20_balances: LegacyMap, + ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + + /// Emitted when tokens are moved from address `from` to address `to`. + #[derive(Drop, starknet::Event)] + struct Transfer { + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + value: u256 + } + + /// Emitted when the allowance of a `spender` for an `owner` is set by a call + /// to [approve](approve). `value` is the new allowance. + #[derive(Drop, starknet::Event)] + struct Approval { + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + value: u256 + } + + mod Errors { + const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + } + + /// Initializes the state of the ERC20 contract. This includes setting the + /// initial supply of tokens as well as the recipient of the initial supply. + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) { + self.initializer(name, symbol); + self._mint(recipient, initial_supply); + } + + // + // External + // + + #[external(v0)] + impl ERC20Impl of super::IERC20 { + /// Returns the name of the token. + fn name(self: @ContractState) -> felt252 { + self.ERC20_name.read() + } + + /// Returns the ticker symbol of the token, usually a shorter version of the name. + fn symbol(self: @ContractState) -> felt252 { + self.ERC20_symbol.read() + } + + /// Returns the number of decimals used to get its user representation. + fn decimals(self: @ContractState) -> u8 { + 18 + } + + /// Returns the value of tokens in existence. + fn total_supply(self: @ContractState) -> u256 { + self.ERC20_total_supply.read() + } + + /// Returns the amount of tokens owned by `account`. + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.ERC20_balances.read(account) + } + + /// Returns the remaining number of tokens that `spender` is + /// allowed to spend on behalf of `owner` through [transfer_from](transfer_from). + /// This is zero by default. + /// This value changes when [approve](approve) or [transfer_from](transfer_from) + /// are called. + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.ERC20_allowances.read((owner, spender)) + } + + /// Moves `amount` tokens from the caller's token balance to `to`. + /// Emits a [Transfer](Transfer) event. + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + /// Moves `amount` tokens from `from` to `to` using the allowance mechanism. + /// `amount` is then deducted from the caller's allowance. + /// Emits a [Transfer](Transfer) event. + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + let caller = get_caller_address(); + self._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + true + } + + /// Sets `amount` as the allowance of `spender` over the caller’s tokens. + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + self._approve(caller, spender, amount); + true + } + } + + /// Increases the allowance granted from the caller to `spender` by `added_value`. + /// Emits an [Approval](Approval) event indicating the updated allowance. + #[external(v0)] + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + self._increase_allowance(spender, added_value) + } + + /// Decreases the allowance granted from the caller to `spender` by `subtracted_value`. + /// Emits an [Approval](Approval) event indicating the updated allowance. + #[external(v0)] + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + self._decrease_allowance(spender, subtracted_value) + } + + #[external(v0)] + impl ERC20CamelOnlyImpl of super::IERC20CamelOnly { + /// Camel case support. + /// See [total_supply](total-supply). + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + /// Camel case support. + /// See [balance_of](balance_of). + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + /// Camel case support. + /// See [transfer_from](transfer_from). + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + /// Camel case support. + /// See [increase_allowance](increase_allowance). + #[external(v0)] + fn increaseAllowance( + ref self: ContractState, spender: ContractAddress, addedValue: u256 + ) -> bool { + increase_allowance(ref self, spender, addedValue) + } + + /// Camel case support. + /// See [decrease_allowance](decrease_allowance). + #[external(v0)] + fn decreaseAllowance( + ref self: ContractState, spender: ContractAddress, subtractedValue: u256 + ) -> bool { + decrease_allowance(ref self, spender, subtractedValue) + } + + // + // Internal + // + + #[generate_trait] + impl InternalImpl of InternalTrait { + /// Initializes the contract by setting the token name and symbol. + /// To prevent reinitialization, this should only be used inside of a contract's constructor. + fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { + self.ERC20_name.write(name); + self.ERC20_symbol.write(symbol); + } + + /// Internal method that moves an `amount` of tokens from `from` to `to`. + /// Emits a [Transfer](Transfer) event. + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount); + self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); + self.emit(Transfer { from: sender, to: recipient, value: amount }); + } + + /// Internal method that sets `amount` as the allowance of `spender` over the + /// `owner`s tokens. + /// Emits an [Approval](Approval) event. + fn _approve( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); + self.ERC20_allowances.write((owner, spender), amount); + self.emit(Approval { owner, spender, value: amount }); + } + + /// Creates a `value` amount of tokens and assigns them to `account`. + /// Emits a [Transfer](Transfer) event with `from` set to the zero address. + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount); + self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); + self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); + } + + /// Destroys a `value` amount of tokens from `account`. + /// Emits a [Transfer](Transfer) event with `to` set to the zero address. + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self.ERC20_total_supply.write(self.ERC20_total_supply.read() - amount); + self.ERC20_balances.write(account, self.ERC20_balances.read(account) - amount); + self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount }); + } + + /// Internal method for the external [increase_allowance](increase_allowance). + /// Emits an [Approval](Approval) event indicating the updated allowance. + fn _increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + let caller = get_caller_address(); + self + ._approve( + caller, spender, self.ERC20_allowances.read((caller, spender)) + added_value + ); + true + } + + /// Internal method for the external [decrease_allowance](decrease_allowance). + /// Emits an [Approval](Approval) event indicating the updated allowance. + fn _decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + let caller = get_caller_address(); + self + ._approve( + caller, + spender, + self.ERC20_allowances.read((caller, spender)) - subtracted_value + ); + true + } + + /// Updates `owner`s allowance for `spender` based on spent `amount`. + /// Does not update the allowance value in case of infinite allowance. + /// Possibly emits an [Approval](Approval) event. + fn _spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self.ERC20_allowances.read((owner, spender)); + if current_allowance != BoundedInt::max() { + self._approve(owner, spender, current_allowance - amount); + } + } + } +} diff --git a/flex_marketplace/src/mocks/erc721.cairo b/flex_marketplace/src/mocks/erc721.cairo index a2db45a..d260d34 100644 --- a/flex_marketplace/src/mocks/erc721.cairo +++ b/flex_marketplace/src/mocks/erc721.cairo @@ -8,12 +8,42 @@ trait IER721CamelOnly { ); } +#[starknet::interface] +trait IERC2981 { + fn royaltyInfo( + ref self: TContractState, tokenId: u256, salePrice: u128 + ) -> (starknet::ContractAddress, u128); +} + #[starknet::contract] mod ERC721 { use starknet::{ContractAddress, get_caller_address}; + use openzeppelin::introspection::src5::SRC5Component; + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + #[abi(embed_v0)] + impl SRC5CamelImpl = SRC5Component::SRC5CamelImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; #[storage] - struct Storage {} + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState,) { + self.src5.register_interface(0x80ac58cd); + } #[external(v0)] impl IERC721CamelOnlyImpl of super::IER721CamelOnly { @@ -24,4 +54,18 @@ mod ERC721 { token_id: u256 ) {} } + + + fn RECIPIENT() -> starknet::ContractAddress { + starknet::contract_address_const::<'RECIPIENT'>() + } + + #[external(v0)] + impl IERC2981Impl of super::IERC2981 { + fn royaltyInfo( + ref self: ContractState, tokenId: u256, salePrice: u128 + ) -> (starknet::ContractAddress, u128) { + (RECIPIENT(), 5000) + } + } } diff --git a/flex_marketplace/src/mocks/strategy.cairo b/flex_marketplace/src/mocks/strategy.cairo new file mode 100644 index 0000000..d6fe25b --- /dev/null +++ b/flex_marketplace/src/mocks/strategy.cairo @@ -0,0 +1,63 @@ +use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; +use starknet::ContractAddress; + +#[starknet::interface] +trait IExecutionStrategy { + fn protocolFee(self: @TState) -> u128; + fn canExecuteTakerAsk( + self: @TState, taker_ask: TakerOrder, maker_bid: MakerOrder, extra_params: Array + ) -> (bool, u256, u128); + fn canExecuteTakerBid( + self: @TState, taker_bid: TakerOrder, maker_ask: MakerOrder + ) -> (bool, u256, u128); +} + +#[starknet::interface] +trait IAuctionStrategy { + fn auctionRelayer(self: @TState) -> ContractAddress; + fn canExecuteAuctionSale( + self: @TState, maker_ask: MakerOrder, maker_bid: MakerOrder + ) -> (bool, u256, u128); +} + +#[starknet::contract] +mod Strategy { + use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[external(v0)] + impl MockExecutionStrategyImpl of super::IExecutionStrategy { + fn protocolFee(self: @ContractState) -> u128 { + 1000 + } + fn canExecuteTakerAsk( + self: @ContractState, + taker_ask: super::TakerOrder, + maker_bid: super::MakerOrder, + extra_params: Array + ) -> (bool, u256, u128) { + (true, 1, 1) + } + fn canExecuteTakerBid( + self: @ContractState, taker_bid: super::TakerOrder, maker_ask: super::MakerOrder + ) -> (bool, u256, u128) { + (true, 1, 1) + } + } + + #[external(v0)] + impl MockAuctionStrategyImpl of super::IAuctionStrategy { + fn auctionRelayer(self: @ContractState) -> ContractAddress { + starknet::contract_address_const::<'RELAYER'>() + } + fn canExecuteAuctionSale( + self: @ContractState, maker_ask: MakerOrder, maker_bid: MakerOrder + ) -> (bool, u256, u128) { + (true, 1, 1) + } + } +} + diff --git a/flex_marketplace/tests/marketplace_test.cairo b/flex_marketplace/tests/marketplace_test.cairo new file mode 100644 index 0000000..6323029 --- /dev/null +++ b/flex_marketplace/tests/marketplace_test.cairo @@ -0,0 +1,364 @@ +use snforge_std::{start_prank, stop_prank, PrintTrait, CheatTarget}; +use tests::utils::{ + setup, initialize_test, deploy_mock_nft, ACCOUNT1, ACCOUNT2, OWNER, ZERO_ADDRESS, RELAYER, + deploy_mock_execution_strategy, deploy_mock_account, deploy_mock_erc20, E18 +}; +use flex::marketplace::execution_manager::{ + IExecutionManagerDispatcher, IExecutionManagerDispatcherTrait +}; +use flex::marketplace::marketplace::{IMarketPlaceDispatcher, IMarketPlaceDispatcherTrait}; +use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; +use flex::DefaultContractAddress; + +#[test] +fn test_cancel_all_orders_for_sender_success() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.cancel_all_orders_for_sender(1); + let new_min_nonce = dsp.marketplace.get_user_min_order_nonce(ACCOUNT1()); + assert(new_min_nonce == 1, 'wrong min nonce'); +} + +#[test] +#[should_panic(expected: ("MarketPlace: current min nonce 0 is not < than 0",))] +fn test_cancel_all_orders_for_sender_fails_wrong_min_nonce() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.cancel_all_orders_for_sender(0); +} + +#[test] +fn test_carcel_maker_order_success() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.cancel_maker_order(1); + let is_orde_cancelled = dsp + .marketplace + .get_is_user_order_nonce_executed_or_cancelled(ACCOUNT1(), 1); + assert(is_orde_cancelled, 'orded not cancelled'); +} + +#[test] +#[should_panic(expected: ("MarketPlace: current min nonce 0 is not < than 0",))] +fn test_carcel_maker_order_fails_wrong_min_nonce() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.cancel_maker_order(0); +} + +#[test] +fn test_match_ask_with_taker_bid_success() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_order: MakerOrder = Default::default(); + maker_order.is_order_ask = true; + maker_order.collection = mocks.erc721; + maker_order.signer = mocks.account; + maker_order.amount = 1; + maker_order.strategy = mocks.strategy; + maker_order.currency = mocks.erc20; + + let mut taker_bid: TakerOrder = Default::default(); + taker_bid.price = 1000000; + taker_bid.taker = ACCOUNT1(); + + dsp.marketplace.match_ask_with_taker_bid(taker_bid, maker_order, array![r, s], ACCOUNT1()); +} + +#[test] +#[should_panic(expected: "MarketPlace: invalid caller address 0",)] +fn test_match_ask_with_taker_bid_fails_invalid_caller() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ZERO_ADDRESS()); + let mut maker_order: MakerOrder = Default::default(); + let mut taker_bid: TakerOrder = Default::default(); + + dsp.marketplace.match_ask_with_taker_bid(taker_bid, maker_order, array![r, s], ACCOUNT1()); +} + +#[test] +#[should_panic(expected: "MarketPlace: maker ask is not an ask order",)] +fn test_match_ask_with_taker_bid_fails_maker_ask_not_ask_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_order: MakerOrder = Default::default(); + let mut taker_bid: TakerOrder = Default::default(); + + dsp.marketplace.match_ask_with_taker_bid(taker_bid, maker_order, array![r, s], ACCOUNT1()); +} + +#[test] +#[should_panic(expected: "MarketPlace: taker bid is an ask order",)] +fn test_match_ask_with_taker_bid_fails_taker_bid_is_ask_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_order: MakerOrder = Default::default(); + maker_order.is_order_ask = true; + + let mut taker_bid: TakerOrder = Default::default(); + taker_bid.is_order_ask = true; + + dsp.marketplace.match_ask_with_taker_bid(taker_bid, maker_order, array![r, s], ACCOUNT1()); +} + +#[test] +fn test_match_bid_with_taker_ask_success() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_bid: MakerOrder = Default::default(); + maker_bid.collection = mocks.erc721; + maker_bid.signer = mocks.account; + maker_bid.amount = 1; + maker_bid.strategy = mocks.strategy; + maker_bid.currency = mocks.erc20; + + let mut taker_ask: TakerOrder = Default::default(); + taker_ask.is_order_ask = true; + taker_ask.price = 1000000; + taker_ask.taker = ACCOUNT1(); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.match_bid_with_taker_ask(taker_ask, maker_bid, array![r, s], array![]); +} + +#[test] +#[should_panic(expected: "MarketPlace: invalid caller address 0",)] +fn test_match_bid_with_taker_ask_fails_invalid_caller_address() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_bid: MakerOrder = Default::default(); + + let mut taker_ask: TakerOrder = Default::default(); + taker_ask.is_order_ask = true; + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ZERO_ADDRESS()); + dsp.marketplace.match_bid_with_taker_ask(taker_ask, maker_bid, array![r, s], array![]); +} + +#[test] +#[should_panic(expected: "MarketPlace: maker bid is an ask order",)] +fn test_match_bid_with_taker_ask_fails_maker_bid_is_ask_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_bid: MakerOrder = Default::default(); + maker_bid.is_order_ask = true; + + let mut taker_ask: TakerOrder = Default::default(); + + dsp.marketplace.match_bid_with_taker_ask(taker_ask, maker_bid, array![r, s], array![]); +} + +#[test] +#[should_panic()] +fn test_match_bid_with_taker_ask_fails_taker_ask_is_not_an_ask_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r, s) = mocks.maker_signature; + + let mut maker_bid: MakerOrder = Default::default(); + + let mut taker_ask: TakerOrder = Default::default(); + + dsp.marketplace.match_bid_with_taker_ask(taker_ask, maker_bid, array![r, s], array![]); +} + +#[test] +fn test_execute_auction_sale_success() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r1, s1) = mocks.maker_signature; + let (r2, s2) = mocks.taker_signature; + + let mut maker_ask: MakerOrder = Default::default(); + maker_ask.is_order_ask = true; + maker_ask.strategy = mocks.strategy; + maker_ask.signer = mocks.account; + maker_ask.amount = 1; + maker_ask.collection = mocks.erc721; + maker_ask.currency = mocks.erc20; + + let mut maker_bid: MakerOrder = Default::default(); + maker_bid.signer = mocks.account; + maker_bid.strategy = mocks.strategy; + maker_bid.price = 1_000_000; + maker_bid.amount = 1; + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), RELAYER()); + dsp.marketplace.execute_auction_sale(maker_ask, array![r1, s1], maker_bid, array![r2, s2]); +} + +#[test] +#[should_panic(expected: "MarketPlace: invalid caller address 0",)] +fn test_execute_auction_sale_fails_invalid_caller_address() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r1, s1) = mocks.maker_signature; + let (r2, s2) = mocks.taker_signature; + + let mut maker_ask: MakerOrder = Default::default(); + + let mut maker_bid: MakerOrder = Default::default(); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ZERO_ADDRESS()); + dsp.marketplace.execute_auction_sale(maker_ask, array![r1, s1], maker_bid, array![r2, s2]); +} + +#[test] +#[should_panic(expected: "MarketPlace: maker ask is not an ask order",)] +fn test_execute_auction_sale_fails_maker_ask_not_maker_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r1, s1) = mocks.maker_signature; + let (r2, s2) = mocks.taker_signature; + + let mut maker_ask: MakerOrder = Default::default(); + + let mut maker_bid: MakerOrder = Default::default(); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), RELAYER()); + dsp.marketplace.execute_auction_sale(maker_ask, array![r1, s1], maker_bid, array![r2, s2]); +} + +#[test] +#[should_panic()] +fn test_execute_auction_sale_fails_maker_bid_is_maker_order() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r1, s1) = mocks.maker_signature; + let (r2, s2) = mocks.taker_signature; + + let mut maker_ask: MakerOrder = Default::default(); + + let mut maker_bid: MakerOrder = Default::default(); + maker_bid.is_order_ask = true; + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), RELAYER()); + dsp.marketplace.execute_auction_sale(maker_ask, array![r1, s1], maker_bid, array![r2, s2]); +} + +#[test] +#[should_panic(expected: "MarketPlace: caller is not relayer",)] +fn test_execute_auction_sale_fails_caller_not_relayer() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let (r1, s1) = mocks.maker_signature; + let (r2, s2) = mocks.taker_signature; + + let mut maker_ask: MakerOrder = Default::default(); + maker_ask.is_order_ask = true; + maker_ask.strategy = mocks.strategy; + maker_ask.signer = mocks.account; + maker_ask.amount = 1; + maker_ask.collection = mocks.erc721; + maker_ask.currency = mocks.erc20; + + let mut maker_bid: MakerOrder = Default::default(); + maker_bid.signer = mocks.account; + maker_bid.strategy = mocks.strategy; + maker_bid.price = 1_000_000; + maker_bid.amount = 1; + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), ACCOUNT1()); + dsp.marketplace.execute_auction_sale(maker_ask, array![r1, s1], maker_bid, array![r2, s2]); +} + +#[test] +fn test_update_hash_domain() { + let dsp = setup(); + let mocks = initialize_test(dsp); + let new_hash_domain = 'new_hash_domain'; + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_hash_domain(new_hash_domain); + let actual = dsp.marketplace.get_hash_domain(); + assert(actual == new_hash_domain, 'failed hash domain update'); +} + +#[test] +fn test_update_protocol_fee_recepient() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_protocol_fee_recepient(ACCOUNT1()); + let actual = dsp.marketplace.get_protocol_fee_recipient(); + assert(actual == ACCOUNT1(), 'failed fee recipient update'); +} + +#[test] +fn test_update_currency_manager() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_currency_manager(ACCOUNT1()); + let actual = dsp.marketplace.get_currency_manager(); + assert(actual == ACCOUNT1(), 'failed currency manager update'); +} + +#[test] +fn test_update_execution_manager() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_execution_manager(ACCOUNT1()); + let actual = dsp.marketplace.get_execution_manager(); + assert(actual == ACCOUNT1(), 'failed execution manager update'); +} + +#[test] +fn test_update_royalty_fee_manager() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_royalty_fee_manager(ACCOUNT1()); + let actual = dsp.marketplace.get_royalty_fee_manager(); + assert(actual == ACCOUNT1(), 'failed royalty manager update'); +} + +#[test] +fn test_update_transfer_selector_NFT() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_transfer_selector_NFT(ACCOUNT1()); + let actual = dsp.marketplace.get_transfer_selector_NFT(); + assert(actual == ACCOUNT1(), 'failed selector nft update'); +} + +#[test] +fn test_update_signature_checker() { + let dsp = setup(); + let mocks = initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.marketplace.contract_address), OWNER()); + dsp.marketplace.update_signature_checker(ACCOUNT1()); + let actual = dsp.marketplace.get_signature_checker(); + assert(actual == ACCOUNT1(), 'failed selector sig checker'); +} + diff --git a/flex_marketplace/tests/marketplacet_test.cairo b/flex_marketplace/tests/marketplacet_test.cairo deleted file mode 100644 index 1ce17d0..0000000 --- a/flex_marketplace/tests/marketplacet_test.cairo +++ /dev/null @@ -1,429 +0,0 @@ -use snforge_std::{start_prank, stop_prank, PrintTrait, CheatTarget}; -use tests::utils::{ - setup, initialize_test, deploy_mock_nft, ACCOUNT1, ACCOUNT2, OWNER, ZERO_ADDRESS -}; -use flex::marketplace::marketplace::{IMarketPlaceDispatcher, IMarketPlaceDispatcherTrait}; -use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; -use flex::DefaultContractAddress; - -#[test] -fn test_cancel_all_orders_for_sender_success() { - let dsp = setup(); - initialize_test(dsp); - - start_prank( - CheatTarget::One(dsp.marketplace.contract_address), - ACCOUNT1() - ); - dsp.marketplace.cancel_all_orders_for_sender(1); - let new_min_nonce = dsp.marketplace.get_user_min_order_nonce(ACCOUNT1()); - assert(new_min_nonce == 1, 'wrong min nonce'); -} - -#[test] -#[should_panic(expected: ("MarketPlace: current min nonce 0 is not < than 0", ))] -fn test_cancel_all_orders_for_sender_fails_wrong_min_nonce() { - let dsp = setup(); - initialize_test(dsp); - - start_prank( - CheatTarget::One(dsp.marketplace.contract_address), - ACCOUNT1() - ); - dsp.marketplace.cancel_all_orders_for_sender(0); -} - -#[test] -fn test_carcel_maker_order_success() { - let dsp = setup(); - initialize_test(dsp); - - start_prank( - CheatTarget::One(dsp.marketplace.contract_address), - ACCOUNT1() - ); - dsp.marketplace.cancel_maker_order(1); - let is_orde_cancelled = dsp.marketplace.get_is_user_order_nonce_executed_or_cancelled(ACCOUNT1(), 1); - assert(is_orde_cancelled, 'orded not cancelled'); -} - -#[test] -#[should_panic(expected: ("MarketPlace: current min nonce 0 is not < than 0", ))] -fn test_carcel_maker_order_fails_wrong_min_nonce() { - let dsp = setup(); - initialize_test(dsp); - - start_prank( - CheatTarget::One(dsp.marketplace.contract_address), - ACCOUNT1() - ); - dsp.marketplace.cancel_maker_order(0); -} - -#[test] -fn test_match_ask_with_taker_bid_success() { - let dsp = setup(); - initialize_test(dsp); - let mut maker_order: MakerOrder = Default::default(); - maker_order.is_order_ask = true; - let mut taker_bid: TakerOrder = Default::default(); - - dsp.marketplace.match_ask_with_taker_bid(taker_bid, maker_ask, ) - -// TODO -} - -#[test] -#[should_panic()] -fn test_match_ask_with_taker_bid_fails_invalid_caller() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_ask_with_taker_bid_fails_maker_ask_not_ask_orde() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_ask_with_taker_bid_fails_taker_bid_is_ask_order() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_ask_with_taker_bid_fails_order_cannot_be_executed() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} -#[test] -fn test_match_bid_with_taker_ask_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_bid_with_taker_ask_fails_invalid_caller_address() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_bid_with_taker_ask_fails_maker_bid_is_ask_order() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_bid_with_taker_ask_fails_taker_ask_is_not_an_ask_order() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_match_bid_with_taker_ask_fails_taker_ask_cannot_be_executed() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -fn test_execute_auction_sale_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -#[should_panic()] -fn test_execute_auction_sale_fails_invalid_caller_address() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_execute_auction_sale_fails_maker_ask_not_maker_order() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_execute_auction_sale_fails_maker_bid_is_maker_order() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_execute_auction_sale_fails_caller_not_relayer() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_execute_auction_sale_fails_strategy_cannot_be_executed() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -fn test_update_hash_domain() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_protocol_fee_recepient() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_currency_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_execution_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_royalty_fee_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_transfer_selector_NFT() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_update_signature_checker() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -// TESTS VIWES - -#[test] -fn test_get_hash_domain() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_protocol_fee_recipient() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_currency_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_execution_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_royalty_fee_manager() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_transfer_selector_NFT() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_signature_checker() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn get_user_min_order_nonce() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_is_user_order_nonce_executed_or_cancelled() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -// TESTS INTERNALS -#[test] -fn test_transfer_fees_and_funds_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -#[should_panic()] -fn test_transfer_fees_and_funds_fails_amount_is_zero() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -fn test_transfer_non_fungible_token_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -#[should_panic()] -fn test_transfer_non_fungible_token_fails_invalid_amount() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_transfer_non_fungible_token_fails_invalid_transfer_manager() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -fn test_calculate_protocol_fee_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_validate_order_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_order_is_cancelled() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_invalid_min_nonce() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_invalid_order_signer() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_invalid_order_amount() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_currency_not_whitelisted() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - -#[test] -#[should_panic()] -fn test_validate_order_fails_strategy_not_whitelisted() { - let dsp = setup(); - initialize_test(dsp); - assert(false, ''); -// TODO -} - diff --git a/flex_marketplace/tests/transfer_manager_erc1155_test.cairo b/flex_marketplace/tests/transfer_manager_erc1155_test.cairo index 055a9a7..7a8d5d8 100644 --- a/flex_marketplace/tests/transfer_manager_erc1155_test.cairo +++ b/flex_marketplace/tests/transfer_manager_erc1155_test.cairo @@ -12,8 +12,7 @@ const TOKEN_ID: u256 = 1; #[test] fn test_transfer_non_fungible_token_success() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_1155(); + let mocks = initialize_test(dsp); start_prank( CheatTarget::One(dsp.transfer_manager_erc1155.contract_address), @@ -22,7 +21,7 @@ fn test_transfer_non_fungible_token_success() { dsp .transfer_manager_erc1155 .transfer_non_fungible_token( - collection, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1, array![].span() + mocks.erc1155, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1, array![].span() ); } @@ -30,22 +29,20 @@ fn test_transfer_non_fungible_token_success() { #[should_panic(expected: ("ERC1155TransferManager: caller 0 is not marketplace",))] fn test_transfer_non_fungible_token_fails_caller_not_marketplace() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_1155(); + let mocks = initialize_test(dsp); start_prank(CheatTarget::One(dsp.transfer_manager_erc1155.contract_address), ZERO_ADDRESS()); dsp .transfer_manager_erc1155 .transfer_non_fungible_token( - collection, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1, array![].span() + mocks.erc1155, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1, array![].span() ); } #[test] fn test_update_marketplace_success() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_1155(); + let mocks = initialize_test(dsp); let new_marketplace = starknet::contract_address_const::<'new_marketplace'>(); start_prank(CheatTarget::One(dsp.transfer_manager_erc1155.contract_address), OWNER()); diff --git a/flex_marketplace/tests/transfer_manager_erc721_test.cairo b/flex_marketplace/tests/transfer_manager_erc721_test.cairo index dba362e..0799146 100644 --- a/flex_marketplace/tests/transfer_manager_erc721_test.cairo +++ b/flex_marketplace/tests/transfer_manager_erc721_test.cairo @@ -11,8 +11,7 @@ const TOKEN_ID: u256 = 1; #[test] fn test_transfer_non_fungible_token_success() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_nft(); + let mocks = initialize_test(dsp); start_prank( CheatTarget::One(dsp.transfer_manager_erc721.contract_address), @@ -20,27 +19,25 @@ fn test_transfer_non_fungible_token_success() { ); dsp .transfer_manager_erc721 - .transfer_non_fungible_token(collection, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1); + .transfer_non_fungible_token(mocks.erc721, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1); } #[test] #[should_panic(expected: ("TransferManagerNFT: caller 0 is not MarketPlace",))] fn test_transfer_non_fungible_token_fails_caller_not_marketplace() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_nft(); + let mocks = initialize_test(dsp); start_prank(CheatTarget::One(dsp.transfer_manager_erc721.contract_address), ZERO_ADDRESS()); dsp .transfer_manager_erc721 - .transfer_non_fungible_token(collection, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1); + .transfer_non_fungible_token(mocks.erc721, ACCOUNT1(), ACCOUNT2(), TOKEN_ID, 1); } #[test] fn test_update_marketplace_success() { let dsp = setup(); - initialize_test(dsp); - let collection = deploy_mock_nft(); + let mocks = initialize_test(dsp); let new_marketplace = starknet::contract_address_const::<'new_marketplace'>(); start_prank( diff --git a/flex_marketplace/tests/utils.cairo b/flex_marketplace/tests/utils.cairo index 2aad911..5317cae 100644 --- a/flex_marketplace/tests/utils.cairo +++ b/flex_marketplace/tests/utils.cairo @@ -1,10 +1,16 @@ +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; use starknet::{ ContractAddress, contract_address_const, get_block_timestamp, get_contract_address, get_caller_address, class_hash::ClassHash }; use snforge_std::{ - declare, ContractClassTrait, start_warp, start_prank, stop_prank, PrintTrait, CheatTarget + PrintTrait, declare, ContractClassTrait, start_warp, start_prank, stop_prank, CheatTarget }; +use snforge_std::signature::KeyPairTrait; +use snforge_std::signature::stark_curve::{ + StarkCurveKeyPairImpl, StarkCurveSignerImpl, StarkCurveVerifierImpl +}; + use flex::marketplace::{ marketplace::{MarketPlace, IMarketPlaceDispatcher, IMarketPlaceDispatcherTrait}, currency_manager::{ @@ -34,11 +40,16 @@ use flex::marketplace::{ }, }; use flex::mocks::erc721::ERC721; - +use flex::mocks::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use flex::mocks::erc1155::ERC1155; +use flex::mocks::strategy::Strategy; +use flex::mocks::account::Account; const HASH_DOMAIN: felt252 = 'HASH_DOMAIN'; const FEE_LIMIT: u128 = 1_000; +const E18: u128 = 1_000_000_000_000_000_000; +const PRICE: u256 = 1000_000_000_000_000_000_000; +const SUPPLY: u256 = 1_000_000_000_000_000_000_000_000; #[derive(Copy, Drop, Serde)] struct Dispatchers { @@ -60,16 +71,16 @@ fn RECIPIENT() -> ContractAddress { contract_address_const::<'RECIPIENT'>() } fn ACCOUNT1() -> ContractAddress { - contract_address_const::<1>() + contract_address_const::<'ACCOUNT1'>() } fn ACCOUNT2() -> ContractAddress { - contract_address_const::<2>() + contract_address_const::<'ACCOUNT2'>() } fn ACCOUNT3() -> ContractAddress { - contract_address_const::<3>() + contract_address_const::<'ACCOUNT3'>() } fn ACCOUNT4() -> ContractAddress { - contract_address_const::<4>() + contract_address_const::<'ACCOUNT4'>() } fn PROXY_ADMIN() -> ContractAddress { contract_address_const::<'PROXY_ADMIN'>() @@ -77,6 +88,9 @@ fn PROXY_ADMIN() -> ContractAddress { fn ZERO_ADDRESS() -> ContractAddress { contract_address_const::<0>() } +fn RELAYER() -> ContractAddress { + contract_address_const::<'RELAYER'>() +} fn setup() -> Dispatchers { let contract = declare('MarketPlace'); @@ -123,7 +137,18 @@ fn setup() -> Dispatchers { } } -fn initialize_test(dsp: Dispatchers) { +#[derive(Copy, Drop, Serde)] +struct Mocks { + account: ContractAddress, + erc20: ContractAddress, + erc721: ContractAddress, + erc1155: ContractAddress, + strategy: ContractAddress, + maker_signature: (felt252, felt252), + taker_signature: (felt252, felt252) +} + +fn initialize_test(dsp: Dispatchers) -> Mocks { // Initialise MarketPlace dsp .marketplace @@ -142,7 +167,7 @@ fn initialize_test(dsp: Dispatchers) { // Initialise ExecutionManager dsp.execution_manager.initializer(OWNER()); // Initialise RoyaltyFeeManager - dsp.fee_manager.initializer(OWNER(), dsp.fee_registry.contract_address); + dsp.fee_manager.initializer(dsp.fee_registry.contract_address, OWNER()); // Initialise RoyaltyFeeRegistry dsp.fee_registry.initializer(FEE_LIMIT, OWNER()); // Initialise TransferSelectorNFT @@ -159,6 +184,57 @@ fn initialize_test(dsp: Dispatchers) { .initializer(dsp.marketplace.contract_address, OWNER(), PROXY_ADMIN()); // Initialise TransferManagerERC1155 dsp.transfer_manager_erc1155.initializer(dsp.marketplace.contract_address, OWNER()); + + let key_pair = KeyPairTrait::::generate(); + let msg_hash = 123456; + let (r1, s1): (felt252, felt252) = key_pair.sign(msg_hash); + + let msg_hash = 654321; + let (r2, s2): (felt252, felt252) = key_pair.sign(msg_hash); + + let account = deploy_mock_account(key_pair.public_key); + let erc20 = deploy_mock_erc20(); + let erc721 = deploy_mock_nft(); + let erc1155 = deploy_mock_1155(); + let strategy = deploy_mock_execution_strategy(); + + start_prank(CheatTarget::All(()), OWNER()); + + IERC20Dispatcher { contract_address: erc20 }.transfer(ACCOUNT1(), (1000 * E18).into()); + IERC20Dispatcher { contract_address: erc20 }.transfer(account, (1000 * E18).into()); + + dsp.execution_manager.add_strategy(strategy); + + dsp + .transfer_selector + .add_collection_transfer_manager(erc721, dsp.transfer_manager_erc721.contract_address); + + dsp.marketplace.update_transfer_selector_NFT(dsp.transfer_selector.contract_address); + + dsp.fee_registry.update_royalty_fee_limit(1000); + + dsp.fee_registry.update_royalty_info_collection(erc721, OWNER(), RECIPIENT(), 1000); + + stop_prank(CheatTarget::All(())); + + start_prank(CheatTarget::One(erc20), ACCOUNT1()); + IERC20CamelDispatcher { contract_address: erc20 } + .approve(dsp.marketplace.contract_address, SUPPLY); + + start_prank(CheatTarget::One(erc20), account); + IERC20CamelDispatcher { contract_address: erc20 } + .approve(dsp.marketplace.contract_address, SUPPLY); + stop_prank(CheatTarget::One(erc20)); + + Mocks { + account, + erc20, + erc721, + erc1155, + strategy, + maker_signature: (r1, s1), + taker_signature: (r2, s2) + } } fn deploy_mock_1155() -> ContractAddress { @@ -171,8 +247,26 @@ fn deploy_mock_nft() -> ContractAddress { contract.deploy(@array![]).expect('failed ERC721') } +fn deploy_mock_execution_strategy() -> ContractAddress { + let contract = declare('Strategy'); + contract.deploy(@array![]).expect('failed ExecutionStrategy') +} + +fn deploy_mock_account(public_key: felt252) -> ContractAddress { + let contract = declare('Account'); + contract.deploy(@array![public_key]).expect('failed account') +} + +fn deploy_mock_erc20() -> ContractAddress { + let contract = declare('ERC20'); + let calldata: Array = array![ + 'name', 'symbol', SUPPLY.try_into().unwrap(), 0, OWNER().into() + ]; + contract.deploy(@calldata).expect('failed erc20') +} + #[test] fn deploy_test() { let dsp = setup(); initialize_test(dsp); -} \ No newline at end of file +}