-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from casper-network/part3
part 3 tutorial
- Loading branch information
Showing
12 changed files
with
552 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.idea | ||
.vscode | ||
/target | ||
Cargo.lock | ||
.backend* | ||
.builder* | ||
/wasm | ||
.env |
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 @@ | ||
Changelog for `auctions`. | ||
|
||
## [0.1.0] - 2024-06-06 | ||
### Added | ||
- `flipper` module. |
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,31 @@ | ||
[package] | ||
name = "auctions" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
odra = { version = "1.0.0-rc.1", features = [], default-features = false } | ||
odra-modules = "1.0.0-rc.1" | ||
|
||
[dev-dependencies] | ||
odra-test = { version = "1.0.0-rc.1", features = [], default-features = false } | ||
|
||
[build-dependencies] | ||
odra-build = { version = "1.0.0-rc.1", features = [], default-features = false } | ||
|
||
[[bin]] | ||
name = "auctions_build_contract" | ||
path = "bin/build_contract.rs" | ||
test = false | ||
|
||
[[bin]] | ||
name = "auctions_build_schema" | ||
path = "bin/build_schema.rs" | ||
test = false | ||
|
||
[profile.release] | ||
codegen-units = 1 | ||
lto = true | ||
|
||
[profile.dev.package."*"] | ||
opt-level = 3 |
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,2 @@ | ||
[[contracts]] | ||
fqn = "mycontract::Mycontract" |
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,31 @@ | ||
# auctions | ||
|
||
## Usage | ||
It's recommended to install | ||
[cargo-odra](https://github.com/odradev/cargo-odra) first. | ||
|
||
### Build | ||
|
||
``` | ||
$ cargo odra build | ||
``` | ||
To build a wasm file, you need to pass the -b parameter. | ||
The result files will be placed in `${project-root}/wasm` directory. | ||
|
||
``` | ||
$ cargo odra build -b casper | ||
``` | ||
|
||
### Test | ||
To run test on your local machine, you can basically execute the command: | ||
|
||
``` | ||
$ cargo odra test | ||
``` | ||
|
||
To test actual wasm files against a backend, | ||
you need to specify the backend passing -b argument to `cargo-odra`. | ||
|
||
``` | ||
$ cargo odra test -b casper | ||
``` |
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 @@ | ||
#![doc = "Binary for building wasm files from odra contracts."] | ||
#![no_std] | ||
#![no_main] | ||
#![allow(unused_imports, clippy::single_component_path_imports)] | ||
use auctions; |
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,69 @@ | ||
#![doc = "Binary for building schema definitions from odra contracts."] | ||
#[allow(unused_imports)] | ||
use auctions; | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
extern "Rust" { | ||
fn module_schema() -> odra::contract_def::ContractBlueprint; | ||
fn casper_contract_schema() -> odra::schema::casper_contract_schema::ContractSchema; | ||
} | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
fn main() { | ||
let module = std::env::var("ODRA_MODULE").expect("ODRA_MODULE environment variable is not set"); | ||
let module = to_snake_case(&module); | ||
|
||
let contract_schema = unsafe { crate::casper_contract_schema() }; | ||
let module_schema = unsafe { crate::module_schema() }; | ||
|
||
write_schema_file( | ||
"resources/casper_contract_schemas", | ||
&module, | ||
contract_schema | ||
.as_json() | ||
.expect("Failed to convert schema to JSON") | ||
); | ||
|
||
write_schema_file( | ||
"resources/legacy", | ||
&module, | ||
module_schema | ||
.as_json() | ||
.expect("Failed to convert schema to JSON") | ||
); | ||
} | ||
|
||
fn write_schema_file(path: &str, module: &str, json: String) { | ||
if !std::path::Path::new(path).exists() { | ||
std::fs::create_dir_all(path).expect("Failed to create resources directory"); | ||
} | ||
let filename = format!("{}/{}_schema.json", path, module); | ||
let mut schema_file = std::fs::File::create(filename).expect("Failed to create schema file"); | ||
|
||
std::io::Write::write_all(&mut schema_file, &json.into_bytes()) | ||
.expect("Failed to write to schema file"); | ||
} | ||
|
||
fn to_snake_case(s: &str) -> String { | ||
let mut result = String::with_capacity(s.len()); | ||
let mut chars = s.chars().peekable(); | ||
let mut is_first = true; | ||
|
||
while let Some(c) = chars.next() { | ||
if c.is_uppercase() { | ||
if !is_first { | ||
if let Some(next) = chars.peek() { | ||
if next.is_lowercase() { | ||
result.push('_'); | ||
} | ||
} | ||
} | ||
result.push(c.to_lowercase().next().unwrap()); | ||
} else { | ||
result.push(c); | ||
} | ||
is_first = false; | ||
} | ||
|
||
result | ||
} |
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,6 @@ | ||
//! Odra's contracts build script. | ||
/// Uses the ENV variable `ODRA_MODULE` to set the `odra_module` cfg flag. | ||
pub fn main() { | ||
odra_build::build(); | ||
} |
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 @@ | ||
nightly-2024-02-09 |
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,189 @@ | ||
use odra::{ | ||
args::Maybe, | ||
casper_types::{U256, U512}, | ||
module::Module, | ||
Address, ContractRef, Mapping, SubModule, Var, | ||
}; | ||
use odra_modules::cep78::token::Cep78ContractRef; | ||
use odra_modules::{access::Ownable, security::Pauseable}; | ||
|
||
#[odra::module] | ||
/// This contract facilitates NFT auctions, allowing users to create and participate in auctions for CEP-78 NFTs. | ||
pub struct Auctions { | ||
/// Ownable submodule for managing contract ownership and permissions. | ||
ownable: SubModule<Ownable>, | ||
/// Pauseable submodule for pausing/unpausing contract functionality. | ||
pausable: SubModule<Pauseable>, | ||
/// Storage for active auctions, indexed by a unique auction ID. | ||
auctions: Mapping<U256, Auction>, | ||
/// Counter to track the total number of auctions created. | ||
auction_counter: Var<U256>, | ||
/// Minimum allowed duration for an auction, set by the contract owner. | ||
min_auction_duration: Var<u64>, | ||
} | ||
|
||
#[odra::odra_error] | ||
/// Errors that may occur during the contract execution. | ||
pub enum Error { | ||
/// Invalid auction duration, shorter than the minimum allowed. | ||
InvalidAuctionDuration = 1, | ||
/// Invalid bid amount, lower than the starting price or the current highest bid. | ||
InvalidBid = 2, | ||
/// Attempted to end an auction that has already ended. | ||
AuctionHasEnded = 3, | ||
/// Attempted to end an auction that is still in progress. | ||
AuctionStillInProgress = 4, | ||
} | ||
|
||
#[odra::odra_type] | ||
/// Represents an active auction for an NFT. | ||
pub struct Auction { | ||
/// Address of the seller who initiated the auction. | ||
seller: Address, | ||
/// Address of the CEP-78 NFT contract. | ||
nft_contract: Address, | ||
/// ID of the NFT being auctioned. | ||
nft_id: u64, | ||
/// Starting price of the auction in CSPR. | ||
starting_price: U512, | ||
/// Timestamp of when the auction ends. | ||
ends_at: u64, | ||
/// Optional address of the highest bidder (None if no bids yet). | ||
highest_bidder: Option<Address>, | ||
/// Amount of the highest bid in CSPR. | ||
highest_bid: U512, | ||
} | ||
|
||
#[odra::module] | ||
impl Auctions { | ||
/// Initializes the contract, setting the owner (optional) and minimum auction duration. | ||
pub fn init(&mut self, admin: Option<Address>, min_auction_duration: u64) { | ||
self.ownable.init(); | ||
if let Some(a) = admin { | ||
self.ownable.transfer_ownership(&a); // Transfer ownership to the provided admin | ||
} | ||
self.auction_counter.set(U256::one()); // Start auction counter from 1 | ||
self.min_auction_duration.set(min_auction_duration); | ||
} | ||
|
||
/********** | ||
* TRANSACTIONS | ||
**********/ | ||
|
||
/// Creates a new auction for a CEP-78 NFT. | ||
pub fn create_auction( | ||
&mut self, | ||
nft_contract: Address, | ||
nft_id: u64, | ||
starting_price: U512, | ||
duration: u64, | ||
) { | ||
self.pausable.require_not_paused(); // Ensure contract is not paused | ||
|
||
if duration < self.min_auction_duration.get_or_default() { | ||
self.env().revert(Error::InvalidAuctionDuration) // Revert if duration is too short | ||
} | ||
|
||
let seller = self.env().caller(); | ||
|
||
// Transfer the NFT to the auction contract | ||
Cep78ContractRef::new(self.env(), nft_contract).transfer( | ||
Maybe::Some(nft_id), | ||
Maybe::None, | ||
seller, | ||
self.env().self_address(), | ||
); | ||
|
||
// Create and store the auction details | ||
let auction = Auction { | ||
nft_contract, | ||
nft_id, | ||
seller, | ||
starting_price, | ||
highest_bid: U512::zero(), | ||
highest_bidder: None, | ||
ends_at: self.env().get_block_time() + duration, | ||
}; | ||
self.auctions | ||
.set(&self.auction_counter.get_or_default(), auction); | ||
self.auction_counter.add(U256::one()); // Increment auction counter | ||
} | ||
|
||
/// Places a bid on an active auction. | ||
#[odra(payable)] // Indicates this function accepts CSPR payments | ||
pub fn bid(&mut self, auction_id: U256) { | ||
self.pausable.require_not_paused(); | ||
|
||
let bidder = self.env().caller(); | ||
let amount = self.env().attached_value(); // Get the attached CSPR amount | ||
let mut auction = self.auctions.get(&auction_id).unwrap(); | ||
|
||
// Validate bid amount | ||
if amount < auction.starting_price || amount < auction.highest_bid { | ||
self.env().revert(Error::InvalidBid); | ||
} | ||
|
||
// Check if auction is still ongoing | ||
if self.env().get_block_time() > auction.ends_at { | ||
self.env().revert(Error::AuctionHasEnded); | ||
} | ||
|
||
// Refund the previous highest bidder (if any) | ||
if let Some(highest_bidder) = auction.highest_bidder { | ||
self.env() | ||
.transfer_tokens(&highest_bidder, &auction.highest_bid); | ||
} | ||
|
||
// Update the auction with the new highest bid and bidder | ||
auction.highest_bid = amount; | ||
auction.highest_bidder = Some(bidder); | ||
self.auctions.set(&auction_id, auction); | ||
} | ||
|
||
/// Ends an auction and distributes the NFT and funds accordingly. | ||
pub fn end_auction(&mut self, auction_id: U256) { | ||
self.pausable.require_not_paused(); | ||
let auction = self.auctions.get(&auction_id).unwrap(); | ||
|
||
// Check if auction has ended | ||
if self.env().get_block_time() < auction.ends_at { | ||
self.env().revert(Error::AuctionStillInProgress); | ||
} | ||
|
||
// Transfer the NFT and funds | ||
if let Some(winner) = auction.highest_bidder { | ||
Cep78ContractRef::new(self.env(), auction.nft_contract).transfer( | ||
Maybe::Some(auction.nft_id), | ||
Maybe::None, | ||
self.env().self_address(), | ||
winner, | ||
); | ||
self.env() | ||
.transfer_tokens(&auction.seller, &auction.highest_bid); | ||
} else { | ||
// No bids were placed, return the NFT to the seller | ||
Cep78ContractRef::new(self.env(), auction.nft_contract).transfer( | ||
Maybe::Some(auction.nft_id), | ||
Maybe::None, | ||
self.env().self_address(), | ||
auction.seller, | ||
); | ||
} | ||
} | ||
|
||
/********** | ||
* ADMIN | ||
**********/ | ||
|
||
/// Pauses the contract, preventing further interactions. | ||
pub fn pause(&mut self) { | ||
self.ownable.assert_owner(&self.env().caller()); | ||
self.pausable.pause(); | ||
} | ||
|
||
/// Unpauses the contract, resuming normal operation. | ||
pub fn unpause(&mut self) { | ||
self.ownable.assert_owner(&self.env().caller()); | ||
self.pausable.unpause(); | ||
} | ||
} |
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 @@ | ||
#![cfg_attr(not(test), no_std)] | ||
#![cfg_attr(not(test), no_main)] | ||
extern crate alloc; | ||
|
||
pub mod auctions; |
Oops, something went wrong.