diff --git a/contract-rs/01-basic-auction/src/ext.rs b/contract-rs/01-basic-auction/src/ext.rs deleted file mode 100644 index df239f03..00000000 --- a/contract-rs/01-basic-auction/src/ext.rs +++ /dev/null @@ -1,16 +0,0 @@ -// 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/01-basic-auction/src/lib.rs b/contract-rs/01-basic-auction/src/lib.rs index 1d385126..567e7df4 100644 --- a/contract-rs/01-basic-auction/src/lib.rs +++ b/contract-rs/01-basic-auction/src/lib.rs @@ -1,110 +1,89 @@ // 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::*; +use near_sdk::json_types::U64; +use near_sdk::{env, near,require, AccountId, NearToken, PanicOnDefault, Promise}; #[near(serializers = [json, borsh])] #[derive(Clone)] pub struct Bid { pub bidder: AccountId, - pub bid: U128, + pub bid: NearToken, } -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 { + pub fn init(end_time: U64) -> Self { Self { highest_bid: Bid { bidder: env::current_account_id(), - bid: U128(0), + bid: NearToken::from_yoctonear(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 { + #[payable] + pub fn bid(&mut self) -> Promise { + // Assert the auction is still ongoing 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"); + // current bid + let bid = env::attached_deposit(); + let bidder = env::predecessor_account_id(); + // last bid let Bid { bidder: last_bidder, bid: last_bid, } = self.highest_bid.clone(); - require!(amount >= last_bid, "You must place a higher bid"); + // Check if the deposit is higher than the current bid + require!(bid > last_bid, "You must place a higher bid"); - self.highest_bid = Bid { - bidder: sender_id, - bid: amount, - }; + // Update the highest bid + self.highest_bid = Bid { bidder, bid }; - 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); - } + // Transfer tokens back to the last bidder + Promise::new(last_bidder).transfer(last_bid) + } + + pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() + } - U128(0) + pub fn get_auction_end_time(&self) -> U64 { + self.auction_end_time } } + +/* + * The rest of this file holds the inline tests for the code above + * Learn more about Rust tests: https://doc.rust-lang.org/book/ch11-01-writing-tests.html + */ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init_contract() { + let contract = Contract::init(U64::from(1000)); + + let default_bid = contract.get_highest_bid(); + assert_eq!(default_bid.bidder, env::current_account_id()); + assert_eq!(default_bid.bid, NearToken::from_yoctonear(1)); + + let end_time = contract.get_auction_end_time(); + assert_eq!(end_time, U64::from(1000)); + } +} \ No newline at end of file diff --git a/contract-rs/01-basic-auction/tests/test_basics.rs b/contract-rs/01-basic-auction/tests/test_basics.rs index 7fa63fd8..f643ff17 100644 --- a/contract-rs/01-basic-auction/tests/test_basics.rs +++ b/contract-rs/01-basic-auction/tests/test_basics.rs @@ -1,73 +1,37 @@ 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 contract_rs::Bid; +use near_sdk::{log, NearToken}; 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()?; + let root = 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), - })) + let alice = root + .create_subaccount("alice") + .initial_balance(FIVE_NEAR) .transact() - .await?; - - assert!(res.is_success()); + .await? + .unwrap(); - let res = nft_contract - .call("new_default_meta") - .args_json(serde_json::json!({"owner_id": root.id()})) + let bob = root + .create_subaccount("bob") + .initial_balance(FIVE_NEAR) .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" - }, - }); + .await? + .unwrap(); - let res = contract_account - .call(nft_contract.id(), "nft_mint") - .args_json(request_payload) - .deposit(NearToken::from_millinear(80)) + let contract_account = root + .create_subaccount("contract") + .initial_balance(FIVE_NEAR) .transact() - .await?; - assert!(res.is_success()); + .await? + .unwrap(); // Deploy and initialize contract let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); @@ -76,276 +40,68 @@ async fn test_contract_is_operational() -> Result<(), Box let a_minute_from_now = (now + 60) * 1000000000; log!("a_minute_from_now: {}", a_minute_from_now); - let init: ExecutionFinalResult = contract + let init = 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" }), - ) + .args_json(json!({"end_time": a_minute_from_now.to_string()})) .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?; + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() + .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 highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; - assert_eq!(contract_account_balance, U128(50_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(1) + ); + assert_eq!(highest_bid.bidder, *alice.id()); - 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), - ) + let bob_bid = bob + .call(contract.id(), "bid") + .deposit(NearToken::from_near(2)) + .transact() .await?; + assert!(bob_bid.is_success()); - let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(2) + ); 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), - ) + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() .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?; + assert!(alice_bid.is_failure()); - // 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(); + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; assert_eq!( - owner_id, - alice.id().to_string(), - "token owner is not first_buyer" + highest_bid.bid, + NearToken::from_near(2) ); - - // Auctioneer claims auction back but fails - let auctioneer_claim: ExecutionFinalResult = - claim(auctioneer.clone(), contract_account.id()).await?; - assert!(auctioneer_claim.is_failure()); + assert_eq!(highest_bid.bidder, *bob.id()); 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) -} +} \ No newline at end of file diff --git a/contract-rs/02-owner-claims-money/src/ext.rs b/contract-rs/02-owner-claims-money/src/ext.rs deleted file mode 100644 index df239f03..00000000 --- a/contract-rs/02-owner-claims-money/src/ext.rs +++ /dev/null @@ -1,16 +0,0 @@ -// 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 index 1d385126..6ebda18c 100644 --- a/contract-rs/02-owner-claims-money/src/lib.rs +++ b/contract-rs/02-owner-claims-money/src/lib.rs @@ -1,19 +1,14 @@ // 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::*; +use near_sdk::json_types::U64; +use near_sdk::{env, near, require, AccountId, NearToken, PanicOnDefault, Promise}; #[near(serializers = [json, borsh])] #[derive(Clone)] pub struct Bid { pub bidder: AccountId, - pub bid: U128, + pub bid: NearToken, } -pub type TokenId = String; - #[near(contract_state)] #[derive(PanicOnDefault)] pub struct Contract { @@ -21,90 +16,69 @@ pub struct Contract { 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 { + pub fn init(end_time: U64, auctioneer: AccountId) -> Self { Self { highest_bid: Bid { bidder: env::current_account_id(), - bid: U128(0), + bid: NearToken::from_yoctonear(0), }, auction_end_time: end_time, - auctioneer, auction_was_claimed: false, - ft_contract, - nft_contract, - token_id, + auctioneer, } } - 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 { + #[payable] + pub fn bid(&mut self) -> Promise { + // Assert the auction is still ongoing 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"); + // current bid + let bid = env::attached_deposit(); + let bidder = env::predecessor_account_id(); + // last bid let Bid { bidder: last_bidder, bid: last_bid, } = self.highest_bid.clone(); - require!(amount >= last_bid, "You must place a higher bid"); + // Check if the deposit is higher than the current bid + require!(bid > last_bid, "You must place a higher bid"); - self.highest_bid = Bid { - bidder: sender_id, - bid: amount, - }; + // Update the highest bid + self.highest_bid = Bid { bidder, bid }; - 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); - } + // Transfer tokens back to the last bidder + Promise::new(last_bidder).transfer(last_bid) + } + + pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() + } - U128(0) + pub fn get_auction_end_time(&self) -> U64 { + self.auction_end_time + } + + pub fn claim(&mut self) -> Promise { + require!( + env::block_timestamp() > self.auction_end_time.into(), + "Auction has ended" + ); + + require!(!self.auction_was_claimed, "Auction has been claimed"); + self.auction_was_claimed = true; + let auctioneer = self.auctioneer.clone(); + Promise::new(auctioneer).transfer(self.highest_bid.bid) } } diff --git a/contract-rs/02-owner-claims-money/tests/fungible_token.wasm b/contract-rs/02-owner-claims-money/tests/fungible_token.wasm deleted file mode 100755 index 40045b57..00000000 Binary files a/contract-rs/02-owner-claims-money/tests/fungible_token.wasm and /dev/null 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 index 7fa63fd8..007e62d8 100644 --- a/contract-rs/02-owner-claims-money/tests/test_basics.rs +++ b/contract-rs/02-owner-claims-money/tests/test_basics.rs @@ -1,73 +1,44 @@ 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 contract_rs::Bid; +use near_sdk::{log, NearToken, Gas}; 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()?; + let root = 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), - })) + let alice = root + .create_subaccount("alice") + .initial_balance(FIVE_NEAR) .transact() - .await?; - - assert!(res.is_success()); + .await? + .unwrap(); - let res = nft_contract - .call("new_default_meta") - .args_json(serde_json::json!({"owner_id": root.id()})) + let bob = root + .create_subaccount("bob") + .initial_balance(FIVE_NEAR) .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?; + .await? + .unwrap(); - // 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 auctioneer = root + .create_subaccount("auctioneer") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); - let res = contract_account - .call(nft_contract.id(), "nft_mint") - .args_json(request_payload) - .deposit(NearToken::from_millinear(80)) + let contract_account = root + .create_subaccount("contract") + .initial_balance(FIVE_NEAR) .transact() - .await?; - assert!(res.is_success()); + .await? + .unwrap(); // Deploy and initialize contract let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); @@ -76,142 +47,76 @@ async fn test_contract_is_operational() -> Result<(), Box let a_minute_from_now = (now + 60) * 1000000000; log!("a_minute_from_now: {}", a_minute_from_now); - let init: ExecutionFinalResult = contract + let init = 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" }), - ) + .args_json(json!({"end_time": a_minute_from_now.to_string(),"auctioneer":auctioneer.id()})) .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?; + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() + .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 highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; - assert_eq!(contract_account_balance, U128(50_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(1) + ); + assert_eq!(highest_bid.bidder, *alice.id()); - 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), - ) + let bob_bid = bob + .call(contract.id(), "bid") + .deposit(NearToken::from_near(2)) + .transact() .await?; + assert!(bob_bid.is_success()); - let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(2) + ); 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), - ) + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() .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)); + assert!(alice_bid.is_failure()); - 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)); + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - // Alicia claims the auction but fails - let alice_claim: ExecutionFinalResult = claim(alice.clone(), contract_account.id()).await?; - assert!(alice_claim.is_failure()); + assert_eq!( + highest_bid.bid, + NearToken::from_near(2) + ); + assert_eq!(highest_bid.bidder, *bob.id()); // Auctioneer claims auction but did not finish - let auctioneer_claim: ExecutionFinalResult = - claim(auctioneer.clone(), contract_account.id()).await?; + let auctioneer_claim = auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; assert!(auctioneer_claim.is_failure()); // Fast forward @@ -219,133 +124,24 @@ async fn test_contract_is_operational() -> Result<(), Box 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 = auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; - 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?; + let auctioneer_claim =auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .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) -} +} \ No newline at end of file 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 index df239f03..c204b225 100644 --- a/contract-rs/03-owner-claims-winner-gets-nft/src/ext.rs +++ b/contract-rs/03-owner-claims-winner-gets-nft/src/ext.rs @@ -5,11 +5,6 @@ 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 index 1d385126..1d66750e 100644 --- a/contract-rs/03-owner-claims-winner-gets-nft/src/lib.rs +++ b/contract-rs/03-owner-claims-winner-gets-nft/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}; +use near_sdk::json_types::{U64}; +use near_sdk::{env, near, require, AccountId, Gas, NearToken, PanicOnDefault,Promise}; pub mod ext; pub use crate::ext::*; @@ -9,7 +9,7 @@ pub use crate::ext::*; #[derive(Clone)] pub struct Bid { pub bidder: AccountId, - pub bid: U128, + pub bid: NearToken, } pub type TokenId = String; @@ -21,7 +21,6 @@ pub struct Contract { auction_end_time: U64, auctioneer: AccountId, auction_was_claimed: bool, - ft_contract: AccountId, nft_contract: AccountId, token_id: TokenId, } @@ -33,19 +32,17 @@ impl Contract { 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), + bid: NearToken::from_yoctonear(0), }, auction_end_time: end_time, auctioneer, auction_was_claimed: false, - ft_contract, nft_contract, token_id, } @@ -55,6 +52,34 @@ impl Contract { self.highest_bid.clone() } + #[payable] + pub fn bid(&mut self) -> Promise { + // Assert the auction is still ongoing + require!( + env::block_timestamp() < self.auction_end_time.into(), + "Auction has ended" + ); + + // current bid + let bid = env::attached_deposit(); + let bidder = env::predecessor_account_id(); + + // last bid + let Bid { + bidder: last_bidder, + bid: last_bid, + } = self.highest_bid.clone(); + + // Check if the deposit is higher than the current bid + require!(bid > last_bid, "You must place a higher bid"); + + // Update the highest bid + self.highest_bid = Bid { bidder, bid }; + + // Transfer tokens back to the last bidder + Promise::new(last_bidder).transfer(last_bid) + } + pub fn claim(&mut self) { assert!( env::block_timestamp() > self.auction_end_time.into(), @@ -66,45 +91,11 @@ impl Contract { 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); + Promise::new(auctioneer).transfer(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 deleted file mode 100755 index 40045b57..00000000 Binary files a/contract-rs/03-owner-claims-winner-gets-nft/tests/fungible_token.wasm and /dev/null 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 index 7fa63fd8..8c8b2ab6 100644 --- 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 @@ -7,7 +7,6 @@ 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] @@ -16,26 +15,12 @@ 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()})) @@ -45,10 +30,33 @@ async fn test_contract_is_operational() -> Result<(), Box 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?; + let alice = root + .create_subaccount("alice") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + let bob = root + .create_subaccount("bob") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + let auctioneer = root + .create_subaccount("auctioneer") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); + + let contract_account = root + .create_subaccount("contract") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); // Mint NFT let request_payload = json!({ @@ -79,139 +87,75 @@ async fn test_contract_is_operational() -> Result<(), Box 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" }), + json!({"end_time": a_minute_from_now.to_string(),"auctioneer": auctioneer.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), - ) + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() .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 highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; - assert_eq!(contract_account_balance, U128(50_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(1) + ); + assert_eq!(highest_bid.bidder, *alice.id()); - 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), - ) + let bob_bid = bob + .call(contract.id(), "bid") + .deposit(NearToken::from_near(2)) + .transact() .await?; + assert!(bob_bid.is_success()); - let highest_bid: Bid = contract.view("get_highest_bid").await?.json()?; + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - assert_eq!(highest_bid.bid, U128(60_000)); + assert_eq!( + highest_bid.bid, + NearToken::from_near(2) + ); 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), - ) + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() .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()); + assert!(alice_bid.is_failure()); - let contract_account_balance: U128 = ft_balance_of(&ft_contract, contract_account.id()).await?; - assert_eq!(contract_account_balance, U128(100_000)); + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; - 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()); + assert_eq!( + highest_bid.bid, + NearToken::from_near(2) + ); + assert_eq!(highest_bid.bidder, *bob.id()); // Auctioneer claims auction but did not finish - let auctioneer_claim: ExecutionFinalResult = - claim(auctioneer.clone(), contract_account.id()).await?; + let auctioneer_claim = auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; assert!(auctioneer_claim.is_failure()); // Fast forward @@ -219,133 +163,25 @@ async fn test_contract_is_operational() -> Result<(), Box 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 = auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .await?; - 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?; + let auctioneer_claim =auctioneer + .call(contract_account.id(), "claim") + .args_json(json!({})) + .gas(Gas::from_tgas(300)) + .transact() + .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/tests/test_basics.rs b/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs index 7fa63fd8..2c0d0e28 100644 --- 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 @@ -87,7 +87,6 @@ async fn test_contract_is_operational() -> Result<(), Box assert!(init.is_success()); // Register accounts - // number magic 8000000000000000000000 for account in [ alice.clone(), bob.clone(), diff --git a/contract-ts/README.md b/contract-ts/01-basic-auction/README.md similarity index 100% rename from contract-ts/README.md rename to contract-ts/01-basic-auction/README.md diff --git a/contract-ts/package.json b/contract-ts/01-basic-auction/package.json similarity index 100% rename from contract-ts/package.json rename to contract-ts/01-basic-auction/package.json diff --git a/contract-ts/01-basic-auction/sandbox-test/main.ava.js b/contract-ts/01-basic-auction/sandbox-test/main.ava.js new file mode 100644 index 00000000..4a2d22be --- /dev/null +++ b/contract-ts/01-basic-auction/sandbox-test/main.ava.js @@ -0,0 +1,87 @@ +import anyTest from 'ava'; +import { NEAR, Worker } from 'near-workspaces'; +import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); // temp fix for node >v17 + +/** + * @typedef {import('near-workspaces').NearAccount} NearAccount + * @type {import('ava').TestFn<{worker: Worker, accounts: Record}>} + */ +const test = anyTest; +test.beforeEach(async (t) => { + console.log("Init the worker and start a Sandbox server") + // Init the worker and start a Sandbox server + const worker = t.context.worker = await Worker.init(); + + // Create accounts + console.log("Create accounts"); + const root = worker.rootAccount; + + const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("50 N").toString() }); + const bob = await root.createSubAccount("bob", { initialBalance: NEAR.parse("50 N").toString() }); + const contract = await root.createSubAccount("contract", { initialBalance: NEAR.parse("50 N").toString() }); + + // Deploy contract (input from package.json) + console.log("Deploy contract (input from package.json)"); + await contract.deploy(process.argv[2]); + + // Initialize contract, finishes in 1 minute + console.log("Initialize contract, finishes in 1 minute"); + await contract.call(contract, "init", { + end_time: String((Date.now() + 60000) * 10 ** 6), + }); + + // Save state for test runs, it is unique for each test + t.context.worker = worker; + t.context.accounts = { alice, bob, contract}; +}); + +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) => { + console.log("Bids are placed"); + const { alice, contract } = t.context.accounts; + + await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); + + const highest_bid = await contract.view("get_highest_bid", {}); + + t.is(highest_bid.bidder, alice.accountId); + t.is(highest_bid.bid, NEAR.parse("1 N").toString()); + console.log("Bids are placed finished"); + +}); + +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 = 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, 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() })) +}); + diff --git a/contract-ts/01-basic-auction/src/contract.ts b/contract-ts/01-basic-auction/src/contract.ts new file mode 100644 index 00000000..03a5792c --- /dev/null +++ b/contract-ts/01-basic-auction/src/contract.ts @@ -0,0 +1,53 @@ +// 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(0) }; + auction_end_time: bigint = BigInt(0); + + + @initialize({ privateFunction: true }) + init({ end_time}: { end_time: bigint}) { + this.auction_end_time = end_time; + this.highest_bid = { bidder: near.currentAccountId(), bid: BigInt(0) }; + + } + + @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; + } +} \ No newline at end of file diff --git a/contract-ts/tsconfig.json b/contract-ts/01-basic-auction/tsconfig.json similarity index 100% rename from contract-ts/tsconfig.json rename to contract-ts/01-basic-auction/tsconfig.json diff --git a/contract-ts/02-owner-claims-money/README.md b/contract-ts/02-owner-claims-money/README.md new file mode 100644 index 00000000..43242c2a --- /dev/null +++ b/contract-ts/02-owner-claims-money/README.md @@ -0,0 +1,83 @@ +# Hello NEAR Contract + +The smart contract exposes two methods to enable storing and retrieving a greeting in the NEAR network. + +```ts +@NearBindgen({}) +class HelloNear { + greeting: string = "Hello"; + + @view // This method is read-only and can be called for free + get_greeting(): string { + return this.greeting; + } + + @call // This method changes the state, for which it cost gas + set_greeting({ greeting }: { greeting: string }): void { + // Record a log permanently to the blockchain! + near.log(`Saving greeting ${greeting}`); + this.greeting = greeting; + } +} +``` + +
+ +# Quickstart + +1. Make sure you have installed [node.js](https://nodejs.org/en/download/package-manager/) >= 16. +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. Retrieve the Greeting + +`get_greeting` is a read-only method (aka `view` method). + +`View` methods can be called for **free** by anyone, even people **without a NEAR account**! + +```bash +# Use near-cli to get the greeting +near view get_greeting +``` + +
+ +## 4. Store a New Greeting +`set_greeting` changes the contract's state, for which it is a `call` method. + +`Call` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. + +```bash +# Use near-cli to set a new greeting +near call set_greeting '{"greeting":"howdy"}' --accountId +``` + +**Tip:** If you would like to call `set_greeting` using another account, first login into NEAR using: + +```bash +# Use near-cli to login your NEAR account +near login +``` + +and then use the logged account to sign the transaction: `--accountId `. \ No newline at end of file diff --git a/contract-ts/02-owner-claims-money/package.json b/contract-ts/02-owner-claims-money/package.json new file mode 100644 index 00000000..784b9f91 --- /dev/null +++ b/contract-ts/02-owner-claims-money/package.json @@ -0,0 +1,22 @@ +{ + "name": "hello_near", + "version": "1.0.0", + "license": "(MIT AND Apache-2.0)", + "type": "module", + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "test": "$npm_execpath run build && ava -- ./build/hello_near.wasm" + }, + "dependencies": { + "near-sdk-js": "1.0.0" + }, + "devDependencies": { + "ava": "^6.1.3", + "near-workspaces": "^3.5.0", + "typescript": "^5.4.5" + }, + "ava": { + "timeout": "20000", + "files": ["sandbox-test/*.ava.js"] + } +} diff --git a/contract-ts/02-owner-claims-money/sandbox-test/main.ava.js b/contract-ts/02-owner-claims-money/sandbox-test/main.ava.js new file mode 100644 index 00000000..1929434a --- /dev/null +++ b/contract-ts/02-owner-claims-money/sandbox-test/main.ava.js @@ -0,0 +1,135 @@ +import anyTest from 'ava'; +import { NEAR, Worker } from 'near-workspaces'; +import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); // temp fix for node >v17 + +/** + * @typedef {import('near-workspaces').NearAccount} NearAccount + * @type {import('ava').TestFn<{worker: Worker, accounts: Record}>} + */ +const test = anyTest; +test.beforeEach(async (t) => { + console.log("Init the worker and start a Sandbox server") + // Init the worker and start a Sandbox server + const worker = t.context.worker = await Worker.init(); + + // Create accounts + console.log("Create accounts"); + const root = worker.rootAccount; + + const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("50 N").toString() }); + const bob = await root.createSubAccount("bob", { initialBalance: NEAR.parse("50 N").toString() }); + const contract = await root.createSubAccount("contract", { initialBalance: NEAR.parse("50 N").toString() }); + const auctioneer = await root.createSubAccount("auctioneer", { initialBalance: NEAR.parse("50 N").toString() }); + + // Deploy contract (input from package.json) + console.log("Deploy contract (input from package.json)"); + await contract.deploy(process.argv[2]); + + // Initialize contract, finishes in 1 minute + console.log("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) => { + console.log("Bids are placed"); + const { alice, contract } = t.context.accounts; + + await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); + + const highest_bid = await contract.view("get_highest_bid", {}); + + t.is(highest_bid.bidder, alice.accountId); + t.is(highest_bid.bid, NEAR.parse("1 N").toString()); + console.log("Bids are placed finished"); + +}); + +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 = 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, 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; + + console.log("Claim auction"); + await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString(), gas: "300000000000000" }); + console.log("alice"); + await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString(), gas: "300000000000000" }); + console.log("bob"); + 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, "claim", {}, { gas: "300000000000000" }); + + 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, "claim", {}, { gas: "300000000000000" })) +}); + +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(), gas: "300000000000000" }); + await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString(), gas: "300000000000000" }); + + // fast forward approx a minute + await t.context.worker.provider.fastForward(60) + + await auctioneer.call(contract, "claim", {}, { gas: "300000000000000" }); + + await t.throwsAsync(auctioneer.call(contract, "claim", {}, { gas: "300000000000000" })) +}); \ No newline at end of file diff --git a/contract-ts/02-owner-claims-money/src/contract.ts b/contract-ts/02-owner-claims-money/src/contract.ts new file mode 100644 index 00000000..93a9f04f --- /dev/null +++ b/contract-ts/02-owner-claims-money/src/contract.ts @@ -0,0 +1,71 @@ +// 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; +} + +const THIRTY_TGAS = BigInt("20000000000000"); +const NO_DEPOSIT = BigInt(0); + +@NearBindgen({ requireInit: true }) +class AuctionContract { + highest_bid: Bid = { bidder: '', bid: BigInt(0) }; + 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(0) }; + 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(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-ts/02-owner-claims-money/tsconfig.json b/contract-ts/02-owner-claims-money/tsconfig.json new file mode 100644 index 00000000..c3d38e60 --- /dev/null +++ b/contract-ts/02-owner-claims-money/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "target": "ES5", + "noEmit": true, + "noImplicitAny": false, + }, + "files": [ + "src/contract.ts" + ], + "exclude": [ + "node_modules" + ], +} \ No newline at end of file diff --git a/contract-ts/03-owner-claims-winner-gets-nft/README.md b/contract-ts/03-owner-claims-winner-gets-nft/README.md new file mode 100644 index 00000000..43242c2a --- /dev/null +++ b/contract-ts/03-owner-claims-winner-gets-nft/README.md @@ -0,0 +1,83 @@ +# Hello NEAR Contract + +The smart contract exposes two methods to enable storing and retrieving a greeting in the NEAR network. + +```ts +@NearBindgen({}) +class HelloNear { + greeting: string = "Hello"; + + @view // This method is read-only and can be called for free + get_greeting(): string { + return this.greeting; + } + + @call // This method changes the state, for which it cost gas + set_greeting({ greeting }: { greeting: string }): void { + // Record a log permanently to the blockchain! + near.log(`Saving greeting ${greeting}`); + this.greeting = greeting; + } +} +``` + +
+ +# Quickstart + +1. Make sure you have installed [node.js](https://nodejs.org/en/download/package-manager/) >= 16. +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. Retrieve the Greeting + +`get_greeting` is a read-only method (aka `view` method). + +`View` methods can be called for **free** by anyone, even people **without a NEAR account**! + +```bash +# Use near-cli to get the greeting +near view get_greeting +``` + +
+ +## 4. Store a New Greeting +`set_greeting` changes the contract's state, for which it is a `call` method. + +`Call` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. + +```bash +# Use near-cli to set a new greeting +near call set_greeting '{"greeting":"howdy"}' --accountId +``` + +**Tip:** If you would like to call `set_greeting` using another account, first login into NEAR using: + +```bash +# Use near-cli to login your NEAR account +near login +``` + +and then use the logged account to sign the transaction: `--accountId `. \ No newline at end of file diff --git a/contract-ts/03-owner-claims-winner-gets-nft/package.json b/contract-ts/03-owner-claims-winner-gets-nft/package.json new file mode 100644 index 00000000..784b9f91 --- /dev/null +++ b/contract-ts/03-owner-claims-winner-gets-nft/package.json @@ -0,0 +1,22 @@ +{ + "name": "hello_near", + "version": "1.0.0", + "license": "(MIT AND Apache-2.0)", + "type": "module", + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "test": "$npm_execpath run build && ava -- ./build/hello_near.wasm" + }, + "dependencies": { + "near-sdk-js": "1.0.0" + }, + "devDependencies": { + "ava": "^6.1.3", + "near-workspaces": "^3.5.0", + "typescript": "^5.4.5" + }, + "ava": { + "timeout": "20000", + "files": ["sandbox-test/*.ava.js"] + } +} diff --git a/contract-ts/03-owner-claims-winner-gets-nft/sandbox-test/main.ava.js b/contract-ts/03-owner-claims-winner-gets-nft/sandbox-test/main.ava.js new file mode 100644 index 00000000..9fcd167f --- /dev/null +++ b/contract-ts/03-owner-claims-winner-gets-nft/sandbox-test/main.ava.js @@ -0,0 +1,162 @@ +import anyTest from 'ava'; +import { NEAR, Worker } from 'near-workspaces'; +import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); // temp fix for node >v17 + +/** + * @typedef {import('near-workspaces').NearAccount} NearAccount + * @type {import('ava').TestFn<{worker: Worker, accounts: Record}>} + */ +const test = anyTest; +const NFT_WASM_FILEPATH = "./sandbox-test/non_fungible_token.wasm"; +test.beforeEach(async (t) => { + console.log("Init the worker and start a Sandbox server") + // Init the worker and start a Sandbox server + const worker = t.context.worker = await Worker.init(); + + // Create accounts + console.log("Create accounts"); + const root = worker.rootAccount; + + const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("50 N").toString() }); + const bob = await root.createSubAccount("bob", { initialBalance: NEAR.parse("50 N").toString() }); + const contract = await root.createSubAccount("contract", { initialBalance: NEAR.parse("50 N").toString() }); + const nft_contract = await root.createSubAccount("nft_contract"); + const auctioneer = await root.createSubAccount("auctioneer", { initialBalance: NEAR.parse("50 N").toString() }); + + // Deploy contract nft + console.log("Deploy contract nft"); + await nft_contract.deploy(NFT_WASM_FILEPATH); + await nft_contract.call(nft_contract, "new_default_meta", { "owner_id": nft_contract.accountId }); + + const token_id = "1"; + // Mint NFT + console.log("Mint NFT"); + let request_payload = { + "token_id": token_id, + "receiver_id": contract.accountId, + "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" + }, + }; + + await nft_contract.call(nft_contract, "nft_mint", request_payload, { attachedDeposit: NEAR.from("8000000000000000000000").toString(), gas: "300000000000000" }); + + // Deploy contract (input from package.json) + console.log("Deploy contract (input from package.json)"); + await contract.deploy(process.argv[2]); + + // Initialize contract, finishes in 1 minute + console.log("Initialize contract, finishes in 1 minute"); + await contract.call(contract, "init", { + end_time: String((Date.now() + 60000) * 10 ** 6), + auctioneer: auctioneer.accountId, + nft_contract: nft_contract.accountId, + token_id: token_id + }); + + // Save state for test runs, it is unique for each test + t.context.worker = worker; + t.context.accounts = { alice, bob, contract, auctioneer, nft_contract }; +}); + +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) => { + console.log("Bids are placed"); + const { alice, contract } = t.context.accounts; + + await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); + + const highest_bid = await contract.view("get_highest_bid", {}); + + t.is(highest_bid.bidder, alice.accountId); + t.is(highest_bid.bid, NEAR.parse("1 N").toString()); + console.log("Bids are placed finished"); + +}); + +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 = 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, 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,nft_contract} = t.context.accounts; + + console.log("Claim auction"); + await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString(), gas: "300000000000000" }); + console.log("alice"); + await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString(), gas: "300000000000000" }); + console.log("bob"); + 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, "claim", {}, { gas: "300000000000000" }); + + const contractNewBalance = await auctioneer.balance(); + const new_available = parseFloat(contractNewBalance.available.toHuman()); + + t.is(new_available.toFixed(2), (available + 2).toFixed(2)); + + const response = await nft_contract.call(nft_contract, "nft_token",{"token_id": "1"},{ gas: "300000000000000" }); + t.is(response.owner_id,bob.accountId); +}); + +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, "claim", {}, { gas: "300000000000000" })) +}); + +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(), gas: "300000000000000" }); + await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString(), gas: "300000000000000" }); + + // fast forward approx a minute + await t.context.worker.provider.fastForward(60) + + await auctioneer.call(contract, "claim", {}, { gas: "300000000000000" }); + + await t.throwsAsync(auctioneer.call(contract, "claim", {}, { gas: "300000000000000" })) +}); \ No newline at end of file diff --git a/contract-rs/01-basic-auction/tests/non_fungible_token.wasm b/contract-ts/03-owner-claims-winner-gets-nft/sandbox-test/non_fungible_token.wasm similarity index 100% rename from contract-rs/01-basic-auction/tests/non_fungible_token.wasm rename to contract-ts/03-owner-claims-winner-gets-nft/sandbox-test/non_fungible_token.wasm diff --git a/contract-ts/03-owner-claims-winner-gets-nft/src/contract.ts b/contract-ts/03-owner-claims-winner-gets-nft/src/contract.ts new file mode 100644 index 00000000..8ec07fc6 --- /dev/null +++ b/contract-ts/03-owner-claims-winner-gets-nft/src/contract.ts @@ -0,0 +1,86 @@ +// 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; +} + +const THIRTY_TGAS = BigInt("20000000000000"); +const NO_DEPOSIT = BigInt(0); + +@NearBindgen({ requireInit: true }) +class AuctionContract { + highest_bid: Bid = { bidder: '', bid: BigInt(0) }; + auction_end_time: bigint = BigInt(0); + auctioneer: string = ""; + auction_was_claimed: boolean = false; + nft_contract: AccountId = ""; + token_id: string = ""; + + @initialize({ privateFunction: true }) + init({ end_time, auctioneer, nft_contract, token_id }: { end_time: bigint, auctioneer: string, nft_contract: AccountId, token_id: string }) { + this.auction_end_time = end_time; + this.highest_bid = { bidder: near.currentAccountId(), bid: BigInt(0) }; + this.auctioneer = auctioneer; + this.nft_contract = nft_contract; + this.token_id = token_id; + } + + @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(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.nft_contract) + .functionCall("nft_transfer", JSON.stringify({ receiver_id: this.highest_bid.bidder, token_id: this.token_id }), BigInt(1), THIRTY_TGAS) + .and(NearPromise.new(this.auctioneer).transfer(this.highest_bid.bid)) + .then( + NearPromise.new(near.currentAccountId()) + .functionCall("nft_transfer_callback", JSON.stringify({}), NO_DEPOSIT, THIRTY_TGAS) + ) + .asReturn() + + } + + @call({ privateFunction: true }) + nft_transfer_callback({ }): BigInt { + return BigInt(0); + } +} \ No newline at end of file diff --git a/contract-ts/03-owner-claims-winner-gets-nft/tsconfig.json b/contract-ts/03-owner-claims-winner-gets-nft/tsconfig.json new file mode 100644 index 00000000..c3d38e60 --- /dev/null +++ b/contract-ts/03-owner-claims-winner-gets-nft/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "target": "ES5", + "noEmit": true, + "noImplicitAny": false, + }, + "files": [ + "src/contract.ts" + ], + "exclude": [ + "node_modules" + ], +} \ No newline at end of file diff --git a/contract-ts/04-ft-owner-claims-winner-gets-nft/README.md b/contract-ts/04-ft-owner-claims-winner-gets-nft/README.md new file mode 100644 index 00000000..43242c2a --- /dev/null +++ b/contract-ts/04-ft-owner-claims-winner-gets-nft/README.md @@ -0,0 +1,83 @@ +# Hello NEAR Contract + +The smart contract exposes two methods to enable storing and retrieving a greeting in the NEAR network. + +```ts +@NearBindgen({}) +class HelloNear { + greeting: string = "Hello"; + + @view // This method is read-only and can be called for free + get_greeting(): string { + return this.greeting; + } + + @call // This method changes the state, for which it cost gas + set_greeting({ greeting }: { greeting: string }): void { + // Record a log permanently to the blockchain! + near.log(`Saving greeting ${greeting}`); + this.greeting = greeting; + } +} +``` + +
+ +# Quickstart + +1. Make sure you have installed [node.js](https://nodejs.org/en/download/package-manager/) >= 16. +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. Retrieve the Greeting + +`get_greeting` is a read-only method (aka `view` method). + +`View` methods can be called for **free** by anyone, even people **without a NEAR account**! + +```bash +# Use near-cli to get the greeting +near view get_greeting +``` + +
+ +## 4. Store a New Greeting +`set_greeting` changes the contract's state, for which it is a `call` method. + +`Call` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. + +```bash +# Use near-cli to set a new greeting +near call set_greeting '{"greeting":"howdy"}' --accountId +``` + +**Tip:** If you would like to call `set_greeting` using another account, first login into NEAR using: + +```bash +# Use near-cli to login your NEAR account +near login +``` + +and then use the logged account to sign the transaction: `--accountId `. \ No newline at end of file diff --git a/contract-ts/04-ft-owner-claims-winner-gets-nft/package.json b/contract-ts/04-ft-owner-claims-winner-gets-nft/package.json new file mode 100644 index 00000000..784b9f91 --- /dev/null +++ b/contract-ts/04-ft-owner-claims-winner-gets-nft/package.json @@ -0,0 +1,22 @@ +{ + "name": "hello_near", + "version": "1.0.0", + "license": "(MIT AND Apache-2.0)", + "type": "module", + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "test": "$npm_execpath run build && ava -- ./build/hello_near.wasm" + }, + "dependencies": { + "near-sdk-js": "1.0.0" + }, + "devDependencies": { + "ava": "^6.1.3", + "near-workspaces": "^3.5.0", + "typescript": "^5.4.5" + }, + "ava": { + "timeout": "20000", + "files": ["sandbox-test/*.ava.js"] + } +} diff --git a/contract-rs/01-basic-auction/tests/fungible_token.wasm b/contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/fungible_token.wasm similarity index 100% rename from contract-rs/01-basic-auction/tests/fungible_token.wasm rename to contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/fungible_token.wasm diff --git a/contract-ts/sandbox-test/main.ava.js b/contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/main.ava.js similarity index 59% rename from contract-ts/sandbox-test/main.ava.js rename to contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/main.ava.js index 8b1bcd91..032b5e92 100644 --- a/contract-ts/sandbox-test/main.ava.js +++ b/contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/main.ava.js @@ -8,14 +8,11 @@ import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); */ const test = anyTest; const FT_WASM_FILEPATH = "./sandbox-test/fungible_token.wasm"; -// const NFT_WASM_FILEPATH = "./tests/non_fungible_token.wasm"; +const NFT_WASM_FILEPATH = "./sandbox-test/non_fungible_token.wasm"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = t.context.worker = await Worker.init(); - // const ft_wasm = std::fs::read(FT_WASM_FILEPATH)?; - // let ft_contract = sandbox.dev_deploy(&ft_wasm).await?; - // Create accounts const root = worker.rootAccount; @@ -23,13 +20,31 @@ test.beforeEach(async (t) => { const bob = await root.createSubAccount("bob",{initialBalance: NEAR.parse("50 N").toString()}); const contract = await root.createSubAccount("contract",{initialBalance: NEAR.parse("50 N").toString()}); const ft_contract = await root.createSubAccount("ft_contract"); - // const contract = await root.createSubAccount("contract"); + const nft_contract = await root.createSubAccount("nft_contract"); const auctioneer = await root.createSubAccount("auctioneer",{initialBalance: NEAR.parse("50 N").toString()}); // Deploy contract ft await ft_contract.deploy(FT_WASM_FILEPATH); - let res = await ft_contract.call(ft_contract,"new_default_meta",{"owner_id":ft_contract.accountId,"total_supply":BigInt(1_000_000).toString()}); + await ft_contract.call(ft_contract,"new_default_meta",{"owner_id":ft_contract.accountId,"total_supply":BigInt(1_000_000).toString()}); + + // Deploy contract nft + await nft_contract.deploy(NFT_WASM_FILEPATH); + await nft_contract.call(nft_contract,"new_default_meta",{"owner_id":nft_contract.accountId}); + + const token_id = "1"; + // Mint NFT + let request_payload = { + "token_id": token_id, + "receiver_id": contract.accountId, + "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" + }, + }; + + await nft_contract.call(nft_contract,"nft_mint",request_payload,{ attachedDeposit: NEAR.from("8000000000000000000000").toString(),gas: "300000000000000" }); const contracts = [alice,bob,contract,auctioneer]; @@ -39,21 +54,24 @@ test.beforeEach(async (t) => { await ft_contract.call(ft_contract,"ft_transfer",{"receiver_id":alice.accountId,"amount":BigInt(150_000).toString()},{ attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); - await ft_contract.call(ft_contract,"ft_transfer",{"receiver_id":bob.accountId,"amount":BigInt(150_000).toString()},{ attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + await ft_contract.call(ft_contract,"ft_transfer",{"receiver_id":bob.accountId,"amount":BigInt(150_000).toString()},{ attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); - // Deploy contract (input from package.json) - await contract.deploy(process.argv[2]); + // 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, - ft_contract: ft_contract.accountId + ft_contract: ft_contract.accountId, + nft_contract: nft_contract.accountId, + token_id: token_id }); // Save state for test runs, it is unique for each test t.context.worker = worker; - t.context.accounts = { alice, bob, contract, auctioneer,ft_contract}; + t.context.accounts = { alice, bob, contract, auctioneer,ft_contract,nft_contract}; }); test.afterEach.always(async (t) => { @@ -175,65 +193,70 @@ test("Outbid returns previous bid", async (t) => { await bob.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); const highest_bid = await contract.view("get_highest_bid", {}); - // t.is(highest_bid.bidder, bob.accountId); - // t.is(highest_bid.bid, BigInt(60_000).toString()); + t.is(highest_bid.bidder, bob.accountId); + t.is(highest_bid.bid, BigInt(60_000).toString()); // we returned the money to alice const aliceNewBalance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId}); - t.deepEqual(BigInt(150_000).toString(),aliceNewBalance); + t.is(BigInt(150_000).toString(),aliceNewBalance); }); -// test("Auction closes", async (t) => { -// const { alice, bob, contract } = t.context.accounts; +test("Auction closes", async (t) => { + const { alice, ft_contract, contract } = t.context.accounts; -// // alice can bid -// await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); + // alice can bid + await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); -// // fast forward approx a minute -// await t.context.worker.provider.fastForward(60) + // 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() })) -// }); + // alice cannot bid anymore + alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + const contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId}); + t.is(BigInt(50_000).toString(),contract_balance); +}); -// test("Claim auction", async (t) => { -// const { alice, bob, contract, auctioneer} = t.context.accounts; +test("Claim auction", async (t) => { + const { alice, bob, contract, auctioneer, ft_contract,nft_contract} = 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 alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + await bob.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); -// await auctioneer.call(contract, "claim",{}); + const auctioneer_balance = await ft_contract.view("ft_balance_of",{"account_id": auctioneer.accountId}); -// const contractNewBalance = await auctioneer.balance(); -// const new_available = parseFloat(contractNewBalance.available.toHuman()); + // fast forward approx a minute + await t.context.worker.provider.fastForward(60) -// t.is(new_available.toFixed(2), (available + 2).toFixed(2)); -// }); + await auctioneer.call(contract, "claim",{},{ gas: "300000000000000" }); -// test("Auction open", async (t) => { -// const { alice, bob, contract, auctioneer} = t.context.accounts; + const auctioneer_new_balance = await ft_contract.view("ft_balance_of",{"account_id": auctioneer.accountId}); -// await alice.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("1 N").toString() }); -// await bob.call(contract, "bid", {}, { attachedDeposit: NEAR.parse("2 N").toString() }); + t.is(auctioneer_balance, BigInt(0).toString()); + t.is(auctioneer_new_balance, BigInt(60_000).toString()); -// await t.throwsAsync(auctioneer.call(contract, "claim",{})) -// }); + const response = await nft_contract.call(nft_contract, "nft_token",{"token_id": "1"},{ gas: "300000000000000" }); + t.is(response.owner_id,bob.accountId); +}); -// test("Auction has been claimed", async (t) => { -// const { alice, bob, contract, auctioneer} = t.context.accounts; +test("Auction open", async (t) => { + const { alice, bob, contract, auctioneer, ft_contract} = 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 alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + await bob.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + + await t.throwsAsync(auctioneer.call(contract, "claim",{},{ gas: "300000000000000" })) +}); + +test("Auction has been claimed", async (t) => { + const { alice, bob, contract, auctioneer,ft_contract} = t.context.accounts; + + await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); + await bob.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" }); -// // fast forward approx a minute -// await t.context.worker.provider.fastForward(60) + // fast forward approx a minute + await t.context.worker.provider.fastForward(60) -// await auctioneer.call(contract, "claim",{}); + await auctioneer.call(contract, "claim",{},{ gas: "300000000000000" }); -// await t.throwsAsync(auctioneer.call(contract, "claim",{})) -// }); \ No newline at end of file + await t.throwsAsync(auctioneer.call(contract, "claim",{},{ gas: "300000000000000" })) +}); \ No newline at end of file diff --git a/contract-rs/02-owner-claims-money/tests/non_fungible_token.wasm b/contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/non_fungible_token.wasm similarity index 100% rename from contract-rs/02-owner-claims-money/tests/non_fungible_token.wasm rename to contract-ts/04-ft-owner-claims-winner-gets-nft/sandbox-test/non_fungible_token.wasm diff --git a/contract-ts/src/contract.ts b/contract-ts/04-ft-owner-claims-winner-gets-nft/src/contract.ts similarity index 60% rename from contract-ts/src/contract.ts rename to contract-ts/04-ft-owner-claims-winner-gets-nft/src/contract.ts index 95169eee..de6b7958 100644 --- a/contract-ts/src/contract.ts +++ b/contract-ts/04-ft-owner-claims-winner-gets-nft/src/contract.ts @@ -1,6 +1,5 @@ // Find all our documentation at https://docs.near.org -import { NearBindgen, near, call, view, AccountId, NearPromise, initialize, assert, PromiseIndex } from "near-sdk-js"; -import { predecessorAccountId } from "near-sdk-js/lib/api"; +import { NearBindgen, near, call, view, AccountId, NearPromise, initialize, assert } from "near-sdk-js"; class Bid { bidder: AccountId; @@ -9,7 +8,6 @@ class Bid { const THIRTY_TGAS = BigInt("30000000000000"); const NO_DEPOSIT = BigInt(0); -const NO_ARGS = JSON.stringify({}); @NearBindgen({ requireInit: true }) class AuctionContract { @@ -18,37 +16,19 @@ class AuctionContract { auctioneer: string = ""; auction_was_claimed: boolean = false; ft_contract: AccountId = ""; + nft_contract: AccountId = ""; + token_id: string = ""; @initialize({ privateFunction: true }) - init({ end_time, auctioneer, ft_contract }: { end_time: bigint, auctioneer: string, ft_contract: AccountId }) { + init({ end_time, auctioneer, ft_contract, nft_contract, token_id }: { end_time: bigint, auctioneer: string, ft_contract: AccountId, nft_contract: AccountId, token_id: string }) { this.auction_end_time = end_time; this.highest_bid = { bidder: near.currentAccountId(), bid: BigInt(0) }; this.auctioneer = auctioneer; this.ft_contract = ft_contract; + this.nft_contract = nft_contract; + this.token_id = token_id; } - // @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; @@ -61,12 +41,25 @@ class AuctionContract { @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); + + return NearPromise.new(this.nft_contract) + .functionCall("nft_transfer", JSON.stringify({ receiver_id: this.highest_bid.bidder, token_id: this.token_id }), BigInt(1), THIRTY_TGAS) + .and(NearPromise.new(this.ft_contract) + .functionCall("ft_transfer", JSON.stringify({ receiver_id: this.auctioneer, amount: this.highest_bid.bid }), BigInt(1), THIRTY_TGAS) + .then( + NearPromise.new(near.currentAccountId()) + .functionCall("ft_transfer_callback", JSON.stringify({}), NO_DEPOSIT, THIRTY_TGAS) + )) + .then( + NearPromise.new(near.currentAccountId()) + .functionCall("nft_transfer_callback", JSON.stringify({}), NO_DEPOSIT, THIRTY_TGAS) + ) + .asReturn() } @call({}) @@ -102,19 +95,9 @@ class AuctionContract { ft_transfer_callback({ }): BigInt { return BigInt(0); } -} - -function promiseResult(i: PromiseIndex): { result: string; success: boolean } { - near.log("promiseResult"); - let result, success; - try { - result = near.promiseResult(i); - success = true; - } catch { - result = undefined; - success = false; + @call({ privateFunction: true }) + nft_transfer_callback({ }): BigInt { + return BigInt(0); } - - return { result, success }; } \ No newline at end of file diff --git a/contract-ts/04-ft-owner-claims-winner-gets-nft/tsconfig.json b/contract-ts/04-ft-owner-claims-winner-gets-nft/tsconfig.json new file mode 100644 index 00000000..c3d38e60 --- /dev/null +++ b/contract-ts/04-ft-owner-claims-winner-gets-nft/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "target": "ES5", + "noEmit": true, + "noImplicitAny": false, + }, + "files": [ + "src/contract.ts" + ], + "exclude": [ + "node_modules" + ], +} \ No newline at end of file diff --git a/contract-ts/sandbox-test/fungible_token.wasm b/contract-ts/sandbox-test/fungible_token.wasm deleted file mode 100755 index 40045b57..00000000 Binary files a/contract-ts/sandbox-test/fungible_token.wasm and /dev/null differ diff --git a/contract-ts/sandbox-test/non_fungible_token.wasm b/contract-ts/sandbox-test/non_fungible_token.wasm deleted file mode 100755 index 68f4e83f..00000000 Binary files a/contract-ts/sandbox-test/non_fungible_token.wasm and /dev/null differ