diff --git a/CHANGELOG.md b/CHANGELOG.md index d91c149f3..e43621cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## [unreleased] -* Update `panic` and `panic_utf8` syscall signatures to indicate they do not return. [PR 492](https://github.com/near/near-sdk-rs/pull/492) +- Update `panic` and `panic_utf8` syscall signatures to indicate they do not return. [PR 492](https://github.com/near/near-sdk-rs/pull/492) - Removes `PublicKey` generic on `env` promise batch calls. Functions now just take a reference to the `PublicKey`. [PR 495](https://github.com/near/near-sdk-rs/pull/495) - fix: Public keys can no longer be borsh deserialized from invalid bytes. [PR 502](https://github.com/near/near-sdk-rs/pull/502) - Adds `Hash` derive to `PublicKey` diff --git a/HELP.md b/HELP.md index 64b297977..7d03c5456 100644 --- a/HELP.md +++ b/HELP.md @@ -474,14 +474,12 @@ Let's assume the calculator is deployed on `calc.near`, we can use the following ```rust const CALCULATOR_ACCOUNT_ID: &str = "calc.near"; -const NO_DEPOSIT: Balance = 0; -const BASE_GAS: Gas = 5_000_000_000_000; #[near_bindgen] impl Contract { pub fn sum_a_b(&mut self, a: U128, b: U128) -> Promise { let calculator_account_id: AccountId = CALCULATOR_ACCOUNT_ID.to_string(); - ext_calculator::sum(a, b, &calculator_account_id, NO_DEPOSIT, BASE_GAS) + ext_calculator::sum(a, b, calculator_account_id) } } ``` diff --git a/examples/cross-contract-high-level/res/cross_contract_high_level.wasm b/examples/cross-contract-high-level/res/cross_contract_high_level.wasm index d3c2bda19..8b6442fe9 100755 Binary files a/examples/cross-contract-high-level/res/cross_contract_high_level.wasm and b/examples/cross-contract-high-level/res/cross_contract_high_level.wasm differ diff --git a/examples/cross-contract-high-level/src/lib.rs b/examples/cross-contract-high-level/src/lib.rs index 1909fbe9f..29fe4ba80 100644 --- a/examples/cross-contract-high-level/src/lib.rs +++ b/examples/cross-contract-high-level/src/lib.rs @@ -59,12 +59,12 @@ impl CrossContract { let pivot = arr.len() / 2; let arr0 = arr[..pivot].to_vec(); let arr1 = arr[pivot..].to_vec(); - let prepaid_gas = env::prepaid_gas(); let account_id = env::current_account_id(); - ext::merge_sort(arr0, account_id.clone(), 0, prepaid_gas / 4) - .and(ext::merge_sort(arr1, account_id.clone(), 0, prepaid_gas / 4)) - .then(ext::merge(account_id, 0, prepaid_gas / 4)) + ext::merge_sort(arr0, account_id.clone()) + .into_promise() + .and(ext::merge_sort(arr1, account_id.clone()).into()) + .then(ext::merge(account_id).into()) .into() } @@ -119,23 +119,17 @@ impl CrossContract { // } pub fn simple_call(&mut self, account_id: AccountId, message: String) { - ext_status_message::set_status(message, account_id, 0, env::prepaid_gas() / 2); + ext_status_message::set_status(message, account_id); } pub fn complex_call(&mut self, account_id: AccountId, message: String) -> Promise { // 1) call status_message to record a message from the signer. // 2) call status_message to retrieve the message of the signer. // 3) return that message as its own result. // Note, for a contract to simply call another contract (1) is sufficient. - let prepaid_gas = env::prepaid_gas(); log!("complex_call"); - ext_status_message::set_status(message, account_id.clone(), 0, prepaid_gas / 3).then( - ext_status_message::get_status( - env::signer_account_id(), - account_id, - 0, - prepaid_gas / 3, - ), - ) + ext_status_message::set_status(message, account_id.clone()) + .into_promise() + .then(ext_status_message::get_status(env::signer_account_id(), account_id).into()) } pub fn transfer_money(&mut self, account_id: AccountId, amount: u64) { diff --git a/examples/cross-contract-low-level/res/cross_contract_low_level.wasm b/examples/cross-contract-low-level/res/cross_contract_low_level.wasm index fef287c99..813ce95ac 100755 Binary files a/examples/cross-contract-low-level/res/cross_contract_low_level.wasm and b/examples/cross-contract-low-level/res/cross_contract_low_level.wasm differ diff --git a/examples/fungible-token/res/defi.wasm b/examples/fungible-token/res/defi.wasm index fa9e840ef..5e0a68bfb 100755 Binary files a/examples/fungible-token/res/defi.wasm and b/examples/fungible-token/res/defi.wasm differ diff --git a/examples/fungible-token/res/fungible_token.wasm b/examples/fungible-token/res/fungible_token.wasm index 03fbf5845..11da391d3 100755 Binary files a/examples/fungible-token/res/fungible_token.wasm and b/examples/fungible-token/res/fungible_token.wasm differ diff --git a/examples/fungible-token/test-contract-defi/src/lib.rs b/examples/fungible-token/test-contract-defi/src/lib.rs index e88c9a535..6158d6d02 100644 --- a/examples/fungible-token/test-contract-defi/src/lib.rs +++ b/examples/fungible-token/test-contract-defi/src/lib.rs @@ -5,16 +5,13 @@ use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::json_types::U128; use near_sdk::{ - env, ext_contract, log, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, - PromiseOrValue, + env, ext_contract, log, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, PromiseOrValue, }; const BASE_GAS: u64 = 5_000_000_000_000; const PROMISE_CALL: u64 = 5_000_000_000_000; const GAS_FOR_FT_ON_TRANSFER: Gas = Gas(BASE_GAS + PROMISE_CALL); -const NO_DEPOSIT: Balance = 0; - #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] pub struct DeFi { @@ -64,13 +61,9 @@ impl FungibleTokenReceiver for DeFi { _ => { let prepaid_gas = env::prepaid_gas(); let account_id = env::current_account_id(); - ext_self::value_please( - msg, - account_id, - NO_DEPOSIT, - prepaid_gas - GAS_FOR_FT_ON_TRANSFER, - ) - .into() + ext_self::value_please(msg, account_id) + .with_gas(prepaid_gas - GAS_FOR_FT_ON_TRANSFER) + .into() } } } diff --git a/examples/gas-fee-tester/res/gas_fee_tester.wasm b/examples/gas-fee-tester/res/gas_fee_tester.wasm index 5c2d89970..e8a16a3c2 100755 Binary files a/examples/gas-fee-tester/res/gas_fee_tester.wasm and b/examples/gas-fee-tester/res/gas_fee_tester.wasm differ diff --git a/examples/lockable-fungible-token/res/lockable_fungible_token.wasm b/examples/lockable-fungible-token/res/lockable_fungible_token.wasm index d89bbc4c8..2455d1119 100755 Binary files a/examples/lockable-fungible-token/res/lockable_fungible_token.wasm and b/examples/lockable-fungible-token/res/lockable_fungible_token.wasm differ diff --git a/examples/mission-control/res/mission_control.wasm b/examples/mission-control/res/mission_control.wasm index 40bdd6a00..1fcd462bf 100755 Binary files a/examples/mission-control/res/mission_control.wasm and b/examples/mission-control/res/mission_control.wasm differ diff --git a/examples/non-fungible-token/res/approval_receiver.wasm b/examples/non-fungible-token/res/approval_receiver.wasm index 346c77e7b..f29b39d74 100755 Binary files a/examples/non-fungible-token/res/approval_receiver.wasm and b/examples/non-fungible-token/res/approval_receiver.wasm differ diff --git a/examples/non-fungible-token/res/non_fungible_token.wasm b/examples/non-fungible-token/res/non_fungible_token.wasm index 313342d13..9ca72c0b7 100755 Binary files a/examples/non-fungible-token/res/non_fungible_token.wasm and b/examples/non-fungible-token/res/non_fungible_token.wasm differ diff --git a/examples/non-fungible-token/res/token_receiver.wasm b/examples/non-fungible-token/res/token_receiver.wasm index 56ef5b3ba..440cf0a08 100755 Binary files a/examples/non-fungible-token/res/token_receiver.wasm and b/examples/non-fungible-token/res/token_receiver.wasm differ diff --git a/examples/non-fungible-token/test-approval-receiver/src/lib.rs b/examples/non-fungible-token/test-approval-receiver/src/lib.rs index 7a44af203..43d7180c8 100644 --- a/examples/non-fungible-token/test-approval-receiver/src/lib.rs +++ b/examples/non-fungible-token/test-approval-receiver/src/lib.rs @@ -5,16 +5,13 @@ use near_contract_standards::non_fungible_token::approval::NonFungibleTokenAppro 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, Balance, Gas, PanicOnDefault, - PromiseOrValue, + env, ext_contract, log, near_bindgen, AccountId, Gas, PanicOnDefault, PromiseOrValue, }; const BASE_GAS: u64 = 5_000_000_000_000; const PROMISE_CALL: u64 = 5_000_000_000_000; const GAS_FOR_NFT_ON_APPROVE: Gas = Gas(BASE_GAS + PROMISE_CALL); -const NO_DEPOSIT: Balance = 0; - #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] pub struct ApprovalReceiver { @@ -73,7 +70,8 @@ impl NonFungibleTokenApprovalReceiver for ApprovalReceiver { _ => { let prepaid_gas = env::prepaid_gas(); let account_id = env::current_account_id(); - ext_self::ok_go(msg, account_id, NO_DEPOSIT, prepaid_gas - GAS_FOR_NFT_ON_APPROVE) + ext_self::ok_go(msg, account_id) + .with_gas(prepaid_gas - GAS_FOR_NFT_ON_APPROVE) .into() } } diff --git a/examples/non-fungible-token/test-token-receiver/src/lib.rs b/examples/non-fungible-token/test-token-receiver/src/lib.rs index 97e7e2a85..3a2e87167 100644 --- a/examples/non-fungible-token/test-token-receiver/src/lib.rs +++ b/examples/non-fungible-token/test-token-receiver/src/lib.rs @@ -5,16 +5,13 @@ 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, Balance, Gas, PanicOnDefault, - PromiseOrValue, + env, ext_contract, log, near_bindgen, AccountId, Gas, PanicOnDefault, PromiseOrValue, }; const BASE_GAS: u64 = 5_000_000_000_000; const PROMISE_CALL: u64 = 5_000_000_000_000; const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas(BASE_GAS + PROMISE_CALL); -const NO_DEPOSIT: Balance = 0; - #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] pub struct TokenReceiver { @@ -74,25 +71,17 @@ impl NonFungibleTokenReceiver for TokenReceiver { "return-it-later" => { let prepaid_gas = env::prepaid_gas(); let account_id = env::current_account_id(); - ext_self::ok_go( - true, - account_id, - NO_DEPOSIT, - prepaid_gas - GAS_FOR_NFT_ON_TRANSFER, - ) - .into() + ext_self::ok_go(true, account_id) + .with_gas(prepaid_gas - GAS_FOR_NFT_ON_TRANSFER) + .into() } "keep-it-now" => PromiseOrValue::Value(false), "keep-it-later" => { let prepaid_gas = env::prepaid_gas(); let account_id = env::current_account_id(); - ext_self::ok_go( - false, - account_id, - NO_DEPOSIT, - prepaid_gas - GAS_FOR_NFT_ON_TRANSFER, - ) - .into() + ext_self::ok_go(false, account_id) + .with_gas(prepaid_gas - GAS_FOR_NFT_ON_TRANSFER) + .into() } _ => env::panic_str("unsupported msg"), } diff --git a/examples/status-message-collections/res/status_message_collections.wasm b/examples/status-message-collections/res/status_message_collections.wasm index ceb772464..71abc406d 100755 Binary files a/examples/status-message-collections/res/status_message_collections.wasm and b/examples/status-message-collections/res/status_message_collections.wasm differ diff --git a/examples/status-message/res/status_message.wasm b/examples/status-message/res/status_message.wasm index bd919bfca..96594d816 100755 Binary files a/examples/status-message/res/status_message.wasm and b/examples/status-message/res/status_message.wasm differ diff --git a/examples/test-contract/res/test_contract.wasm b/examples/test-contract/res/test_contract.wasm index 3a10f4eea..06fbf9c2d 100755 Binary files a/examples/test-contract/res/test_contract.wasm and b/examples/test-contract/res/test_contract.wasm differ diff --git a/near-contract-standards/src/fungible_token/core_impl.rs b/near-contract-standards/src/fungible_token/core_impl.rs index 1c4da022a..ae523bdd0 100644 --- a/near-contract-standards/src/fungible_token/core_impl.rs +++ b/near-contract-standards/src/fungible_token/core_impl.rs @@ -11,8 +11,6 @@ use near_sdk::{ const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas(5_000_000_000_000); const GAS_FOR_FT_TRANSFER_CALL: Gas = Gas(25_000_000_000_000 + GAS_FOR_RESOLVE_TRANSFER.0); -const NO_DEPOSIT: Balance = 0; - #[ext_contract(ext_self)] trait FungibleTokenResolver { fn ft_resolve_transfer( @@ -176,17 +174,19 @@ impl FungibleTokenCore for FungibleToken { amount.into(), msg, receiver_id.clone(), - NO_DEPOSIT, - env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL, ) - .then(ext_self::ft_resolve_transfer( - sender_id, - receiver_id, - amount.into(), - env::current_account_id(), - NO_DEPOSIT, - GAS_FOR_RESOLVE_TRANSFER, - )) + .with_gas(env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL) + .into_promise() + .then( + ext_self::ft_resolve_transfer( + sender_id, + receiver_id, + amount.into(), + env::current_account_id(), + ) + .with_gas(GAS_FOR_RESOLVE_TRANSFER) + .into_promise(), + ) .into() } diff --git a/near-contract-standards/src/non_fungible_token/approval/approval_impl.rs b/near-contract-standards/src/non_fungible_token/approval/approval_impl.rs index ed1b1088b..e3321bcc1 100644 --- a/near-contract-standards/src/non_fungible_token/approval/approval_impl.rs +++ b/near-contract-standards/src/non_fungible_token/approval/approval_impl.rs @@ -7,10 +7,9 @@ use crate::non_fungible_token::utils::{ refund_approved_account_ids_iter, refund_deposit, }; use crate::non_fungible_token::NonFungibleToken; -use near_sdk::{assert_one_yocto, env, ext_contract, AccountId, Balance, Gas, Promise}; +use near_sdk::{assert_one_yocto, env, ext_contract, AccountId, Gas, Promise}; const GAS_FOR_NFT_APPROVE: Gas = Gas(10_000_000_000_000); -const NO_DEPOSIT: Balance = 0; fn expect_token_found(option: Option) -> T { option.unwrap_or_else(|| env::panic_str("Token not found")) @@ -69,15 +68,9 @@ impl NonFungibleTokenApproval for NonFungibleToken { // if given `msg`, schedule call to `nft_on_approve` and return it. Else, return None. msg.map(|msg| { - ext_approval_receiver::nft_on_approve( - token_id, - owner_id, - approval_id, - msg, - account_id, - NO_DEPOSIT, - env::prepaid_gas() - GAS_FOR_NFT_APPROVE, - ) + ext_approval_receiver::nft_on_approve(token_id, owner_id, approval_id, msg, account_id) + .with_gas(env::prepaid_gas() - GAS_FOR_NFT_APPROVE) + .into() }) } diff --git a/near-contract-standards/src/non_fungible_token/core/core_impl.rs b/near-contract-standards/src/non_fungible_token/core/core_impl.rs index 1cf7d4d9f..1f6f19673 100644 --- a/near-contract-standards/src/non_fungible_token/core/core_impl.rs +++ b/near-contract-standards/src/non_fungible_token/core/core_impl.rs @@ -9,7 +9,7 @@ use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::{LookupMap, TreeMap, UnorderedSet}; use near_sdk::json_types::Base64VecU8; use near_sdk::{ - assert_one_yocto, env, ext_contract, log, AccountId, Balance, BorshStorageKey, CryptoHash, Gas, + assert_one_yocto, env, ext_contract, log, AccountId, BorshStorageKey, CryptoHash, Gas, IntoStorageKey, PromiseOrValue, PromiseResult, StorageUsage, }; use std::collections::HashMap; @@ -17,8 +17,6 @@ use std::collections::HashMap; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas(5_000_000_000_000); const GAS_FOR_FT_TRANSFER_CALL: Gas = Gas(25_000_000_000_000 + GAS_FOR_RESOLVE_TRANSFER.0); -const NO_DEPOSIT: Balance = 0; - #[ext_contract(ext_self)] trait NFTResolver { fn nft_resolve_transfer( @@ -312,18 +310,20 @@ impl NonFungibleTokenCore for NonFungibleToken { token_id.clone(), msg, receiver_id.clone(), - NO_DEPOSIT, - env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL, ) - .then(ext_self::nft_resolve_transfer( - old_owner, - receiver_id, - token_id, - old_approvals, - env::current_account_id(), - NO_DEPOSIT, - GAS_FOR_RESOLVE_TRANSFER, - )) + .with_gas(env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL) + .into_promise() + .then( + ext_self::nft_resolve_transfer( + old_owner, + receiver_id, + token_id, + old_approvals, + env::current_account_id(), + ) + .with_gas(GAS_FOR_RESOLVE_TRANSFER) + .into(), + ) .into() } diff --git a/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs b/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs index c7966aca9..748d94443 100644 --- a/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs @@ -133,9 +133,11 @@ impl ImplItemMethodInfo { }; quote! { #contract_deser - let result = #method_invocation; - #value_ser - near_sdk::env::value_return(&result); + { + let result = #method_invocation; + #value_ser + near_sdk::env::value_return(&result); + } #contract_ser } } @@ -147,6 +149,10 @@ impl ImplItemMethodInfo { #value } }); + + let schedule_function_calls = quote! { + near_sdk::schedule_queued_promises(); + }; quote! { #non_bindgen_attrs #[cfg(target_arch = "wasm32")] @@ -160,6 +166,7 @@ impl ImplItemMethodInfo { #callback_deser #callback_vec_deser #body + #schedule_function_calls } } } diff --git a/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs b/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs index 794f419bd..42bf4e4fc 100644 --- a/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs @@ -58,6 +58,7 @@ mod tests { near_sdk::env::setup_panic_hook(); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -76,6 +77,7 @@ mod tests { near_sdk::env::setup_panic_hook(); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -98,6 +100,7 @@ mod tests { let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -125,6 +128,7 @@ mod tests { .expect("Failed to deserialize input from JSON."); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(k, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -158,6 +162,7 @@ mod tests { let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(k, m, ); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -189,11 +194,14 @@ mod tests { ) .expect("Failed to deserialize input from JSON."); let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); - let result = contract.method(k, m, ); - let result = - near_sdk::serde_json::to_vec(&result).expect("Failed to serialize the return value using JSON."); - near_sdk::env::value_return(&result); + { + let result = contract.method(k, m, ); + let result = + near_sdk::serde_json::to_vec(&result).expect("Failed to serialize the return value using JSON."); + near_sdk::env::value_return(&result); + } near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -212,10 +220,13 @@ mod tests { pub extern "C" fn method() { near_sdk::env::setup_panic_hook(); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); - let result = contract.method(); - let result = - near_sdk::serde_json::to_vec(&result).expect("Failed to serialize the return value using JSON."); - near_sdk::env::value_return(&result); + { + let result = contract.method(); + let result = + near_sdk::serde_json::to_vec(&result).expect("Failed to serialize the return value using JSON."); + near_sdk::env::value_return(&result); + } + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -243,6 +254,7 @@ mod tests { .expect("Failed to deserialize input from JSON."); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(&k, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -271,6 +283,7 @@ mod tests { .expect("Failed to deserialize input from JSON."); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(&mut k, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -315,6 +328,7 @@ mod tests { near_sdk::serde_json::from_slice(&data).expect("Failed to deserialize callback using JSON"); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(&mut x, y, z, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -350,6 +364,7 @@ mod tests { near_sdk::serde_json::from_slice(&data).expect("Failed to deserialize callback using JSON"); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(&mut x, y, ); + near_sdk::schedule_queued_promises(); } ); @@ -392,6 +407,7 @@ mod tests { .collect(); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(x, y, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -428,6 +444,7 @@ mod tests { } let contract = Hello::method(&mut k,); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -461,6 +478,7 @@ mod tests { .expect("Failed to deserialize input from JSON."); let contract = Hello::method(&mut k,); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -495,6 +513,7 @@ mod tests { } let contract = Hello::method(&mut k,); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -527,11 +546,14 @@ mod tests { ) .expect("Failed to deserialize input from Borsh."); let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); - let result = contract.method(k, m, ); - let result = near_sdk::borsh::BorshSerialize::try_to_vec(&result) - .expect("Failed to serialize the return value using Borsh."); - near_sdk::env::value_return(&result); + { + let result = contract.method(k, m, ); + let result = near_sdk::borsh::BorshSerialize::try_to_vec(&result) + .expect("Failed to serialize the return value using Borsh."); + near_sdk::env::value_return(&result); + } near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -575,6 +597,7 @@ mod tests { near_sdk::serde_json::from_slice(&data).expect("Failed to deserialize callback using JSON"); let contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(&mut x, y, z, ); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -594,6 +617,7 @@ mod tests { let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.method(); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); @@ -619,6 +643,7 @@ mod tests { let mut contract: Hello = near_sdk::env::state_read().unwrap_or_default(); contract.private_method(); near_sdk::env::state_write(&contract); + near_sdk::schedule_queued_promises(); } ); assert_eq!(expected.to_string(), actual.to_string()); diff --git a/near-sdk-macros/src/core_impl/code_generator/item_trait_info.rs b/near-sdk-macros/src/core_impl/code_generator/item_trait_info.rs index 5eb5e510e..67d795e65 100644 --- a/near-sdk-macros/src/core_impl/code_generator/item_trait_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/item_trait_info.rs @@ -55,10 +55,8 @@ mod tests { use near_sdk::{Gas, Balance, AccountId, Promise}; pub fn merge_sort( arr: Vec, - __account_id: AccountId, - __balance: near_sdk::Balance, - __gas: near_sdk::Gas - ) -> near_sdk::Promise { + __account_id: AccountId + ) -> near_sdk::__private::FunctionCallBuilder { #[derive(near_sdk :: serde :: Serialize)] #[serde(crate = "near_sdk::serde")] struct Input { @@ -67,20 +65,18 @@ mod tests { let args = Input { arr, }; let args = near_sdk::serde_json::to_vec(&args) .expect("Failed to serialize the cross contract args using JSON."); - near_sdk::Promise::new(__account_id).function_call( + near_sdk::__private::FunctionCallBuilder::new( + near_sdk::Promise::new(__account_id), "merge_sort".to_string(), - args, - __balance, - __gas, + args ) } - pub fn merge(__account_id: AccountId, __balance: near_sdk::Balance, __gas: near_sdk::Gas) -> near_sdk::Promise { + pub fn merge(__account_id: AccountId) -> near_sdk::__private::FunctionCallBuilder { let args = vec![]; - near_sdk::Promise::new(__account_id).function_call( + near_sdk::__private::FunctionCallBuilder::new( + near_sdk::Promise::new(__account_id), "merge".to_string(), - args, - __balance, - __gas, + args ) } } @@ -107,10 +103,8 @@ mod tests { use near_sdk::{Gas, Balance, AccountId, Promise}; pub fn test( v: Vec, - __account_id: AccountId, - __balance: near_sdk::Balance, - __gas: near_sdk::Gas - ) -> near_sdk::Promise { + __account_id: AccountId + ) -> near_sdk::__private::FunctionCallBuilder { #[derive(near_sdk :: borsh :: BorshSerialize)] struct Input { v: Vec, @@ -118,11 +112,10 @@ mod tests { let args = Input { v, }; let args = near_sdk::borsh::BorshSerialize::try_to_vec(&args) .expect("Failed to serialize the cross contract args using Borsh."); - near_sdk::Promise::new(__account_id).function_call( + near_sdk::__private::FunctionCallBuilder::new( + near_sdk::Promise::new(__account_id), "test".to_string(), - args, - __balance, - __gas, + args ) } } diff --git a/near-sdk-macros/src/core_impl/code_generator/trait_item_method_info.rs b/near-sdk-macros/src/core_impl/code_generator/trait_item_method_info.rs index 6999c6860..531eedc85 100644 --- a/near-sdk-macros/src/core_impl/code_generator/trait_item_method_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/trait_item_method_info.rs @@ -16,14 +16,12 @@ impl TraitItemMethodInfo { &self.attr_sig_info.result_serializer, ); quote! { - pub fn #ident(#pat_type_list __account_id: AccountId, __balance: near_sdk::Balance, __gas: near_sdk::Gas) -> near_sdk::Promise { + pub fn #ident(#pat_type_list __account_id: AccountId) -> near_sdk::__private::FunctionCallBuilder { #serialize - near_sdk::Promise::new(__account_id) - .function_call( + near_sdk::__private::FunctionCallBuilder::new( + near_sdk::Promise::new(__account_id), #ident_byte_str.to_string(), - args, - __balance, - __gas, + args ) } } diff --git a/near-sdk/src/lib.rs b/near-sdk/src/lib.rs index b5e777788..a1253293a 100644 --- a/near-sdk/src/lib.rs +++ b/near-sdk/src/lib.rs @@ -17,7 +17,7 @@ pub use environment::env; pub use environment::sys; mod promise; -pub use promise::{Promise, PromiseOrValue}; +pub use promise::{schedule_queued_promises, Promise, PromiseOrValue}; mod metadata; pub use metadata::{Metadata, MethodMetadata}; @@ -44,6 +44,11 @@ pub use crate::utils::*; #[cfg(not(target_arch = "wasm32"))] pub mod test_utils; +// Not public API. +#[doc(hidden)] +#[path = "private.rs"] +pub mod __private; + // Set up global allocator by default if custom-allocator feature is not set in wasm32 architecture. #[cfg(all(feature = "wee_alloc", target_arch = "wasm32"))] #[global_allocator] diff --git a/near-sdk/src/private.rs b/near-sdk/src/private.rs new file mode 100644 index 000000000..d45fb6487 --- /dev/null +++ b/near-sdk/src/private.rs @@ -0,0 +1,61 @@ +use crate::{Balance, Gas, Promise, PromiseOrValue}; + +pub struct FunctionCallBuilder { + promise: Option, + method_name: String, + args: Vec, + amount: Balance, + gas: Option, +} + +impl FunctionCallBuilder { + pub fn new(promise: Promise, method_name: String, args: Vec) -> Self { + Self { promise: Some(promise), method_name, args, amount: 0, gas: None } + } + + pub fn into_promise(mut self) -> Promise { + self.construct_promise() + } + + fn construct_promise(&mut self) -> Promise { + let method_name = core::mem::take(&mut self.method_name); + let arguments = core::mem::take(&mut self.args); + let gas = core::mem::take(&mut self.gas); + + core::mem::take(&mut self.promise) + .map(|p| p.queued_function_call(method_name, arguments, self.amount, gas)) + // Promise guaranteed to be Some unless promise is constructed, and only constructed + // when dropped + .unwrap_or_else(|| unreachable!()) + } + + pub fn with_amount(mut self, amount: Balance) -> Self { + self.amount = amount; + self + } + + pub fn with_gas(mut self, gas: Gas) -> Self { + self.gas = Some(gas); + self + } +} + +impl From for Promise { + fn from(mut f: FunctionCallBuilder) -> Self { + f.construct_promise() + } +} + +impl From for PromiseOrValue { + fn from(mut f: FunctionCallBuilder) -> Self { + PromiseOrValue::Promise(f.construct_promise()) + } +} + +impl Drop for FunctionCallBuilder { + fn drop(&mut self) { + if self.promise.is_some() { + self.construct_promise(); + } + } +} diff --git a/near-sdk/src/promise.rs b/near-sdk/src/promise/mod.rs similarity index 65% rename from near-sdk/src/promise.rs rename to near-sdk/src/promise/mod.rs index 2ee5d8e1f..156862b70 100644 --- a/near-sdk/src/promise.rs +++ b/near-sdk/src/promise/mod.rs @@ -1,10 +1,12 @@ +mod queue; + use borsh::BorshSchema; -use std::cell::RefCell; use std::collections::HashMap; use std::io::{Error, Write}; -use std::rc::Rc; -use crate::{AccountId, Balance, Gas, PromiseIndex, PublicKey}; +use self::queue::{queue_promise_event, PromiseQueueEvent}; +pub use self::queue::{schedule_queued_promises, QueueIndex}; +use crate::{AccountId, Balance, Gas, PublicKey}; pub enum PromiseAction { CreateAccount, @@ -15,7 +17,7 @@ pub enum PromiseAction { method_name: String, arguments: Vec, amount: Balance, - gas: Gas, + gas: Option, }, Transfer { amount: Balance, @@ -43,106 +45,6 @@ pub enum PromiseAction { }, } -impl PromiseAction { - pub fn add(&self, promise_index: PromiseIndex) { - use PromiseAction::*; - match self { - CreateAccount => crate::env::promise_batch_action_create_account(promise_index), - DeployContract { code } => { - crate::env::promise_batch_action_deploy_contract(promise_index, code) - } - FunctionCall { method_name, arguments, amount, gas } => { - crate::env::promise_batch_action_function_call( - promise_index, - method_name, - arguments, - *amount, - *gas, - ) - } - Transfer { amount } => { - crate::env::promise_batch_action_transfer(promise_index, *amount) - } - Stake { amount, public_key } => { - crate::env::promise_batch_action_stake(promise_index, *amount, public_key) - } - AddFullAccessKey { public_key, nonce } => { - crate::env::promise_batch_action_add_key_with_full_access( - promise_index, - public_key, - *nonce, - ) - } - AddAccessKey { public_key, allowance, receiver_id, method_names, nonce } => { - crate::env::promise_batch_action_add_key_with_function_call( - promise_index, - public_key, - *nonce, - *allowance, - receiver_id, - method_names, - ) - } - DeleteKey { public_key } => { - crate::env::promise_batch_action_delete_key(promise_index, public_key) - } - DeleteAccount { beneficiary_id } => { - crate::env::promise_batch_action_delete_account(promise_index, beneficiary_id) - } - } - } -} - -pub struct PromiseSingle { - pub account_id: AccountId, - pub actions: RefCell>, - pub after: RefCell>, - /// Promise index that is computed only once. - pub promise_index: RefCell>, -} - -impl PromiseSingle { - pub fn construct_recursively(&self) -> PromiseIndex { - let mut promise_lock = self.promise_index.borrow_mut(); - if let Some(res) = promise_lock.as_ref() { - return *res; - } - let promise_index = if let Some(after) = self.after.borrow().as_ref() { - crate::env::promise_batch_then(after.construct_recursively(), &self.account_id) - } else { - crate::env::promise_batch_create(&self.account_id) - }; - let actions_lock = self.actions.borrow(); - for action in actions_lock.iter() { - action.add(promise_index); - } - *promise_lock = Some(promise_index); - promise_index - } -} - -pub struct PromiseJoint { - pub promise_a: Promise, - pub promise_b: Promise, - /// Promise index that is computed only once. - pub promise_index: RefCell>, -} - -impl PromiseJoint { - pub fn construct_recursively(&self) -> PromiseIndex { - let mut promise_lock = self.promise_index.borrow_mut(); - if let Some(res) = promise_lock.as_ref() { - return *res; - } - let res = crate::env::promise_and(&[ - self.promise_a.construct_recursively(), - self.promise_b.construct_recursively(), - ]); - *promise_lock = Some(res); - res - } -} - /// A structure representing a result of the scheduled execution on another contract. /// /// Smart contract developers will explicitly use `Promise` in two situations: @@ -166,7 +68,7 @@ impl PromiseJoint { /// #[near_bindgen] /// impl ContractA { /// pub fn a(&self) -> Promise { -/// contract_b::b("bob_near".parse().unwrap(), 0, Gas(1_000)) +/// contract_b::b("bob_near".parse().unwrap()).into_promise() /// } /// } /// ``` @@ -183,10 +85,8 @@ impl PromiseJoint { /// .transfer(1000) /// .add_full_access_key(env::signer_account_pk()); /// ``` -#[derive(Clone)] pub struct Promise { subtype: PromiseSubtype, - should_return: RefCell, } /// Until we implement strongly typed promises we serialize them as unit struct. @@ -202,29 +102,32 @@ impl BorshSchema for Promise { } } -#[derive(Clone)] pub enum PromiseSubtype { - Single(Rc), - Joint(Rc), + Single(QueueIndex), + Joint(QueueIndex), +} + +impl PromiseSubtype { + fn index(&self) -> QueueIndex { + match self { + Self::Single(x) => *x, + Self::Joint(x) => *x, + } + } } impl Promise { /// Create a promise that acts on the given account. pub fn new(account_id: AccountId) -> Self { - Self { - subtype: PromiseSubtype::Single(Rc::new(PromiseSingle { - account_id, - actions: RefCell::new(vec![]), - after: RefCell::new(None), - promise_index: RefCell::new(None), - })), - should_return: RefCell::new(false), - } + let index = queue_promise_event(PromiseQueueEvent::CreateBatch { account_id }); + Self { subtype: PromiseSubtype::Single(index) } } fn add_action(self, action: PromiseAction) -> Self { match &self.subtype { - PromiseSubtype::Single(x) => x.actions.borrow_mut().push(action), + PromiseSubtype::Single(x) => { + queue_promise_event(PromiseQueueEvent::Action { index: *x, action }); + } PromiseSubtype::Joint(_) => { crate::env::panic_str("Cannot add action to a joint promise.") } @@ -249,6 +152,22 @@ impl Promise { arguments: Vec, amount: Balance, gas: Gas, + ) -> Self { + self.add_action(PromiseAction::FunctionCall { + method_name, + arguments, + amount, + gas: Some(gas), + }) + } + + /// Queue a function call which will be scheduled with the promise + pub fn queued_function_call( + self, + method_name: String, + arguments: Vec, + amount: Balance, + gas: Option, ) -> Self { self.add_action(PromiseAction::FunctionCall { method_name, arguments, amount, gas }) } @@ -328,14 +247,11 @@ impl Promise { /// // p3.create_account(); /// ``` pub fn and(self, other: Promise) -> Promise { - Promise { - subtype: PromiseSubtype::Joint(Rc::new(PromiseJoint { - promise_a: self, - promise_b: other, - promise_index: RefCell::new(None), - })), - should_return: RefCell::new(false), - } + let idx = queue_promise_event(PromiseQueueEvent::BatchAnd { + promise_a: self.subtype.index(), + promise_b: other.subtype.index(), + }); + Promise { subtype: PromiseSubtype::Joint(idx) } } /// Schedules execution of another promise right after the current promise finish executing. @@ -351,11 +267,14 @@ impl Promise { /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account(); /// p1.then(p2).and(p3).then(p4); /// ``` - pub fn then(self, mut other: Promise) -> Promise { - match &mut other.subtype { - PromiseSubtype::Single(x) => *x.after.borrow_mut() = Some(self), + pub fn then(self, other: Promise) -> Promise { + match &other.subtype { + PromiseSubtype::Single(x) => { + queue::upgrade_to_then(self.subtype.index(), *x); + } PromiseSubtype::Joint(_) => crate::env::panic_str("Cannot callback joint promise."), } + other } @@ -378,36 +297,21 @@ impl Promise { /// #[near_bindgen] /// impl ContractA { /// pub fn a1(&self) { - /// contract_b::b("bob_near".parse().unwrap(), 0, Gas(1_000)).as_return(); + /// contract_b::b("bob_near".parse().unwrap()).into_promise().as_return(); /// } /// /// pub fn a2(&self) -> Promise { - /// contract_b::b("bob_near".parse().unwrap(), 0, Gas(1_000)) + /// contract_b::b("bob_near".parse().unwrap()).into() /// } /// } /// ``` #[allow(clippy::wrong_self_convention)] pub fn as_return(self) -> Self { - *self.should_return.borrow_mut() = true; + // TODO handle duplicate should return, before would just be queued once + queue_promise_event(PromiseQueueEvent::Return { index: self.subtype.index() }); + // *self.should_return.borrow_mut() = true; self } - - fn construct_recursively(&self) -> PromiseIndex { - let res = match &self.subtype { - PromiseSubtype::Single(x) => x.construct_recursively(), - PromiseSubtype::Joint(x) => x.construct_recursively(), - }; - if *self.should_return.borrow() { - crate::env::promise_return(res); - } - res - } -} - -impl Drop for Promise { - fn drop(&mut self) { - self.construct_recursively(); - } } impl serde::Serialize for Promise { @@ -415,14 +319,14 @@ impl serde::Serialize for Promise { where S: serde::Serializer, { - *self.should_return.borrow_mut() = true; + queue_promise_event(PromiseQueueEvent::Return { index: self.subtype.index() }); serializer.serialize_unit() } } impl borsh::BorshSerialize for Promise { fn serialize(&self, _writer: &mut W) -> Result<(), Error> { - *self.should_return.borrow_mut() = true; + queue_promise_event(PromiseQueueEvent::Return { index: self.subtype.index() }); // Intentionally no bytes written for the promise, the return value from the promise // will be considered as the return value from the contract call. diff --git a/near-sdk/src/promise/queue.rs b/near-sdk/src/promise/queue.rs new file mode 100644 index 000000000..070af016d --- /dev/null +++ b/near-sdk/src/promise/queue.rs @@ -0,0 +1,180 @@ +use std::cell::RefCell; + +use super::PromiseAction; +use crate::{env, AccountId, Gas, PromiseIndex}; + +thread_local! { + static QUEUED_PROMISES: RefCell> = RefCell::new(Vec::new()); +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct QueueIndex(usize); + +#[non_exhaustive] +pub enum PromiseQueueEvent { + CreateBatch { account_id: AccountId }, + BatchAnd { promise_a: QueueIndex, promise_b: QueueIndex }, + BatchThen { previous_index: QueueIndex, account_id: AccountId }, + Action { index: QueueIndex, action: PromiseAction }, + Return { index: QueueIndex }, +} + +pub(super) fn queue_promise_event(event: PromiseQueueEvent) -> QueueIndex { + QUEUED_PROMISES.with(|q| { + let mut q = q.borrow_mut(); + q.push(event); + QueueIndex(q.len() - 1) + }) +} + +pub(super) fn upgrade_to_then(previous_index: QueueIndex, next: QueueIndex) { + QUEUED_PROMISES.with(|q| { + let mut queue = q.borrow_mut(); + let event_mut = queue.get_mut(next.0).unwrap_or_else(|| unreachable!()); + + // Replace current event with low cost variant. It gets replaced at the end of the function + // so this doesn't matter. This is to avoid a clone of the account_id. + let event = + core::mem::replace(event_mut, PromiseQueueEvent::Return { index: QueueIndex(0) }); + let account_id = if let PromiseQueueEvent::CreateBatch { account_id } = event { + account_id + } else { + // This is unreachable because `then` can only be called on a promise once + // and it is always in `CreateBatch` state until then. + unreachable!() + }; + + *event_mut = PromiseQueueEvent::BatchThen { previous_index, account_id }; + }) +} + +fn calc_gas_per_unspecified(events: &[PromiseQueueEvent]) -> Gas { + // let mut remaining_gas = env::prepaid_gas() - env::used_gas(); + + // // Only take a factor of the gas remaining because of constant fees + // // TODO remove this in the future, this is a horrible pattern + // remaining_gas = (remaining_gas / 10) * 6; + + // subtract any defined amounts and count number of unspecified + let mut count_remaining = 0; + for event in events.iter() { + if let PromiseQueueEvent::Action { action: PromiseAction::FunctionCall { .. }, .. } = event + { + count_remaining += 1; + // if let Some(specified) = gas { + // // Gas was specified, remove this from pool of funds + // remaining_gas -= *specified; + // } else { + // count_remaining += 1; + // } + } + } + + if count_remaining == 0 { + Gas(0) + } else { + env::prepaid_gas() / (count_remaining + 1) + // // Split remaining gas among the count of unspecified gas + // remaining_gas / count_remaining + } +} + +/// Schedules queued function calls, which will split remaining gas +pub fn schedule_queued_promises() { + QUEUED_PROMISES.with(|q| { + let mut queue = q.borrow_mut(); + + // Would be ideal if this is calculated only if an unspecified gas found + let function_call_gas = calc_gas_per_unspecified(&queue); + + let mut lookup = vec![PromiseIndex::MAX; queue.len()]; + + for (i, event) in queue.iter().enumerate() { + match event { + PromiseQueueEvent::CreateBatch { account_id } => { + let promise_idx = crate::env::promise_batch_create(account_id); + lookup.insert(i, promise_idx); + } + PromiseQueueEvent::BatchAnd { promise_a, promise_b } => { + //* indices guaranteed to be in lookup as the queue index would be used + // to create the `and` promise. + let a = lookup[promise_a.0]; + let b = lookup[promise_b.0]; + let promise_idx = crate::env::promise_and(&[a, b]); + lookup[i] = promise_idx; + } + PromiseQueueEvent::BatchThen { previous_index, account_id } => { + let index = lookup[previous_index.0]; + let promise_idx = crate::env::promise_batch_then(index, account_id); + lookup[i] = promise_idx; + } + PromiseQueueEvent::Action { index, action } => { + let promise_index = lookup[index.0]; + use PromiseAction::*; + match action { + CreateAccount => { + crate::env::promise_batch_action_create_account(promise_index) + } + DeployContract { code } => { + crate::env::promise_batch_action_deploy_contract(promise_index, code) + } + FunctionCall { method_name, arguments, amount, gas } => { + crate::env::promise_batch_action_function_call( + promise_index, + method_name, + arguments, + *amount, + gas.unwrap_or(function_call_gas), + ) + } + Transfer { amount } => { + crate::env::promise_batch_action_transfer(promise_index, *amount) + } + Stake { amount, public_key } => crate::env::promise_batch_action_stake( + promise_index, + *amount, + public_key, + ), + AddFullAccessKey { public_key, nonce } => { + crate::env::promise_batch_action_add_key_with_full_access( + promise_index, + public_key, + *nonce, + ) + } + AddAccessKey { + public_key, + allowance, + receiver_id, + method_names, + nonce, + } => crate::env::promise_batch_action_add_key_with_function_call( + promise_index, + public_key, + *nonce, + *allowance, + receiver_id, + method_names, + ), + DeleteKey { public_key } => { + crate::env::promise_batch_action_delete_key(promise_index, public_key) + } + DeleteAccount { beneficiary_id } => { + crate::env::promise_batch_action_delete_account( + promise_index, + beneficiary_id, + ) + } + } + } + PromiseQueueEvent::Return { index } => { + let promise_index = lookup[index.0]; + crate::env::promise_return(promise_index); + } + } + } + + // TODO should this actually be cleared? Is there any benefit + queue.clear(); + }); +}