diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c2da5c..f6da58a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,4 +47,4 @@ jobs: run: cargo build --all-features - name: Run tests - run: cargo test --all -- --test-threads=1 + run: cargo test --all diff --git a/Cargo.lock b/Cargo.lock index e886624..96aa3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6364,6 +6364,14 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-ft-contract" +version = "0.1.0" +dependencies = [ + "near-sdk", + "near-sdk-contract-tools", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 46097dc..4fbce74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/crosschain-indexer-centralized", "crates/oracle-contract", "crates/reclaim-gpt-contract", + "crates/test-ft-contract", ] resolver = "2" diff --git a/README.md b/README.md index 2716662..6900e3f 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,10 @@ Some data producers may choose to charge a fee for requesting some data using th `set_fee(fee: ProducerFee)`, ProducerFee is defined in [crates/oracle-contract/src/producer.rs](crates/oracle-contract/src/producer.rs). -The oracle contract uses prepaid balance to optimize the amount of receipts generated, reducing the latency, but may allow per-request pricing in -the future, if someone wants this feature added. To top up your balance, use these methods: +The oracle contract uses prepaid balance to optimize the amount of receipts generated, reducing the latency, and allows per-request pricing. To top up your balance, use these methods: `deposit_near(account_id: Option default predecessor, producer_id: Option default all)` -`ft_transfer_call` on the token with msg `account_id: Option default predecessor, producer_id: Option default all` +`ft_transfer_call` on the token with msg `account_id: Option default sender, producer_id: Option default all` Parameters: - `account_id`: Account to top up. If not specified, defaults to the predecessor account, but you can top up someone else's balance in the contract diff --git a/crates/oracle-contract/Cargo.toml b/crates/oracle-contract/Cargo.toml index 0b229c7..d25ae07 100644 --- a/crates/oracle-contract/Cargo.toml +++ b/crates/oracle-contract/Cargo.toml @@ -7,20 +7,13 @@ repository = "https://github.com/INTEARnear/oracle" license = "MIT OR Apache-2.0" [package.metadata.near.reproducible_build] -# docker image, descriptor of build environment image = "sourcescan/cargo-near:0.12.2-rust-1.82.0" -# tag after colon above serves only descriptive purpose; image is identified by digest image_digest = "sha256:5013a742e19a95c108bdfce085a57bda2f1047248e5eb9f005a8adc1ec8a1e42" -# build command inside of docker container -# if docker image from default gallery is used https://hub.docker.com/r/sourcescan/cargo-near/tags, -# the command may be any combination of flags of `cargo-near`, -# supported by respective version of binary inside the container besides `--no-locked` flag container_build_command = ["cargo", "near", "build"] [lib] crate-type = ["cdylib", "rlib"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] near-sdk = "5.5" near-sdk-contract-tools = "3.0.2" diff --git a/crates/oracle-contract/rust-toolchain.toml b/crates/oracle-contract/rust-toolchain.toml deleted file mode 100644 index ba926b0..0000000 --- a/crates/oracle-contract/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "1.81.0" -profile = "default" -targets = ["wasm32-unknown-unknown"] diff --git a/crates/oracle-contract/src/balance.rs b/crates/oracle-contract/src/balance.rs index 90fe030..e52dd41 100644 --- a/crates/oracle-contract/src/balance.rs +++ b/crates/oracle-contract/src/balance.rs @@ -2,7 +2,7 @@ use near_sdk::{ env, json_types::U128, log, near, serde_json, AccountId, Gas, NearToken, Promise, PromiseOrValue, }; -use near_sdk_contract_tools::ft::Nep141Receiver; +use near_sdk_contract_tools::ft::{ext_nep141, Nep141Receiver}; use crate::producer::ProducerId; #[cfg(feature = "contract")] @@ -161,17 +161,11 @@ impl Oracle { amount = amount.0 ); } - // TODO replace with high-level call when not using git near-sdk - Promise::new(ft_id).function_call( - "ft_transfer".to_string(), - serde_json::to_vec(&serde_json::json!({ - "amount": amount, - "receiver_id": account_id, - })) - .unwrap(), - NearToken::from_near(0), - Gas::from_tgas(10), - ); + + ext_nep141::ext(ft_id) + .with_static_gas(Gas::from_tgas(10)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .ft_transfer(account_id, amount, None); } } diff --git a/crates/oracle-contract/src/fees.rs b/crates/oracle-contract/src/fees.rs index a832c9a..f7d21f5 100644 --- a/crates/oracle-contract/src/fees.rs +++ b/crates/oracle-contract/src/fees.rs @@ -1,4 +1,5 @@ -use near_sdk::{env, json_types::U128, near, NearToken, Promise}; +use near_sdk::{env, json_types::U128, near, Gas, NearToken, Promise}; +use near_sdk_contract_tools::ft::ext_nep141; use crate::{balance::FtId, consumer::ConsumerId, producer::ProducerId}; #[cfg(feature = "contract")] @@ -24,23 +25,30 @@ pub enum PrepaidFee { None, Near { amount: NearToken, - payment_type: PaymentType, + payment_type: NearPaymentType, }, FungibleToken { token: FtId, amount: U128, - payment_type: PaymentType, + payment_type: FtPaymentType, }, } #[near(serializers=[json, borsh])] #[derive(Debug, Clone, PartialEq)] -pub enum PaymentType { +pub enum NearPaymentType { ForSpecificProducer, ForAllProducers, AttachedToCall, } +#[near(serializers=[json, borsh])] +#[derive(Debug, Clone, PartialEq)] +pub enum FtPaymentType { + ForSpecificProducer, + ForAllProducers, +} + #[cfg(feature = "contract")] #[near] impl Oracle { @@ -77,7 +85,7 @@ impl Oracle { if !env::attached_deposit().is_zero() { return Some(PrepaidFee::Near { amount: env::attached_deposit(), - payment_type: PaymentType::AttachedToCall, + payment_type: NearPaymentType::AttachedToCall, }); } @@ -94,7 +102,7 @@ impl Oracle { } return Some(PrepaidFee::Near { amount: *prepaid_amount, - payment_type: PaymentType::ForSpecificProducer, + payment_type: NearPaymentType::ForSpecificProducer, }); } } @@ -104,16 +112,56 @@ impl Oracle { consumer.near_balance.checked_sub(*prepaid_amount).unwrap(); return Some(PrepaidFee::Near { amount: *prepaid_amount, - payment_type: PaymentType::ForAllProducers, + payment_type: NearPaymentType::ForAllProducers, }); } None } ProducerFee::FungibleToken { - token: _, - prepaid_amount: _, - } => unimplemented!(), + token, + prepaid_amount, + } => { + let consumer = self + .consumers + .get_mut(consumer_id) + .expect("Consumer is not registered"); + + if let Some(ft_balance) = consumer + .ft_balances_producer + .get_mut(&(producer_id.clone(), token.clone())) + { + if *ft_balance >= *prepaid_amount { + *ft_balance = ft_balance.0.checked_sub(prepaid_amount.0).unwrap().into(); + if ft_balance.0 == 0 { + consumer + .ft_balances_producer + .remove(&(producer_id.clone(), token.clone())); + } + return Some(PrepaidFee::FungibleToken { + token: token.clone(), + amount: *prepaid_amount, + payment_type: FtPaymentType::ForSpecificProducer, + }); + } + } + + if let Some(ft_balance) = consumer.ft_balances.get_mut(token) { + if *ft_balance >= *prepaid_amount { + *ft_balance = ft_balance.0.checked_sub(prepaid_amount.0).unwrap().into(); + if ft_balance.0 == 0 { + consumer.ft_balances.remove(token); + } + return Some(PrepaidFee::FungibleToken { + token: token.clone(), + amount: *prepaid_amount, + payment_type: FtPaymentType::ForAllProducers, + }); + } + } + + None + } } } @@ -130,7 +178,7 @@ impl Oracle { amount: _, payment_type, } => match payment_type { - PaymentType::ForSpecificProducer => { + NearPaymentType::ForSpecificProducer => { let consumer = self .consumers .get_mut(consumer_id) @@ -148,7 +196,7 @@ impl Oracle { ); } } - PaymentType::ForAllProducers => { + NearPaymentType::ForAllProducers => { let consumer = self .consumers .get_mut(consumer_id) @@ -159,16 +207,46 @@ impl Oracle { .checked_add(NearToken::from_yoctonear(refund_amount.0)) .unwrap(); } - PaymentType::AttachedToCall => { + NearPaymentType::AttachedToCall => { Promise::new(consumer_id.clone()) .transfer(NearToken::from_yoctonear(refund_amount.0)); } }, PrepaidFee::FungibleToken { - token: _, + token, amount: _, - payment_type: _, - } => unimplemented!(), + payment_type, + } => match payment_type { + FtPaymentType::ForSpecificProducer => { + let consumer = self + .consumers + .get_mut(consumer_id) + .expect("Consumer is not registered"); + + if let Some(ft_balance) = consumer + .ft_balances_producer + .get_mut(&(producer_id.clone(), token.clone())) + { + *ft_balance = ft_balance.0.checked_add(refund_amount.0).unwrap().into(); + } else { + consumer + .ft_balances_producer + .insert((producer_id.clone(), token.clone()), refund_amount); + } + } + FtPaymentType::ForAllProducers => { + let consumer = self + .consumers + .get_mut(consumer_id) + .expect("Consumer is not registered"); + + if let Some(ft_balance) = consumer.ft_balances.get_mut(token) { + *ft_balance = ft_balance.0.checked_add(refund_amount.0).unwrap().into(); + } else { + consumer.ft_balances.insert(token.clone(), refund_amount); + } + } + }, } } @@ -190,7 +268,7 @@ impl Oracle { .expect("Consumer is not registered"); match payment_type { - PaymentType::ForSpecificProducer => { + NearPaymentType::ForSpecificProducer => { if let Some(near_balance) = consumer.near_balance_producer.get_mut(producer_id) { @@ -201,19 +279,49 @@ impl Oracle { .insert(producer_id.clone(), *amount); } } - PaymentType::ForAllProducers => { + NearPaymentType::ForAllProducers => { consumer.near_balance = consumer.near_balance.checked_add(*amount).unwrap(); } - PaymentType::AttachedToCall => { + NearPaymentType::AttachedToCall => { Promise::new(consumer_id.clone()).transfer(*amount); } } } PrepaidFee::FungibleToken { - token: _, - amount: _, - payment_type: _, - } => unimplemented!(), + token, + amount, + payment_type, + } => match payment_type { + FtPaymentType::ForSpecificProducer => { + let consumer = self + .consumers + .get_mut(consumer_id) + .expect("Consumer is not registered"); + + if let Some(ft_balance) = consumer + .ft_balances_producer + .get_mut(&(producer_id.clone(), token.clone())) + { + *ft_balance = ft_balance.0.checked_add(amount.0).unwrap().into(); + } else { + consumer + .ft_balances_producer + .insert((producer_id.clone(), token.clone()), *amount); + } + } + FtPaymentType::ForAllProducers => { + let consumer = self + .consumers + .get_mut(consumer_id) + .expect("Consumer is not registered"); + + if let Some(ft_balance) = consumer.ft_balances.get_mut(token) { + *ft_balance = ft_balance.0.checked_add(amount.0).unwrap().into(); + } else { + consumer.ft_balances.insert(token.clone(), *amount); + } + } + }, } } @@ -238,10 +346,22 @@ impl Oracle { } } PrepaidFee::FungibleToken { - token: _, - amount: _, + token, + amount, payment_type: _, - } => unimplemented!(), + } => { + if let Some(deposit_amount) = + amount.0.checked_sub(refund_amount.unwrap_or(U128(0)).0) + { + // TODO handle when the token is not registered in producer's account + ext_nep141::ext(token.clone()) + .with_static_gas(Gas::from_tgas(10)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .ft_transfer(producer_id.clone(), deposit_amount.into(), None); + } else { + env::panic_str("Refund amount is greater than prepaid amount") + } + } } } } diff --git a/crates/oracle-contract/tests/deposits.rs b/crates/oracle-contract/tests/deposits.rs deleted file mode 100644 index e16654b..0000000 --- a/crates/oracle-contract/tests/deposits.rs +++ /dev/null @@ -1,234 +0,0 @@ -use near_sdk::NearToken; -use serde_json::json; - -#[tokio::test] -async fn near_deposits() -> Result<(), Box> { - let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); - - let contract = sandbox.dev_deploy(contract_wasm).await?; - - let consumer_account = sandbox.dev_create_account().await?; - let initial_balance = consumer_account.view_account().await?.balance; - - let outcome = consumer_account - .call(contract.id(), "register_consumer") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .transact() - .await?; - assert!(outcome.is_success()); - - let outcome = consumer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(0) - ); - - let outcome = consumer_account - .call(contract.id(), "deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .deposit(NearToken::from_near(1)) - .transact() - .await?; - assert!(outcome.is_success()); - assert_eq!( - outcome.logs(), - vec![format!( - "Deposited 1.00 NEAR to {consumer_id}", - consumer_id = consumer_account.id() - )] - ); - - let outcome = consumer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(1) - ); - - let outcome = consumer_account - .call(contract.id(), "deposit_near") - .args_json(json!({})) - .deposit(NearToken::from_near(1)) - .transact() - .await?; - assert!(outcome.is_success()); - assert_eq!( - outcome.logs(), - vec![format!( - "Deposited 1.00 NEAR to {consumer_id}", - consumer_id = consumer_account.id() - )] - ); - - let outcome = consumer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(2) - ); - - let new_balance = consumer_account.view_account().await?.balance; - assert!( - initial_balance - .checked_sub(new_balance) - .unwrap() - .checked_sub(NearToken::from_near(2)) - .unwrap() - < NearToken::from_millinear(5) // gas fee - ); - - let outcome = consumer_account - .call(contract.id(), "withdraw_near") - .args_json(json!({ - "amount": NearToken::from_near(1), - })) - .transact() - .await?; - assert!(outcome.is_success()); - assert_eq!( - outcome.logs(), - vec![format!( - "Withdrew 1.00 NEAR from {account_id}", - account_id = consumer_account.id() - )] - ); - - let new_balance = consumer_account.view_account().await?.balance; - assert!( - initial_balance - .checked_sub(new_balance) - .unwrap() - .checked_sub(NearToken::from_near(2 - 1)) - .unwrap() - < NearToken::from_millinear(5) // gas fee - ); - - let outcome = consumer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(2 - 1) - ); - - Ok(()) -} - -#[tokio::test] -async fn near_deposit_for_producer() -> Result<(), Box> { - let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); - - let contract = sandbox.dev_deploy(contract_wasm).await?; - - let consumer_account = sandbox.dev_create_account().await?; - let producer_account = sandbox.dev_create_account().await?; - let initial_balance = consumer_account.view_account().await?.balance; - - let outcome = consumer_account - .call(contract.id(), "register_consumer") - .args_json(json!({ - "account_id": consumer_account.id(), - })) - .transact() - .await?; - assert!(outcome.is_success()); - - let outcome = consumer_account - .call(contract.id(), "deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - "producer_id": producer_account.id(), - })) - .deposit(NearToken::from_near(1)) - .transact() - .await?; - assert!(outcome.is_failure()); - - let outcome = producer_account - .call(contract.id(), "add_producer") - .args_json(json!({ - "account_id": producer_account.id(), - })) - .transact() - .await?; - assert!(outcome.is_success()); - - let outcome = producer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - "producer_id": producer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(0) - ); - - let outcome = consumer_account - .call(contract.id(), "deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - "producer_id": producer_account.id(), - })) - .deposit(NearToken::from_near(1)) - .transact() - .await?; - assert!(outcome.is_success()); - assert_eq!( - outcome.logs(), - vec![format!( - "Deposited 1.00 NEAR to {account_id} for {producer_id}", - account_id = consumer_account.id(), - producer_id = producer_account.id(), - )] - ); - - let outcome = producer_account - .view(contract.id(), "get_deposit_near") - .args_json(json!({ - "account_id": consumer_account.id(), - "producer_id": producer_account.id(), - })) - .await?; - assert_eq!( - outcome.json::().unwrap(), - NearToken::from_near(1) - ); - - let new_balance = consumer_account.view_account().await?.balance; - assert!( - initial_balance - .checked_sub(new_balance) - .unwrap() - .checked_sub(NearToken::from_near(1)) - .unwrap() - < NearToken::from_millinear(5) // gas fee - ); - - Ok(()) -} - -// TODO ft deposits diff --git a/crates/oracle-contract/tests/test_env.rs b/crates/oracle-contract/tests/test_env.rs new file mode 100644 index 0000000..d1dac7b --- /dev/null +++ b/crates/oracle-contract/tests/test_env.rs @@ -0,0 +1,27 @@ +mod tests; + +use tokio::sync::OnceCell; + +static CONTRACT_WASM: OnceCell> = OnceCell::const_new(); + +pub async fn get_contract_wasm() -> &'static Vec { + CONTRACT_WASM + .get_or_init(|| async { + near_workspaces::compile_project("./") + .await + .expect("compiling `intear-oracle` contract for tests") + }) + .await +} + +static FT_CONTRACT_WASM: OnceCell> = OnceCell::const_new(); + +pub async fn get_ft_contract_wasm() -> &'static Vec { + FT_CONTRACT_WASM + .get_or_init(|| async { + near_workspaces::compile_project("../test-ft-contract") + .await + .expect("compiling `test-ft-contract` contract for tests") + }) + .await +} diff --git a/crates/oracle-contract/tests/tests/deposits.rs b/crates/oracle-contract/tests/tests/deposits.rs new file mode 100644 index 0000000..5f8bd10 --- /dev/null +++ b/crates/oracle-contract/tests/tests/deposits.rs @@ -0,0 +1,531 @@ +use near_sdk::NearToken; +use serde_json::json; + +#[tokio::test] +async fn near_deposits() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + + let consumer_account = sandbox.dev_create_account().await?; + let initial_balance = consumer_account.view_account().await?.balance; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(0) + ); + + let outcome = consumer_account + .call(contract.id(), "deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + assert_eq!( + outcome.logs(), + vec![format!( + "Deposited 1.00 NEAR to {consumer_id}", + consumer_id = consumer_account.id() + )] + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(1) + ); + + let outcome = consumer_account + .call(contract.id(), "deposit_near") + .args_json(json!({})) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + assert_eq!( + outcome.logs(), + vec![format!( + "Deposited 1.00 NEAR to {consumer_id}", + consumer_id = consumer_account.id() + )] + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(2) + ); + + let new_balance = consumer_account.view_account().await?.balance; + assert!( + initial_balance + .checked_sub(new_balance) + .unwrap() + .checked_sub(NearToken::from_near(2)) + .unwrap() + < NearToken::from_millinear(5) // gas fee + ); + + let outcome = consumer_account + .call(contract.id(), "withdraw_near") + .args_json(json!({ + "amount": NearToken::from_near(1), + })) + .transact() + .await?; + assert!(outcome.is_success()); + assert_eq!( + outcome.logs(), + vec![format!( + "Withdrew 1.00 NEAR from {account_id}", + account_id = consumer_account.id() + )] + ); + + let new_balance = consumer_account.view_account().await?.balance; + assert!( + initial_balance + .checked_sub(new_balance) + .unwrap() + .checked_sub(NearToken::from_near(2 - 1)) + .unwrap() + < NearToken::from_millinear(5) // gas fee + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(2 - 1) + ); + + Ok(()) +} + +#[tokio::test] +async fn near_deposit_for_producer() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + + let consumer_account = sandbox.dev_create_account().await?; + let producer_account = sandbox.dev_create_account().await?; + let initial_balance = consumer_account.view_account().await?.balance; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(contract.id(), "deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + "producer_id": producer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_failure()); + + let outcome = producer_account + .call(contract.id(), "add_producer") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = producer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(0) + ); + + let outcome = consumer_account + .call(contract.id(), "deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + "producer_id": producer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + assert_eq!( + outcome.logs(), + vec![format!( + "Deposited 1.00 NEAR to {account_id} for {producer_id}", + account_id = consumer_account.id(), + producer_id = producer_account.id(), + )] + ); + + let outcome = producer_account + .view(contract.id(), "get_deposit_near") + .args_json(json!({ + "account_id": consumer_account.id(), + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(1) + ); + + let new_balance = consumer_account.view_account().await?.balance; + assert!( + initial_balance + .checked_sub(new_balance) + .unwrap() + .checked_sub(NearToken::from_near(1)) + .unwrap() + < NearToken::from_millinear(5) // gas fee + ); + + Ok(()) +} + +#[tokio::test] +async fn ft_deposits() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + let token = sandbox + .dev_deploy(crate::get_ft_contract_wasm().await) + .await?; + + let consumer_account = sandbox.dev_create_account().await?; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": contract.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "mint") + .args_json(json!({ + "account_id": consumer_account.id(), + "amount": "1000000000000000000000000000", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000000" + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!(outcome.json::().unwrap(), "0"); + + let outcome = consumer_account + .call(token.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": contract.id(), + "amount": "1000000000000000000000000", + "msg": "{}", + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "999000000000000000000000000" + ); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": contract.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000" + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000" + ); + + let outcome = consumer_account + .call(contract.id(), "withdraw_ft") + .max_gas() + .args_json(json!({ + "ft_id": token.id(), + "amount": "500000000000000000000000", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "999500000000000000000000000" + ); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": contract.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "500000000000000000000000" + ); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "500000000000000000000000" + ); + + Ok(()) +} + +#[tokio::test] +async fn ft_deposit_for_producer() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + let token = sandbox + .dev_deploy(crate::get_ft_contract_wasm().await) + .await?; + + let consumer_account = sandbox.dev_create_account().await?; + let producer_account = sandbox.dev_create_account().await?; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": contract.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "mint") + .args_json(json!({ + "account_id": consumer_account.id(), + "amount": "1000000000000000000000000000", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000000" + ); + + let outcome = producer_account + .call(contract.id(), "add_producer") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = producer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!(outcome.json::().unwrap(), "0"); + + let outcome = consumer_account + .call(token.id(), "ft_transfer_call") + .max_gas() + .args_json(json!({ + "receiver_id": contract.id(), + "amount": "1000000000000000000000000", + "msg": format!("{{\"producer_id\":\"{}\"}}", producer_account.id()), + })) + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "999000000000000000000000000" + ); + + let outcome = consumer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": contract.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000" + ); + + let outcome = producer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + "1000000000000000000000000" + ); + + Ok(()) +} diff --git a/crates/oracle-contract/tests/fees.rs b/crates/oracle-contract/tests/tests/fees.rs similarity index 57% rename from crates/oracle-contract/tests/fees.rs rename to crates/oracle-contract/tests/tests/fees.rs index fa142ea..70b1b1b 100644 --- a/crates/oracle-contract/tests/fees.rs +++ b/crates/oracle-contract/tests/tests/fees.rs @@ -1,12 +1,12 @@ use intear_oracle::fees::ProducerFee; -use near_sdk::NearToken; +use near_sdk::{json_types::U128, NearToken}; use near_workspaces::result::ValueOrReceiptId; use serde_json::json; #[tokio::test] async fn no_fee() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; @@ -133,11 +133,11 @@ async fn no_fee() -> Result<(), Box> { .map(|outcome| outcome.into_result().unwrap()) .any(|outcome| { if let ValueOrReceiptId::Value(value) = outcome { - value.json::().unwrap() - == serde_json::json!({ + value.json::().ok() + == Some(serde_json::json!({ "response_data": "Hello World!", "refund_amount": null, - }) + })) } else { false } @@ -150,7 +150,7 @@ async fn no_fee() -> Result<(), Box> { #[tokio::test] async fn near_fee() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; @@ -312,7 +312,7 @@ async fn near_fee() -> Result<(), Box> { #[tokio::test] async fn near_fee_refund() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; @@ -428,6 +428,8 @@ async fn near_fee_refund() -> Result<(), Box> { .await?; assert!(outcome.is_success()); + sandbox.fast_forward(1).await?; + let outcome = producer_account .view(contract.id(), "get_deposit_near") .args_json(json!({ @@ -436,7 +438,7 @@ async fn near_fee_refund() -> Result<(), Box> { .await?; assert_eq!( outcome.json::().unwrap(), - NearToken::from_millinear(1000 - 100) // 0.9 NEAR + NearToken::from_millinear(1000 - 100 + 50) // 0.95 NEAR ); let response_is_correct = request @@ -474,7 +476,7 @@ async fn near_fee_refund() -> Result<(), Box> { #[tokio::test] async fn direct_near_fee() -> Result<(), Box> { let sandbox = near_workspaces::sandbox_with_version("2.4.0").await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; let producer_account = sandbox.dev_create_account().await?; @@ -538,11 +540,11 @@ async fn direct_near_fee() -> Result<(), Box> { .map(|outcome| outcome.into_result().unwrap()) .any(|outcome| { if let ValueOrReceiptId::Value(value) = outcome { - value.json::().unwrap() - == serde_json::json!({ + value.json::().ok() + == Some(serde_json::json!({ "response_data": "Hello World", "refund_amount": "50000000000000000000000" - }) + })) } else { false } @@ -565,4 +567,401 @@ async fn direct_near_fee() -> Result<(), Box> { Ok(()) } +#[tokio::test] +async fn ft_fee() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + + let token_wasm = crate::get_ft_contract_wasm().await; + let token = sandbox.dev_deploy(token_wasm).await?; + + let producer_account = sandbox.dev_create_account().await?; + let producer_initial_balance = producer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .await? + .json::() + .unwrap() + .0; + + let outcome = producer_account + .call(contract.id(), "add_producer") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = producer_account + .view(contract.id(), "get_fee") + .args_json(json!({ + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!(outcome.json::().unwrap(), ProducerFee::None); + + let outcome = producer_account + .call(contract.id(), "set_fee") + .args_json(json!({ + "fee": ProducerFee::FungibleToken { + token: token.id().clone(), + prepaid_amount: (10u128.pow(24) / 10).into(), // 0.1 NEAR + }, + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = producer_account + .view(contract.id(), "get_fee") + .args_json(json!({ + "producer_id": producer_account.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + ProducerFee::FungibleToken { + token: token.id().clone(), + prepaid_amount: (10u128.pow(24) / 10).into(), + } + ); + + let consumer_account = sandbox.dev_create_account().await?; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + NearToken::from_near(0) + ); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": contract.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "mint") + .args_json(json!({ + "account_id": consumer_account.id(), + "amount": "1000000000000000000000000000", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "ft_transfer_call") + .max_gas() + .args_json(json!({ + "receiver_id": contract.id(), + "amount": U128(10u128.pow(24)), + "msg": "{}", + })) + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!(outcome.json::().unwrap(), 10u128.pow(24).into()); + + let request = consumer_account + .call(contract.id(), "request") + .max_gas() + .args_json(json!({ + "producer_id": producer_account.id(), + "request_data": "Hello World!", + })) + .transact_async() + .await?; + + sandbox.fast_forward(1).await?; + + let outcome = producer_account + .call(contract.id(), "respond") + .args_json(json!({ + "request_id": "0", + "response": { + "response_data": "Hello World", + // no refund + } + })) + .max_gas() + .transact() + .await?; + assert!(dbg!(&outcome).is_success()); + + let outcome = consumer_account + .view(contract.id(), "get_deposit_ft") + .args_json(json!({ + "account_id": consumer_account.id(), + "ft_id": token.id(), + })) + .await?; + assert_eq!( + outcome.json::().unwrap(), + (10u128.pow(24) - 10u128.pow(24) / 10).into() + ); + + let response_is_correct = request + .await? + .outcomes() + .into_iter() + .cloned() + .map(|outcome| outcome.into_result().unwrap()) + .any(|outcome| { + if let ValueOrReceiptId::Value(value) = outcome { + value.json::().ok() + == Some(serde_json::json!({ + "response_data": "Hello World", + "refund_amount": null, + })) + } else { + false + } + }); + assert!(response_is_correct); + + sandbox.fast_forward(2).await?; + + let producer_new_balance = producer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .await? + .json::() + .unwrap() + .0; + assert_eq!( + producer_new_balance + .checked_sub(producer_initial_balance) + .unwrap(), + (10u128.pow(24) / 10) + ); + + Ok(()) +} + +#[tokio::test] +async fn ft_fee_refund() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = crate::get_contract_wasm().await; + + let contract = sandbox.dev_deploy(contract_wasm).await?; + + let token_wasm = crate::get_ft_contract_wasm().await; + let token = sandbox.dev_deploy(token_wasm).await?; + + let producer_account = sandbox.dev_create_account().await?; + let producer_initial_balance = producer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .await? + .json::() + .unwrap() + .0; + + let outcome = producer_account + .call(contract.id(), "add_producer") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = producer_account + .call(contract.id(), "set_fee") + .args_json(json!({ + "fee": ProducerFee::FungibleToken { + token: token.id().clone(), + prepaid_amount: (10u128.pow(24) / 10).into(), // 0.1 NEAR + }, + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let consumer_account = sandbox.dev_create_account().await?; + + let outcome = consumer_account + .call(contract.id(), "register_consumer") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": consumer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "storage_deposit") + .args_json(json!({ + "account_id": contract.id(), + })) + .deposit(NearToken::from_near(1)) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "mint") + .args_json(json!({ + "account_id": consumer_account.id(), + "amount": "1000000000000000000000000000", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let outcome = consumer_account + .call(token.id(), "ft_transfer_call") + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .args_json(json!({ + "receiver_id": contract.id(), + "amount": "1000000000000000000000000", // 1 token + "msg": "{}", + })) + .transact() + .await?; + assert!(outcome.is_success()); + + let request = consumer_account + .call(contract.id(), "request") + .max_gas() + .args_json(json!({ + "producer_id": producer_account.id(), + "request_data": "Hello World", + })) + .transact_async() + .await?; + + sandbox.fast_forward(1).await?; + + let outcome = producer_account + .call(contract.id(), "respond") + .args_json(json!({ + "request_id": "0", + "response": { + "response_data": "Hello World", + "refund_amount": "50000000000000000000000", // 0.5 tokens + } + })) + .max_gas() + .transact() + .await?; + assert!(outcome.is_success()); + + let response_is_correct = request + .await? + .outcomes() + .into_iter() + .cloned() + .map(|outcome| outcome.into_result().unwrap()) + .any(|outcome| { + if let ValueOrReceiptId::Value(value) = outcome { + value.json::().ok() + == Some(serde_json::json!({ + "response_data": "Hello World", + "refund_amount": "50000000000000000000000", + })) + } else { + false + } + }); + assert!(response_is_correct); + + sandbox.fast_forward(2).await?; + + let producer_new_balance = producer_account + .view(token.id(), "ft_balance_of") + .args_json(json!({ + "account_id": producer_account.id(), + })) + .await? + .json::() + .unwrap() + .0; + assert_eq!( + producer_new_balance - producer_initial_balance, + 50000000000000000000000 + ); + + Ok(()) +} + // TODO more refund tests diff --git a/crates/oracle-contract/tests/tests/mod.rs b/crates/oracle-contract/tests/tests/mod.rs new file mode 100644 index 0000000..db39fab --- /dev/null +++ b/crates/oracle-contract/tests/tests/mod.rs @@ -0,0 +1,5 @@ +mod deposits; +mod fees; +mod register; +mod request_response; +mod two_responses; diff --git a/crates/oracle-contract/tests/register.rs b/crates/oracle-contract/tests/tests/register.rs similarity index 91% rename from crates/oracle-contract/tests/register.rs rename to crates/oracle-contract/tests/tests/register.rs index 0b181bb..9e939e6 100644 --- a/crates/oracle-contract/tests/register.rs +++ b/crates/oracle-contract/tests/tests/register.rs @@ -4,7 +4,7 @@ use serde_json::json; #[tokio::test] async fn register_consumer() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; @@ -41,7 +41,7 @@ async fn register_consumer() -> Result<(), Box> { #[tokio::test] async fn register_producer() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; diff --git a/crates/oracle-contract/tests/request_response.rs b/crates/oracle-contract/tests/tests/request_response.rs similarity index 95% rename from crates/oracle-contract/tests/request_response.rs rename to crates/oracle-contract/tests/tests/request_response.rs index 2bc3c38..4f21b79 100644 --- a/crates/oracle-contract/tests/request_response.rs +++ b/crates/oracle-contract/tests/tests/request_response.rs @@ -3,7 +3,7 @@ use serde_json::json; #[tokio::test] async fn contract_is_operational() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; diff --git a/crates/oracle-contract/tests/two_responses.rs b/crates/oracle-contract/tests/tests/two_responses.rs similarity index 95% rename from crates/oracle-contract/tests/two_responses.rs rename to crates/oracle-contract/tests/tests/two_responses.rs index 98ac3fa..d19077e 100644 --- a/crates/oracle-contract/tests/two_responses.rs +++ b/crates/oracle-contract/tests/tests/two_responses.rs @@ -5,7 +5,7 @@ use serde_json::json; #[tokio::test] async fn cannot_send_two_responses() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = include_bytes!("../../../target/near/intear_oracle/intear_oracle.wasm"); + let contract_wasm = crate::get_contract_wasm().await; let contract = sandbox.dev_deploy(contract_wasm).await?; diff --git a/crates/reclaim-gpt-contract/rust-toolchain.toml b/crates/reclaim-gpt-contract/rust-toolchain.toml deleted file mode 100644 index a82ade3..0000000 --- a/crates/reclaim-gpt-contract/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "stable" -components = ["rustfmt"] -targets = ["wasm32-unknown-unknown"] diff --git a/crates/test-ft-contract/Cargo.toml b/crates/test-ft-contract/Cargo.toml new file mode 100644 index 0000000..412b3a7 --- /dev/null +++ b/crates/test-ft-contract/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "test-ft-contract" +description = "cargo-near-new-project-description" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[package.metadata.near.reproducible_build] +image = "sourcescan/cargo-near:0.12.1-rust-1.82.0" +image_digest = "sha256:841e72af53e151e3d8f65ef2e50dcec162699d876348a8ec0e77b6cdede667d9" +passed_env = [] +container_build_command = ["cargo", "near", "build"] + +[dependencies] +near-sdk = "5.5" +near-sdk-contract-tools = "3.0.2" diff --git a/crates/test-ft-contract/README.md b/crates/test-ft-contract/README.md new file mode 100644 index 0000000..d4df081 --- /dev/null +++ b/crates/test-ft-contract/README.md @@ -0,0 +1,3 @@ +# test-ft-contract + +A simple FT contract used in tests for [oracle-contract](../oracle-contract). diff --git a/crates/test-ft-contract/src/lib.rs b/crates/test-ft-contract/src/lib.rs new file mode 100644 index 0000000..126a573 --- /dev/null +++ b/crates/test-ft-contract/src/lib.rs @@ -0,0 +1,30 @@ +use near_sdk::{json_types::U128, near, AccountId}; +use near_sdk_contract_tools::ft::*; + +#[derive(Default, FungibleToken)] +#[near(contract_state)] +pub struct MyFtContract {} + +#[near] +impl MyFtContract { + #[init] + pub fn new() -> Self { + let mut contract = Self {}; + + contract.set_metadata(&ContractMetadata::new("My Fungible Token", "MYFT", 24)); + + contract + } + + pub fn mint(&mut self, account_id: AccountId, amount: U128) { + Nep141Controller::mint( + self, + &Nep141Mint { + receiver_id: account_id.into(), + amount: amount.0, + memo: None, + }, + ) + .unwrap(); + } +}