Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/.tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
scarb 0.12.0
scarb 2.11.2
starknet-foundry 0.48.0
11 changes: 6 additions & 5 deletions contracts/src/mods.cairo
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
pub mod types;
pub mod events;
pub mod errors;
pub mod events;
pub mod types;

pub mod token {
pub mod WeaverNFT;
}

pub mod interfaces {
pub mod ICustomNFT;
pub mod IERC721;
pub mod IWeaver;
pub mod IWeaverNFT;
pub mod ICustomNFT;
pub mod IWeaverScoreCardNFTMinter;
pub mod Iprotocol;
pub mod IERC721;
}

pub mod weaver_contract {
pub mod weaver;
}

pub mod protocol {
pub mod protocolcomponent;
pub mod protocolNFT;
pub mod protocolcomponent;
pub mod protocols;
}

Expand Down
9 changes: 9 additions & 0 deletions contracts/src/mods/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ pub mod Errors {
pub const TASK_NOT_YET_COMPLETED: felt252 = 'TASK_NOT_YET_COMPLETED';
pub const INVALID_PROTOCOL_ID: felt252 = 'INVALID_PROTOCOL_ID';
pub const NOT_IN_PROTOCOL_CAMPAIGN: felt252 = 'NOT_IN_PROTOCOL_CAMPAIGN';
pub const UNAUTHORIZED: felt252 = 'Unauthorized access';
pub const ZERO_ADDRESS: felt252 = 'Address cannot be zero';
pub const INSUFFICIENT_BALANCE: felt252 = 'Insufficient STRK balance';
pub const INSUFFICIENT_ALLOWANCE: felt252 = 'Insufficient STRK allowance';
pub const TRANSFER_FAILED: felt252 = 'STRK transfer failed';
pub const PRICE_FETCH_FAILED: felt252 = 'Failed to fetch STRK price';
pub const MINTING_FAILED: felt252 = 'NFT minting failed';
pub const WITHDRAWAL_FAILED: felt252 = 'Withdrawal failed';
pub const INVALID_PRICE: felt252 = 'Invalid price from oracle';
}
14 changes: 14 additions & 0 deletions contracts/src/mods/interfaces/IWeaverScoreCardNftMinter.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use starknet::ContractAddress;
// *************************************************************************
// INTERFACE of WEAVER SCORE CARD NFT MINTER
// *************************************************************************
#[starknet::interface]
pub trait IWeaverScoreCardNFTMinter<TState> {
fn mint_weaver_score_card_nft(ref self: TContractState);
fn get_asset_price(self: @TContractState, asset_id: felt252) -> u128;
fn set_erc721(ref self: TContractState, address: ContractAddress);
fn withdraw(ref self: TContractState, amount: u256);
fn get_owner(self: @TContractState) -> ContractAddress;
fn get_weaver_nft_address(self: @TContractState) -> ContractAddress;
fn get_pragma_address(self: @TContractState) -> ContractAddress;
}
256 changes: 256 additions & 0 deletions contracts/src/mods/score_card/weaver_score_card_contract.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Pragma Oracle Interface
#[starknet::interface]
trait IPragmaABI<TContractState> {
fn get_data(
self: @TContractState, data_type: felt252, aggregation_mode: felt252,
) -> (u128, u32, u32, u32);
}

// ERC20 Interface for STRK token
#[starknet::interface]
trait IERC20<TContractState> {
fn balance_of(self: @TContractState, account: ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256,
) -> bool;
fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;
}


#[starknet::contract]
mod WeaverScoreCardNftMinter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::{ContractAddress, get_caller_address, get_contract_address};
use crate::mods::errors::Errors;
use crate::mods::interfaces::IWeaverNFT::{
IWeaverNFT, IWeaverNFTDispatcher, IWeaverNFTDispatcherTrait,
};
use crate::mods::interfaces::IWeaverScoreCardNFTMinter::{
IWeaverScoreCardNFTMinter, IWeaverScoreCardNFTMinterDispatcher,
IWeaverScoreCardNFTMinterDispatcherTrait,
};
use super::{
IERC20Dispatcher, IERC20DispatcherTrait, IPragmaABIDispatcher, IPragmaABIDispatcherTrait,
};

// Constants
const ASSET_PRICE_USD: u128 = 100000000; // $1 with 8 decimals
const DECIMALS: u128 = 100000000; // 10^8
const STRK_USD_PAIR_ID: felt252 = 19514442401534788; // 'STRK/USD' pair ID
const AGGREGATION_MODE: felt252 = 120282243752302; // 'TWAP' mode

// STRK token address on Starknet mainnet
const STRK_TOKEN_ADDRESS: felt252 =
0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d;

#[storage]
struct Storage {
pragma_contract: ContractAddress,
asset_price: u128,
weaver_nft_contract_address: ContractAddress,
owner: ContractAddress,
next_token_id: u256,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
WeaverScoreCardNftMinted: WeaverScoreCardNftMinted,
AssetPriceUpdated: AssetPriceUpdated,
WeaverNftAddressSet: WeaverNftAddressSet,
FundsWithdrawn: FundsWithdrawn,
}

#[derive(Drop, starknet::Event)]
struct WeaverScoreCardNftMinted {
#[key]
to: ContractAddress,
token_id: u256,
strk_amount_paid: u256,
strk_price: u128,
}

#[derive(Drop, starknet::Event)]
struct AssetPriceUpdated {
old_price: u128,
new_price: u128,
}

#[derive(Drop, starknet::Event)]
struct WeaverNftAddressSet {
#[key]
old_address: ContractAddress,
#[key]
new_address: ContractAddress,
}

#[derive(Drop, starknet::Event)]
struct FundsWithdrawn {
#[key]
to: ContractAddress,
amount: u256,
}


#[constructor]
fn constructor(
ref self: ContractState,
pragma_contract: ContractAddress,
weaver_nft_contract_address: ContractAddress,
owner: ContractAddress,
) {
assert(!pragma_contract.is_zero(), Errors::ZERO_ADDRESS);
assert(!weaver_nft_contract_address.is_zero(), Errors::ZERO_ADDRESS);
assert(!owner.is_zero(), Errors::ZERO_ADDRESS);

self.pragma_contract.write(pragma_contract);
self.weaver_nft_contract_address.write(weaver_nft_contract_address);
self.owner.write(owner);
self.asset_price.write(ASSET_PRICE_USD);
self.next_token_id.write(1);
}

impl WeaverScoreCardNftMinterImpl of IWeaverScoreCardNftMinter<ContractState> {
fn mint_weaver_score_card_nft(ref self: ContractState) {
let caller = get_caller_address();
assert(!caller.is_zero(), Errors::ZERO_ADDRESS);

// Fetch current STRK price from Pragma Oracle
let strk_price = self._get_strk_price_from_oracle();
assert(strk_price > 0, Errors::INVALID_PRICE);

// Calculate required STRK amount: (asset_price * DECIMALS) / strk_price
let strk_needed_u128 = (ASSET_PRICE_USD * DECIMALS) / strk_price;
let strk_needed = strk_needed_u128.into();

// Get STRK token contract
let strk_token = IERC20Dispatcher {
contract_address: STRK_TOKEN_ADDRESS.try_into().unwrap(),
};

// Check caller's STRK balance
let caller_balance = strk_token.balance_of(caller);
assert(caller_balance >= strk_needed, Errors::INSUFFICIENT_BALANCE);

// Check allowance
let current_allowance = strk_token.allowance(caller, get_contract_address());
assert(current_allowance >= strk_needed, Errors::INSUFFICIENT_ALLOWANCE);

// Transfer STRK from caller to contract (assuming caller give approval)
let transfer_success = strk_token
.transfer_from(caller, get_contract_address(), strk_needed);
assert(transfer_success, Errors::TRANSFER_FAILED);

// Mint NFT to caller - if this fails, we need to refund
let weaver_nft = IWeaverNFTDispatcher {
contract_address: self.weaver_nft_contract_address.read(),
};

// Get next token ID and increment
let last_token_id_minted = weaver_nft.get_last_minted_id();
self.next_token_id.write(last_token_id_minted);

// Try to mint NFT
let mint_success = self._try_mint_nft(weaver_nft, caller);

if !mint_success {
// Refund STRK tokens to caller
let refund_success = strk_token.transfer(caller, strk_needed);
assert(refund_success, Errors::MINTING_FAILED);
}

// Emit success event
self
.emit(
WeaverScoreCardNftMinted {
to: caller, token_id, strk_amount_paid: strk_needed, strk_price,
},
);
}

fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 {
let pragma_dispatcher = IPragmaABIDispatcher {
contract_address: self.pragma_contract.read(),
};

let (price, _decimals, _last_updated_timestamp, _num_sources_aggregated) =
pragma_dispatcher
.get_data(asset_id, AGGREGATION_MODE);

price
}

fn set_erc721(ref self: ContractState, address: ContractAddress) {
self._only_owner();
assert(!address.is_zero(), Errors::ZERO_ADDRESS);

let old_address = self.weaver_nft_contract_address.read();
self.weaver_nft_contract_address.write(address);

self.emit(WeaverNftAddressSet { old_address, new_address: address });
}

fn withdraw(ref self: ContractState, amount: u256) {
self._only_owner();

let strk_token = IERC20Dispatcher {
contract_address: STRK_TOKEN_ADDRESS.try_into().unwrap(),
};

let contract_balance = strk_token.balance_of(get_contract_address());
assert(contract_balance >= amount, Errors::INSUFFICIENT_BALANCE);

let owner = self.owner.read();
let success = strk_token.transfer(owner, amount);
assert(success, Errors::WITHDRAWAL_FAILED);

self.emit(FundsWithdrawn { to: owner, amount });
}

fn get_owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}

fn get_weaver_nft_address(self: @ContractState) -> ContractAddress {
self.weaver_nft_contract_address.read()
}

fn get_pragma_address(self: @ContractState) -> ContractAddress {
self.pragma_contract.read()
}
}


#[generate_trait]
impl PrivateImpl of PrivateTrait {
fn _only_owner(self: @ContractState) {
let caller = get_caller_address();
let owner = self.owner.read();
assert(caller == owner, Errors::UNAUTHORIZED);
}

fn _get_strk_price_from_oracle(self: @ContractState) -> u128 {
let pragma_dispatcher = IPragmaABIDispatcher {
contract_address: self.pragma_contract.read(),
};

let (price, _decimals, _last_updated_timestamp, _num_sources_aggregated) =
pragma_dispatcher
.get_data(STRK_USD_PAIR_ID, AGGREGATION_MODE);

assert(price > 0, Errors::PRICE_FETCH_FAILED);
price
}

fn _try_mint_nft(
self: @ContractState, weaver_nft: IWeaverNFTDispatcher, to: ContractAddress,
) -> bool {
// In Cairo, we can't directly catch panics, so we'll assume the mint succeeds
// The actual implementation would depend on how the WeaverNFT contract handles errors
weaver_nft.mint_weaver_nft(to);
true
}
}
}
Loading