From 01ea77004026e4754009a6e1232b1db12b1e88f4 Mon Sep 17 00:00:00 2001 From: Daniel Garbanzo <75431447+bitfalt@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:24:24 -0600 Subject: [PATCH] Deploy smart contracts to Starknet Sepolia (#95) --- .../contracts/src/cofi_collection.cairo | 1 + .../snfoundry/contracts/src/marketplace.cairo | 157 +++++++++++------- .../contracts/src/test/test_marketplace.cairo | 1 + apps/snfoundry/scripts-ts/deploy.ts | 32 ++-- .../scripts-ts/helpers/deploy-wrapper.ts | 2 +- 5 files changed, 120 insertions(+), 73 deletions(-) diff --git a/apps/snfoundry/contracts/src/cofi_collection.cairo b/apps/snfoundry/contracts/src/cofi_collection.cairo index a502076..0cff1b9 100644 --- a/apps/snfoundry/contracts/src/cofi_collection.cairo +++ b/apps/snfoundry/contracts/src/cofi_collection.cairo @@ -256,6 +256,7 @@ mod CofiCollection { self.accesscontrol._grant_role(MINTER_ROLE, minter); self.accesscontrol._grant_role(URI_SETTER_ROLE, uri_setter); self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader); + self.erc1155._set_base_uri("ipfs://"); } impl ERC1155HooksImpl of ERC1155Component::ERC1155HooksTrait { diff --git a/apps/snfoundry/contracts/src/marketplace.cairo b/apps/snfoundry/contracts/src/marketplace.cairo index 4b2f72b..dd14f9f 100644 --- a/apps/snfoundry/contracts/src/marketplace.cairo +++ b/apps/snfoundry/contracts/src/marketplace.cairo @@ -10,7 +10,9 @@ pub trait IMarketplace { fn assign_admin_role(ref self: ContractState, assignee: ContractAddress); fn buy_product(ref self: ContractState, token_id: u256, token_amount: u256); fn buy_products(ref self: ContractState, token_ids: Span, token_amount: Span); - fn create_product(ref self: ContractState, initial_stock: u256, price: u256, data: Span); + fn create_product( + ref self: ContractState, initial_stock: u256, price: u256, data: Span + ); fn create_products(ref self: ContractState, initial_stock: Span, price: Span); fn delete_product(ref self: ContractState, token_id: u256); fn delete_products(ref self: ContractState, token_ids: Span); @@ -19,18 +21,22 @@ pub trait IMarketplace { #[starknet::contract] mod Marketplace { - use starknet::event::EventEmitter; -use openzeppelin::access::accesscontrol::AccessControlComponent; + use contracts::cofi_collection::{ICofiCollectionDispatcher, ICofiCollectionDispatcherTrait}; + use openzeppelin::access::accesscontrol::AccessControlComponent; use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; - use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use openzeppelin::token::erc1155::erc1155_receiver::ERC1155ReceiverComponent; use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::erc1155_receiver::ERC1155ReceiverComponent; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin::upgrades::UpgradeableComponent; - use contracts::cofi_collection::{ICofiCollectionDispatcher, ICofiCollectionDispatcherTrait}; - use starknet::{ContractAddress, get_caller_address, get_contract_address, contract_address_const}; + use starknet::event::EventEmitter; use starknet::storage::Map; + use starknet::{ + ContractAddress, get_caller_address, get_contract_address, contract_address_const + }; - component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); + component!( + path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent + ); component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -41,7 +47,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; // ERC1155Receiver #[abi(embed_v0)] - impl ERC1155ReceiverImpl = ERC1155ReceiverComponent::ERC1155ReceiverImpl; + impl ERC1155ReceiverImpl = + ERC1155ReceiverComponent::ERC1155ReceiverImpl; impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; // SRC5 @@ -50,13 +57,15 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; // Access Control #[abi(embed_v0)] - impl AccessControlImpl = AccessControlComponent::AccessControlImpl; + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - + // Upgradeable impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - const STRK_TOKEN_ADDRESS: felt252 = 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d; + const STRK_TOKEN_ADDRESS: felt252 = + 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d; #[storage] struct Storage { @@ -94,8 +103,6 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; BuyProduct: BuyProduct, BuyBatchProducts: BuyBatchProducts, PaymentSeller: PaymentSeller, - - } // Emitted when a product is unlisted from the Marketplace #[derive(Drop, PartialEq, starknet::Event)] @@ -147,7 +154,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; /// * `cofi_collection_address` - The address of the CofiCollection contract /// * `admin` - The address of the admin role /// * `market_fee` - The fee that the marketplace will take from the sales - /// * `base_uri` - The base uri for the NFTs metadata. Should contain `{id}` so that metadata gets + /// * `base_uri` - The base uri for the NFTs metadata. Should contain `{id}` so that metadata + /// gets /// replace per each token id. Example: https://example.com/metadata/{id}.json /// #[constructor] @@ -156,7 +164,6 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; cofi_collection_address: ContractAddress, admin: ContractAddress, market_fee: u256, - base_uri: ByteArray, ) { self.erc1155_receiver.initializer(); self.accesscontrol.initializer(); @@ -164,8 +171,6 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; self.cofi_collection_address.write(cofi_collection_address); self.market_fee.write(market_fee); self.current_token_id.write(1); - - ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}.set_base_uri(base_uri); } #[abi(embed_v0)] @@ -174,7 +179,7 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE); self.accesscontrol._grant_role(PRODUCER, assignee); } - + fn assign_consumer_role(ref self: ContractState, assignee: ContractAddress) { self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE); self.accesscontrol._grant_role(CONSUMER, assignee); @@ -183,8 +188,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; fn assign_admin_role(ref self: ContractState, assignee: ContractAddress) { self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE); self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, assignee); - } - + } + fn buy_product(ref self: ContractState, token_id: u256, token_amount: u256) { let stock = self.listed_product_stock.read(token_id); assert(stock >= token_amount, 'Not enough stock'); @@ -196,14 +201,23 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; }; // Get payment from buyer let mut producer_fee = self.listed_product_price.read(token_id) * token_amount; - let mut total_price = producer_fee + self.calculate_fee(producer_fee, self.market_fee.read()); - assert(strk_token_dispatcher.balance_of(get_caller_address()) >= total_price, 'insufficient funds'); + let mut total_price = producer_fee + + self.calculate_fee(producer_fee, self.market_fee.read()); + assert( + strk_token_dispatcher.balance_of(get_caller_address()) >= total_price, + 'insufficient funds' + ); strk_token_dispatcher.transfer_from(buyer, contract_address, total_price); - + // Transfer the nft products - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; - cofi_collection.safe_transfer_from(contract_address, buyer, token_id, token_amount, array![0].span()); - + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; + cofi_collection + .safe_transfer_from( + contract_address, buyer, token_id, token_amount, array![0].span() + ); + // Update stock let new_stock = stock - token_amount; self.update_stock(token_id, new_stock); @@ -215,7 +229,9 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; // Send payment to the producer let seller_address = self.seller_products.read(token_id); - self.claim_balances.write(seller_address, self.claim_balances.read(seller_address) + producer_fee); + self + .claim_balances + .write(seller_address, self.claim_balances.read(seller_address) + producer_fee); let token_ids = array![token_id].span(); self.emit(PaymentSeller { token_ids, seller: seller_address, payment: producer_fee }); } @@ -238,7 +254,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; assert(stock > 0, 'Product not available'); let token_amount = *token_amount.at(token_idx); assert(stock >= token_amount, 'Not enough stock'); - producer_fee += self.listed_product_price.read(*token_ids.at(token_idx)) * token_amount; + producer_fee += self.listed_product_price.read(*token_ids.at(token_idx)) + * token_amount; let producer = self.seller_products.read(*token_ids.at(token_idx)); if producers_found.len() == 0 { @@ -249,7 +266,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; }; // Transfer the funds - let total_price = producer_fee + self.calculate_fee(producer_fee, self.market_fee.read()); + let total_price = producer_fee + + self.calculate_fee(producer_fee, self.market_fee.read()); let strk_token_dispatcher = IERC20Dispatcher { contract_address: contract_address_const::() }; @@ -257,10 +275,13 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; strk_token_dispatcher.transfer_from(buyer, contract_address, total_price); // Transfer the nft products - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; - cofi_collection.safe_batch_transfer_from( - contract_address, buyer, token_ids, token_amount, array![0].span() - ); + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; + cofi_collection + .safe_batch_transfer_from( + contract_address, buyer, token_ids, token_amount, array![0].span() + ); self.emit(BuyBatchProducts { token_ids, token_amount, total_price }); // Update stock for products @@ -282,22 +303,28 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; // Send payment to the producer let seller_address = *producers_found.at(0); - self.claim_balances.write(seller_address, self.claim_balances.read(seller_address) + producer_fee); + self + .claim_balances + .write(seller_address, self.claim_balances.read(seller_address) + producer_fee); self.emit(PaymentSeller { token_ids, seller: seller_address, payment: producer_fee }); } - + /// /// Adds a new product to the marketplace /// Arguments: /// * `initial_stock` - The amount of stock that the product will have /// * `price` - The price of the product per unity expresed in fri (1e-18 strk) /// * `data` - Additional context or metadata for the token transfer process - fn create_product(ref self: ContractState, initial_stock: u256, price: u256, data: Span) { + fn create_product( + ref self: ContractState, initial_stock: u256, price: u256, data: Span + ) { self.accesscontrol.assert_only_role(PRODUCER); let token_id = self.current_token_id.read(); - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; cofi_collection.mint(get_contract_address(), token_id, initial_stock, data); - + let producer = get_caller_address(); self.seller_products.write(token_id, producer); @@ -305,15 +332,13 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; self.initialize_product(token_id, producer, initial_stock, price); } - fn create_products( - ref self: ContractState, - initial_stock: Span, - price: Span - ) { + fn create_products(ref self: ContractState, initial_stock: Span, price: Span) { assert(initial_stock.len() == price.len(), 'wrong len of arrays'); self.accesscontrol.assert_only_role(PRODUCER); let producer = get_caller_address(); - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; // Create token ids and mint the nfts let mut token_ids = array![]; @@ -326,7 +351,10 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; token_ids.append(current_token_id + token_idx.into()); token_idx += 1; }; - cofi_collection.batch_mint(get_contract_address(), token_ids.span(), initial_stock, array![0].span()); + cofi_collection + .batch_mint( + get_contract_address(), token_ids.span(), initial_stock, array![0].span() + ); self.current_token_id.write(current_token_id + initial_stock.len().into()); // Initialize the products @@ -335,19 +363,25 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; if token_idx == initial_stock.len() { break; } - self.initialize_product( - *token_ids.at(token_idx), producer, *initial_stock.at(token_idx), *price.at(token_idx) - ); + self + .initialize_product( + *token_ids.at(token_idx), + producer, + *initial_stock.at(token_idx), + *price.at(token_idx) + ); token_idx += 1; }; } - + fn delete_product(ref self: ContractState, token_id: u256) { self.accesscontrol.assert_only_role(PRODUCER); let producer = get_caller_address(); assert(self.seller_products.read(token_id) == producer, 'Not your product'); - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; let token_holder = get_contract_address(); let amount_tokens = cofi_collection.balance_of(token_holder, token_id); self.update_stock(token_id, 0); @@ -372,7 +406,9 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; // Burn nfts token_idx = 0; let token_holder = get_contract_address(); - let cofi_collection = ICofiCollectionDispatcher {contract_address: self.cofi_collection_address.read()}; + let cofi_collection = ICofiCollectionDispatcher { + contract_address: self.cofi_collection_address.read() + }; loop { if token_idx == token_ids.len() { break; @@ -394,7 +430,8 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; contract_address: contract_address_const::() }; strk_token_dispatcher.approve(producer, claim_balance); - let transfer = strk_token_dispatcher.transfer_from(get_contract_address(), producer, claim_balance); + let transfer = strk_token_dispatcher + .transfer_from(get_contract_address(), producer, claim_balance); assert(transfer, 'Error claiming'); self.claim_balances.write(producer, 0); @@ -403,7 +440,13 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; #[generate_trait] impl InternalImpl of InternalTrait { - fn initialize_product(ref self: ContractState, token_id: u256, producer: ContractAddress, stock: u256, price: u256) { + fn initialize_product( + ref self: ContractState, + token_id: u256, + producer: ContractAddress, + stock: u256, + price: u256 + ) { //let product = Product { stock, price }; //self.seller_products.entry(producer).write(token_id); self.listed_product_stock.write(token_id, stock); @@ -416,9 +459,9 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; self.emit(UpdateStock { token_id, new_stock }); } - // Amount is the total amount + // Amount is the total amount // BPS is the percentage you want to calculate. (Example: 2.5% = 250bps, 7,48% = 748bps) - // Use example: + // Use example: // Calculate the 3% fee of 250 STRK // calculate_fee(250, 300) = 7.5 fn calculate_fee(ref self: ContractState, amount: u256, bps: u256) -> u256 { @@ -426,4 +469,4 @@ use openzeppelin::access::accesscontrol::AccessControlComponent; amount * bps / 10_000 } } -} \ No newline at end of file +} diff --git a/apps/snfoundry/contracts/src/test/test_marketplace.cairo b/apps/snfoundry/contracts/src/test/test_marketplace.cairo index e69de29..8b13789 100644 --- a/apps/snfoundry/contracts/src/test/test_marketplace.cairo +++ b/apps/snfoundry/contracts/src/test/test_marketplace.cairo @@ -0,0 +1 @@ + diff --git a/apps/snfoundry/scripts-ts/deploy.ts b/apps/snfoundry/scripts-ts/deploy.ts index 00819ad..0e93278 100644 --- a/apps/snfoundry/scripts-ts/deploy.ts +++ b/apps/snfoundry/scripts-ts/deploy.ts @@ -1,3 +1,4 @@ +import { addAddressPadding } from "starknet"; import { deployContract, deployer, @@ -44,30 +45,31 @@ import { green } from "./helpers/colorize-log"; const deployScript = async (): Promise => { console.log("🚀 Deploying with address:", green(deployer.address)); - await deployContract({ - contract: "cofi_collection.cairo", - contractName: "CofiCollection", - constructorArgs: { - default_admin: deployer.address, - pauser: deployer.address, - minter: deployer.address, - uri_setter: deployer.address, - upgrader: deployer.address, - }, - }); + // await deployContract({ + // contract: "cofi_collection.cairo", + // contractName: "CofiCollection", + // constructorArgs: { + // default_admin: deployer.address, + // pauser: deployer.address, + // minter: deployer.address, + // uri_setter: deployer.address, + // upgrader: deployer.address, + // }, + // }); // Deploy Marketplace await deployContract({ - contract: "Marketplace.cairo", + contract: "marketplace.cairo", contractName: "Marketplace", // TODO: incluide constructor args for deploy // cofi_collection_address: ContractAddress // cofi_vault_address: ContractAddress // strk_contract: ContractAddress constructorArgs: { - cofi_collection_address: deployer.address, - cofi_vault_address: deployer.address, - strk_contract: deployer.address, + cofi_collection_address: addAddressPadding( + "0x0448d8cc3403303a76d89a56b7e8ecf9aa9fcd41e7bb66d10f4be5b67b2f8aab", + ), admin: deployer.address, + market_fee: BigInt(300000), }, }); }; diff --git a/apps/snfoundry/scripts-ts/helpers/deploy-wrapper.ts b/apps/snfoundry/scripts-ts/helpers/deploy-wrapper.ts index 61addc0..84dbad4 100644 --- a/apps/snfoundry/scripts-ts/helpers/deploy-wrapper.ts +++ b/apps/snfoundry/scripts-ts/helpers/deploy-wrapper.ts @@ -30,7 +30,7 @@ process.env.NO_RESET = !argv.reset ? "true" : "false"; // Execute the deploy script without the reset option try { execSync( - `cd apps/snfoundry/contracts && scarb build && ts-node ../scripts-ts/deploy.ts --network ${process.env.NETWORK} --fee ${process.env.FEE_TOKEN} --no-reset ${process.env.NO_RESET} && ts-node ../scripts-ts/helpers/parse-deployments.ts && cd ../../..`, + `cd contracts && scarb build && ts-node ../scripts-ts/deploy.ts --network ${process.env.NETWORK} --fee ${process.env.FEE_TOKEN} --no-reset ${process.env.NO_RESET} && ts-node ../scripts-ts/helpers/parse-deployments.ts && cd ../../..`, { stdio: "inherit" }, ); } catch (error) {