From 83956ae571f1cba3f686e565f8bc6ad8080e8a5c Mon Sep 17 00:00:00 2001 From: garikbesson Date: Sun, 27 Oct 2024 18:04:08 +0100 Subject: [PATCH] update dependencies, code structure, tests, README --- Cargo.toml | 35 +- README-Windows.md | 98 --- README.md | 166 +--- integration-tests/Cargo.toml | 17 - integration-tests/src/tests.rs | 714 ------------------ nft/Cargo.toml | 15 - res/README.md | 1 - scripts/build.bat | 7 - scripts/build.sh | 13 - scripts/flags.sh | 7 - scripts/test.sh | 15 - {nft/src => src}/lib.rs | 0 test-approval-receiver/Cargo.toml | 12 - test-token-receiver/Cargo.toml | 12 - tests/contracts/approval-receiver/Cargo.toml | 15 + .../contracts/approval-receiver}/src/lib.rs | 40 +- tests/contracts/token-receiver/Cargo.toml | 15 + .../contracts/token-receiver}/src/lib.rs | 50 +- tests/init.rs | 122 +++ tests/main.rs | 2 + tests/tests/approval.rs | 332 ++++++++ tests/tests/core.rs | 281 +++++++ tests/tests/enumeration.rs | 187 +++++ tests/tests/mod.rs | 3 + 24 files changed, 1062 insertions(+), 1097 deletions(-) delete mode 100644 README-Windows.md delete mode 100644 integration-tests/Cargo.toml delete mode 100644 integration-tests/src/tests.rs delete mode 100644 nft/Cargo.toml delete mode 100644 res/README.md delete mode 100644 scripts/build.bat delete mode 100755 scripts/build.sh delete mode 100644 scripts/flags.sh delete mode 100755 scripts/test.sh rename {nft/src => src}/lib.rs (100%) delete mode 100644 test-approval-receiver/Cargo.toml delete mode 100644 test-token-receiver/Cargo.toml create mode 100644 tests/contracts/approval-receiver/Cargo.toml rename {test-approval-receiver => tests/contracts/approval-receiver}/src/lib.rs (69%) create mode 100644 tests/contracts/token-receiver/Cargo.toml rename {test-token-receiver => tests/contracts/token-receiver}/src/lib.rs (64%) create mode 100644 tests/init.rs create mode 100644 tests/main.rs create mode 100644 tests/tests/approval.rs create mode 100644 tests/tests/core.rs create mode 100644 tests/tests/enumeration.rs create mode 100644 tests/tests/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 8b79ead7..29da0408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,18 @@ -[profile.release] -codegen-units = 1 -# Tell `rustc` to optimize for small code size. -opt-level = "z" -lto = true -debug = false -panic = "abort" -overflow-checks = true +[package] +name = "non-fungible-token" +version = "1.1.0" +authors = ["Near Inc "] +edition = "2018" -[workspace] -# remember to include a member for each contract -members = [ - "nft", - "test-approval-receiver", - "test-token-receiver", -] -exclude = [ - "integration-tests" -] +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +near-sdk = "5.5.0" +near-contract-standards = "5.5.0" + +[dev-dependencies] +near-sdk = { version = "5.5.0", features = ["unit-testing"] } +near-workspaces = { version = "0.14.1", features = ["unstable"] } +anyhow = "1.0" +tokio = { version = "1.41.0", features = ["full"] } \ No newline at end of file diff --git a/README-Windows.md b/README-Windows.md deleted file mode 100644 index e7317c51..00000000 --- a/README-Windows.md +++ /dev/null @@ -1,98 +0,0 @@ -Non-fungible Token (NFT) -=================== - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/near-examples/NFT) - - -This repository includes an example implementation of a [non-fungible token] contract which uses [near-contract-standards] and [simulation] tests. - - [non-fungible token]: https://nomicon.io/Standards/NonFungibleToken/README.html - [near-contract-standards]: https://github.com/near/near-sdk-rs/tree/master/near-contract-standards - [simulation]: https://github.com/near/near-sdk-rs/tree/master/near-sdk-sim -Prerequisites -============= -If you're using Gitpod, you can skip this step. - - * Make sure Rust is installed per the prerequisites in [`near-sdk-rs`](https://github.com/near/near-sdk-rs). - * Make sure [near-cli](https://github.com/near/near-cli) is installed. - -Explore this contract -===================== - -The source for this contract is in `nft/src/lib.rs`. It provides methods to manage access to tokens, transfer tokens, check access, and get token owner. Note, some further exploration inside the rust macros is needed to see how the `NonFungibleToken` contract is implemented. - -Building this contract -====================== -Run the following, and we'll build our rust project up via cargo. This will generate our WASM binaries into our `res/` directory. This is the smart contract we'll be deploying onto the NEAR blockchain later. -```batch -build.bat -``` - -Testing this contract -===================== -We have some tests that you can run. For example, the following will run our simple tests to verify that our contract code is working. -```bash -cargo test -- --nocapture -``` -The more complex simulation tests aren't run with this command, but we can find them in `tests/sim`. - -Using this contract -=================== - -This smart contract will get deployed to your NEAR account. For this example, please create a new NEAR account. Because NEAR allows the ability to upgrade contracts on the same account, initialization functions must be cleared. If you'd like to run this example on a NEAR account that has had prior contracts deployed, please use the `near-cli` command `near delete`, and then recreate it in Wallet. To create (or recreate) an account, please follow the directions in [Test Wallet](https://wallet.testnet.near.org) or ([NEAR Wallet](https://wallet.near.org/) if we're using mainnet). - -In the project root, log in to your newly created account with `near-cli` by following the instructions after this command. - - near login - -To make this tutorial easier to copy/paste, we're going to set an environment variable for our account id. In the below command, replace `MY_ACCOUNT_NAME` with the account name we just logged in with, including the `.testnet` (or `.near` for `mainnet`): - - set ID=MY_ACCOUNT_NAME - -We can tell if the environment variable is set correctly if our command line prints the account name after this command: - - echo %ID% - -Now we can deploy the compiled contract in this example to your account: - - near deploy --wasmFile res/non_fungible_token.wasm --accountId %ID% - -NFT contract should be initialized before usage. More info about the metadata at [nomicon.io](https://nomicon.io/Standards/NonFungibleToken/Metadata.html). But for now, we'll initialize with the default metadata. - - near call %ID% new_default_meta "{\"owner_id\": \""%ID%"\"}" --accountId %ID% - -We'll be able to view our metadata right after: - - near view %ID% nft_metadata - -Then, let's mint our first token. This will create a NFT based on Olympus Mons where only one copy exists: - - near call %ID% nft_mint "{\"token_id\": \"0\", \"receiver_id\": \""%ID%"\", \"token_metadata\": { \"title\": \"Olympus Mons\", \"description\": \"Tallest mountain in charted solar system\", \"copies\": 1}}" --accountId %ID% --deposit 0.1 - -Transferring our NFT -==================== - -Let's set up an account to transfer our freshly minted token to. This account will be a sub-account of the NEAR account we logged in with originally via `near login`. - - near create-account alice.%ID% --masterAccount %ID% --initialBalance 10 - -Checking Alice's account for tokens: - - near view %ID% nft_tokens_for_owner "{\"account_id\": \""alice.%ID%"\"}" - -Then we'll transfer over the NFT into Alice's account. Exactly 1 yoctoNEAR of deposit should be attached: - - near call %ID% nft_transfer "{\"token_id\": \"0\", \"receiver_id\": \""alice.%ID%"\", \"memo\": \"transfer ownership\"}" --accountId %ID% --depositYocto 1 - -Checking Alice's account again shows us that she has the Olympus Mons token. - -Notes -===== - -* The maximum balance value is limited by U128 (2**128 - 1). -* JSON calls should pass U128 as a base-10 string. E.g. "100". -* This does not include escrow functionality, as ft_transfer_call provides a superior approach. An escrow system can, of course, be added as a separate contract or additional functionality within this contract. - -AssemblyScript -============== -Currently, AssemblyScript is not supported for this example. An old version can be found in the [NEP4 example](https://github.com/near-examples/NFT/releases/tag/nep4-example), but this is not recommended as it is out of date and does not follow the standards the NEAR SDK has set currently. diff --git a/README.md b/README.md index 2b5a7d8c..ad27d98c 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,70 @@ -Non-fungible Token (NFT) -=================== +Non-fungible Token (NFT) Example 👋 +[![](https://img.shields.io/badge/⋈%20Examples-Basics-green)](https://docs.near.org/tutorials/welcome) +[![](https://img.shields.io/badge/Contract-Rust-red)](contract-rs) +![example workflow](https://github.com/near-examples/NFT/actions/workflows/tests-rs.yml/badge.svg) ->**Note**: If you'd like to learn how to create an NFT contract from scratch that explores every aspect of the [NEP-171](https://github.com/near/NEPs/blob/master/neps/nep-0171.md) standard including an NFT marketplace, check out the NFT [Zero to Hero Tutorial](https://docs.near.org/tutorials/nfts/introduction). - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/near-examples/NFT) - -This repository includes an example implementation of a [non-fungible token] contract which uses [near-contract-standards] and workspaces-js and -rs tests. +This repository contains an example implementation of a [non-fungible token] contract in Rust which uses [near-contract-standards] and workspaces-rs tests. [non-fungible token]: https://nomicon.io/Standards/NonFungibleToken/README.html [near-contract-standards]: https://github.com/near/near-sdk-rs/tree/master/near-contract-standards - [simulation]: https://github.com/near/near-sdk-rs/tree/master/near-sdk-sim - ---- - -Prerequisites -============= - -If you're using Gitpod, you can skip this step. + [near-workspaces-rs]: https://github.com/near/near-workspaces-rs - * Make sure Rust is installed per the prerequisites in [`near-sdk-rs`](https://github.com/near/near-sdk-rs). - * Make sure [near-cli](https://github.com/near/near-cli) is installed. - -Explore this contract -===================== +>**Note**: If you'd like to learn how to create an NFT contract from scratch that explores every aspect of the [NEP-171](https://github.com/near/NEPs/blob/master/neps/nep-0171.md) standard including an NFT marketplace, check out the NFT [Zero to Hero Tutorial](https://docs.near.org/tutorials/nfts/introduction). -The source for this contract is in `nft/src/lib.rs`. It provides methods to manage access to tokens, transfer tokens, check access, and get token owner. Note, some further exploration inside the rust macros is needed to see how the `NonFungibleToken` contract is implemented. +
-Building this contract -====================== -Run the following, and we'll build our rust project up via cargo. This will generate our WASM binaries into our `res/` directory. This is the smart contract we'll be deploying onto the NEAR blockchain later. -```bash -./scripts/build.sh -``` +## How to Build Locally? -Testing this contract -===================== -We have some tests that you can run. For example, the following will run our simple tests to verify that our contract code is working. +Install [`cargo-near`](https://github.com/near/cargo-near) and run: -*Unit Tests* ```bash -cd nft -cargo test -- --nocapture +cargo near build ``` -*Integration Tests* -*Rust* -```bash -cd integration-tests/rs -cargo run --example integration-tests -``` +## How to Test Locally? -*TypeScript* ```bash -cd integration-tests/ts -yarn && yarn test +cargo test ``` -Using this contract -=================== +## How to Deploy? -### Quickest deploy - -You can build and deploy this smart contract to a development account. [Dev Accounts](https://docs.near.org/concepts/basics/account#dev-accounts) are auto-generated accounts to assist in developing and testing smart contracts. Please see the [Standard deploy](#standard-deploy) section for creating a more personalized account to deploy to. +To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: ```bash -near dev-deploy --wasmFile res/non_fungible_token.wasm -``` - -Behind the scenes, this is creating an account and deploying a contract to it. On the console, notice a message like: +# Create a new account +cargo near create-dev-account ->Done deploying to dev-1234567890123 +# Deploy the contract on it +cargo near deploy -In this instance, the account is `dev-1234567890123`. A file has been created containing a key pair to -the account, located at `neardev/dev-account`. To make the next few steps easier, we're going to set an -environment variable containing this development account id and use that when copy/pasting commands. -Run this command to set the environment variable: - -```bash -source neardev/dev-account.env +# Initialize the contract +near call new_default_meta '{"owner_id": ""}' --accountId ``` -You can tell if the environment variable is set correctly if your command line prints the account name after this command: +## Basic methods ```bash -echo $CONTRACT_NAME -``` +# View metadata +near view nft_metadata -The next command will initialize the contract using the `new` method: +# Mint a NFT +near call nft_mint '{"token_id": "0", "receiver_id": "", "token_metadata": { "title": "Olympus Mons", "description": "Tallest mountain in charted solar system", "media": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Olympus_Mons_alt.jpg/1024px-Olympus_Mons_alt.jpg", "copies": 1}}' --accountId --deposit 0.1 -```bash -near call $CONTRACT_NAME new_default_meta '{"owner_id": "'$CONTRACT_NAME'"}' --accountId $CONTRACT_NAME -``` - -To view the NFT metadata: +# View tokens for owner +near view nft_tokens_for_owner '{"account_id": ""}' -```bash -near view $CONTRACT_NAME nft_metadata +# Transfer a NFT +near call nft_transfer '{"token_id": "0", "receiver_id": "", "memo": "transfer ownership"}' --accountId --depositYocto 1 ``` -### Standard deploy - -This smart contract will get deployed to your NEAR account. For this example, please create a new NEAR account. Because NEAR allows the ability to upgrade contracts on the same account, initialization functions must be cleared. If you'd like to run this example on a NEAR account that has had prior contracts deployed, please use the `near-cli` command `near delete`, and then recreate it in Wallet. To create (or recreate) an account, please follow the directions in [Test Wallet](https://wallet.testnet.near.org) or ([NEAR Wallet](https://wallet.near.org/) if we're using `mainnet`). - -In the project root, log in to your newly created account with `near-cli` by following the instructions after this command. - - near login - -To make this tutorial easier to copy/paste, we're going to set an environment variable for our account id. In the below command, replace `MY_ACCOUNT_NAME` with the account name we just logged in with, including the `.testnet` (or `.near` for `mainnet`): - - ID=MY_ACCOUNT_NAME - -We can tell if the environment variable is set correctly if our command line prints the account name after this command: - - echo $ID - -Now we can deploy the compiled contract in this example to your account: - - near deploy --wasmFile res/non_fungible_token.wasm --accountId $ID - -NFT contract should be initialized before usage. More info about the metadata at [nomicon.io](https://nomicon.io/Standards/NonFungibleToken/Metadata.html). But for now, we'll initialize with the default metadata. - - near call $ID new_default_meta '{"owner_id": "'$ID'"}' --accountId $ID - -We'll be able to view our metadata right after: - - near view $ID nft_metadata - -Then, let's mint our first token. This will create a NFT based on Olympus Mons where only one copy exists: - - near call $ID nft_mint '{"token_id": "0", "receiver_id": "'$ID'", "token_metadata": { "title": "Olympus Mons", "description": "Tallest mountain in charted solar system", "media": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Olympus_Mons_alt.jpg/1024px-Olympus_Mons_alt.jpg", "copies": 1}}' --accountId $ID --deposit 0.1 - -Transferring our NFT -==================== - -Let's set up an account to transfer our freshly minted token to. This account will be a sub-account of the NEAR account we logged in with originally via `near login`. - - near create-account alice.$ID --masterAccount $ID --initialBalance 10 - -Checking Alice's account for tokens: - - near view $ID nft_tokens_for_owner '{"account_id": "'alice.$ID'"}' - -Then we'll transfer over the NFT into Alice's account. Exactly 1 yoctoNEAR of deposit should be attached: - - near call $ID nft_transfer '{"token_id": "0", "receiver_id": "alice.'$ID'", "memo": "transfer ownership"}' --accountId $ID --depositYocto 1 - -Checking Alice's account again shows us that she has the Olympus Mons token. - -Notes -===== - -* The maximum balance value is limited by U128 (2**128 - 1). -* JSON calls should pass U128 as a base-10 string. E.g. "100". -* This does not include escrow functionality, as ft_transfer_call provides a superior approach. An escrow system can, of course, be added as a separate contract or additional functionality within this contract. \ No newline at end of file +## Useful Links + +- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust +- [near CLI](https://near.cli.rs) - Iteract with NEAR blockchain from command line +- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction) +- [NEAR Documentation](https://docs.near.org) +- [NFT Zero to Hero Tutorial](https://docs.near.org/tutorials/nfts/introduction) +- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol) +- [NEAR Discord](https://near.chat) +- [NEAR Telegram Developers Community Group](https://t.me/neardev) +- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub) diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml deleted file mode 100644 index c9d779b8..00000000 --- a/integration-tests/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "non-fungible-token-integration-tests" -version = "1.0.0" -publish = false -edition = "2018" - -[dev-dependencies] -near-sdk = "5.5.0" -anyhow = "1.0" -tokio = { version = "1.18.1", features = ["full"] } -near-workspaces = "0.14" -serde_json = "1.0.128" - -[[example]] -name = "integration-tests" -path = "src/tests.rs" - diff --git a/integration-tests/src/tests.rs b/integration-tests/src/tests.rs deleted file mode 100644 index 3df30049..00000000 --- a/integration-tests/src/tests.rs +++ /dev/null @@ -1,714 +0,0 @@ -use serde_json::json; -use near_workspaces::{Account, Contract, types::NearToken}; - -const NFT_WASM_FILEPATH: &str = "../../res/non_fungible_token.wasm"; -const TR_WASM_FILEPATH: &str = "../../res/token_receiver.wasm"; -const AR_WASM_FILEPATH: &str = "../../res/approval_receiver.wasm"; - -const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // initiate environemnt - let worker = near_workspaces::sandbox().await?; - - // deploy contracts - let nft_wasm = std::fs::read(NFT_WASM_FILEPATH)?; - let nft_contract = worker.dev_deploy(&nft_wasm).await?; - let tr_wasm = std::fs::read(TR_WASM_FILEPATH)?; - let tr_contract = worker.dev_deploy(&tr_wasm).await?; - let ar_wasm = std::fs::read(AR_WASM_FILEPATH)?; - let ar_contract = worker.dev_deploy(&ar_wasm).await?; - - // create accounts - let owner = worker.root_account().unwrap(); - let alice = owner - .create_subaccount("alice") - .initial_balance(NearToken::from_near(30)) - .transact() - .await? - .into_result()?; - - // Initialize contracts - let _ = nft_contract - .call("new_default_meta") - .args_json(serde_json::json!({ - "owner_id": owner.id() - })) - .transact() - .await?; - let _ = tr_contract - .call("new") - .args_json(serde_json::json!({ - "non_fungible_token_account_id": nft_contract.id() - })) - .transact() - .await?; - let _ = ar_contract - .call("new") - .args_json(serde_json::json!({ - "non_fungible_token_account_id": nft_contract.id() - })) - .transact() - .await?; - - // begin tests - test_simple_approve(&owner, &alice, &nft_contract).await?; - test_approval_simple_call(&owner, &nft_contract, &ar_contract).await?; - test_approved_account_transfers_token(&owner, &alice, &nft_contract).await?; - test_revoke(&owner, &alice, &nft_contract, &tr_contract).await?; - test_revoke_all(&owner, &alice, &nft_contract, &tr_contract).await?; - test_simple_transfer(&owner, &alice, &nft_contract).await?; - test_transfer_call_fast_return_to_sender(&owner, &tr_contract, &nft_contract).await?; - test_transfer_call_slow_return_to_sender(&owner, &tr_contract, &nft_contract).await?; - test_transfer_call_fast_keep_with_sender(&owner, &tr_contract, &nft_contract).await?; - test_transfer_call_slow_keep_with_sender(&owner, &tr_contract, &nft_contract).await?; - test_transfer_call_receiver_panics(&owner, &tr_contract, &nft_contract).await?; - test_enum_total_supply(&nft_contract).await?; - test_enum_nft_tokens(&nft_contract).await?; - test_enum_nft_supply_for_owner(&owner, &alice, &nft_contract).await?; - test_enum_nft_tokens_for_owner(&owner, &alice, &nft_contract).await?; - Ok(()) -} - -async fn test_simple_approve( - owner: &Account, - user: &Account, - nft_contract: &Contract -) -> anyhow::Result<()> { - let _ = owner - .call(nft_contract.id(), "nft_mint") - .args_json(json!({ - "token_id": "0", - "receiver_id": owner.id(), - "token_metadata": { - "title": "Olympus Mons", - "description": "The tallest mountain in the charted solar system", - "copies": 10000, - } - })) - .deposit(NearToken::from_yoctonear(5950000000000000000000)) - .transact() - .await; - - // root approves alice - let _ = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "0", - "account_id": user.id(), - })) - .deposit(NearToken::from_yoctonear(5950000000000000000000)) - .transact() - .await; - - let approval_no_id: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "0", - "approved_account_id": user.id() - })) - .transact() - .await? - .json()?; - - assert!(approval_no_id); - - let approval: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "0", - "approved_account_id": user.id(), - "approval_id": 1 - })) - .transact() - .await? - .json()?; - - assert!(approval); - - let approval_wrong_id: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "0", - "approved_account_id": user.id(), - "approval_id": 2 - })) - .transact() - .await? - .json()?; - - assert!(!approval_wrong_id); - println!(" Passed ✅ test_simple_approve"); - Ok(()) -} - -async fn test_approval_simple_call( - owner: &Account, - nft_contract: &Contract, - approval_receiver: &Contract -) -> anyhow::Result<()> { - let _ = owner - .call(nft_contract.id(), "nft_mint") - .args_json(json!({ - "token_id": "1", - "receiver_id": owner.id(), - "token_metadata": { - "title": "Olympus Mons 2", - "description": "The tallest mountain in the charted solar system", - "copies": 1, - } - })) - .deposit(NearToken::from_yoctonear(5950000000000000000000)) - .transact() - .await; - - let outcome: String = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": approval_receiver.id(), - "msg": "return-now" - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await? - .json()?; - assert_eq!("cool", outcome); - - let msg = "test message"; - let outcome: String = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": approval_receiver.id(), - "msg": msg, - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await? - .json()?; - assert_eq!(msg, outcome); - - println!(" Passed ✅ test_approval_simple_call"); - Ok(()) -} - -async fn test_approved_account_transfers_token( - owner: &Account, - user: &Account, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_transfer") - .args_json(json!({ - "receiver_id": user.id(), - "token_id": '0', - "approval_id": 1, - "memo": "message for test 3", - })) - .deposit(NearToken::from_yoctonear(1)) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "0"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(user.id().to_string()))); - - println!(" Passed ✅ test_approved_account_transfers_token"); - Ok(()) -} - -async fn test_revoke( - owner: &Account, - user: &Account, - nft_contract: &Contract, - token_receiver: &Contract -) -> anyhow::Result<()> { - let _ = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": user.id(), - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await; - - // root approves token_receiver - let _ = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": token_receiver.id(), - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await; - - // root revokes user - let _ = owner - .call(nft_contract.id(), "nft_revoke") - .args_json(json!({ - "token_id": "1", - "account_id": user.id(), - })) - .deposit(ONE_YOCTO) - .transact() - .await; - - // assert user is revoked - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": user.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, false); - - // assert token receiver still approved - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": token_receiver.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, true); - - // root revokes token_receiver - let _ = owner - .call(nft_contract.id(), "nft_revoke") - .args_json(json!({ - "token_id": "1", - "account_id": token_receiver.id(), - })) - .deposit(NearToken::from_yoctonear(1)) - .transact() - .await; - - // assert alice is still revoked - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": user.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, false); - - // and now so is token_receiver - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": token_receiver.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, false); - - println!(" Passed ✅ test_revoke"); - Ok(()) -} - -async fn test_revoke_all( - owner: &Account, - user: &Account, - nft_contract: &Contract, - token_receiver: &Contract -) -> anyhow::Result<()> { - // root approves alice - let _ = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": user.id(), - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await; - - // root approves token_receiver - let _ = owner - .call(nft_contract.id(), "nft_approve") - .args_json(json!({ - "token_id": "1", - "account_id": token_receiver.id(), - })) - .max_gas() - .deposit(NearToken::from_yoctonear(450000000000000000000)) - .transact() - .await; - - // assert everyone is revoked - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": user.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, true); - - let revoke_bool: bool = nft_contract - .call("nft_is_approved") - .args_json(json!({ - "token_id": "1", - "approved_account_id": token_receiver.id() - })) - .transact() - .await? - .json()?; - assert_eq!(revoke_bool, true); - - println!(" Passed ✅ test_revoke_all"); - Ok(()) -} - -async fn test_simple_transfer( - owner: &Account, - user: &Account, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "1"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(owner.id().to_string()))); - - let _ = owner - .call(nft_contract.id(), "nft_transfer") - .args_json(json!({ - "token_id": "1", - "receiver_id": user.id(), - })) - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "1"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(user.id().to_string()))); - - println!(" Passed ✅ test_simple_transfer"); - Ok(()) -} - -async fn test_transfer_call_fast_return_to_sender( - owner: &Account, - token_receiver: &Contract, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_mint") - .args_json(json!({ - "token_id": "2", - "receiver_id": owner.id(), - "token_metadata": { - "title": "Olympus Mons 3", - "description": "The tallest mountain in the charted solar system", - "copies": 1, - } - })) - .deposit(NearToken::from_yoctonear(6050000000000000000000)) - .transact() - .await; - - let _ = owner - .call(nft_contract.id(), "nft_transfer_call") - .args_json(json!({ - "token_id": "2", - "receiver_id": token_receiver.id(), - "memo": "transfer & call", - "msg": "return-it-now", - })) - .max_gas() - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "2"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(owner.id().to_string()))); - - println!(" Passed ✅ test_transfer_call_fast_return_to_sender"); - Ok(()) -} - -async fn test_transfer_call_slow_return_to_sender( - owner: &Account, - token_receiver: &Contract, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_transfer_call") - .args_json(json!({ - "token_id": "2", - "receiver_id": token_receiver.id(), - "memo": "transfer & call", - "msg": "return-it-later", - })) - .max_gas() - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "2"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(owner.id().to_string()))); - - println!(" Passed ✅ test_transfer_call_slow_return_to_sender"); - Ok(()) -} - -async fn test_transfer_call_fast_keep_with_sender( - owner: &Account, - token_receiver: &Contract, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_transfer_call") - .args_json(json!({ - "token_id": "2", - "receiver_id": token_receiver.id(), - "memo": "transfer & call", - "msg": "keep-it-now", - })) - .max_gas() - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "2"})) - .transact() - .await? - .json()?; - assert_eq!( - token.get("owner_id"), - Some(&String(token_receiver.id().to_string())) - ); - - println!(" Passed ✅ test_transfer_call_fast_keep_with_sender"); - Ok(()) -} - -async fn test_transfer_call_slow_keep_with_sender( - owner: &Account, - token_receiver: &Contract, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_mint") - .args_json(json!({ - "token_id": "3", - "receiver_id": owner.id(), - "token_metadata": { - "title": "Olympus Mons 4", - "description": "The tallest mountain in the charted solar system", - "copies": 1, - } - })) - .deposit(NearToken::from_yoctonear(6050000000000000000000)) - .transact() - .await; - - let _ = owner - .call(nft_contract.id(), "nft_transfer_call") - .args_json(json!({ - "token_id": "3", - "receiver_id": token_receiver.id(), - "memo": "transfer & call", - "msg": "keep-it-later", - })) - .max_gas() - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "3"})) - .transact() - .await? - .json()?; - assert_eq!( - token.get("owner_id"), - Some(&String(token_receiver.id().to_string())) - ); - - println!(" Passed ✅ test_transfer_call_slow_keep_with_sender"); - Ok(()) -} - -async fn test_transfer_call_receiver_panics( - owner: &Account, - token_receiver: &Contract, - nft_contract: &Contract -) -> anyhow::Result<()> { - use serde_json::Value::String; - let _ = owner - .call(nft_contract.id(), "nft_mint") - .args_json(json!({ - "token_id": "4", - "receiver_id": owner.id(), - "token_metadata": { - "title": "Olympus Mons 5", - "description": "The tallest mountain in the charted solar system", - "copies": 1, - } - })) - .max_gas() - .deposit(NearToken::from_yoctonear(6050000000000000000000)) - .transact() - .await; - - let _ = owner - .call(nft_contract.id(), "nft_transfer_call") - .args_json(json!({ - "token_id": "4", - "receiver_id": token_receiver.id(), - "memo": "transfer & call", - "msg": "incorrect message", - })) - .max_gas() - .deposit(ONE_YOCTO) - .transact() - .await; - - let token: serde_json::Value = nft_contract - .call("nft_token") - .args_json(json!({"token_id": "4"})) - .transact() - .await? - .json()?; - assert_eq!(token.get("owner_id"), Some(&String(owner.id().to_string()))); - - println!(" Passed ✅ test_transfer_call_receiver_panics"); - Ok(()) -} - -async fn test_enum_total_supply( - nft_contract: &Contract -) -> anyhow::Result<()> { - let supply: String = nft_contract - .call("nft_total_supply") - .args_json(json!({})) - .transact() - .await? - .json()?; - assert_eq!(supply, "5"); - - println!(" Passed ✅ test_enum_total_supply"); - Ok(()) -} - -async fn test_enum_nft_tokens( - nft_contract: &Contract -) -> anyhow::Result<()> { - let tokens: Vec = nft_contract - .call("nft_tokens") - .args_json(json!({})) - .transact() - .await? - .json()?; - - assert_eq!(tokens.len(), 5); - - println!(" Passed ✅ test_enum_nft_tokens"); - Ok(()) -} - -async fn test_enum_nft_supply_for_owner( - owner: &Account, - user: &Account, - nft_contract: &Contract -) -> anyhow::Result<()> { - let owner_tokens: String = nft_contract - .call("nft_supply_for_owner") - .args_json(json!({"account_id": owner.id()})) - .transact() - .await? - .json()?; - assert_eq!(owner_tokens, "1"); - - let user_tokens: String = nft_contract - .call("nft_supply_for_owner") - .args_json(json!({"account_id": user.id()})) - .transact() - .await? - .json()?; - assert_eq!(user_tokens, "2"); - - println!(" Passed ✅ test_enum_nft_supply_for_owner"); - Ok(()) -} - -async fn test_enum_nft_tokens_for_owner( - owner: &Account, - user: &Account, - nft_contract: &Contract -) -> anyhow::Result<()> { - let tokens: Vec = nft_contract - .call("nft_tokens_for_owner") - .args_json(json!({ - "account_id": user.id() - })) - .transact() - .await? - .json()?; - assert_eq!(tokens.len(), 2); - - let tokens: Vec = nft_contract - .call("nft_tokens_for_owner") - .args_json(json!({ - "account_id": owner.id() - })) - .transact() - .await? - .json()?; - assert_eq!(tokens.len(), 1); - println!(" Passed ✅ test_enum_nft_tokens_for_owner"); - Ok(()) -} diff --git a/nft/Cargo.toml b/nft/Cargo.toml deleted file mode 100644 index 8fe04465..00000000 --- a/nft/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "non-fungible-token" -version = "1.1.0" -authors = ["Near Inc "] -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -near-sdk = "5.5.0" -near-contract-standards = "5.5.0" - -[dev-dependencies] -near-sdk = { version = "5.5.0", features = ["unit-testing"] } \ No newline at end of file diff --git a/res/README.md b/res/README.md deleted file mode 100644 index 6a7e5c12..00000000 --- a/res/README.md +++ /dev/null @@ -1 +0,0 @@ -# Folder that contains wasm files \ No newline at end of file diff --git a/scripts/build.bat b/scripts/build.bat deleted file mode 100644 index fe407aa2..00000000 --- a/scripts/build.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off - -cd .. -title NFT build -cargo build --all --target wasm32-unknown-unknown --release -xcopy %CD%\target\wasm32-unknown-unknown\release\*.wasm %CD%\res /Y -pause \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index 6e14a8f6..00000000 --- a/scripts/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e -cd "`dirname $0`"/.. - -if [ -z "$KEEP_NAMES" ]; then - export RUSTFLAGS='-C link-arg=-s' -else - export RUSTFLAGS='' -fi - -rustup target add wasm32-unknown-unknown -cargo build --all --target wasm32-unknown-unknown --release -cp target/wasm32-unknown-unknown/release/*.wasm ./res/ \ No newline at end of file diff --git a/scripts/flags.sh b/scripts/flags.sh deleted file mode 100644 index d513d51c..00000000 --- a/scripts/flags.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -if [ -z "$KEEP_NAMES" ]; then - export RUSTFLAGS='-C link-arg=-s' -else - export RUSTFLAGS='' -fi \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index 9daf0207..00000000 --- a/scripts/test.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -./scripts/build.sh - -cd ./test-approval-receiver -cargo build - -cd ../test-token-receiver -cargo build - -cd ../integration-tests/rs -cargo run --example integration-tests - -cd ../ts -npm run test \ No newline at end of file diff --git a/nft/src/lib.rs b/src/lib.rs similarity index 100% rename from nft/src/lib.rs rename to src/lib.rs diff --git a/test-approval-receiver/Cargo.toml b/test-approval-receiver/Cargo.toml deleted file mode 100644 index 660bc336..00000000 --- a/test-approval-receiver/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "approval-receiver" -version = "0.0.1" -authors = ["Near Inc "] -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -near-sdk = "5.5.0" -near-contract-standards = "5.5.0" diff --git a/test-token-receiver/Cargo.toml b/test-token-receiver/Cargo.toml deleted file mode 100644 index f088aa7c..00000000 --- a/test-token-receiver/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "token-receiver" -version = "0.0.1" -authors = ["Near Inc "] -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -near-sdk = "5.5.0" -near-contract-standards = "5.5.0" diff --git a/tests/contracts/approval-receiver/Cargo.toml b/tests/contracts/approval-receiver/Cargo.toml new file mode 100644 index 00000000..804c54c1 --- /dev/null +++ b/tests/contracts/approval-receiver/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "approval-receiver" +version = "0.0.1" +authors = ["Near Inc "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "5.5.0" +near-contract-standards = "5.5.0" + +[dev-dependencies] +near-sdk = { version = "5.5.0", features = ["unit-testing"] } diff --git a/test-approval-receiver/src/lib.rs b/tests/contracts/approval-receiver/src/lib.rs similarity index 69% rename from test-approval-receiver/src/lib.rs rename to tests/contracts/approval-receiver/src/lib.rs index b40826e4..975e798e 100644 --- a/test-approval-receiver/src/lib.rs +++ b/tests/contracts/approval-receiver/src/lib.rs @@ -1,27 +1,25 @@ /*! -A stub contract that implements nft_on_approve for simulation testing nft_approve. +A stub contract that implements nft_on_approve for e2e testing nft_approve. */ use near_contract_standards::non_fungible_token::approval::NonFungibleTokenApprovalReceiver; use near_contract_standards::non_fungible_token::TokenId; -use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::{ - env, ext_contract, log, near_bindgen, AccountId, PanicOnDefault, - PromiseOrValue, -}; +use near_sdk::{env, log, near, require, AccountId, Gas, PanicOnDefault, PromiseOrValue}; -#[near_bindgen] -#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] +/// It is estimated that we need to attach 5 TGas for the code execution and 5 TGas for cross-contract call +const GAS_FOR_NFT_ON_APPROVE: Gas = Gas::from_tgas(10); + +#[near(contract_state)] +#[derive(PanicOnDefault)] pub struct ApprovalReceiver { non_fungible_token_account_id: AccountId, } -// Defining cross-contract interface. This allows to create a new promise. -#[ext_contract(ext_self)] +// Have to repeat the same trait for our own implementation. pub trait ValueReturnTrait { fn ok_go(&self, msg: String) -> PromiseOrValue; } -#[near_bindgen] +#[near] impl ApprovalReceiver { #[init] pub fn new(non_fungible_token_account_id: AccountId) -> Self { @@ -29,7 +27,7 @@ impl ApprovalReceiver { } } -#[near_bindgen] +#[near] impl NonFungibleTokenApprovalReceiver for ApprovalReceiver { /// Could do anything useful to the approval-receiving contract, such as store the given /// approval_id for use later when calling the NFT contract. Can also return whatever it wants, @@ -45,9 +43,8 @@ impl NonFungibleTokenApprovalReceiver for ApprovalReceiver { msg: String, ) -> PromiseOrValue { // Verifying that we were called by non-fungible token contract that we expect. - assert_eq!( - &env::predecessor_account_id(), - &self.non_fungible_token_account_id, + require!( + env::predecessor_account_id() == self.non_fungible_token_account_id, "Only supports the one non-fungible token contract" ); log!( @@ -60,18 +57,21 @@ impl NonFungibleTokenApprovalReceiver for ApprovalReceiver { match msg.as_str() { "return-now" => PromiseOrValue::Value("cool".to_string()), _ => { - // Call ok_go with no attached deposit and all unspent GAS (weight of 1) - Self::ext(env::current_account_id()) - .ok_go(msg).into() + let prepaid_gas = env::prepaid_gas(); + let account_id = env::current_account_id(); + Self::ext(account_id) + .with_static_gas(prepaid_gas.saturating_sub(GAS_FOR_NFT_ON_APPROVE)) + .ok_go(msg) + .into() } } } } -#[near_bindgen] +#[near] impl ValueReturnTrait for ApprovalReceiver { fn ok_go(&self, msg: String) -> PromiseOrValue { log!("in ok_go, msg={}", msg); PromiseOrValue::Value(msg) } -} \ No newline at end of file +} diff --git a/tests/contracts/token-receiver/Cargo.toml b/tests/contracts/token-receiver/Cargo.toml new file mode 100644 index 00000000..04b34727 --- /dev/null +++ b/tests/contracts/token-receiver/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "token-receiver" +version = "0.0.1" +authors = ["Near Inc "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "5.5.0" +near-contract-standards = "5.5.0" + +[dev-dependencies] +near-sdk = { version = "5.5.0", features = ["unit-testing"] } \ No newline at end of file diff --git a/test-token-receiver/src/lib.rs b/tests/contracts/token-receiver/src/lib.rs similarity index 64% rename from test-token-receiver/src/lib.rs rename to tests/contracts/token-receiver/src/lib.rs index 74946e2e..ccedf988 100644 --- a/test-token-receiver/src/lib.rs +++ b/tests/contracts/token-receiver/src/lib.rs @@ -3,32 +3,31 @@ A stub contract that implements nft_on_transfer for simulation testing nft_trans */ use near_contract_standards::non_fungible_token::core::NonFungibleTokenReceiver; use near_contract_standards::non_fungible_token::TokenId; -use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::{ - env, ext_contract, log, near_bindgen, AccountId, PanicOnDefault, - PromiseOrValue, -}; -#[near_bindgen] -#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] +use near_sdk::{env, log, near, require, AccountId, Gas, PanicOnDefault, PromiseOrValue}; + +/// It is estimated that we need to attach 5 TGas for the code execution and 5 TGas for cross-contract call +const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(10); + +#[near(contract_state)] +#[derive(PanicOnDefault)] pub struct TokenReceiver { non_fungible_token_account_id: AccountId, } -// Defining cross-contract interface. This allows to create a new promise. -#[ext_contract(ext_self)] +// Have to repeat the same trait for our own implementation. pub trait ValueReturnTrait { fn ok_go(&self, return_it: bool) -> PromiseOrValue; } -#[near_bindgen] +#[near] impl TokenReceiver { #[init] pub fn new(non_fungible_token_account_id: AccountId) -> Self { - Self { non_fungible_token_account_id: non_fungible_token_account_id.into() } + Self { non_fungible_token_account_id } } } -#[near_bindgen] +#[near] impl NonFungibleTokenReceiver for TokenReceiver { /// Returns true if token should be returned to `sender_id` /// Four supported `msg`s: @@ -45,9 +44,8 @@ impl NonFungibleTokenReceiver for TokenReceiver { msg: String, ) -> PromiseOrValue { // Verifying that we were called by non-fungible token contract that we expect. - assert_eq!( - &env::predecessor_account_id(), - &self.non_fungible_token_account_id, + require!( + env::predecessor_account_id() == self.non_fungible_token_account_id, "Only supports the one non-fungible token contract" ); log!( @@ -60,25 +58,31 @@ impl NonFungibleTokenReceiver for TokenReceiver { match msg.as_str() { "return-it-now" => PromiseOrValue::Value(true), "return-it-later" => { - // Call ok_go with no attached deposit and all unspent GAS (weight of 1) - Self::ext(env::current_account_id()) - .ok_go(true).into() + let prepaid_gas = env::prepaid_gas(); + let account_id = env::current_account_id(); + Self::ext(account_id) + .with_static_gas(prepaid_gas.saturating_sub(GAS_FOR_NFT_ON_TRANSFER)) + .ok_go(true) + .into() } "keep-it-now" => PromiseOrValue::Value(false), "keep-it-later" => { - // Call ok_go with no attached deposit and all unspent GAS (weight of 1) - Self::ext(env::current_account_id()) - .ok_go(false).into() + let prepaid_gas = env::prepaid_gas(); + let account_id = env::current_account_id(); + Self::ext(account_id) + .with_static_gas(prepaid_gas.saturating_sub(GAS_FOR_NFT_ON_TRANSFER)) + .ok_go(false) + .into() } _ => env::panic_str("unsupported msg"), } } } -#[near_bindgen] +#[near] impl ValueReturnTrait for TokenReceiver { fn ok_go(&self, return_it: bool) -> PromiseOrValue { log!("in ok_go, return_it={}", return_it); PromiseOrValue::Value(return_it) } -} \ No newline at end of file +} diff --git a/tests/init.rs b/tests/init.rs new file mode 100644 index 00000000..00360c58 --- /dev/null +++ b/tests/init.rs @@ -0,0 +1,122 @@ +use near_contract_standards::non_fungible_token::metadata::TokenMetadata; +use near_contract_standards::non_fungible_token::TokenId; + +use near_workspaces::types::NearToken; +use near_workspaces::{Account, Contract, DevNetwork, Worker}; + +pub const TOKEN_ID: &str = "0"; + +pub async fn helper_mint( + nft_contract: &Contract, + token_id: TokenId, + title: String, + desc: String, +) -> anyhow::Result<()> { + let token_metadata = TokenMetadata { + title: Some(title), + description: Some(desc), + media: None, + media_hash: None, + copies: Some(1u64), + issued_at: None, + expires_at: None, + starts_at: None, + updated_at: None, + extra: None, + reference: None, + reference_hash: None, + }; + let res = nft_contract + .call("nft_mint") + .args_json((token_id, nft_contract.id(), token_metadata)) + .max_gas() + .deposit(NearToken::from_millinear(7)) + .transact() + .await?; + assert!(res.is_success()); + + Ok(()) +} + +/// Deploy and initialize contracts and return: +/// * nft_contract: the NFT contract, callable with `call!` and `view!` +/// * alice: a user account, does not yet own any tokens +/// * token_receiver_contract: a contract implementing `nft_on_transfer` for use with `transfer_and_call` +/// * approval_receiver_contract: a contract implementing `nft_on_approve` for use with `nft_approve` +pub async fn init( + worker: &Worker, +) -> anyhow::Result<(Contract, Contract, Contract, Account)> { + let nft_wasm = near_workspaces::compile_project(".").await?; + let nft_contract = worker.dev_deploy(&nft_wasm).await?; + + let res = nft_contract + .call("new_default_meta") + .args_json((nft_contract.id(),)) + .max_gas() + .transact() + .await?; + assert!(res.is_success()); + + let token_metadata = TokenMetadata { + title: Some("Olympus Mons".into()), + description: Some("The tallest mountain in the charted solar system".into()), + media: None, + media_hash: None, + copies: Some(1u64), + issued_at: None, + expires_at: None, + starts_at: None, + updated_at: None, + extra: None, + reference: None, + reference_hash: None, + }; + let res = nft_contract + .call("nft_mint") + .args_json((TOKEN_ID, nft_contract.id(), token_metadata)) + .max_gas() + .deposit(NearToken::from_millinear(7)) + .transact() + .await?; + assert!(res.is_success()); + + let res = nft_contract + .as_account() + .create_subaccount("alice") + .initial_balance(NearToken::from_near(10)) + .transact() + .await?; + assert!(res.is_success()); + let alice = res.result; + + let token_receiver_wasm = + near_workspaces::compile_project("./tests/contracts/token-receiver").await?; + let token_receiver_contract = worker.dev_deploy(&token_receiver_wasm).await?; + + let res = token_receiver_contract + .call("new") + .args_json((nft_contract.id(),)) + .max_gas() + .transact() + .await?; + assert!(res.is_success()); + + let approval_receiver_wasm = + near_workspaces::compile_project("./tests/contracts/approval-receiver").await?; + let approval_receiver_contract = worker.dev_deploy(&approval_receiver_wasm).await?; + + let res = approval_receiver_contract + .call("new") + .args_json((nft_contract.id(),)) + .max_gas() + .transact() + .await?; + assert!(res.is_success()); + + return Ok(( + nft_contract, + approval_receiver_contract, + token_receiver_contract, + alice, + )); +} diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 00000000..ee78f7f2 --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,2 @@ +mod init; +mod tests; diff --git a/tests/tests/approval.rs b/tests/tests/approval.rs new file mode 100644 index 00000000..2789ba51 --- /dev/null +++ b/tests/tests/approval.rs @@ -0,0 +1,332 @@ +use crate::init::init; + +use near_contract_standards::non_fungible_token::Token; +use near_workspaces::{types::NearToken, AccountId}; + +use std::collections::HashMap; +use std::convert::TryFrom; + +pub const TOKEN_ID: &str = "0"; + +const ONE_NEAR: NearToken = NearToken::from_near(1); +const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); + +#[tokio::test] +async fn test_simple_approve() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, alice) = init(&worker).await?; + + // root approves alice + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .max_gas() + .deposit(NearToken::from_yoctonear(510000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // check nft_is_approved, don't provide approval_id + let alice_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .view() + .await? + .json::()?; + assert!(alice_approved); + + // check nft_is_approved, with approval_id=1 + let alice_approval_id_is_1 = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Some(1u64))) + .view() + .await? + .json::()?; + assert!(alice_approval_id_is_1); + + // check nft_is_approved, with approval_id=2 + let alice_approval_id_is_2 = nft_contract + .call("nft_is_approved") + .args_json(&(TOKEN_ID, alice.id(), Some(2u64))) + .view() + .await? + .json::()?; + assert!(!alice_approval_id_is_2); + + // alternatively, one could check the data returned by nft_token + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + let mut expected_approvals: HashMap = HashMap::new(); + expected_approvals.insert(AccountId::try_from(alice.id().to_string())?, 1); + assert_eq!(token.approved_account_ids.unwrap(), expected_approvals); + + // root approves alice again, which changes the approval_id and doesn't require as much deposit + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .max_gas() + .deposit(ONE_NEAR) + .transact() + .await?; + assert!(res.is_success()); + + let alice_approval_id_is_2 = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Some(2u64))) + .view() + .await? + .json::()?; + assert!(alice_approval_id_is_2); + + // approving another account gives different approval_id + let res = nft_contract + .call("nft_approve") + .args_json(( + TOKEN_ID, + token_receiver_contract.id(), + Option::::None, + )) + .max_gas() + .deposit(NearToken::from_yoctonear(510000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + let token_receiver_approval_id_is_3 = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, token_receiver_contract.id(), Some(3u64))) + .view() + .await? + .json::()?; + assert!(token_receiver_approval_id_is_3); + + Ok(()) +} + +#[tokio::test] +async fn test_approval_with_call() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, approval_receiver_contract, _, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_approve") + .args_json(( + TOKEN_ID, + approval_receiver_contract.id(), + Some("return-now".to_string()), + )) + .max_gas() + .deposit(NearToken::from_yoctonear(450000000000000000000)) + .transact() + .await?; + assert_eq!(res.json::()?, "cool".to_string()); + + // Approve again; will set different approval_id (ignored by approval_receiver). + // The approval_receiver implementation will return given `msg` after subsequent promise call, + // if given something other than "return-now". + let msg = "hahaha".to_string(); + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, approval_receiver_contract.id(), Some(msg.clone()))) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert_eq!(res.json::()?, msg); + + Ok(()) +} + +#[tokio::test] +async fn test_approved_account_transfers_token() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, alice) = init(&worker).await?; + + // root approves alice + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .max_gas() + .deposit(NearToken::from_yoctonear(510000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // alice sends to self + let res = alice + .call(nft_contract.id(), "nft_transfer") + .args_json(( + alice.id(), + TOKEN_ID, + Some(1u64), + Some("gotcha! bahahaha".to_string()), + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // token now owned by alice + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), alice.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_revoke() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, alice) = init(&worker).await?; + + // root approves alice + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .max_gas() + .deposit(NearToken::from_yoctonear(510000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // root approves token_receiver + let res = nft_contract + .call("nft_approve") + .args_json(( + TOKEN_ID, + token_receiver_contract.id(), + Option::::None, + )) + .max_gas() + .deposit(NearToken::from_yoctonear(450000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // root revokes alice + let res = nft_contract + .call("nft_revoke") + .args_json((TOKEN_ID, alice.id())) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // alice is revoked... + let alice_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Some(3u64))) + .view() + .await? + .json::()?; + assert!(!alice_approved); + + // but token_receiver is still approved + let token_receiver_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, token_receiver_contract.id(), Option::::None)) + .view() + .await? + .json::()?; + assert!(token_receiver_approved); + + // root revokes token_receiver + let res = nft_contract + .call("nft_revoke") + .args_json((TOKEN_ID, token_receiver_contract.id())) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // alice is still revoked... + let alice_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Some(3u64))) + .view() + .await? + .json::()?; + assert!(!alice_approved); + + // ...and now so is token_receiver + let token_receiver_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, token_receiver_contract.id(), Option::::None)) + .view() + .await? + .json::()?; + assert!(!token_receiver_approved); + + Ok(()) +} + +#[tokio::test] +async fn test_revoke_all() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, alice) = init(&worker).await?; + + // root approves alice + let res = nft_contract + .call("nft_approve") + .args_json((TOKEN_ID, alice.id(), Option::::None)) + .max_gas() + .deposit(NearToken::from_yoctonear(510000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // root approves token_receiver + let res = nft_contract + .call("nft_approve") + .args_json(( + TOKEN_ID, + token_receiver_contract.id(), + Option::::None, + )) + .max_gas() + .deposit(NearToken::from_yoctonear(450000000000000000000)) + .transact() + .await?; + assert!(res.is_success()); + + // root revokes all + let res = nft_contract + .call("nft_revoke_all") + .args_json((TOKEN_ID,)) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // alice is revoked... + let alice_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, alice.id(), Some(3u64))) + .view() + .await? + .json::()?; + assert!(!alice_approved); + + // and so is token_receiver + let token_receiver_approved = nft_contract + .call("nft_is_approved") + .args_json((TOKEN_ID, token_receiver_contract.id(), Option::::None)) + .view() + .await? + .json::()?; + assert!(!token_receiver_approved); + + Ok(()) +} diff --git a/tests/tests/core.rs b/tests/tests/core.rs new file mode 100644 index 00000000..7dfbb87b --- /dev/null +++ b/tests/tests/core.rs @@ -0,0 +1,281 @@ +use crate::init::{init, TOKEN_ID}; +use near_contract_standards::non_fungible_token::Token; + +use near_workspaces::types::NearToken; + +const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); + +#[tokio::test] +async fn test_simple_transfer() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, alice) = init(&worker).await?; + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + let res = nft_contract + .call("nft_transfer") + .args_json(( + alice.id(), + TOKEN_ID, + Option::::None, + Some("simple transfer".to_string()), + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // A single NFT transfer event should have been logged: + assert_eq!(res.logs().len(), 1); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), alice.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_fast_return_to_sender() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "return-it-now", + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_slow_return_to_sender() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "return-it-later", + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_fast_keep_with_sender() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "keep-it-now", + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + assert_eq!(res.logs().len(), 2); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!( + token.owner_id.to_string(), + token_receiver_contract.id().to_string() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_slow_keep_with_sender() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "keep-it-later", + )) + .max_gas() + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!( + token.owner_id.to_string(), + token_receiver_contract.id().to_string() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_receiver_panics() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "incorrect message", + )) + .gas(near_sdk::Gas::from_gas(35_000_000_000_000 + 1)) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + // Prints final logs + assert_eq!(res.logs().len(), 3); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_transfer_call_receiver_panics_and_nft_resolve_transfer_produces_no_log_if_not_enough_gas( +) -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, token_receiver_contract, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer_call") + .args_json(( + token_receiver_contract.id(), + TOKEN_ID, + Option::::None, + Some("transfer & call"), + "incorrect message", + )) + .gas(near_sdk::Gas::from_tgas(30)) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_failure()); + + // Prints no logs + assert_eq!(res.logs().len(), 0); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_simple_transfer_no_logs_on_failure() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, _) = init(&worker).await?; + + let res = nft_contract + .call("nft_transfer") + // transfer to the current owner should fail and not print log + .args_json(( + nft_contract.id(), + TOKEN_ID, + Option::::None, + Some("simple transfer"), + )) + .gas(near_sdk::Gas::from_tgas(200)) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_failure()); + + // Prints no logs + assert_eq!(res.logs().len(), 0); + + let token = nft_contract + .call("nft_token") + .args_json((TOKEN_ID,)) + .view() + .await? + .json::()?; + assert_eq!(token.owner_id.to_string(), nft_contract.id().to_string()); + + Ok(()) +} diff --git a/tests/tests/enumeration.rs b/tests/tests/enumeration.rs new file mode 100644 index 00000000..712f79a0 --- /dev/null +++ b/tests/tests/enumeration.rs @@ -0,0 +1,187 @@ +use crate::init::{helper_mint, init}; +use near_contract_standards::non_fungible_token::Token; +use near_sdk::json_types::U128; +use near_workspaces::Contract; + +async fn mint_more(nft_contract: &Contract) -> anyhow::Result<()> { + helper_mint( + nft_contract, + "1".to_string(), + "Black as the Night".to_string(), + "In charcoal".to_string(), + ) + .await?; + helper_mint( + nft_contract, + "2".to_string(), + "Hamakua".to_string(), + "Vintage recording".to_string(), + ) + .await?; + helper_mint( + nft_contract, + "3".to_string(), + "Aloha ke akua".to_string(), + "Original with piano".to_string(), + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn test_enum_total_supply() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, _) = init(&worker).await?; + mint_more(&nft_contract).await?; + + let total_supply: U128 = nft_contract.call("nft_total_supply").view().await?.json()?; + assert_eq!(total_supply, U128::from(4)); + + Ok(()) +} + +#[tokio::test] +async fn test_enum_nft_tokens() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, _) = init(&worker).await?; + mint_more(&nft_contract).await?; + + // No optional args should return all + let mut tokens: Vec = nft_contract + .call("nft_tokens") + .args_json((Option::::None, Option::::None)) + .view() + .await? + .json()?; + assert_eq!(tokens.len(), 4); + // Start at "1", with no limit arg + tokens = nft_contract + .call("nft_tokens") + .args_json((Some(U128::from(1)), Option::::None)) + .view() + .await? + .json()?; + assert_eq!(tokens.len(), 3); + assert_eq!(tokens.get(0).unwrap().token_id, "1".to_string()); + assert_eq!(tokens.get(1).unwrap().token_id, "2".to_string()); + assert_eq!(tokens.get(2).unwrap().token_id, "3".to_string()); + + // Start at "2", with limit 1 + tokens = nft_contract + .call("nft_tokens") + .args_json((Some(U128::from(2)), Some(1u64))) + .view() + .await? + .json()?; + assert_eq!(tokens.len(), 1); + assert_eq!(tokens.get(0).unwrap().token_id, "2".to_string()); + + // Don't specify from_index, but limit 2 + tokens = nft_contract + .call("nft_tokens") + .args_json((Option::::None, Some(2u64))) + .view() + .await? + .json()?; + assert_eq!(tokens.len(), 2); + assert_eq!(tokens.get(0).unwrap().token_id, "0".to_string()); + assert_eq!(tokens.get(1).unwrap().token_id, "1".to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_enum_nft_supply_for_owner() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, alice) = init(&worker).await?; + + // Get number from account with no NFTs + let owner_num_tokens: U128 = nft_contract + .call("nft_supply_for_owner") + .args_json((alice.id(),)) + .view() + .await? + .json()?; + assert_eq!(owner_num_tokens, U128::from(0)); + + let owner_num_tokens: U128 = nft_contract + .call("nft_supply_for_owner") + .args_json((nft_contract.id(),)) + .view() + .await? + .json()?; + assert_eq!(owner_num_tokens, U128::from(1)); + + mint_more(&nft_contract).await?; + + let owner_num_tokens: U128 = nft_contract + .call("nft_supply_for_owner") + .args_json((nft_contract.id(),)) + .view() + .await? + .json()?; + assert_eq!(owner_num_tokens, U128::from(4)); + + Ok(()) +} + +#[tokio::test] +async fn test_enum_nft_tokens_for_owner() -> anyhow::Result<()> { + let worker = near_workspaces::sandbox().await?; + let (nft_contract, _, _, alice) = init(&worker).await?; + mint_more(&nft_contract).await?; + + // Get tokens from account with no NFTs + let owner_tokens: Vec = nft_contract + .call("nft_tokens_for_owner") + .args_json((alice.id(), Option::::None, Option::::None)) + .view() + .await? + .json()?; + assert_eq!(owner_tokens.len(), 0); + + // Get tokens with no optional args + let owner_tokens: Vec = nft_contract + .call("nft_tokens_for_owner") + .args_json((nft_contract.id(), Option::::None, Option::::None)) + .view() + .await? + .json()?; + assert_eq!(owner_tokens.len(), 4); + + // With from_index and no limit + let owner_tokens: Vec = nft_contract + .call("nft_tokens_for_owner") + .args_json((nft_contract.id(), Some(U128::from(2)), Option::::None)) + .view() + .await? + .json()?; + assert_eq!(owner_tokens.len(), 2); + assert_eq!(owner_tokens.get(0).unwrap().token_id, "2".to_string()); + assert_eq!(owner_tokens.get(1).unwrap().token_id, "3".to_string()); + + // With from_index and limit 1 + let owner_tokens: Vec = nft_contract + .call("nft_tokens_for_owner") + .args_json((nft_contract.id(), Some(U128::from(1)), Some(1u64))) + .view() + .await? + .json()?; + assert_eq!(owner_tokens.len(), 1); + assert_eq!(owner_tokens.get(0).unwrap().token_id, "1".to_string()); + + // No from_index but limit 3 + let owner_tokens: Vec = nft_contract + .call("nft_tokens_for_owner") + .args_json((nft_contract.id(), Option::::None, Some(3u64))) + .view() + .await? + .json()?; + assert_eq!(owner_tokens.len(), 3); + assert_eq!(owner_tokens.get(0).unwrap().token_id, "0".to_string()); + assert_eq!(owner_tokens.get(1).unwrap().token_id, "1".to_string()); + assert_eq!(owner_tokens.get(2).unwrap().token_id, "2".to_string()); + + Ok(()) +} diff --git a/tests/tests/mod.rs b/tests/tests/mod.rs new file mode 100644 index 00000000..928ebc4f --- /dev/null +++ b/tests/tests/mod.rs @@ -0,0 +1,3 @@ +mod approval; +mod core; +mod enumeration;