diff --git a/contract-rs/Cargo.toml b/contract-rs/01-basic-auction/Cargo.toml similarity index 100% rename from contract-rs/Cargo.toml rename to contract-rs/01-basic-auction/Cargo.toml diff --git a/contract-rs/README.md b/contract-rs/01-basic-auction/README.md similarity index 100% rename from contract-rs/README.md rename to contract-rs/01-basic-auction/README.md diff --git a/contract-rs/rust-toolchain.toml b/contract-rs/01-basic-auction/rust-toolchain.toml similarity index 100% rename from contract-rs/rust-toolchain.toml rename to contract-rs/01-basic-auction/rust-toolchain.toml diff --git a/contract-rs/src/ext.rs b/contract-rs/01-basic-auction/src/ext.rs similarity index 100% rename from contract-rs/src/ext.rs rename to contract-rs/01-basic-auction/src/ext.rs diff --git a/contract-rs/src/lib.rs b/contract-rs/01-basic-auction/src/lib.rs similarity index 99% rename from contract-rs/src/lib.rs rename to contract-rs/01-basic-auction/src/lib.rs index 0f810ade..1d385126 100644 --- a/contract-rs/src/lib.rs +++ b/contract-rs/01-basic-auction/src/lib.rs @@ -1,6 +1,6 @@ // Find all our documentation at https://docs.near.org use near_sdk::json_types::{U128, U64}; -use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault, Promise}; +use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault}; pub mod ext; pub use crate::ext::*; diff --git a/contract-rs/tests/fungible_token.wasm b/contract-rs/01-basic-auction/tests/fungible_token.wasm similarity index 100% rename from contract-rs/tests/fungible_token.wasm rename to contract-rs/01-basic-auction/tests/fungible_token.wasm diff --git a/contract-rs/tests/non_fungible_token.wasm b/contract-rs/01-basic-auction/tests/non_fungible_token.wasm similarity index 100% rename from contract-rs/tests/non_fungible_token.wasm rename to contract-rs/01-basic-auction/tests/non_fungible_token.wasm diff --git a/contract-rs/tests/test_basics.rs b/contract-rs/01-basic-auction/tests/test_basics.rs similarity index 100% rename from contract-rs/tests/test_basics.rs rename to contract-rs/01-basic-auction/tests/test_basics.rs diff --git a/contract-rs/02-owner-claims-money/Cargo.toml b/contract-rs/02-owner-claims-money/Cargo.toml new file mode 100644 index 00000000..d704430b --- /dev/null +++ b/contract-rs/02-owner-claims-money/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "contract-rs" +description = "Hello Near Example" +version = "0.1.0" +edition = "2021" +# TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project. +# NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near. +# Link to the repository will be available via `contract_source_metadata` view-function. +#repository = "https://github.com/xxx/xxx" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +near-sdk = { version = "5.1.0", features = ["legacy"] } +chrono = "0.4.38" +tracing = "0.1" +tracing-subscriber = "0.3" + +[dev-dependencies] +near-sdk = { version = "5.1.0", features = ["unit-testing"] } +near-workspaces = { version = "0.10.0", features = ["unstable"] } +tokio = { version = "1.12.0", features = ["full"] } +serde_json = "1.0.117" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" +# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 +overflow-checks = true diff --git a/contract-rs/02-owner-claims-money/README.md b/contract-rs/02-owner-claims-money/README.md new file mode 100644 index 00000000..ebc0f142 --- /dev/null +++ b/contract-rs/02-owner-claims-money/README.md @@ -0,0 +1,39 @@ +# contract-rs + +cargo-near-new-project-description + +## How to Build Locally? + +Install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +cargo near build +``` + +## How to Test Locally? + +```bash +cargo test +``` + +## How to Deploy? + +To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +# Create a new account +cargo near create-dev-account + +# Deploy the contract on it +cargo near deploy +``` +## Useful Links + +- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust +- [near CLI](https://near.cli.rs) - Iteract with NEAR blockchain from command line +- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction) +- [NEAR Documentation](https://docs.near.org) +- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol) +- [NEAR Discord](https://near.chat) +- [NEAR Telegram Developers Community Group](https://t.me/neardev) +- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub) diff --git a/contract-rs/02-owner-claims-money/rust-toolchain.toml b/contract-rs/02-owner-claims-money/rust-toolchain.toml new file mode 100644 index 00000000..a82ade34 --- /dev/null +++ b/contract-rs/02-owner-claims-money/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/contract-rs/02-owner-claims-money/src/ext.rs b/contract-rs/02-owner-claims-money/src/ext.rs new file mode 100644 index 00000000..df239f03 --- /dev/null +++ b/contract-rs/02-owner-claims-money/src/ext.rs @@ -0,0 +1,16 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::U128; +use near_sdk::{ext_contract, AccountId}; + +use crate::TokenId; + +// Validator interface, for cross-contract calls +#[ext_contract(ft_contract)] +trait FT { + fn ft_transfer(&self, receiver_id: AccountId, amount: U128) -> String; +} + +#[ext_contract(nft_contract)] +trait NFT { + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> String; +} diff --git a/contract-rs/02-owner-claims-money/src/lib.rs b/contract-rs/02-owner-claims-money/src/lib.rs new file mode 100644 index 00000000..1d385126 --- /dev/null +++ b/contract-rs/02-owner-claims-money/src/lib.rs @@ -0,0 +1,110 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::{U128, U64}; +use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault}; + +pub mod ext; +pub use crate::ext::*; + +#[near(serializers = [json, borsh])] +#[derive(Clone)] +pub struct Bid { + pub bidder: AccountId, + pub bid: U128, +} + +pub type TokenId = String; + +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + highest_bid: Bid, + auction_end_time: U64, + auctioneer: AccountId, + auction_was_claimed: bool, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, +} + +#[near] +impl Contract { + #[init] + #[private] // only callable by the contract's account + pub fn init( + end_time: U64, + auctioneer: AccountId, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, + ) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: U128(0), + }, + auction_end_time: end_time, + auctioneer, + auction_was_claimed: false, + ft_contract, + nft_contract, + token_id, + } + } + + pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() + } + + pub fn claim(&mut self) { + assert!( + env::block_timestamp() > self.auction_end_time.into(), + "Auction has not ended yet" + ); + + assert!(!self.auction_was_claimed, "Auction has been claimed"); + + self.auction_was_claimed = true; + let auctioneer = self.auctioneer.clone(); + + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(auctioneer, self.highest_bid.bid); + + nft_contract::ext(self.nft_contract.clone()) + .with_static_gas(Gas::from_tgas(30)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); + } + + pub fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> U128 { + require!( + env::block_timestamp() < self.auction_end_time.into(), + "Auction has ended" + ); + + let ft = env::predecessor_account_id(); + require!(ft == self.ft_contract, "The token is not supported"); + + let Bid { + bidder: last_bidder, + bid: last_bid, + } = self.highest_bid.clone(); + + require!(amount >= last_bid, "You must place a higher bid"); + + self.highest_bid = Bid { + bidder: sender_id, + bid: amount, + }; + + if last_bid > U128(0) { + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(last_bidder, last_bid); + } + + U128(0) + } +} diff --git a/contract-rs/02-owner-claims-money/tests/fungible_token.wasm b/contract-rs/02-owner-claims-money/tests/fungible_token.wasm new file mode 100755 index 00000000..40045b57 Binary files /dev/null and b/contract-rs/02-owner-claims-money/tests/fungible_token.wasm differ diff --git a/contract-rs/02-owner-claims-money/tests/non_fungible_token.wasm b/contract-rs/02-owner-claims-money/tests/non_fungible_token.wasm new file mode 100755 index 00000000..68f4e83f Binary files /dev/null and b/contract-rs/02-owner-claims-money/tests/non_fungible_token.wasm differ diff --git a/contract-rs/02-owner-claims-money/tests/test_basics.rs b/contract-rs/02-owner-claims-money/tests/test_basics.rs new file mode 100644 index 00000000..7fa63fd8 --- /dev/null +++ b/contract-rs/02-owner-claims-money/tests/test_basics.rs @@ -0,0 +1,351 @@ +use chrono::Utc; +use contract_rs::{Bid, TokenId}; +use near_sdk::{json_types::U128, log, NearToken}; +use near_sdk::{AccountId, Gas}; +use near_workspaces::result::ExecutionFinalResult; +use near_workspaces::{Account, Contract}; +use serde_json::json; + +const FIVE_NEAR: NearToken = NearToken::from_near(5); +const FT_WASM_FILEPATH: &str = "./tests/fungible_token.wasm"; +const NFT_WASM_FILEPATH: &str = "./tests/non_fungible_token.wasm"; + +#[tokio::test] + +async fn test_contract_is_operational() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = near_workspaces::compile_project("./").await?; + + let ft_wasm = std::fs::read(FT_WASM_FILEPATH)?; + let ft_contract = sandbox.dev_deploy(&ft_wasm).await?; + + let nft_wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let nft_contract = sandbox.dev_deploy(&nft_wasm).await?; + + let root: near_workspaces::Account = sandbox.root_account()?; + + // Initialize contracts + let res = ft_contract + .call("new_default_meta") + .args_json(serde_json::json!({ + "owner_id": root.id(), + "total_supply": U128(1_000_000), + })) + .transact() + .await?; + + assert!(res.is_success()); + + let res = nft_contract + .call("new_default_meta") + .args_json(serde_json::json!({"owner_id": root.id()})) + .transact() + .await?; + + assert!(res.is_success()); + + // Create subaccounts + let alice = create_subaccount(&root, "alice").await?; + let auctioneer = create_subaccount(&root, "auctioneer").await?; + let bob = create_subaccount(&root, "bob").await?; + let contract_account = create_subaccount(&root, "contract").await?; + + // Mint NFT + let request_payload = json!({ + "token_id": "1", + "receiver_id": contract_account.id(), + "metadata": { + "title": "LEEROYYYMMMJENKINSSS", + "description": "Alright time's up, let's do this.", + "media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1" + }, + }); + + let res = contract_account + .call(nft_contract.id(), "nft_mint") + .args_json(request_payload) + .deposit(NearToken::from_millinear(80)) + .transact() + .await?; + assert!(res.is_success()); + + // Deploy and initialize contract + let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); + + let now = Utc::now().timestamp(); + let a_minute_from_now = (now + 60) * 1000000000; + log!("a_minute_from_now: {}", a_minute_from_now); + + let init: ExecutionFinalResult = contract + .call("init") + .args_json( + json!({"end_time": a_minute_from_now.to_string(),"auctioneer": auctioneer.id(),"ft_contract": ft_contract.id(),"nft_contract":nft_contract.id(),"token_id":"1" }), + ) + .transact() + .await?; + + assert!(init.is_success()); + + // Register accounts + // number magic 8000000000000000000000 + for account in [ + alice.clone(), + bob.clone(), + contract_account.clone(), + auctioneer.clone(), + ].iter() + { + let register = account + .call(ft_contract.id(), "storage_deposit") + .args_json(serde_json::json!({ "account_id": account.id() })) + .deposit(NearToken::from_yoctonear(8000000000000000000000)) + .transact() + .await?; + assert!(register.is_success()); + } + + // Transfer tokens + let transfer_amount = U128(150_000); + + let root_transfer_alice = + ft_transfer(&root, alice.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_alice.is_success()); + + let alice_balance: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance, U128(150_000)); + + let root_transfer_bob = + ft_transfer(&root, bob.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_bob.is_success()); + + let bob_balance: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance, U128(150_000)); + + // Alice makes first bid + let alice_bid = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(50_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(50_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(100_000)); + + // Bob makes second bid + let bob_bid = ft_transfer_call( + bob.clone(), + ft_contract.id(), + contract_account.id(), + U128(60_000), + ) + .await?; + assert!(bob_bid.is_success()); + + let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + + assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!(highest_bid.bidder, *bob.id()); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid but fails + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(60_000)); + assert_eq!(highest_bid_alice.bidder, *bob.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(60_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(100_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(100_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(100_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(50_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(150_000)); + + // Alicia claims the auction but fails + let alice_claim: ExecutionFinalResult = claim(alice.clone(), contract_account.id()).await?; + assert!(alice_claim.is_failure()); + + // Auctioneer claims auction but did not finish + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + // Fast forward + // ~0.3 seconds * 400 = 120 seconds = 2 minutes + let blocks_to_advance = 400; + sandbox.fast_forward(blocks_to_advance).await?; + + // Bob makes a bid but it ends + // this cannot be tested + // let bob_bid = ft_transfer_call(bob.clone(),ft_contract.id(),contract_account.id(),U128(120_000)).await?; + // assert!(bob_bid.is_failure()); + + // Auctioneer claims auction + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + contract.id().to_string(), + "token owner is not first_buyer" + ); + + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + println!("auctioneer_claim outcome: {:#?}", auctioneer_claim); + assert!(auctioneer_claim.is_success()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(0)); + + let auctioneer_balance_after_claim: U128 = ft_balance_of(&ft_contract, auctioneer.id()).await?; + assert_eq!(auctioneer_balance_after_claim, U128(100_000)); + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + alice.id().to_string(), + "token owner is not first_buyer" + ); + + // Auctioneer claims auction back but fails + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + Ok(()) +} + +async fn create_subaccount( + root: &near_workspaces::Account, + name: &str, +) -> Result> { + let subaccount = root + .create_subaccount(name) + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + Ok(subaccount) +} + +async fn ft_transfer( + root: &near_workspaces::Account, + account: Account, + ft_contract: Contract, + transfer_amount: U128, +) -> Result> { + let transfer = root + .call(ft_contract.id(), "ft_transfer") + .args_json(serde_json::json!({ + "receiver_id": account.id(), + "amount": transfer_amount + })) + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + Ok(transfer) +} + +async fn ft_balance_of( + ft_contract: &Contract, + account_id: &AccountId, +) -> Result> { + let result = ft_contract + .view("ft_balance_of") + .args_json(json!({"account_id": account_id})) // Aquí usamos el account_id pasado como argumento + .await? + .json()?; + + Ok(result) +} + +async fn ft_transfer_call( + account: Account, + ft_contract_id: &AccountId, + receiver_id: &AccountId, + amount: U128, +) -> Result> { + let transfer = account + .call(ft_contract_id, "ft_transfer_call") + .args_json(serde_json::json!({ + "receiver_id": receiver_id, "amount":amount, "msg": "0" })) + .deposit(NearToken::from_yoctonear(1)) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(transfer) +} + +async fn claim( + account: Account, + contract_id: &AccountId, +) -> Result> { + let claim: ExecutionFinalResult = account + .call(contract_id, "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(claim) +} diff --git a/contract-rs/03-owner-claims-winner-gets-nft/Cargo.toml b/contract-rs/03-owner-claims-winner-gets-nft/Cargo.toml new file mode 100644 index 00000000..d704430b --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "contract-rs" +description = "Hello Near Example" +version = "0.1.0" +edition = "2021" +# TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project. +# NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near. +# Link to the repository will be available via `contract_source_metadata` view-function. +#repository = "https://github.com/xxx/xxx" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +near-sdk = { version = "5.1.0", features = ["legacy"] } +chrono = "0.4.38" +tracing = "0.1" +tracing-subscriber = "0.3" + +[dev-dependencies] +near-sdk = { version = "5.1.0", features = ["unit-testing"] } +near-workspaces = { version = "0.10.0", features = ["unstable"] } +tokio = { version = "1.12.0", features = ["full"] } +serde_json = "1.0.117" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" +# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 +overflow-checks = true diff --git a/contract-rs/03-owner-claims-winner-gets-nft/README.md b/contract-rs/03-owner-claims-winner-gets-nft/README.md new file mode 100644 index 00000000..ebc0f142 --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/README.md @@ -0,0 +1,39 @@ +# contract-rs + +cargo-near-new-project-description + +## How to Build Locally? + +Install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +cargo near build +``` + +## How to Test Locally? + +```bash +cargo test +``` + +## How to Deploy? + +To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +# Create a new account +cargo near create-dev-account + +# Deploy the contract on it +cargo near deploy +``` +## Useful Links + +- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust +- [near CLI](https://near.cli.rs) - Iteract with NEAR blockchain from command line +- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction) +- [NEAR Documentation](https://docs.near.org) +- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol) +- [NEAR Discord](https://near.chat) +- [NEAR Telegram Developers Community Group](https://t.me/neardev) +- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub) diff --git a/contract-rs/03-owner-claims-winner-gets-nft/rust-toolchain.toml b/contract-rs/03-owner-claims-winner-gets-nft/rust-toolchain.toml new file mode 100644 index 00000000..a82ade34 --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/contract-rs/03-owner-claims-winner-gets-nft/src/ext.rs b/contract-rs/03-owner-claims-winner-gets-nft/src/ext.rs new file mode 100644 index 00000000..df239f03 --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/src/ext.rs @@ -0,0 +1,16 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::U128; +use near_sdk::{ext_contract, AccountId}; + +use crate::TokenId; + +// Validator interface, for cross-contract calls +#[ext_contract(ft_contract)] +trait FT { + fn ft_transfer(&self, receiver_id: AccountId, amount: U128) -> String; +} + +#[ext_contract(nft_contract)] +trait NFT { + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> String; +} diff --git a/contract-rs/03-owner-claims-winner-gets-nft/src/lib.rs b/contract-rs/03-owner-claims-winner-gets-nft/src/lib.rs new file mode 100644 index 00000000..1d385126 --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/src/lib.rs @@ -0,0 +1,110 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::{U128, U64}; +use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault}; + +pub mod ext; +pub use crate::ext::*; + +#[near(serializers = [json, borsh])] +#[derive(Clone)] +pub struct Bid { + pub bidder: AccountId, + pub bid: U128, +} + +pub type TokenId = String; + +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + highest_bid: Bid, + auction_end_time: U64, + auctioneer: AccountId, + auction_was_claimed: bool, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, +} + +#[near] +impl Contract { + #[init] + #[private] // only callable by the contract's account + pub fn init( + end_time: U64, + auctioneer: AccountId, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, + ) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: U128(0), + }, + auction_end_time: end_time, + auctioneer, + auction_was_claimed: false, + ft_contract, + nft_contract, + token_id, + } + } + + pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() + } + + pub fn claim(&mut self) { + assert!( + env::block_timestamp() > self.auction_end_time.into(), + "Auction has not ended yet" + ); + + assert!(!self.auction_was_claimed, "Auction has been claimed"); + + self.auction_was_claimed = true; + let auctioneer = self.auctioneer.clone(); + + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(auctioneer, self.highest_bid.bid); + + nft_contract::ext(self.nft_contract.clone()) + .with_static_gas(Gas::from_tgas(30)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); + } + + pub fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> U128 { + require!( + env::block_timestamp() < self.auction_end_time.into(), + "Auction has ended" + ); + + let ft = env::predecessor_account_id(); + require!(ft == self.ft_contract, "The token is not supported"); + + let Bid { + bidder: last_bidder, + bid: last_bid, + } = self.highest_bid.clone(); + + require!(amount >= last_bid, "You must place a higher bid"); + + self.highest_bid = Bid { + bidder: sender_id, + bid: amount, + }; + + if last_bid > U128(0) { + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(last_bidder, last_bid); + } + + U128(0) + } +} diff --git a/contract-rs/03-owner-claims-winner-gets-nft/tests/fungible_token.wasm b/contract-rs/03-owner-claims-winner-gets-nft/tests/fungible_token.wasm new file mode 100755 index 00000000..40045b57 Binary files /dev/null and b/contract-rs/03-owner-claims-winner-gets-nft/tests/fungible_token.wasm differ diff --git a/contract-rs/03-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm b/contract-rs/03-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm new file mode 100755 index 00000000..68f4e83f Binary files /dev/null and b/contract-rs/03-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm differ diff --git a/contract-rs/03-owner-claims-winner-gets-nft/tests/test_basics.rs b/contract-rs/03-owner-claims-winner-gets-nft/tests/test_basics.rs new file mode 100644 index 00000000..7fa63fd8 --- /dev/null +++ b/contract-rs/03-owner-claims-winner-gets-nft/tests/test_basics.rs @@ -0,0 +1,351 @@ +use chrono::Utc; +use contract_rs::{Bid, TokenId}; +use near_sdk::{json_types::U128, log, NearToken}; +use near_sdk::{AccountId, Gas}; +use near_workspaces::result::ExecutionFinalResult; +use near_workspaces::{Account, Contract}; +use serde_json::json; + +const FIVE_NEAR: NearToken = NearToken::from_near(5); +const FT_WASM_FILEPATH: &str = "./tests/fungible_token.wasm"; +const NFT_WASM_FILEPATH: &str = "./tests/non_fungible_token.wasm"; + +#[tokio::test] + +async fn test_contract_is_operational() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = near_workspaces::compile_project("./").await?; + + let ft_wasm = std::fs::read(FT_WASM_FILEPATH)?; + let ft_contract = sandbox.dev_deploy(&ft_wasm).await?; + + let nft_wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let nft_contract = sandbox.dev_deploy(&nft_wasm).await?; + + let root: near_workspaces::Account = sandbox.root_account()?; + + // Initialize contracts + let res = ft_contract + .call("new_default_meta") + .args_json(serde_json::json!({ + "owner_id": root.id(), + "total_supply": U128(1_000_000), + })) + .transact() + .await?; + + assert!(res.is_success()); + + let res = nft_contract + .call("new_default_meta") + .args_json(serde_json::json!({"owner_id": root.id()})) + .transact() + .await?; + + assert!(res.is_success()); + + // Create subaccounts + let alice = create_subaccount(&root, "alice").await?; + let auctioneer = create_subaccount(&root, "auctioneer").await?; + let bob = create_subaccount(&root, "bob").await?; + let contract_account = create_subaccount(&root, "contract").await?; + + // Mint NFT + let request_payload = json!({ + "token_id": "1", + "receiver_id": contract_account.id(), + "metadata": { + "title": "LEEROYYYMMMJENKINSSS", + "description": "Alright time's up, let's do this.", + "media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1" + }, + }); + + let res = contract_account + .call(nft_contract.id(), "nft_mint") + .args_json(request_payload) + .deposit(NearToken::from_millinear(80)) + .transact() + .await?; + assert!(res.is_success()); + + // Deploy and initialize contract + let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); + + let now = Utc::now().timestamp(); + let a_minute_from_now = (now + 60) * 1000000000; + log!("a_minute_from_now: {}", a_minute_from_now); + + let init: ExecutionFinalResult = contract + .call("init") + .args_json( + json!({"end_time": a_minute_from_now.to_string(),"auctioneer": auctioneer.id(),"ft_contract": ft_contract.id(),"nft_contract":nft_contract.id(),"token_id":"1" }), + ) + .transact() + .await?; + + assert!(init.is_success()); + + // Register accounts + // number magic 8000000000000000000000 + for account in [ + alice.clone(), + bob.clone(), + contract_account.clone(), + auctioneer.clone(), + ].iter() + { + let register = account + .call(ft_contract.id(), "storage_deposit") + .args_json(serde_json::json!({ "account_id": account.id() })) + .deposit(NearToken::from_yoctonear(8000000000000000000000)) + .transact() + .await?; + assert!(register.is_success()); + } + + // Transfer tokens + let transfer_amount = U128(150_000); + + let root_transfer_alice = + ft_transfer(&root, alice.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_alice.is_success()); + + let alice_balance: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance, U128(150_000)); + + let root_transfer_bob = + ft_transfer(&root, bob.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_bob.is_success()); + + let bob_balance: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance, U128(150_000)); + + // Alice makes first bid + let alice_bid = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(50_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(50_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(100_000)); + + // Bob makes second bid + let bob_bid = ft_transfer_call( + bob.clone(), + ft_contract.id(), + contract_account.id(), + U128(60_000), + ) + .await?; + assert!(bob_bid.is_success()); + + let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + + assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!(highest_bid.bidder, *bob.id()); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid but fails + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(60_000)); + assert_eq!(highest_bid_alice.bidder, *bob.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(60_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(100_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(100_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(100_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(50_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(150_000)); + + // Alicia claims the auction but fails + let alice_claim: ExecutionFinalResult = claim(alice.clone(), contract_account.id()).await?; + assert!(alice_claim.is_failure()); + + // Auctioneer claims auction but did not finish + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + // Fast forward + // ~0.3 seconds * 400 = 120 seconds = 2 minutes + let blocks_to_advance = 400; + sandbox.fast_forward(blocks_to_advance).await?; + + // Bob makes a bid but it ends + // this cannot be tested + // let bob_bid = ft_transfer_call(bob.clone(),ft_contract.id(),contract_account.id(),U128(120_000)).await?; + // assert!(bob_bid.is_failure()); + + // Auctioneer claims auction + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + contract.id().to_string(), + "token owner is not first_buyer" + ); + + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + println!("auctioneer_claim outcome: {:#?}", auctioneer_claim); + assert!(auctioneer_claim.is_success()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(0)); + + let auctioneer_balance_after_claim: U128 = ft_balance_of(&ft_contract, auctioneer.id()).await?; + assert_eq!(auctioneer_balance_after_claim, U128(100_000)); + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + alice.id().to_string(), + "token owner is not first_buyer" + ); + + // Auctioneer claims auction back but fails + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + Ok(()) +} + +async fn create_subaccount( + root: &near_workspaces::Account, + name: &str, +) -> Result> { + let subaccount = root + .create_subaccount(name) + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + Ok(subaccount) +} + +async fn ft_transfer( + root: &near_workspaces::Account, + account: Account, + ft_contract: Contract, + transfer_amount: U128, +) -> Result> { + let transfer = root + .call(ft_contract.id(), "ft_transfer") + .args_json(serde_json::json!({ + "receiver_id": account.id(), + "amount": transfer_amount + })) + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + Ok(transfer) +} + +async fn ft_balance_of( + ft_contract: &Contract, + account_id: &AccountId, +) -> Result> { + let result = ft_contract + .view("ft_balance_of") + .args_json(json!({"account_id": account_id})) // Aquí usamos el account_id pasado como argumento + .await? + .json()?; + + Ok(result) +} + +async fn ft_transfer_call( + account: Account, + ft_contract_id: &AccountId, + receiver_id: &AccountId, + amount: U128, +) -> Result> { + let transfer = account + .call(ft_contract_id, "ft_transfer_call") + .args_json(serde_json::json!({ + "receiver_id": receiver_id, "amount":amount, "msg": "0" })) + .deposit(NearToken::from_yoctonear(1)) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(transfer) +} + +async fn claim( + account: Account, + contract_id: &AccountId, +) -> Result> { + let claim: ExecutionFinalResult = account + .call(contract_id, "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(claim) +} diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/Cargo.toml b/contract-rs/04-ft-owner-claims-winner-gets-nft/Cargo.toml new file mode 100644 index 00000000..d704430b --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "contract-rs" +description = "Hello Near Example" +version = "0.1.0" +edition = "2021" +# TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project. +# NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near. +# Link to the repository will be available via `contract_source_metadata` view-function. +#repository = "https://github.com/xxx/xxx" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +near-sdk = { version = "5.1.0", features = ["legacy"] } +chrono = "0.4.38" +tracing = "0.1" +tracing-subscriber = "0.3" + +[dev-dependencies] +near-sdk = { version = "5.1.0", features = ["unit-testing"] } +near-workspaces = { version = "0.10.0", features = ["unstable"] } +tokio = { version = "1.12.0", features = ["full"] } +serde_json = "1.0.117" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" +# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 +overflow-checks = true diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/README.md b/contract-rs/04-ft-owner-claims-winner-gets-nft/README.md new file mode 100644 index 00000000..ebc0f142 --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/README.md @@ -0,0 +1,39 @@ +# contract-rs + +cargo-near-new-project-description + +## How to Build Locally? + +Install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +cargo near build +``` + +## How to Test Locally? + +```bash +cargo test +``` + +## How to Deploy? + +To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: + +```bash +# Create a new account +cargo near create-dev-account + +# Deploy the contract on it +cargo near deploy +``` +## Useful Links + +- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust +- [near CLI](https://near.cli.rs) - Iteract with NEAR blockchain from command line +- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction) +- [NEAR Documentation](https://docs.near.org) +- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol) +- [NEAR Discord](https://near.chat) +- [NEAR Telegram Developers Community Group](https://t.me/neardev) +- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub) diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/rust-toolchain.toml b/contract-rs/04-ft-owner-claims-winner-gets-nft/rust-toolchain.toml new file mode 100644 index 00000000..a82ade34 --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/src/ext.rs b/contract-rs/04-ft-owner-claims-winner-gets-nft/src/ext.rs new file mode 100644 index 00000000..df239f03 --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/src/ext.rs @@ -0,0 +1,16 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::U128; +use near_sdk::{ext_contract, AccountId}; + +use crate::TokenId; + +// Validator interface, for cross-contract calls +#[ext_contract(ft_contract)] +trait FT { + fn ft_transfer(&self, receiver_id: AccountId, amount: U128) -> String; +} + +#[ext_contract(nft_contract)] +trait NFT { + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> String; +} diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/src/lib.rs b/contract-rs/04-ft-owner-claims-winner-gets-nft/src/lib.rs new file mode 100644 index 00000000..1d385126 --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/src/lib.rs @@ -0,0 +1,110 @@ +// Find all our documentation at https://docs.near.org +use near_sdk::json_types::{U128, U64}; +use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault}; + +pub mod ext; +pub use crate::ext::*; + +#[near(serializers = [json, borsh])] +#[derive(Clone)] +pub struct Bid { + pub bidder: AccountId, + pub bid: U128, +} + +pub type TokenId = String; + +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + highest_bid: Bid, + auction_end_time: U64, + auctioneer: AccountId, + auction_was_claimed: bool, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, +} + +#[near] +impl Contract { + #[init] + #[private] // only callable by the contract's account + pub fn init( + end_time: U64, + auctioneer: AccountId, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, + ) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: U128(0), + }, + auction_end_time: end_time, + auctioneer, + auction_was_claimed: false, + ft_contract, + nft_contract, + token_id, + } + } + + pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() + } + + pub fn claim(&mut self) { + assert!( + env::block_timestamp() > self.auction_end_time.into(), + "Auction has not ended yet" + ); + + assert!(!self.auction_was_claimed, "Auction has been claimed"); + + self.auction_was_claimed = true; + let auctioneer = self.auctioneer.clone(); + + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(auctioneer, self.highest_bid.bid); + + nft_contract::ext(self.nft_contract.clone()) + .with_static_gas(Gas::from_tgas(30)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); + } + + pub fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> U128 { + require!( + env::block_timestamp() < self.auction_end_time.into(), + "Auction has ended" + ); + + let ft = env::predecessor_account_id(); + require!(ft == self.ft_contract, "The token is not supported"); + + let Bid { + bidder: last_bidder, + bid: last_bid, + } = self.highest_bid.clone(); + + require!(amount >= last_bid, "You must place a higher bid"); + + self.highest_bid = Bid { + bidder: sender_id, + bid: amount, + }; + + if last_bid > U128(0) { + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(last_bidder, last_bid); + } + + U128(0) + } +} diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/fungible_token.wasm b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/fungible_token.wasm new file mode 100755 index 00000000..40045b57 Binary files /dev/null and b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/fungible_token.wasm differ diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm new file mode 100755 index 00000000..68f4e83f Binary files /dev/null and b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/non_fungible_token.wasm differ diff --git a/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs new file mode 100644 index 00000000..7fa63fd8 --- /dev/null +++ b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs @@ -0,0 +1,351 @@ +use chrono::Utc; +use contract_rs::{Bid, TokenId}; +use near_sdk::{json_types::U128, log, NearToken}; +use near_sdk::{AccountId, Gas}; +use near_workspaces::result::ExecutionFinalResult; +use near_workspaces::{Account, Contract}; +use serde_json::json; + +const FIVE_NEAR: NearToken = NearToken::from_near(5); +const FT_WASM_FILEPATH: &str = "./tests/fungible_token.wasm"; +const NFT_WASM_FILEPATH: &str = "./tests/non_fungible_token.wasm"; + +#[tokio::test] + +async fn test_contract_is_operational() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = near_workspaces::compile_project("./").await?; + + let ft_wasm = std::fs::read(FT_WASM_FILEPATH)?; + let ft_contract = sandbox.dev_deploy(&ft_wasm).await?; + + let nft_wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let nft_contract = sandbox.dev_deploy(&nft_wasm).await?; + + let root: near_workspaces::Account = sandbox.root_account()?; + + // Initialize contracts + let res = ft_contract + .call("new_default_meta") + .args_json(serde_json::json!({ + "owner_id": root.id(), + "total_supply": U128(1_000_000), + })) + .transact() + .await?; + + assert!(res.is_success()); + + let res = nft_contract + .call("new_default_meta") + .args_json(serde_json::json!({"owner_id": root.id()})) + .transact() + .await?; + + assert!(res.is_success()); + + // Create subaccounts + let alice = create_subaccount(&root, "alice").await?; + let auctioneer = create_subaccount(&root, "auctioneer").await?; + let bob = create_subaccount(&root, "bob").await?; + let contract_account = create_subaccount(&root, "contract").await?; + + // Mint NFT + let request_payload = json!({ + "token_id": "1", + "receiver_id": contract_account.id(), + "metadata": { + "title": "LEEROYYYMMMJENKINSSS", + "description": "Alright time's up, let's do this.", + "media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1" + }, + }); + + let res = contract_account + .call(nft_contract.id(), "nft_mint") + .args_json(request_payload) + .deposit(NearToken::from_millinear(80)) + .transact() + .await?; + assert!(res.is_success()); + + // Deploy and initialize contract + let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); + + let now = Utc::now().timestamp(); + let a_minute_from_now = (now + 60) * 1000000000; + log!("a_minute_from_now: {}", a_minute_from_now); + + let init: ExecutionFinalResult = contract + .call("init") + .args_json( + json!({"end_time": a_minute_from_now.to_string(),"auctioneer": auctioneer.id(),"ft_contract": ft_contract.id(),"nft_contract":nft_contract.id(),"token_id":"1" }), + ) + .transact() + .await?; + + assert!(init.is_success()); + + // Register accounts + // number magic 8000000000000000000000 + for account in [ + alice.clone(), + bob.clone(), + contract_account.clone(), + auctioneer.clone(), + ].iter() + { + let register = account + .call(ft_contract.id(), "storage_deposit") + .args_json(serde_json::json!({ "account_id": account.id() })) + .deposit(NearToken::from_yoctonear(8000000000000000000000)) + .transact() + .await?; + assert!(register.is_success()); + } + + // Transfer tokens + let transfer_amount = U128(150_000); + + let root_transfer_alice = + ft_transfer(&root, alice.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_alice.is_success()); + + let alice_balance: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance, U128(150_000)); + + let root_transfer_bob = + ft_transfer(&root, bob.clone(), ft_contract.clone(), transfer_amount).await?; + assert!(root_transfer_bob.is_success()); + + let bob_balance: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance, U128(150_000)); + + // Alice makes first bid + let alice_bid = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(50_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(50_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(100_000)); + + // Bob makes second bid + let bob_bid = ft_transfer_call( + bob.clone(), + ft_contract.id(), + contract_account.id(), + U128(60_000), + ) + .await?; + assert!(bob_bid.is_success()); + + let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + + assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!(highest_bid.bidder, *bob.id()); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid but fails + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(50_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(60_000)); + assert_eq!(highest_bid_alice.bidder, *bob.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(60_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(150_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(90_000)); + + // Alice makes the third bid + let alice_bid: ExecutionFinalResult = ft_transfer_call( + alice.clone(), + ft_contract.id(), + contract_account.id(), + U128(100_000), + ) + .await?; + assert!(alice_bid.is_success()); + + let highest_bid_alice: Bid = contract.view("get_highest_bid").await?.json()?; + assert_eq!(highest_bid_alice.bid, U128(100_000)); + assert_eq!(highest_bid_alice.bidder, *alice.id()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(100_000)); + + let alice_balance_after_bid: U128 = ft_balance_of(&ft_contract, alice.id()).await?; + assert_eq!(alice_balance_after_bid, U128(50_000)); + let bob_balance_after_bid: U128 = ft_balance_of(&ft_contract, bob.id()).await?; + assert_eq!(bob_balance_after_bid, U128(150_000)); + + // Alicia claims the auction but fails + let alice_claim: ExecutionFinalResult = claim(alice.clone(), contract_account.id()).await?; + assert!(alice_claim.is_failure()); + + // Auctioneer claims auction but did not finish + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + // Fast forward + // ~0.3 seconds * 400 = 120 seconds = 2 minutes + let blocks_to_advance = 400; + sandbox.fast_forward(blocks_to_advance).await?; + + // Bob makes a bid but it ends + // this cannot be tested + // let bob_bid = ft_transfer_call(bob.clone(),ft_contract.id(),contract_account.id(),U128(120_000)).await?; + // assert!(bob_bid.is_failure()); + + // Auctioneer claims auction + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + contract.id().to_string(), + "token owner is not first_buyer" + ); + + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + println!("auctioneer_claim outcome: {:#?}", auctioneer_claim); + assert!(auctioneer_claim.is_success()); + + let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; + assert_eq!(contract_account_balance, U128(0)); + + let auctioneer_balance_after_claim: U128 = ft_balance_of(&ft_contract, auctioneer.id()).await?; + assert_eq!(auctioneer_balance_after_claim, U128(100_000)); + + let token_info: serde_json::Value = nft_contract + .call("nft_token") + .args_json(json!({"token_id": "1"})) + .transact() + .await? + .json() + .unwrap(); + let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string(); + + assert_eq!( + owner_id, + alice.id().to_string(), + "token owner is not first_buyer" + ); + + // Auctioneer claims auction back but fails + let auctioneer_claim: ExecutionFinalResult = + claim(auctioneer.clone(), contract_account.id()).await?; + assert!(auctioneer_claim.is_failure()); + + Ok(()) +} + +async fn create_subaccount( + root: &near_workspaces::Account, + name: &str, +) -> Result> { + let subaccount = root + .create_subaccount(name) + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + Ok(subaccount) +} + +async fn ft_transfer( + root: &near_workspaces::Account, + account: Account, + ft_contract: Contract, + transfer_amount: U128, +) -> Result> { + let transfer = root + .call(ft_contract.id(), "ft_transfer") + .args_json(serde_json::json!({ + "receiver_id": account.id(), + "amount": transfer_amount + })) + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + Ok(transfer) +} + +async fn ft_balance_of( + ft_contract: &Contract, + account_id: &AccountId, +) -> Result> { + let result = ft_contract + .view("ft_balance_of") + .args_json(json!({"account_id": account_id})) // Aquí usamos el account_id pasado como argumento + .await? + .json()?; + + Ok(result) +} + +async fn ft_transfer_call( + account: Account, + ft_contract_id: &AccountId, + receiver_id: &AccountId, + amount: U128, +) -> Result> { + let transfer = account + .call(ft_contract_id, "ft_transfer_call") + .args_json(serde_json::json!({ + "receiver_id": receiver_id, "amount":amount, "msg": "0" })) + .deposit(NearToken::from_yoctonear(1)) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(transfer) +} + +async fn claim( + account: Account, + contract_id: &AccountId, +) -> Result> { + let claim: ExecutionFinalResult = account + .call(contract_id, "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; + Ok(claim) +} diff --git a/contract-ts/src/contract.ts b/contract-ts/src/contract.ts index b3977e89..95169eee 100644 --- a/contract-ts/src/contract.ts +++ b/contract-ts/src/contract.ts @@ -7,7 +7,7 @@ class Bid { bid: bigint; } -const FIVE_TGAS = BigInt("5000000000000"); +const THIRTY_TGAS = BigInt("30000000000000"); const NO_DEPOSIT = BigInt(0); const NO_ARGS = JSON.stringify({}); @@ -87,10 +87,10 @@ class AuctionContract { near.log("inside bid"); // this.ft_transfer(this.highest_bid.bidder, this.highest_bid.bid) return NearPromise.new(this.ft_contract) - .functionCall("ft_transfer", JSON.stringify({ receiver_id: previous.bidder, amount: previous.bid }), BigInt(1), BigInt("30000000000000")) + .functionCall("ft_transfer", JSON.stringify({ receiver_id: previous.bidder, amount: previous.bid }), BigInt(1), THIRTY_TGAS) .then( NearPromise.new(near.currentAccountId()) - .functionCall("ft_transfer_callback", JSON.stringify({}), NO_DEPOSIT, BigInt("30000000000000")) + .functionCall("ft_transfer_callback", JSON.stringify({}), NO_DEPOSIT, THIRTY_TGAS) ) .asReturn() } else { diff --git a/contract-ts1/README.md b/contract-ts1/README.md deleted file mode 100644 index d9c8d038..00000000 --- a/contract-ts1/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Auction NEAR Contract - -The smart contract exposes a method to bid on an auction. - -```ts -@NearBindgen({}) -class AuctionContract { - highest_bid: Bid = { bidder: '', bid: BigInt(1) }; - auctionEndTime: bigint = BigInt(0); - - @initialize({ privateFunction: true }) - init({ end_time }: { end_time: bigint }) { - this.auctionEndTime = end_time; - this.highest_bid = { bidder: near.currentAccountId(), bid: BigInt(1) }; - } - - @call({ payableFunction: true }) - bid(): NearPromise { - // Assert the auction is still ongoing - assert(this.auctionEndTime > near.blockTimestamp(), "Auction has ended"); - - // Current bid - const bid = near.attachedDeposit(); - const bidder = near.predecessorAccountId(); - - // Last bid - const { bidder: lastBidder, bid: lastBid } = this.highest_bid; - - // Check if the deposit is higher than the current bid - assert(bid > lastBid, "You must place a higher bid"); - - // Update the highest bid - this.highest_bid = { bidder, bid }; // Save the new bid - - // Transfer tokens back to the last bidder - return NearPromise.new(lastBidder).transfer(lastBid); - } -} -``` - -
- -# Quickstart - -1. Make sure you have installed [node.js](https://nodejs.org/en/download/package-manager/) >= 18. -2. Install the [`NEAR CLI`](https://github.com/near/near-cli#setup) - -
- -## 1. Build and Test the Contract -You can automatically compile and test the contract by running: - -```bash -npm run build -``` - -
- -## 2. Create an Account and Deploy the Contract -You can create a new account and deploy the contract by running: - -```bash -near create-account --useFaucet -near deploy build/release/hello_near.wasm -``` - -
- -## 3. Interact with the Contract -You can call the contract by running: - -```bash -near call bid '{}' --accountId --amount 1 - -near view get_highest_bid -``` - - diff --git a/contract-ts1/ava.config.cjs b/contract-ts1/ava.config.cjs deleted file mode 100644 index 01dd0188..00000000 --- a/contract-ts1/ava.config.cjs +++ /dev/null @@ -1,15 +0,0 @@ -require('util').inspect.defaultOptions.depth = 5; // Increase AVA's printing depth - -module.exports = { - timeout: '20000', - files: ['sandbox-ts/*.ava.ts'], - failWithoutAssertions: false, - extensions: { - js: true, - ts: 'module' - }, - require: ['ts-node/register', 'near-workspaces'], - "nodeArguments": [ - "--import=tsimp" - ] -}; \ No newline at end of file diff --git a/contract-ts1/package.json b/contract-ts1/package.json deleted file mode 100644 index 1b7cf271..00000000 --- a/contract-ts1/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "auction", - "version": "1.0.0", - "license": "(MIT AND Apache-2.0)", - "type": "module", - "scripts": { - "build": "near-sdk-js build src/contract.ts build/auction.wasm", - "test": "$npm_execpath run build && ava -- ./build/auction.wasm" - }, - "dependencies": { - "near-cli": "^4.0.13", - "near-sdk-js": "1.0.0" - }, - "devDependencies": { - "@ava/typescript": "^4.1.0", - "ava": "^6.1.2", - "near-workspaces": "^3.5.0", - "ts-morph": "^21.0.1", - "ts-node": "^10.9.2", - "tsimp": "^2.0.11", - "typescript": "^5.3.3" - } -} diff --git a/contract-ts1/sandbox-ts/main.ava.ts b/contract-ts1/sandbox-ts/main.ava.ts deleted file mode 100644 index 66ea054e..00000000 --- a/contract-ts1/sandbox-ts/main.ava.ts +++ /dev/null @@ -1,126 +0,0 @@ -import anyTest, { TestFn } from "ava"; -import { Worker, NearAccount, NEAR } from "near-workspaces"; -import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); // temp fix for node >v17 - -// Global context -const test = anyTest as TestFn<{ worker: Worker, accounts: Record }>; - -interface Bid { - bidder: string; - bid: string; -} - -test.beforeEach(async (t) => { - // Init the worker and start a Sandbox server - const worker = t.context.worker = await Worker.init(); - - // Create accounts - const root = worker.rootAccount; - - const alice = await root.createSubAccount("alice"); - const bob = await root.createSubAccount("bob"); - const contract = await root.createSubAccount("contract"); - const auctioneer = await root.createSubAccount("auctioneer"); - - // Deploy contract (input from package.json) - await contract.deploy(process.argv[2]); - - // Initialize contract, finishes in 1 minute - await contract.call(contract, "init", { - end_time: String((Date.now() + 60000) * 10 ** 6), - auctioneer: auctioneer.accountId, - }); - - // Save state for test runs, it is unique for each test - t.context.worker = worker; - t.context.accounts = { alice, bob, contract, auctioneer }; -}); - -test.afterEach.always(async (t) => { - // Stop Sandbox server - await t.context.worker.tearDown().catch((error) => { - console.log('Failed to stop the Sandbox:', error); - }); -}); - -test("Bids are placed", async (t) => { - const { alice, contract } = t.context.accounts; - - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - - const highest_bid: Bid = await contract.view("get_highest_bid", {}); - - t.is(highest_bid.bidder, alice.accountId); - t.is(highest_bid.bid, NEAR.parse("1 N").toString()); -}); - -test("Outbid returns previous bid", async (t) => { - const { alice, bob, contract } = t.context.accounts; - - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - const aliceBalance = await alice.balance(); - - await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString() }); - const highest_bid: Bid = await contract.view("get_highest_bid", {}); - t.is(highest_bid.bidder, bob.accountId); - t.is(highest_bid.bid, NEAR.parse("2 N").toString()); - - // we returned the money to alice - const aliceNewBalance = await alice.balance(); - t.deepEqual(aliceNewBalance.available, aliceBalance.available.add(NEAR.parse("1 N"))); -}); - -test("Auction closes", async (t) => { - const { alice, bob, contract } = t.context.accounts; - - // alice can bid - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - - // fast forward approx a minute - await t.context.worker.provider.fastForward(60) - - // alice cannot bid anymore - await t.throwsAsync(alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() })) -}); - -test("Claim auction", async (t) => { - const { alice, bob, contract, auctioneer} = t.context.accounts; - - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString() }); - const auctioneerBalance = await auctioneer.balance(); - const available = parseFloat(auctioneerBalance.available.toHuman()); - - // fast forward approx a minute - await t.context.worker.provider.fastForward(60) - - await auctioneer.call(contract, "auction_end",{}); - - const contractNewBalance = await auctioneer.balance(); - const new_available = parseFloat(contractNewBalance.available.toHuman()); - - t.is(new_available.toFixed(2), (available + 2).toFixed(2)); -}); - -test("Auction open", async (t) => { - const { alice, bob, contract, auctioneer} = t.context.accounts; - - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString() }); - - await t.throwsAsync(auctioneer.call(contract, "auction_end",{})) -}); - -test("Auction has been claimed", async (t) => { - const { alice, bob, contract, auctioneer} = t.context.accounts; - - await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); - await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString() }); - - // fast forward approx a minute - await t.context.worker.provider.fastForward(60) - - await auctioneer.call(contract, "auction_end",{}); - - await t.throwsAsync(auctioneer.call(contract, "auction_end",{})) -}); \ No newline at end of file diff --git a/contract-ts1/src/contract.ts b/contract-ts1/src/contract.ts deleted file mode 100644 index 243b7070..00000000 --- a/contract-ts1/src/contract.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Find all our documentation at https://docs.near.org -import { NearBindgen, near, call, view, AccountId, NearPromise, initialize, assert } from "near-sdk-js"; - -class Bid { - bidder: AccountId; - bid: bigint; -} - -@NearBindgen({ requireInit: true }) -class AuctionContract { - highest_bid: Bid = { bidder: '', bid: BigInt(1) }; - auction_end_time: bigint = BigInt(0); - auctioneer: string = ""; - auction_was_claimed: boolean = false; - - @initialize({ privateFunction: true }) - init({ end_time, auctioneer }: { end_time: bigint, auctioneer: string }) { - this.auction_end_time = end_time; - this.highest_bid = { bidder: near.currentAccountId(), bid: BigInt(1) }; - this.auctioneer = auctioneer; - } - - @call({ payableFunction: true }) - bid(): NearPromise { - // Assert the auction is still ongoing - assert(this.auction_end_time > near.blockTimestamp(), "Auction has ended"); - - // Current bid - const bid = near.attachedDeposit(); - const bidder = near.predecessorAccountId(); - - // Last bid - const { bidder: lastBidder, bid: lastBid } = this.highest_bid; - - // Check if the deposit is higher than the current bid - assert(bid > lastBid, "You must place a higher bid"); - - // Update the highest bid - this.highest_bid = { bidder, bid }; // Save the new bid - - // Transfer tokens back to the last bidder - return NearPromise.new(lastBidder).transfer(lastBid); - } - - @view({}) - get_highest_bid(): Bid { - return this.highest_bid; - } - - @view({}) - get_auction_end_time(): BigInt { - return this.auction_end_time; - } - - @call({}) - claim() { - assert(near.predecessorAccountId() == this.auctioneer, "Only auctioneer can end the auction"); - assert(this.auction_end_time <= near.blockTimestamp(), "Auction has not ended yet"); - assert(!this.auction_was_claimed, "Auction has been claimed"); - - this.auction_was_claimed = true; - return NearPromise.new(this.auctioneer).transfer(this.highest_bid.bid); - } -} \ No newline at end of file diff --git a/contract-ts1/tsconfig.json b/contract-ts1/tsconfig.json deleted file mode 100644 index 591eddeb..00000000 --- a/contract-ts1/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "experimentalDecorators": true, - "target": "ES5", - "noEmit": true, - "noImplicitAny": false, - "lib": ["esnext", "esnext.asynciterable"] - }, - "files": [ - "sandbox-ts/main.ava.ts", - "src/contract.ts" - ], - "exclude": [ - "node_modules" - ], -} \ No newline at end of file diff --git a/help.txt b/help.txt new file mode 100644 index 00000000..f1aab107 --- /dev/null +++ b/help.txt @@ -0,0 +1,4 @@ +basic-auction +owner-claims-money +owner-claims-winner-gets-nft +ft-owner-claims-winner-gets-nft \ No newline at end of file