From 18cd1f30ed0596335fdc2c96b83f4e0b5bd75260 Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 5 Mar 2024 16:42:37 +0700 Subject: [PATCH 1/3] validate signature v2 --- src/marketplace/marketplace.cairo | 2 +- src/marketplace/signature_checker2.cairo | 110 +++++++++++++++++++++-- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/marketplace/marketplace.cairo b/src/marketplace/marketplace.cairo index 8b42454..892742c 100644 --- a/src/marketplace/marketplace.cairo +++ b/src/marketplace/marketplace.cairo @@ -747,7 +747,7 @@ mod MarketPlace { self .signature_checker .read() - .verify_maker_order_signature(self.get_hash_domain(), *order, order_signature); + .verify_maker_order_signature_v2(*order, order_signature); let currency_whitelisted = self .currency_manager .read() diff --git a/src/marketplace/signature_checker2.cairo b/src/marketplace/signature_checker2.cairo index 9a0e3cc..ce559c9 100644 --- a/src/marketplace/signature_checker2.cairo +++ b/src/marketplace/signature_checker2.cairo @@ -7,20 +7,41 @@ const STARKNET_MESSAGE: felt252 = 110930206544689809660069706067448260453; const HASH_MESSAGE_SELECTOR: felt252 = 563771258078353655219004671487831885088158240957819730493696170021701903504; +const STARKNET_DOMAIN_TYPE_HASH: felt252 = + selector!("StarkNetDomain(name:felt,version:felt,chainId:felt)"); + +const STARKNET_MAKER_ORDER_TYPE_HASH: felt252 = + selector!( + "MakerOrder(isOrderAsk:u8,signer:felt,collection:felt,price:u128,tokenId:u256,amount:u128,strategy:felt,currency:felt,nonce:u128,startTime:u64,endTime:u64,minPercentageToAsk:u128,params:felt)u256(low:felt,high:felt)" + ); + +const U256_TYPE_HASH: felt252 = selector!("u256(low:felt,high:felt)"); + #[starknet::interface] trait ISignatureChecker2 { fn compute_maker_order_hash(self: @TState, hash_domain: felt252, order: MakerOrder) -> felt252; fn verify_maker_order_signature( self: @TState, hash_domain: felt252, order: MakerOrder, order_signature: Array ); + fn compute_message_hash(self: @TState, order: MakerOrder) -> felt252; + fn verify_maker_order_signature_v2( + self: @TState, order: MakerOrder, order_signature: Array + ); } #[starknet::contract] mod SignatureChecker2 { + use openzeppelin::account::interface::AccountABIDispatcherTrait; + use core::option::OptionTrait; + use core::traits::Into; + use core::traits::TryInto; + use core::box::BoxTrait; use flex::marketplace::signature_checker2::ISignatureChecker2; - use starknet::ContractAddress; + use starknet::{ContractAddress, get_tx_info, contract_address_to_felt252}; use poseidon::poseidon_hash_span; - + use pedersen::PedersenTrait; + use hash::{HashStateTrait, HashStateExTrait}; + use openzeppelin::account::AccountABIDispatcher; use openzeppelin::account::interface::{ISRC6CamelOnlyDispatcher, ISRC6CamelOnlyDispatcherTrait}; use flex::marketplace::utils::order_types::MakerOrder; @@ -29,12 +50,30 @@ mod SignatureChecker2 { struct Storage {} #[constructor] - fn constructor( - ref self: ContractState - ) {} + fn constructor(ref self: ContractState) {} + + #[derive(Drop, Copy, Serde, Hash)] + struct StarknetDomain { + name: felt252, + version: felt252, + chain_id: felt252, + } #[external(v0)] impl SignatureChecker2Impl of super::ISignatureChecker2 { + fn compute_message_hash(self: @ContractState, order: MakerOrder) -> felt252 { + let domain = StarknetDomain { + name: 'Mint Square', version: 2, chain_id: get_tx_info().unbox().chain_id + }; + let mut state = PedersenTrait::new(0); + state = state.update_with('StarkNet Message'); + state = state.update_with(domain.hash_struct()); + state = state.update_with(order.signer); + state = state.update_with(order.hash_struct()); + state = state.update_with(4); + state.finalize() + } + fn compute_maker_order_hash( self: @ContractState, hash_domain: felt252, order: MakerOrder ) -> felt252 { @@ -75,5 +114,66 @@ mod SignatureChecker2 { assert!(result == starknet::VALIDATED, "SignatureChecker: Invalid signature"); } + + fn verify_maker_order_signature_v2( + self: @ContractState, order: MakerOrder, order_signature: Array + ) { + let hash = self.compute_message_hash(order); + let account: AccountABIDispatcher = AccountABIDispatcher { + contract_address: order.signer + }; + let result = account.is_valid_signature(hash, order_signature); + + assert!(result == starknet::VALIDATED, "SignatureChecker: Invalid signature"); + } + } + + trait IStructHash { + fn hash_struct(self: @T) -> felt252; + } + impl StructHashStarknetDomain of IStructHash { + fn hash_struct(self: @StarknetDomain) -> felt252 { + let mut state = PedersenTrait::new(0); + state = state.update_with(super::STARKNET_DOMAIN_TYPE_HASH); + state = state.update_with(*self); + state = state.update_with(4); + state.finalize() + } + } + + impl StructHashMarkerOrder of IStructHash { + fn hash_struct(self: @MakerOrder) -> felt252 { + let mut state = PedersenTrait::new(0); + state = state.update_with(super::STARKNET_MAKER_ORDER_TYPE_HASH); + let mut is_order_ask_u8: u8 = 1; + if !(*self.is_order_ask) { + is_order_ask_u8 = 0; + } + state = state.update_with(is_order_ask_u8); + state = state.update_with(contract_address_to_felt252(*self.signer)); + state = state.update_with(contract_address_to_felt252(*self.collection)); + state = state.update_with(*self.price); + state = state.update_with(self.token_id.hash_struct()); + state = state.update_with(*self.amount); + state = state.update_with(contract_address_to_felt252(*self.strategy)); + state = state.update_with(contract_address_to_felt252(*self.currency)); + state = state.update_with(*self.nonce); + state = state.update_with(*self.start_time); + state = state.update_with(*self.end_time); + state = state.update_with(*self.min_percentage_to_ask); + state = state.update_with(*self.params); + state = state.update_with(14); + state.finalize() + } + } + + impl StructHashU256 of IStructHash { + fn hash_struct(self: @u256) -> felt252 { + let mut state = PedersenTrait::new(0); + state = state.update_with(super::U256_TYPE_HASH); + state = state.update_with(*self); + state = state.update_with(3); + state.finalize() + } } } From 8c60fe4040ff79777ef85bdca2ca3396751df2c4 Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 5 Mar 2024 17:13:19 +0700 Subject: [PATCH 2/3] update domain hash --- src/marketplace/marketplace.cairo | 68 ++++++++++++++++++++---- src/marketplace/signature_checker2.cairo | 32 ++++------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/marketplace/marketplace.cairo b/src/marketplace/marketplace.cairo index 892742c..6665020 100644 --- a/src/marketplace/marketplace.cairo +++ b/src/marketplace/marketplace.cairo @@ -6,7 +6,8 @@ use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; trait IMarketPlace { fn initializer( ref self: TState, - hash: felt252, + domain_name: felt252, + domain_ver: felt252, recipient: ContractAddress, currency: ContractAddress, execution: ContractAddress, @@ -39,7 +40,7 @@ trait IMarketPlace { maker_bid: MakerOrder, maker_bid_signature: Array ); - fn update_hash_domain(ref self: TState, hash: felt252); + fn update_hash_domain(ref self: TState, domain_name: felt252, domain_ver: felt252,); fn update_protocol_fee_recipient(ref self: TState, recipient: ContractAddress); fn update_currency_manager(ref self: TState, manager: ContractAddress); fn update_execution_manager(ref self: TState, manager: ContractAddress); @@ -59,6 +60,9 @@ trait IMarketPlace { ) -> bool; } +const STARKNET_DOMAIN_TYPE_HASH: felt252 = + selector!("StarkNetDomain(name:felt,version:felt,chainId:felt)"); + #[starknet::interface] trait IExecutionStrategy { fn protocolFee(self: @TState) -> u128; @@ -80,14 +84,18 @@ trait IAuctionStrategy { #[starknet::contract] mod MarketPlace { + use flex::marketplace::marketplace::IMarketPlace; use super::{ IExecutionStrategyDispatcher, IExecutionStrategyDispatcherTrait, IAuctionStrategyDispatcher, - IAuctionStrategyDispatcherTrait + IAuctionStrategyDispatcherTrait, STARKNET_DOMAIN_TYPE_HASH }; use starknet::{ - ContractAddress, ClassHash, contract_address_const, get_block_timestamp, get_caller_address + ContractAddress, ClassHash, contract_address_const, get_block_timestamp, get_caller_address, + get_tx_info }; + use pedersen::PedersenTrait; + use hash::{HashStateTrait, HashStateExTrait}; use flex::{DebugContractAddress, DisplayContractAddress}; use flex::marketplace::{ currency_manager::{ICurrencyManagerDispatcher, ICurrencyManagerDispatcherTrait}, @@ -263,10 +271,18 @@ mod MarketPlace { timestamp: u64, } + #[derive(Drop, Copy, Serde, Hash)] + struct StarknetDomain { + name: felt252, + version: felt252, + chain_id: felt252, + } + #[constructor] fn constructor( ref self: ContractState, - hash: felt252, + domain_name: felt252, + domain_ver: felt252, recipient: ContractAddress, currency: ContractAddress, execution: ContractAddress, @@ -275,14 +291,26 @@ mod MarketPlace { owner: ContractAddress, proxy_admin: ContractAddress ) { - self.initializer(hash, recipient, currency, execution, fee_manager, checker, owner, proxy_admin); + self + .initializer( + domain_name, + domain_ver, + recipient, + currency, + execution, + fee_manager, + checker, + owner, + proxy_admin + ); } #[external(v0)] impl MarketPlaceImpl of super::IMarketPlace { fn initializer( ref self: ContractState, - hash: felt252, + domain_name: felt252, + domain_ver: felt252, recipient: ContractAddress, currency: ContractAddress, execution: ContractAddress, @@ -292,7 +320,10 @@ mod MarketPlace { proxy_admin: ContractAddress ) { self.ownable.initializer(owner); - self.hash_domain.write(hash); + let domain = StarknetDomain { + name: domain_name, version: domain_ver, chain_id: get_tx_info().unbox().chain_id + }; + self.hash_domain.write(domain.hash_struct()); self.protocol_fee_recipient.write(recipient); self.currency_manager.write(ICurrencyManagerDispatcher { contract_address: currency }); self @@ -565,8 +596,12 @@ mod MarketPlace { self.reentrancyguard.end(); } - fn update_hash_domain(ref self: ContractState, hash: felt252) { + fn update_hash_domain(ref self: ContractState, domain_name: felt252, domain_ver: felt252,) { self.ownable.assert_only_owner(); + let domain = StarknetDomain { + name: domain_name, version: domain_ver, chain_id: get_tx_info().unbox().chain_id + }; + let hash = domain.hash_struct(); self.hash_domain.write(hash); self.emit(NewHashDomain { hash, timestamp: get_block_timestamp() }); } @@ -747,7 +782,7 @@ mod MarketPlace { self .signature_checker .read() - .verify_maker_order_signature_v2(*order, order_signature); + .verify_maker_order_signature_v2(self.get_hash_domain(), *order, order_signature); let currency_whitelisted = self .currency_manager .read() @@ -766,4 +801,17 @@ mod MarketPlace { ); } } + + trait IStructHash { + fn hash_struct(self: @T) -> felt252; + } + impl StructHashStarknetDomain of IStructHash { + fn hash_struct(self: @StarknetDomain) -> felt252 { + let mut state = PedersenTrait::new(0); + state = state.update_with(STARKNET_DOMAIN_TYPE_HASH); + state = state.update_with(*self); + state = state.update_with(4); + state.finalize() + } + } } diff --git a/src/marketplace/signature_checker2.cairo b/src/marketplace/signature_checker2.cairo index ce559c9..3683e67 100644 --- a/src/marketplace/signature_checker2.cairo +++ b/src/marketplace/signature_checker2.cairo @@ -7,9 +7,6 @@ const STARKNET_MESSAGE: felt252 = 110930206544689809660069706067448260453; const HASH_MESSAGE_SELECTOR: felt252 = 563771258078353655219004671487831885088158240957819730493696170021701903504; -const STARKNET_DOMAIN_TYPE_HASH: felt252 = - selector!("StarkNetDomain(name:felt,version:felt,chainId:felt)"); - const STARKNET_MAKER_ORDER_TYPE_HASH: felt252 = selector!( "MakerOrder(isOrderAsk:u8,signer:felt,collection:felt,price:u128,tokenId:u256,amount:u128,strategy:felt,currency:felt,nonce:u128,startTime:u64,endTime:u64,minPercentageToAsk:u128,params:felt)u256(low:felt,high:felt)" @@ -23,9 +20,9 @@ trait ISignatureChecker2 { fn verify_maker_order_signature( self: @TState, hash_domain: felt252, order: MakerOrder, order_signature: Array ); - fn compute_message_hash(self: @TState, order: MakerOrder) -> felt252; + fn compute_message_hash(self: @TState, domain_hash: felt252, order: MakerOrder) -> felt252; fn verify_maker_order_signature_v2( - self: @TState, order: MakerOrder, order_signature: Array + self: @TState, domain_hash: felt252, order: MakerOrder, order_signature: Array ); } @@ -61,13 +58,12 @@ mod SignatureChecker2 { #[external(v0)] impl SignatureChecker2Impl of super::ISignatureChecker2 { - fn compute_message_hash(self: @ContractState, order: MakerOrder) -> felt252 { - let domain = StarknetDomain { - name: 'Mint Square', version: 2, chain_id: get_tx_info().unbox().chain_id - }; + fn compute_message_hash( + self: @ContractState, domain_hash: felt252, order: MakerOrder + ) -> felt252 { let mut state = PedersenTrait::new(0); state = state.update_with('StarkNet Message'); - state = state.update_with(domain.hash_struct()); + state = state.update_with(domain_hash); state = state.update_with(order.signer); state = state.update_with(order.hash_struct()); state = state.update_with(4); @@ -116,9 +112,12 @@ mod SignatureChecker2 { } fn verify_maker_order_signature_v2( - self: @ContractState, order: MakerOrder, order_signature: Array + self: @ContractState, + domain_hash: felt252, + order: MakerOrder, + order_signature: Array ) { - let hash = self.compute_message_hash(order); + let hash = self.compute_message_hash(domain_hash, order); let account: AccountABIDispatcher = AccountABIDispatcher { contract_address: order.signer }; @@ -131,15 +130,6 @@ mod SignatureChecker2 { trait IStructHash { fn hash_struct(self: @T) -> felt252; } - impl StructHashStarknetDomain of IStructHash { - fn hash_struct(self: @StarknetDomain) -> felt252 { - let mut state = PedersenTrait::new(0); - state = state.update_with(super::STARKNET_DOMAIN_TYPE_HASH); - state = state.update_with(*self); - state = state.update_with(4); - state.finalize() - } - } impl StructHashMarkerOrder of IStructHash { fn hash_struct(self: @MakerOrder) -> felt252 { From f7555ecd135f2af87beb3db98fa603dfe9c3a25e Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 19 Mar 2024 15:19:16 +0700 Subject: [PATCH 3/3] update signature mechanism --- deployment/index.js | 302 ++++++++++++++++-- src/marketplace/marketplace.cairo | 96 ++++-- ...rategy_standard_sale_for_fixed_price.cairo | 32 +- .../transfer_manager_ERC1155.cairo | 8 +- src/marketplace/transfer_manager_ERC721.cairo | 10 +- src/marketplace/transfer_selector_NFT.cairo | 26 +- src/marketplace/utils/order_types.cairo | 1 + src/mocks/erc1155.cairo | 49 +-- src/mocks/erc721.cairo | 2 + 9 files changed, 423 insertions(+), 103 deletions(-) diff --git a/deployment/index.js b/deployment/index.js index c04c1af..3373ffe 100644 --- a/deployment/index.js +++ b/deployment/index.js @@ -30,30 +30,195 @@ const account = new Account(provider, ACCOUNT_ADDRESS, PRIVATE_KEY); async function deployCurrencyManager() { console.log('🚀 Deploying with Account: ' + account.address); - const compiledCurrencyManagerCasm = json.parse( + const compiledContractCasm = json.parse( fs .readFileSync( - '../flex_marketplace/target/dev/flex_CurrencyManager.compiled_contract_class.json' + '../target/dev/flex_CurrencyManager.compiled_contract_class.json' ) .toString('ascii') ); - const compiledCurrencyManagerSierra = json.parse( + const compiledContractSierra = json.parse( fs - .readFileSync( - '../flex_marketplace/target/dev/flex_CurrencyManager.contract_class.json' - ) + .readFileSync('../target/dev/flex_CurrencyManager.contract_class.json') .toString('ascii') ); + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + owner: account.address, + }); + const deployCurrencyManagerResponse = await account.declareAndDeploy({ - contract: compiledCurrencyManagerSierra, - casm: compiledCurrencyManagerCasm, + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, }); console.log( '✅ CurrencyManager Deployed: ', deployCurrencyManagerResponse.deploy.contract_address ); -} // 0x46e3093c6e03847f77c8fdfb1f47e76fa38ddeecf9dd8cfb89c7e0a7f6decc3 +} // 0x7e62ef3e98ab77fc0634aff9d208c0c658b15127cc4b5a0b180b89b2cdf7ab0 + +async function deployExecutionManager() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_ExecutionManager.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync('../target/dev/flex_ExecutionManager.contract_class.json') + .toString('ascii') + ); + + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + owner: account.address, + }); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, + }); + console.log( + '✅ ExecutionManager Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} // 0x4e23fc3548b691836fcb55ff7566f1f3f6b0fc84e0aff4c67e236a840842391 + +async function deployRoyaltyRegistry() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_RoyaltyFeeRegistry.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync('../target/dev/flex_RoyaltyFeeRegistry.contract_class.json') + .toString('ascii') + ); + + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + fee_limit: 500, + owner: account.address, + }); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, + }); + console.log( + '✅ RoyaltyRegistry Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} // 0x928e33b4ce3576f2d370d978d76ab4bb746d3c27772d5f0e238da1f64121cc + +async function deployRoyaltyManager() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_RoyaltyFeeManager.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync('../target/dev/flex_RoyaltyFeeManager.contract_class.json') + .toString('ascii') + ); + + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + fee_registry: + '0x928e33b4ce3576f2d370d978d76ab4bb746d3c27772d5f0e238da1f64121cc', + owner: account.address, + }); + + const deployContractResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, + }); + console.log( + '✅ RoyaltyManager Deployed: ', + deployContractResponse.deploy.contract_address + ); +} // 0x1901b811ad20d3428bef269874d1eaad4ded11fe5f0deb9fb887eed309ff63d + +async function deploySignatureChecker() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_SignatureChecker2.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync('../target/dev/flex_SignatureChecker2.contract_class.json') + .toString('ascii') + ); + + const deployContractResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + }); + + console.log( + '✅ SignatureChecker Deployed: ', + deployContractResponse.deploy.contract_address + ); +} // 0x7b273d41ecdaa4dd96f72835f6c84a4064703e05729d9110518c37506499717 + +async function deployStrategySaleForFixPrice() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_StrategyStandardSaleForFixedPrice.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync( + '../target/dev/flex_StrategyStandardSaleForFixedPrice.contract_class.json' + ) + .toString('ascii') + ); + + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + fee: 200, + owner: account.address, + }); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, + }); + console.log( + '✅ StrategyStandardSaleForFixedPrice Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} // 0x734e0c06969c247594d8e07d778a36b5219fb0b2510af5fb23e8234e3ed7a78 async function deployFlexDrop() { console.log('🚀 Deploying with Account: ' + account.address); @@ -138,27 +303,30 @@ async function deployMarketplace() { const compiledContractCasm = json.parse( fs .readFileSync( - '../flex_marketplace/target/dev/flex_MarketPlace.compiled_contract_class.json' + '../target/dev/flex_MarketPlace.compiled_contract_class.json' ) .toString('ascii') ); const compiledContractSierra = json.parse( fs - .readFileSync( - '../flex_marketplace/target/dev/flex_MarketPlace.contract_class.json' - ) + .readFileSync('../target/dev/flex_MarketPlace.contract_class.json') .toString('ascii') ); const contractCallData = new CallData(compiledContractSierra.abi); const contractConstructor = contractCallData.compile('constructor', { + domain_name: 'Flex', + domain_ver: '1', + recipient: ACCOUNT_ADDRESS, + currency: + '0x7e62ef3e98ab77fc0634aff9d208c0c658b15127cc4b5a0b180b89b2cdf7ab0', + execution: + '0x4e23fc3548b691836fcb55ff7566f1f3f6b0fc84e0aff4c67e236a840842391', + royalty_manager: + '0x1901b811ad20d3428bef269874d1eaad4ded11fe5f0deb9fb887eed309ff63d', + checker: + '0x7b273d41ecdaa4dd96f72835f6c84a4064703e05729d9110518c37506499717', owner: account.address, - name: 'Brian', - symbol: 'BRN', - token_base_uri: '0x0', - allowed_flex_drop: [ - '0x49e94b002a114dc5506d172c0a9872099fe6cab608ed844993aafe382a78f9a', - ], }); const deployContractResponse = await account.declareAndDeploy({ @@ -167,10 +335,100 @@ async function deployMarketplace() { constructorCalldata: contractConstructor, }); console.log( - '✅ CurrencyManager Deployed: ', + '✅ MarketplaceContract Deployed: ', deployContractResponse.deploy.contract_address ); -} +} // 0x499f67cb362b9366d2859fa766395a19e57ea05fb49efe269dcb80480500cc7 + +async function deployTransferManagerERC721() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_TransferManagerNFT.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync('../target/dev/flex_TransferManagerNFT.contract_class.json') + .toString('ascii') + ); + + const contractCallData = new CallData(compiledContractSierra.abi); + const contractConstructor = contractCallData.compile('constructor', { + marketplace: + '0x499f67cb362b9366d2859fa766395a19e57ea05fb49efe269dcb80480500cc7', + owner: account.address, + }); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + constructorCalldata: contractConstructor, + }); + console.log( + '✅ TransferManagerERC721 Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} // 0x3fa016190d64446b24fc4704b2b48f01e4eed70d1dae4ba1d59dec0b7b47861 + +async function deployTransferManagerERC1155() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_ERC1155TransferManager.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync( + '../target/dev/flex_ERC1155TransferManager.contract_class.json' + ) + .toString('ascii') + ); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + }); + console.log( + '✅ TransferManagerERC1155 Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} // 0x114539e3af2192698a6ed3cb9158447c2a81c8cf4b8d29c0950ca2de26be795 + +async function deployTransferSelectorNFT() { + console.log('🚀 Deploying with Account: ' + account.address); + + const compiledContractCasm = json.parse( + fs + .readFileSync( + '../target/dev/flex_TransferSelectorNFT.compiled_contract_class.json' + ) + .toString('ascii') + ); + const compiledContractSierra = json.parse( + fs + .readFileSync( + '../target/dev/flex_TransferSelectorNFT.contract_class.json' + ) + .toString('ascii') + ); + + const deployExecutionManagerResponse = await account.declareAndDeploy({ + contract: compiledContractSierra, + casm: compiledContractCasm, + }); + console.log( + '✅ TransferSelectorNFT Deployed: ', + deployExecutionManagerResponse.deploy.contract_address + ); +} //0x460cefd38c83dfe2afe9e7a17879d6c442198a393984a19cfed9bb6862f5323 async function interactNFT() { const compiledContractSierra = json.parse( @@ -262,4 +520,4 @@ async function buyPublicSale() { console.log(res); } -buyPublicSale(); +deployTransferManagerERC1155(); diff --git a/src/marketplace/marketplace.cairo b/src/marketplace/marketplace.cairo index 4bd482f..8b8182f 100644 --- a/src/marketplace/marketplace.cairo +++ b/src/marketplace/marketplace.cairo @@ -56,6 +56,7 @@ trait IMarketPlace { fn get_is_user_order_nonce_executed_or_cancelled( self: @TState, user: ContractAddress, nonce: u128 ) -> bool; + fn get_counter_usage_signature(self: @TState, user: ContractAddress, nonce: u128) -> u128; } const STARKNET_DOMAIN_TYPE_HASH: felt252 = @@ -103,7 +104,9 @@ mod MarketPlace { transfer_selector_NFT::{ ITransferSelectorNFTDispatcher, ITransferSelectorNFTDispatcherTrait }, - interfaces::nft_transfer_manager::{ITransferManagerNFTDispatcher, ITransferManagerNFTDispatcherTrait} + interfaces::nft_transfer_manager::{ + ITransferManagerNFTDispatcher, ITransferManagerNFTDispatcherTrait + } }; use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; @@ -139,6 +142,7 @@ mod MarketPlace { signature_checker: ISignatureChecker2Dispatcher, user_min_order_nonce: LegacyMap::, is_user_order_nonce_executed_or_cancelled: LegacyMap::<(ContractAddress, u128), bool>, + counter_usage_signature: LegacyMap::<(ContractAddress, u128), u128>, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -373,7 +377,7 @@ mod MarketPlace { 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); + self.validate_order(@maker_ask, maker_ask_signature, taker_bid.amount); let (can_execute, token_id, amount) = IExecutionStrategyDispatcher { contract_address: maker_ask.strategy @@ -382,9 +386,19 @@ mod MarketPlace { assert!(can_execute, "Marketplace: order cannot be executed"); + let new_counter_usage_signature = self + .get_counter_usage_signature(maker_ask.signer, maker_ask.salt_nonce) + + amount; + self - .is_user_order_nonce_executed_or_cancelled - .write((maker_ask.signer, maker_ask.salt_nonce), true); + .counter_usage_signature + .write((maker_ask.signer, maker_ask.salt_nonce), new_counter_usage_signature); + + if new_counter_usage_signature == maker_ask.amount { + self + .is_user_order_nonce_executed_or_cancelled + .write((maker_ask.signer, maker_ask.salt_nonce), true); + } self .transfer_fees_and_funds( @@ -457,7 +471,7 @@ mod MarketPlace { taker_ask.taker ); - self.validate_order(@maker_bid, maker_bid_signature); + self.validate_order(@maker_bid, maker_bid_signature, taker_ask.amount); let (can_execute, token_id, amount) = IExecutionStrategyDispatcher { contract_address: maker_bid.strategy @@ -466,9 +480,20 @@ mod MarketPlace { assert!(can_execute, "Marketplace: taker ask cannot be executed"); + let new_counter_usage_signature = self + .get_counter_usage_signature(maker_bid.signer, maker_bid.salt_nonce) + + amount; + self - .is_user_order_nonce_executed_or_cancelled - .write((maker_bid.signer, maker_bid.salt_nonce), true); + .counter_usage_signature + .write((maker_bid.signer, maker_bid.salt_nonce), new_counter_usage_signature); + + if new_counter_usage_signature == maker_bid.amount { + self + .is_user_order_nonce_executed_or_cancelled + .write((maker_bid.signer, maker_bid.salt_nonce), true); + } + self .transfer_non_fungible_token( maker_bid.collection, taker_ask.taker, maker_bid.signer, token_id, amount @@ -529,19 +554,32 @@ mod MarketPlace { let relayer = auction_strategy.auction_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); + self.validate_order(@maker_ask, maker_ask_signature, maker_bid.amount); + self.validate_order(@maker_bid, maker_bid_signature, maker_ask.amount); let (can_execute, token_id, amount) = auction_strategy .can_execute_auction_sale(maker_ask, maker_bid); assert!(can_execute, "MakerOrder: auction strategy can not be executed"); - self - .is_user_order_nonce_executed_or_cancelled - .write((maker_ask.signer, maker_ask.salt_nonce), true); - self - .is_user_order_nonce_executed_or_cancelled - .write((maker_bid.signer, maker_bid.salt_nonce), true); + let new_counter_maker_ask_usage = self + .get_counter_usage_signature(maker_ask.signer, maker_ask.salt_nonce) + + amount; + + let new_counter_maker_bid_usage = self + .get_counter_usage_signature(maker_bid.signer, maker_bid.salt_nonce) + + amount; + + if new_counter_maker_ask_usage == maker_ask.amount { + self + .is_user_order_nonce_executed_or_cancelled + .write((maker_ask.signer, maker_ask.salt_nonce), true); + } + + if new_counter_maker_bid_usage == maker_bid.amount { + self + .is_user_order_nonce_executed_or_cancelled + .write((maker_bid.signer, maker_bid.salt_nonce), true); + } self .transfer_fees_and_funds( @@ -674,6 +712,12 @@ mod MarketPlace { ) -> bool { self.is_user_order_nonce_executed_or_cancelled.read((user, nonce)) } + + fn get_counter_usage_signature( + self: @ContractState, user: ContractAddress, nonce: u128 + ) -> u128 { + self.counter_usage_signature.read((user, nonce)) + } } #[generate_trait] @@ -695,7 +739,8 @@ mod MarketPlace { let protocol_fee_recipient = self.get_protocol_fee_recipient(); let currency_erc20 = IERC20CamelDispatcher { contract_address: currency }; if !protocol_fee_amount.is_zero() && !protocol_fee_recipient.is_zero() { - currency_erc20.transferFrom(from, protocol_fee_recipient, protocol_fee_amount.into()); + currency_erc20 + .transferFrom(from, protocol_fee_recipient, protocol_fee_amount.into()); } let (royalty_fee_recipient, royalty_amount) = self .royalty_fee_manager @@ -717,9 +762,7 @@ mod MarketPlace { } currency_erc20 - .transferFrom( - from, to, (amount - protocol_fee_amount - royalty_amount).into() - ); + .transferFrom(from, to, (amount - protocol_fee_amount - royalty_amount).into()); } fn transfer_non_fungible_token( @@ -738,7 +781,9 @@ mod MarketPlace { .check_transfer_manager_for_token(collection); assert!(!manager.is_zero(), "MarketPlace: invalid tranfer manager {}", manager); ITransferManagerNFTDispatcher { contract_address: manager } - .transfer_non_fungible_token(collection, from, to, token_id, amount, ArrayTrait::::new().span()); + .transfer_non_fungible_token( + collection, from, to, token_id, amount, ArrayTrait::::new().span() + ); } fn calculate_protocol_fee( @@ -750,11 +795,20 @@ mod MarketPlace { } fn validate_order( - self: @ContractState, order: @MakerOrder, order_signature: Array + self: @ContractState, + order: @MakerOrder, + order_signature: Array, + taker_amount: u128, ) { let executed_order_cancelled = self .get_is_user_order_nonce_executed_or_cancelled(*order.signer, *order.salt_nonce); + let counter_signature_usage = self + .get_counter_usage_signature(*order.signer, *order.salt_nonce); let min_nonce = self.get_user_min_order_nonce(*order.signer); + assert!( + counter_signature_usage + taker_amount <= *order.amount, + "MarketPlace: Insufficient order amount" + ); assert!(!executed_order_cancelled, "MarketPlace: executed order is cancelled"); assert!( min_nonce <= *order.salt_nonce, diff --git a/src/marketplace/strategy_standard_sale_for_fixed_price.cairo b/src/marketplace/strategy_standard_sale_for_fixed_price.cairo index 641644b..062194f 100644 --- a/src/marketplace/strategy_standard_sale_for_fixed_price.cairo +++ b/src/marketplace/strategy_standard_sale_for_fixed_price.cairo @@ -46,11 +46,7 @@ mod StrategyStandardSaleForFixedPrice { } #[constructor] - fn constructor( - ref self: ContractState, - fee: u128, - owner: ContractAddress - ) { + fn constructor(ref self: ContractState, fee: u128, owner: ContractAddress) { self.initializer(fee, owner); } @@ -59,7 +55,9 @@ mod StrategyStandardSaleForFixedPrice { ContractState > { fn initializer(ref self: ContractState, fee: u128, owner: ContractAddress) { - assert!(!self.initialized.read(), "StrategyStandardSaleForFixedPrice: already initialized"); + assert!( + !self.initialized.read(), "StrategyStandardSaleForFixedPrice: already initialized" + ); self.initialized.write(true); self.ownable.initializer(owner); self.protocol_fee.write(fee); @@ -75,34 +73,32 @@ mod StrategyStandardSaleForFixedPrice { } fn can_execute_taker_ask( - self: @ContractState, - taker_ask: TakerOrder, - maker_bid: MakerOrder, + self: @ContractState, taker_ask: TakerOrder, maker_bid: MakerOrder, ) -> (bool, u256, u128) { - let price_match: bool = maker_bid.price == taker_ask.price; + let price_match: bool = (maker_bid.price * taker_ask.amount) + / maker_bid.amount >= 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(); if (price_match && token_id_match && start_time_valid && end_time_valid) { - return (true, maker_bid.token_id, maker_bid.amount); + return (true, maker_bid.token_id, taker_ask.amount); } else { - return (false, maker_bid.token_id, maker_bid.amount); + return (false, maker_bid.token_id, taker_ask.amount); } } fn can_execute_taker_bid( - self: @ContractState, - taker_bid: TakerOrder, - maker_ask: MakerOrder + self: @ContractState, taker_bid: TakerOrder, maker_ask: MakerOrder ) -> (bool, u256, u128) { - let price_match: bool = maker_ask.price == taker_bid.price; + let price_match: bool = (maker_ask.price * taker_bid.amount) + / maker_ask.amount <= 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(); if (price_match && token_id_match && start_time_valid && end_time_valid) { - return (true, maker_ask.token_id, maker_ask.amount); + return (true, maker_ask.token_id, taker_bid.amount); } else { - return (false, maker_ask.token_id, maker_ask.amount); + return (false, maker_ask.token_id, taker_bid.amount); } } } diff --git a/src/marketplace/transfer_manager_ERC1155.cairo b/src/marketplace/transfer_manager_ERC1155.cairo index d37dd8d..38d422e 100644 --- a/src/marketplace/transfer_manager_ERC1155.cairo +++ b/src/marketplace/transfer_manager_ERC1155.cairo @@ -1,5 +1,6 @@ #[starknet::contract] mod ERC1155TransferManager { + use core::traits::Into; use starknet::{ContractAddress, ClassHash, contract_address_const, get_caller_address}; use flex::marketplace::interfaces::nft_transfer_manager::ITransferManagerNFT; @@ -19,6 +20,7 @@ mod ERC1155TransferManager { #[storage] struct Storage { + initialized: bool, marketplace: ContractAddress, #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -40,6 +42,8 @@ mod ERC1155TransferManager { fn initializer( ref self: ContractState, marketplace: ContractAddress, owner: ContractAddress, ) { + assert!(!self.initialized.read(), "Contract already initialized"); + self.initialized.write(true); self.ownable.initializer(owner); self.marketplace.write(marketplace); } @@ -61,7 +65,7 @@ mod ERC1155TransferManager { caller ); IERC1155Dispatcher { contract_address: collection } - .safe_transfer_from(from, to, token_id, amount, data); + .safe_transfer_from(from, to, token_id, amount.into(), data); } fn update_marketplace(ref self: ContractState, new_address: ContractAddress) { @@ -73,4 +77,4 @@ mod ERC1155TransferManager { self.marketplace.read() } } -} \ No newline at end of file +} diff --git a/src/marketplace/transfer_manager_ERC721.cairo b/src/marketplace/transfer_manager_ERC721.cairo index 7bc4d6c..0ed31fc 100644 --- a/src/marketplace/transfer_manager_ERC721.cairo +++ b/src/marketplace/transfer_manager_ERC721.cairo @@ -21,6 +21,7 @@ mod TransferManagerNFT { #[storage] struct Storage { marketplace: ContractAddress, + initialized: bool, #[substorage(v0)] ownable: OwnableComponent::Storage } @@ -35,10 +36,10 @@ mod TransferManagerNFT { #[external(v0)] impl TransferManagerNFTImpl of ITransferManagerNFT { fn initializer( - ref self: ContractState, - marketplace: ContractAddress, - owner: ContractAddress, + ref self: ContractState, marketplace: ContractAddress, owner: ContractAddress, ) { + assert!(!self.initialized.read(), "Contract already initialized"); + self.initialized.write(true); // TODO: verify the role of Proxy here. self.marketplace.write(marketplace); self.ownable.initializer(owner); @@ -64,6 +65,7 @@ mod TransferManagerNFT { } fn update_marketplace(ref self: ContractState, new_address: ContractAddress) { + self.ownable.assert_only_owner(); self.marketplace.write(new_address); } @@ -71,4 +73,4 @@ mod TransferManagerNFT { self.marketplace.read() } } -} \ No newline at end of file +} diff --git a/src/marketplace/transfer_selector_NFT.cairo b/src/marketplace/transfer_selector_NFT.cairo index d4cc52b..83a11bc 100644 --- a/src/marketplace/transfer_selector_NFT.cairo +++ b/src/marketplace/transfer_selector_NFT.cairo @@ -32,7 +32,8 @@ mod TransferSelectorNFT { use flex::{DebugContractAddress, DisplayContractAddress}; use flex::marketplace::utils::order_types::{MakerOrder, TakerOrder}; - use flex::mocks::erc1155::IERC1155_ID; + use flex::mocks::erc1155::{IERC1155_ID, OLD_IERC1155_ID}; + use flex::mocks::erc721::{OLD_IERC721_ID}; use openzeppelin::token::erc721::interface::IERC721_ID; use openzeppelin::introspection::interface::{ISRC5CamelDispatcher, ISRC5CamelDispatcherTrait}; use openzeppelin::access::ownable::OwnableComponent; @@ -47,8 +48,6 @@ mod TransferSelectorNFT { #[storage] struct Storage { initialized: bool, - INTERFACE_ID_ERC721: felt252, - INTERFACE_ID_ERC1155: felt252, TRANSFER_MANAGER_ERC721: ContractAddress, TRANSFER_MANAGER_ERC1155: ContractAddress, transfer_manager_selector_for_collection: LegacyMap::, @@ -88,8 +87,6 @@ mod TransferSelectorNFT { ) { assert!(!self.initialized.read(), "TransferSelectorNFT: already initialized"); self.initialized.write(true); - self.INTERFACE_ID_ERC721.write(IERC721_ID); - self.INTERFACE_ID_ERC1155.write(IERC1155_ID); self.TRANSFER_MANAGER_ERC721.write(transfer_manager_ERC721); self.TRANSFER_MANAGER_ERC1155.write(transfer_manager_ERC1155); self.ownable.initializer(owner); @@ -148,11 +145,11 @@ mod TransferSelectorNFT { } fn get_INTERFACE_ID_ERC721(self: @ContractState) -> felt252 { - self.INTERFACE_ID_ERC721.read() + IERC721_ID } fn get_INTERFACE_ID_ERC1155(self: @ContractState) -> felt252 { - self.INTERFACE_ID_ERC1155.read() + IERC1155_ID } fn get_TRANSFER_MANAGER_ERC721(self: @ContractState) -> ContractAddress { @@ -179,18 +176,23 @@ mod TransferSelectorNFT { let transfer_manager_ERC721 = self.get_TRANSFER_MANAGER_ERC721(); let supports_ERC721 = ISRC5CamelDispatcher { contract_address: collection } - .supportsInterface(self.INTERFACE_ID_ERC721.read()); - if supports_ERC721 { + .supportsInterface(IERC721_ID); + + let supports_old_ERC721 = ISRC5CamelDispatcher { contract_address: collection } + .supportsInterface(OLD_IERC721_ID); + if supports_ERC721 || supports_old_ERC721 { return transfer_manager_ERC721; } let transfer_manager_ERC1155 = self.get_TRANSFER_MANAGER_ERC1155(); let supports_ERC1155 = ISRC5CamelDispatcher { contract_address: collection } - .supportsInterface(self.INTERFACE_ID_ERC1155.read()); - if supports_ERC1155 { + .supportsInterface(IERC1155_ID); + let supports_old_ERC1155 = ISRC5CamelDispatcher { contract_address: collection } + .supportsInterface(OLD_IERC1155_ID); + if supports_ERC1155 || supports_old_ERC1155 { return transfer_manager_ERC1155; } return contract_address_const::<0>(); } } -} \ No newline at end of file +} diff --git a/src/marketplace/utils/order_types.cairo b/src/marketplace/utils/order_types.cairo index 33462b0..f5dad5b 100644 --- a/src/marketplace/utils/order_types.cairo +++ b/src/marketplace/utils/order_types.cairo @@ -24,6 +24,7 @@ struct TakerOrder { taker: ContractAddress, // caller price: u128, // final price for the purchase token_id: u256, + amount: u128, min_percentage_to_ask: u128, // slippage protection (9000 = 90% of the final price must return to ask) params: felt252, } diff --git a/src/mocks/erc1155.cairo b/src/mocks/erc1155.cairo index 8b4a0fa..7f08b1e 100644 --- a/src/mocks/erc1155.cairo +++ b/src/mocks/erc1155.cairo @@ -1,32 +1,33 @@ +use starknet::ContractAddress; + const IERC1155_ID: felt252 = 0x6114a8f75559e1b39fcba08ce02961a1aa082d9256a158dd3e64964e4b1b52; +const OLD_IERC1155_ID: felt252 = 0xd9b67a26; #[starknet::interface] -trait IERC1155 { +trait IERC1155 { + fn balance_of(self: @TState, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Span, token_ids: Span + ) -> Span; fn safe_transfer_from( - ref self: TContractState, - from: starknet::ContractAddress, - to: starknet::ContractAddress, - id: u256, - amount: u128, + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, data: Span ); -} - -#[starknet::contract] -mod ERC1155 { - #[storage] - struct Storage {} - - #[external(v0)] - impl ERC1155Impl of super::IERC1155 { - fn safe_transfer_from( - ref self: ContractState, - from: starknet::ContractAddress, - to: starknet::ContractAddress, - id: u256, - amount: u128, - data: Span - ) {} - } + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); } diff --git a/src/mocks/erc721.cairo b/src/mocks/erc721.cairo index d260d34..ce7ac88 100644 --- a/src/mocks/erc721.cairo +++ b/src/mocks/erc721.cairo @@ -15,6 +15,8 @@ trait IERC2981 { ) -> (starknet::ContractAddress, u128); } +const OLD_IERC721_ID: felt252 = 0x80ac58cd; + #[starknet::contract] mod ERC721 { use starknet::{ContractAddress, get_caller_address};