Skip to content

Commit

Permalink
feat: Integrate contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonatan-Chaverri committed Jan 21, 2025
1 parent fd8a7e9 commit c310ba0
Show file tree
Hide file tree
Showing 39 changed files with 3,419 additions and 165 deletions.
3 changes: 3 additions & 0 deletions apps/snfoundry/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
PRIVATE_KEY_SEPOLIA=
RPC_URL_SEPOLIA=https://starknet-sepolia.public.blastapi.io/rpc/v0_7
ACCOUNT_ADDRESS_SEPOLIA=

# IPFS
TOKEN_METADATA_URL="ipfs://test/{id}.json"
73 changes: 73 additions & 0 deletions apps/snfoundry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# CofiBlocks Contracts

## Deployment Guide

Follow these steps to deploy the CofiBlocks contracts on the StarkNet network.

### 1. Configure the `.env` file

Set the following environment variables in your `.env` file with the details of a prefunded wallet. This wallet will act as the admin address:

- **`PRIVATE_KEY_SEPOLIA`** – The private key of the admin wallet.
- **`ACCOUNT_ADDRESS_SEPOLIA`** – The address of the admin wallet.
- **`TOKEN_METADATA_URL`** – The IPFS URL to serve as the token metadata.

The URL should follow the format: `ipfs://<CID>/{id}.json`, where `{id}` will be dynamically replaced with the actual token ID by clients when fetching metadata.

**Example:**
```
ipfs://bafybeihevtihdmcjkdh6sjdtkbdjnngbfdlr3tjk2dfmvd3demdm57o3va/{id}.json
```
For token ID `1`, the resulting URL will be:
```
ipfs://bafybeihevtihdmcjkdh6sjdtkbdjnngbfdlr3tjk2dfmvd3demdm57o3va/1.json
```

### 2. Install dependencies

Run the following command to install project dependencies:
```bash
bun i
```

### 3. Deploy the contracts

To deploy the contracts on the Sepolia testnet, run:
```bash
bun deploy:sepolia
```

This command will:
- Deploy both the **CofiCollections** and **Marketplace** contracts.
- Set the **Marketplace** contract as the minter in the **CofiCollection** contract.
- Set the `base_uri` in the **CofiCollection** contract using the `TOKEN_METADATA_URL` value from the `.env` file.

### 4. Retrieve deployed contract addresses

Once the deployment is complete, the contract addresses will be available in:
- The terminal output.
- The file located at: `deployments/sepolia_latest.json`.


## Testing
To test the contracts, follow these steps.

1. Go to contracts folder
```bash
cd contracts
```

2. Run test command
```bash
scarb test
```

## Note
When updating the contracts, you need to update them in the web app too. To do that follow this steps

1. Go to page https://scaffold-stark-demo.vercel.app/configure
2. Copy/paste the contract address in the Address field. Use this valid names: CofiCollection, Marketplace.
3. Click on download Contract file. It will give you a json file with the contract abi and contract address.
4. Update the file https://github.com/Vagabonds-Labs/cofiblocks/blob/main/apps/web/src/contracts/configExternalContracts.ts
with the new data (just copy/paste the contract metadata in the corresponding field without affecting other contracts metadata).
5. Do the same for Marketplace contract.
23 changes: 16 additions & 7 deletions apps/snfoundry/contracts/src/marketplace.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ pub trait IMarketplace<ContractState> {
fn create_product(
ref self: ContractState, initial_stock: u256, price: u256, data: Span<felt252>
) -> u256;
fn create_products(ref self: ContractState, initial_stock: Span<u256>, price: Span<u256>) -> Span<u256>;
fn create_products(
ref self: ContractState, initial_stock: Span<u256>, price: Span<u256>
) -> Span<u256>;
fn delete_product(ref self: ContractState, token_id: u256);
fn delete_products(ref self: ContractState, token_ids: Span<u256>);
fn claim_balance(self: @ContractState, producer: ContractAddress) -> u256;
fn claim(ref self: ContractState);
}

Expand Down Expand Up @@ -197,19 +200,19 @@ mod Marketplace {
let buyer = get_caller_address();
let contract_address = get_contract_address();
let strk_token_dispatcher = IERC20Dispatcher {
contract_address: contract_address_const::<STRK_TOKEN_ADDRESS>()
contract_address: contract_address_const::<STRK_TOKEN_ADDRESS>()
};
// 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'
strk_token_dispatcher.balance_of(get_caller_address()) >= total_price,
'insufficient funds'
);
assert(
strk_token_dispatcher.allowance(buyer, contract_address) >= total_price,
'insufficient allowance'
strk_token_dispatcher.allowance(buyer, contract_address) >= total_price,
'insufficient allowance'
);
strk_token_dispatcher.transfer_from(buyer, contract_address, total_price);

Expand Down Expand Up @@ -336,7 +339,9 @@ mod Marketplace {
token_id
}

fn create_products(ref self: ContractState, initial_stock: Span<u256>, price: Span<u256>) -> Span<u256> {
fn create_products(
ref self: ContractState, initial_stock: Span<u256>, price: Span<u256>
) -> Span<u256> {
assert(initial_stock.len() == price.len(), 'wrong len of arrays');
self.accesscontrol.assert_only_role(PRODUCER);
let producer = get_caller_address();
Expand Down Expand Up @@ -427,6 +432,10 @@ mod Marketplace {
};
}

fn claim_balance(self: @ContractState, producer: ContractAddress) -> u256 {
self.claim_balances.read(producer)
}

fn claim(ref self: ContractState) {
self.accesscontrol.assert_only_role(PRODUCER);
let producer = get_caller_address();
Expand Down
73 changes: 40 additions & 33 deletions apps/snfoundry/contracts/src/test/test_marketplace.cairo
Original file line number Diff line number Diff line change
@@ -1,62 +1,61 @@
mod test_marketplace {
use contracts::cofi_collection::ICofiCollectionDispatcher;
use contracts::cofi_collection::ICofiCollectionDispatcherTrait;
use contracts::marketplace::{ IMarketplaceDispatcher, IMarketplaceDispatcherTrait };
use contracts::marketplace::{IMarketplaceDispatcher, IMarketplaceDispatcherTrait};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};


use openzeppelin::utils::serde::SerializedAppend;
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address};
use starknet::ContractAddress;
use starknet::syscalls::call_contract_syscall;

fn OWNER() -> ContractAddress {
starknet::contract_address_const::<'OWNER'>()
}

const STRK_TOKEN_ADDRESS: felt252 =
0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d;

const STRK_TOKEN_MINTER_ADDRESS: felt252 =
0x0594c1582459ea03f77deaf9eb7e3917d6994a03c13405ba42867f83d85f085d;

const ONE_E18: u256 = 1000000000000000000_u256;
const MARKET_FEE: u256 = 250_u256; // 2.5%

fn deploy_receiver() -> ContractAddress {
let contract = declare("Receiver").unwrap().contract_class();
let calldata = array![];
let (contract_address, _) = contract.deploy(@calldata).unwrap();
contract_address
}

fn deploy_cofi_collection() -> ICofiCollectionDispatcher {
let contract = declare("CofiCollection").unwrap().contract_class();

let mut calldata: Array<felt252> = array![];
calldata.append_serde(OWNER()); // default_admin
calldata.append_serde(OWNER()); // pauser
calldata.append_serde(OWNER()); // minter
calldata.append_serde(OWNER()); // uri_setter
calldata.append_serde(OWNER()); // upgrader

let (contract_address, _) = contract.deploy(@calldata).unwrap();
let cofi_collection = ICofiCollectionDispatcher { contract_address };

cofi_collection
}

fn deploy_marketplace(cofi_collection: ContractAddress) -> IMarketplaceDispatcher {
let contract = declare("Marketplace").unwrap().contract_class();

let mut calldata: Array<felt252> = array![];
calldata.append_serde(cofi_collection); // coffi_collection
calldata.append_serde(OWNER()); // admin
calldata.append_serde(MARKET_FEE); // market fee

let (contract_address, _) = contract.deploy(@calldata).unwrap();
let marketplace = IMarketplaceDispatcher { contract_address };

marketplace
}

Expand All @@ -65,12 +64,12 @@ mod test_marketplace {
let market_fee = total_price * MARKET_FEE / 10000;
total_price + market_fee
}

#[test]
fn test_assign_seller_role() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

start_cheat_caller_address(marketplace.contract_address, OWNER());
marketplace.assign_seller_role(OWNER());
}
Expand All @@ -79,7 +78,7 @@ mod test_marketplace {
fn test_assign_consumer_role() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

start_cheat_caller_address(marketplace.contract_address, OWNER());
marketplace.assign_consumer_role(OWNER());
}
Expand All @@ -88,7 +87,7 @@ mod test_marketplace {
fn test_assign_admin_role() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

start_cheat_caller_address(marketplace.contract_address, OWNER());
let ANYONE = starknet::contract_address_const::<'ANYONE'>();
marketplace.assign_admin_role(ANYONE);
Expand All @@ -98,7 +97,7 @@ mod test_marketplace {
fn test_create_product() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand All @@ -120,7 +119,7 @@ mod test_marketplace {
fn test_create_products() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand All @@ -144,7 +143,7 @@ mod test_marketplace {
let cofi_collection = deploy_cofi_collection();
let CONSUMER = deploy_receiver();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand All @@ -168,14 +167,15 @@ mod test_marketplace {
let amount_to_buy = 2;
let required_tokens = calculate_total_token_price(price, amount_to_buy);
let minter_address = starknet::contract_address_const::<STRK_TOKEN_MINTER_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };

start_cheat_caller_address(token_address, minter_address);
let mut calldata = array![];
calldata.append_serde(CONSUMER);
calldata.append_serde(required_tokens);
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap();
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span())
.unwrap();

assert(token_dispatcher.balance_of(CONSUMER) == required_tokens, 'invalid balance');

Expand All @@ -199,7 +199,7 @@ mod test_marketplace {
let cofi_collection = deploy_cofi_collection();
let CONSUMER = deploy_receiver();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand All @@ -221,14 +221,15 @@ mod test_marketplace {

// Fund buyer wallet
let minter_address = starknet::contract_address_const::<STRK_TOKEN_MINTER_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };

start_cheat_caller_address(token_address, minter_address);
let mut calldata = array![];
calldata.append_serde(CONSUMER);
calldata.append_serde(50000000_u256);
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap();
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span())
.unwrap();

assert(token_dispatcher.balance_of(CONSUMER) == 50000000_u256, 'invalid balance');

Expand All @@ -248,7 +249,7 @@ mod test_marketplace {
fn test_delete_product() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand Down Expand Up @@ -279,7 +280,7 @@ mod test_marketplace {
fn test_delete_products() {
let cofi_collection = deploy_cofi_collection();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
let PRODUCER = starknet::contract_address_const::<'PRODUCER'>();
Expand All @@ -297,11 +298,13 @@ mod test_marketplace {

// Delete the product
start_cheat_caller_address(cofi_collection.contract_address, marketplace.contract_address);
let tokens_before = cofi_collection.balance_of(marketplace.contract_address, *token_ids.at(0));
let tokens_before = cofi_collection
.balance_of(marketplace.contract_address, *token_ids.at(0));
assert(tokens_before == 10, 'invalid tokens before');

marketplace.delete_products(token_ids);
let tokens_after = cofi_collection.balance_of(marketplace.contract_address, *token_ids.at(0));
let tokens_after = cofi_collection
.balance_of(marketplace.contract_address, *token_ids.at(0));
assert(tokens_after == 0, 'invalid tokens after');
}

Expand All @@ -312,7 +315,7 @@ mod test_marketplace {
let CONSUMER = deploy_receiver();
let PRODUCER = deploy_receiver();
let marketplace = deploy_marketplace(cofi_collection.contract_address);

// Create a producer
start_cheat_caller_address(marketplace.contract_address, OWNER());
marketplace.assign_seller_role(PRODUCER);
Expand All @@ -335,14 +338,15 @@ mod test_marketplace {
let amount_to_buy = 2;
let required_tokens = calculate_total_token_price(price, amount_to_buy);
let minter_address = starknet::contract_address_const::<STRK_TOKEN_MINTER_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_address = starknet::contract_address_const::<STRK_TOKEN_ADDRESS>();
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };

start_cheat_caller_address(token_address, minter_address);
let mut calldata = array![];
calldata.append_serde(CONSUMER);
calldata.append_serde(required_tokens);
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap();
call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span())
.unwrap();

// Approve marketplace to spend buyer's tokens
start_cheat_caller_address(token_address, CONSUMER);
Expand All @@ -362,6 +366,9 @@ mod test_marketplace {
start_cheat_caller_address(marketplace.contract_address, PRODUCER);
let producer_starks_before = token_dispatcher.balance_of(PRODUCER);

let claim_balance = marketplace.claim_balance(PRODUCER);
assert(claim_balance > 0, 'invalid claim balance');

marketplace.claim();
let producer_starks_after = token_dispatcher.balance_of(PRODUCER);
assert(producer_starks_before < producer_starks_after, 'invalid producer starks');
Expand Down
2 changes: 0 additions & 2 deletions apps/snfoundry/deployments/.gitignore

This file was deleted.

Loading

0 comments on commit c310ba0

Please sign in to comment.