-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5713ff7
commit 70bec42
Showing
9 changed files
with
667 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#[starknet::contract] | ||
pub mod NFTDutchAuction { | ||
use core::integer::u256; | ||
use contracts::interfaces::INFTDutchAuction::INFTDutchAuction; | ||
use contracts::interfaces::IERC721::{IERC721Dispatcher, IERC721DispatcherTrait}; | ||
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; | ||
use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; | ||
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; | ||
|
||
#[storage] | ||
struct Storage { | ||
erc20_token: ContractAddress, | ||
erc721_token: ContractAddress, | ||
starting_price: u64, | ||
seller: ContractAddress, | ||
duration: u64, | ||
discount_rate: u64, | ||
start_at: u64, | ||
expires_at: u64, | ||
purchase_count: u128, | ||
total_supply: u128, | ||
} | ||
|
||
mod Errors { | ||
pub const AUCTION_ENDED: felt252 = 'auction has ended'; | ||
pub const LOW_STARTING_PRICE: felt252 = 'low starting price'; | ||
pub const INSUFFICIENT_BALANCE: felt252 = 'insufficient balance'; | ||
} | ||
|
||
#[constructor] | ||
fn constructor( | ||
ref self: ContractState, | ||
erc20_token: ContractAddress, | ||
erc721_token: ContractAddress, | ||
starting_price: u64, | ||
seller: ContractAddress, | ||
duration: u64, | ||
discount_rate: u64, | ||
total_supply: u128, | ||
) { | ||
assert(starting_price >= discount_rate * duration, Errors::LOW_STARTING_PRICE); | ||
|
||
self.erc20_token.write(erc20_token); | ||
self.erc721_token.write(erc721_token); | ||
self.starting_price.write(starting_price); | ||
self.seller.write(seller); | ||
self.duration.write(duration); | ||
self.discount_rate.write(discount_rate); | ||
self.start_at.write(get_block_timestamp()); | ||
self.expires_at.write(get_block_timestamp() + duration * 1000); | ||
self.total_supply.write(total_supply); | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl NFTDutchAuction of INFTDutchAuction<ContractState> { | ||
fn get_price(self: @ContractState) -> u64 { | ||
let time_elapsed = (get_block_timestamp() - self.start_at.read()) | ||
/ 1000; // Ignore milliseconds | ||
let discount = self.discount_rate.read() * time_elapsed; | ||
self.starting_price.read() - discount | ||
} | ||
|
||
fn buy(ref self: ContractState, token_id: u256) { | ||
// Check duration | ||
assert(get_block_timestamp() < self.expires_at.read(), Errors::AUCTION_ENDED); | ||
// Check total supply | ||
assert(self.purchase_count.read() < self.total_supply.read(), Errors::AUCTION_ENDED); | ||
|
||
let erc20_dispatcher = IERC20Dispatcher { contract_address: self.erc20_token.read() }; | ||
let erc721_dispatcher = IERC721Dispatcher { | ||
contract_address: self.erc721_token.read(), | ||
}; | ||
|
||
let caller = get_caller_address(); | ||
println!("Caller inside the buy: {:?}", caller); | ||
// Get NFT price | ||
let price: u256 = self.get_price().into(); | ||
let buyer_balance: u256 = erc20_dispatcher.balance_of(caller).into(); | ||
// Ensure buyer has enough token for payment | ||
assert(buyer_balance >= price, Errors::INSUFFICIENT_BALANCE); | ||
// Transfer payment token from buyer to seller | ||
erc20_dispatcher.transfer_from(caller, self.seller.read(), price.try_into().unwrap()); | ||
// Mint token to buyer's address | ||
erc721_dispatcher.mint(caller, token_id); | ||
// Increase purchase count | ||
self.purchase_count.write(self.purchase_count.read() + 1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
pub mod IDefiVault; | ||
pub mod IStarkIdentity; | ||
pub mod timelock; | ||
pub mod INFTDutchAuction; | ||
pub mod IERC721; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use starknet::ContractAddress; | ||
|
||
#[starknet::interface] | ||
pub trait IERC721<TContractState> { | ||
fn get_name(self: @TContractState) -> felt252; | ||
fn get_symbol(self: @TContractState) -> felt252; | ||
fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; | ||
fn balance_of(self: @TContractState, account: ContractAddress) -> u256; | ||
fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; | ||
fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; | ||
fn is_approved_for_all( | ||
self: @TContractState, owner: ContractAddress, operator: ContractAddress, | ||
) -> bool; | ||
fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); | ||
fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); | ||
fn transfer_from( | ||
ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, | ||
); | ||
fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#[starknet::interface] | ||
pub trait INFTDutchAuction<TContractState> { | ||
fn buy(ref self: TContractState, token_id: u256); | ||
fn get_price(self: @TContractState) -> u64; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
use starknet::ContractAddress; | ||
|
||
#[starknet::interface] | ||
pub trait IERC721<TContractState> { | ||
fn get_name(self: @TContractState) -> felt252; | ||
fn get_symbol(self: @TContractState) -> felt252; | ||
fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; | ||
fn balance_of(self: @TContractState, account: ContractAddress) -> u256; | ||
fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; | ||
fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; | ||
fn is_approved_for_all( | ||
self: @TContractState, owner: ContractAddress, operator: ContractAddress, | ||
) -> bool; | ||
fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); | ||
fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); | ||
fn transfer_from( | ||
ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, | ||
); | ||
fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); | ||
} | ||
|
||
#[starknet::contract] | ||
mod MockERC721 { | ||
|
||
use starknet::{ContractAddress, get_caller_address}; | ||
use starknet::storage::{ | ||
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, | ||
StoragePointerWriteAccess, | ||
}; | ||
use core::num::traits::Zero; | ||
|
||
#[storage] | ||
struct Storage { | ||
name: felt252, | ||
symbol: felt252, | ||
owners: Map::<u256, ContractAddress>, | ||
balances: Map::<ContractAddress, u256>, | ||
token_approvals: Map::<u256, ContractAddress>, | ||
operator_approvals: Map::<(ContractAddress, ContractAddress), bool>, | ||
token_uri: Map<u256, felt252>, | ||
} | ||
|
||
#[event] | ||
#[derive(Drop, starknet::Event)] | ||
enum Event { | ||
Approval: Approval, | ||
Transfer: Transfer, | ||
ApprovalForAll: ApprovalForAll, | ||
} | ||
|
||
#[derive(Drop, starknet::Event)] | ||
struct Approval { | ||
owner: ContractAddress, | ||
to: ContractAddress, | ||
token_id: u256, | ||
} | ||
|
||
#[derive(Drop, starknet::Event)] | ||
struct Transfer { | ||
from: ContractAddress, | ||
to: ContractAddress, | ||
token_id: u256, | ||
} | ||
|
||
#[derive(Drop, starknet::Event)] | ||
struct ApprovalForAll { | ||
owner: ContractAddress, | ||
operator: ContractAddress, | ||
approved: bool, | ||
} | ||
|
||
#[constructor] | ||
fn constructor(ref self: ContractState, _name: felt252, _symbol: felt252) { | ||
self.name.write(_name); | ||
self.symbol.write(_symbol); | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl IERC721Impl of super::IERC721<ContractState> { | ||
fn get_name(self: @ContractState) -> felt252 { | ||
self.name.read() | ||
} | ||
fn get_symbol(self: @ContractState) -> felt252 { | ||
self.symbol.read() | ||
} | ||
fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { | ||
assert(self._exists(token_id), 'ERC721: invalid token ID'); | ||
self.token_uri.read(token_id) | ||
} | ||
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { | ||
assert(account.is_non_zero(), 'ERC721: address zero'); | ||
self.balances.read(account) | ||
} | ||
fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { | ||
let owner = self.owners.read(token_id); | ||
owner | ||
} | ||
fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { | ||
assert(self._exists(token_id), 'ERC721: invalid token ID'); | ||
self.token_approvals.read(token_id) | ||
} | ||
fn is_approved_for_all( | ||
self: @ContractState, owner: ContractAddress, operator: ContractAddress, | ||
) -> bool { | ||
self.operator_approvals.read((owner, operator)) | ||
} | ||
|
||
fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { | ||
let owner = self.owner_of(token_id); | ||
assert(to != owner, 'Approval to current owner'); | ||
assert( | ||
get_caller_address() == owner | ||
|| self.is_approved_for_all(owner, get_caller_address()), | ||
'Not token owner', | ||
); | ||
self.token_approvals.write(token_id, to); | ||
self.emit(Approval { owner: self.owner_of(token_id), to: to, token_id: token_id }); | ||
} | ||
|
||
fn set_approval_for_all( | ||
ref self: ContractState, operator: ContractAddress, approved: bool, | ||
) { | ||
let owner = get_caller_address(); | ||
assert(owner != operator, 'ERC721: approve to caller'); | ||
self.operator_approvals.write((owner, operator), approved); | ||
self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); | ||
} | ||
|
||
fn transfer_from( | ||
ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, | ||
) { | ||
assert( | ||
self._is_approved_or_owner(get_caller_address(), token_id), | ||
'neither owner nor approved', | ||
); | ||
self._transfer(from, to, token_id); | ||
} | ||
|
||
fn mint(ref self: ContractState, to: ContractAddress, token_id: u256) { | ||
self._mint(to, token_id); | ||
} | ||
} | ||
|
||
#[generate_trait] | ||
impl ERC721HelperImpl of ERC721HelperTrait { | ||
|
||
fn _exists(self: @ContractState, token_id: u256) -> bool { | ||
// check that owner of token is not zero | ||
self.owner_of(token_id).is_non_zero() | ||
} | ||
|
||
fn _is_approved_or_owner( | ||
self: @ContractState, spender: ContractAddress, token_id: u256, | ||
) -> bool { | ||
let owner = self.owners.read(token_id); | ||
spender == owner | ||
|| self.is_approved_for_all(owner, spender) | ||
|| self.get_approved(token_id) == spender | ||
} | ||
|
||
fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { | ||
assert(self._exists(token_id), 'ERC721: invalid token ID'); | ||
self.token_uri.write(token_id, token_uri); | ||
} | ||
|
||
fn _transfer( | ||
ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, | ||
) { | ||
// check that from address is equal to owner of token | ||
assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); | ||
// check that to address is not zero | ||
assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); | ||
|
||
// remove previously made approvals | ||
self.token_approvals.write(token_id, Zero::zero()); | ||
|
||
// increase balance of to address, decrease balance of from address | ||
self.balances.write(from, self.balances.read(from) - 1.into()); | ||
self.balances.write(to, self.balances.read(to) + 1.into()); | ||
|
||
// update token_id owner | ||
self.owners.write(token_id, to); | ||
|
||
// emit the Transfer event | ||
self.emit(Transfer { from: from, to: to, token_id: token_id }); | ||
} | ||
|
||
fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { | ||
assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); | ||
|
||
// Ensures token_id is unique | ||
assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); | ||
|
||
// Increase receiver balance | ||
let receiver_balance = self.balances.read(to); | ||
self.balances.write(to, receiver_balance + 1.into()); | ||
|
||
// Update token_id owner | ||
self.owners.write(token_id, to); | ||
|
||
// emit Transfer event | ||
self.emit(Transfer { from: Zero::zero(), to: to, token_id: token_id }); | ||
} | ||
|
||
fn _burn(ref self: ContractState, token_id: u256) { | ||
let owner = self.owner_of(token_id); | ||
|
||
// Clear approvals | ||
self.token_approvals.write(token_id, Zero::zero()); | ||
|
||
// Decrease owner balance | ||
let owner_balance = self.balances.read(owner); | ||
self.balances.write(owner, owner_balance - 1.into()); | ||
|
||
// Delete owner | ||
self.owners.write(token_id, Zero::zero()); | ||
// emit the Transfer event | ||
self.emit(Transfer { from: owner, to: Zero::zero(), token_id: token_id }); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.