diff --git a/boxes/boxes/vanilla/contracts/src/main.nr b/boxes/boxes/vanilla/contracts/src/main.nr index 08748e29a607..17c3b30100e7 100644 --- a/boxes/boxes/vanilla/contracts/src/main.nr +++ b/boxes/boxes/vanilla/contracts/src/main.nr @@ -12,7 +12,7 @@ use dep::aztec::macros::aztec; pub contract PrivateVoting { use dep::aztec::keys::getters::get_public_keys; use dep::aztec::macros::{ - functions::{external, initializer, internal}, + functions::{external, initializer, only_self}, storage::storage, }; use dep::aztec::state_vars::{Map, PublicImmutable, PublicMutable}; @@ -47,7 +47,7 @@ pub contract PrivateVoting { } #[external("public")] - #[internal] + #[only_self] fn add_to_tally_public(candidate: Field) { assert(self.storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended let new_tally = self.storage.tally.at(candidate).read() + 1; diff --git a/docs/docs/developers/docs/aztec-nr/framework-description/functions/attributes.md b/docs/docs/developers/docs/aztec-nr/framework-description/functions/attributes.md index 3cd6b29e37fe..0f3fefd91607 100644 --- a/docs/docs/developers/docs/aztec-nr/framework-description/functions/attributes.md +++ b/docs/docs/developers/docs/aztec-nr/framework-description/functions/attributes.md @@ -173,12 +173,21 @@ When a function is annotated with `#[noinitcheck]`: - The Aztec macro processor skips the [insertion of the initialization check](#initializer-functions-initializer) for this specific function - The function can be called at any time, even if the contract hasn't been initialized yet -## `Internal` functions #[internal] +## #[only_self] + +External functions marked with #[only_self] attribute can only be called by the contract itself - if other contracts try to make the call it will fail. + +This attribute is commonly used when an action starts in private but needs to be completed in public. The public +function must be marked with #[only_self] to restrict access to only the contract itself. A typical example is a private +token mint operation that needs to enqueue a call to a public function to update the publicly tracked total token +supply. + +It is also useful in private functions when dealing with tasks of an unknown size but with a large upper bound (e.g. when needing to process an unknown amount of notes or nullifiers) as they allow splitting the work in multiple circuits, possibly resulting in performance improvements for low-load scenarios. This macro inserts a check at the beginning of the function to ensure that the caller is the contract itself. This is done by adding the following assertion: ```rust -assert(context.msg_sender() == context.this_address(), "Function can only be called internally"); +assert(self.msg_sender() == self.address, "Function can only be called by the same contract"); ``` ## Implementing notes diff --git a/docs/docs/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md b/docs/docs/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md index 7bd7361c7d72..6a493272ef14 100644 --- a/docs/docs/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md +++ b/docs/docs/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions.md @@ -69,13 +69,13 @@ fn get_config_value() -> Field { View functions cannot modify contract state. They're akin to Ethereum's `view` functions. -## Define internal functions +## Define only-self functions -Create contract-only functions using the `#[internal]` annotation: +Create contract-only functions using the `#[only_self]` annotation: ```rust #[external("public")] -#[internal] +#[only_self] fn update_counter_public(item: Field) { // logic } diff --git a/docs/docs/developers/docs/aztec-nr/framework-description/how_to_use_authwit.md b/docs/docs/developers/docs/aztec-nr/framework-description/how_to_use_authwit.md index f322ba8ef219..bfd6d8b8d773 100644 --- a/docs/docs/developers/docs/aztec-nr/framework-description/how_to_use_authwit.md +++ b/docs/docs/developers/docs/aztec-nr/framework-description/how_to_use_authwit.md @@ -63,7 +63,7 @@ This pattern is commonly used in bridge contracts (like the [uniswap example con ```rust #[external("public")] -#[internal] +#[only_self] fn _approve_and_execute_action( target_contract: AztecAddress, bridge_contract: AztecAddress, diff --git a/docs/docs/developers/docs/aztec-nr/framework-description/macros.md b/docs/docs/developers/docs/aztec-nr/framework-description/macros.md index e00b42e0e9e8..371982fc586f 100644 --- a/docs/docs/developers/docs/aztec-nr/framework-description/macros.md +++ b/docs/docs/developers/docs/aztec-nr/framework-description/macros.md @@ -15,7 +15,8 @@ It is also worth mentioning Noir's `unconstrained` function type [here (Noir doc - `#[initializer]` - If one or more functions are marked as an initializer, then one of them must be called before any non-initializer functions - `#[noinitcheck]` - The function is able to be called before an initializer (if one exists) - `#[view]` - Makes calls to the function static -- `#[internal]` - Function can only be called from within the contract +- `#[only_self]` - Available only for `external` functions - any external caller except the current contract is rejected. +- `#[internal]` - NOT YET IMPLEMENTED - Function can only be called from within the contract and the call itself is inlined (e.g. akin to EVM's JUMP and not EVM's CALL) - `#[note]` - Creates a custom note - `#[storage]` - Defines contract storage diff --git a/docs/docs/developers/docs/foundational-topics/call_types.md b/docs/docs/developers/docs/foundational-topics/call_types.md index 7c67b9b821a8..d840d752eb6c 100644 --- a/docs/docs/developers/docs/foundational-topics/call_types.md +++ b/docs/docs/developers/docs/foundational-topics/call_types.md @@ -117,7 +117,7 @@ Since the public call is made asynchronously, any return values or side effects #include_code enqueue_public /noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr rust -It is also possible to create public functions that can _only_ be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[internal]`. +It is also possible to create public functions that can _only_ be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[only_self]`. A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. diff --git a/docs/docs/developers/docs/resources/migration_notes.md b/docs/docs/developers/docs/resources/migration_notes.md index 28cea99e4cf3..04935f81203a 100644 --- a/docs/docs/developers/docs/resources/migration_notes.md +++ b/docs/docs/developers/docs/resources/migration_notes.md @@ -54,7 +54,7 @@ Because Aztec has native account abstraction, the very first function call of a Previously (before this change) we'd been silently setting this first `msg_sender` to be `AztecAddress::from_field(-1);`, and enforcing this value in the protocol's kernel circuits. Now we're passing explicitness to smart contract developers by wrapping `msg_sender` in an `Option` type. We'll explain the syntax shortly. -We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[internal]` public functions, visibility of `msg_sender` is unavoidable (the caller of an internal function must be the same contract address by definition). But for _some_ use cases, a visible `msg_sender` is an unnecessary privacy leakage. +We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[only_self]` public functions, visibility of `msg_sender` is unavoidable (the caller of an `#[only_self]` function must be the same contract address by definition). But for _some_ use cases, a visible `msg_sender` is an unnecessary privacy leakage. We therefore have added a feature where `msg_sender` can be optionally set to `Option::none()` for enqueued public function calls (aka private->public calls). We've been colloquially referring to this as "setting msg_sender to null". #### Aztec.nr diffs @@ -212,6 +212,7 @@ Aztec contracts now automatically inject a `self` parameter into every contract - `self.emit(...)` - Emit events And soon to be implemented also: + - `self.call(...)` - Make contract calls #### How it works @@ -258,15 +259,15 @@ fn new_transfer(amount: u128, recipient: AztecAddress) { Storage and context are no longer injected into the function as standalone variables and instead you need to access them via `self`: - ```diff - - let balance = storage.balances.at(owner).read(); - + let balance = self.storage.balances.at(owner).read(); - ``` +```diff +- let balance = storage.balances.at(owner).read(); ++ let balance = self.storage.balances.at(owner).read(); +``` - ```diff - - context.push_nullifier(nullifier); - + self.context.push_nullifier(nullifier); - ``` +```diff +- context.push_nullifier(nullifier); ++ self.context.push_nullifier(nullifier); +``` Note that `context` is expected to be use only when needing to access a low-level API (like directly emitting a nullifier). @@ -331,6 +332,30 @@ fn withdraw(amount: u128, recipient: AztecAddress) { } ``` +### Renaming #[internal] as #[only_self] + +We want for internal to mean the same as in Solidity where internal function can be called only from the same contract +and is also inlined (EVM JUMP opcode and not EVM CALL). The original implementation of our `#[internal]` macro also +results in the function being callable only from the same contract but it results in a different call (hence it doesn't +map to EVM JUMP). This is very confusing for people that know Solidity hence we are doing the rename. A true +`#[internal]` will be introduced in the future. + +To migrate your contracts simply rename all the occurrences of `#[internal]` with `#[only_self]` and update the imports: + +```diff +- use aztec::macros::functions::internal; ++ use aztec::macros::functions::only_self; +``` + +```diff +#[external("public")] +- #[internal] ++ #[only_self] +fn _deduct_public_balance(owner: AztecAddress, amount: u64) { + ... +} +``` + ### Replacing #[private], #[public], #[utility] with #[external(...)] macro The original naming was not great in that it did not sufficiently communicate what the given macro did. diff --git a/docs/examples/contracts/bob_token_contract/src/main.nr b/docs/examples/contracts/bob_token_contract/src/main.nr index c2a19f604b18..859b1164527a 100644 --- a/docs/examples/contracts/bob_token_contract/src/main.nr +++ b/docs/examples/contracts/bob_token_contract/src/main.nr @@ -6,7 +6,7 @@ use aztec::macros::aztec; pub contract BobToken { // docs:end:start use aztec::{ - macros::{functions::{external, initializer, internal}, storage::storage}, + macros::{functions::{external, initializer, only_self}, storage::storage}, protocol_types::address::AztecAddress, state_vars::Map, state_vars::public_mutable::PublicMutable, }; @@ -86,7 +86,7 @@ pub contract BobToken { // docs:start:_deduct_public_balance #[external("public")] - #[internal] + #[only_self] fn _deduct_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); assert(balance >= amount, "Insufficient public BOB tokens"); @@ -120,7 +120,7 @@ pub contract BobToken { // docs:start:_assert_is_owner #[external("public")] - #[internal] + #[only_self] fn _assert_is_owner(address: AztecAddress) { assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens"); } @@ -153,7 +153,7 @@ pub contract BobToken { } #[external("public")] - #[internal] + #[only_self] fn _credit_public_balance(owner: AztecAddress, amount: u64) { let balance = self.storage.public_balances.at(owner).read(); self.storage.public_balances.at(owner).write(balance + amount); diff --git a/docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr b/docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr index 632930125d51..be00f0592d08 100644 --- a/docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr +++ b/docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr @@ -5,7 +5,7 @@ pub mod nft; #[aztec] pub contract NFTPunk { use dep::aztec::{ - macros::{storage::storage, functions::{external, utility, initializer, internal}}, + macros::{storage::storage, functions::{external, utility, initializer, only_self}}, protocol_types::{address::AztecAddress}, state_vars::{PrivateSet, PublicImmutable, delayed_public_mutable::DelayedPublicMutable, Map} }; @@ -38,7 +38,7 @@ pub contract NFTPunk { // docs:start:mark_nft_exists #[external("public")] - #[internal] + #[only_self] fn _mark_nft_exists(token_id: Field, exists: bool) { self.storage.nfts.at(token_id).schedule_value_change(exists); } diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr index 1fc8c0509ba4..14c8782deef7 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -14,14 +14,11 @@ use crate::macros::{ }, utils::{is_fn_external, module_has_initializer}, }; -use super::utils::{fn_has_noinitcheck, is_fn_initializer, is_fn_internal, is_fn_view}; +use super::utils::{fn_has_noinitcheck, is_fn_initializer, is_fn_only_self, is_fn_view}; use auth_registry::AUTHORIZE_ONCE_REGISTRY; -// Note: The internal naming below is deprecated and will soon be renamed. For this reason it's so weird (having -// external and internal applicable to one function). -// // Functions can have multiple attributes applied to them, e.g. a single function can have #[external("public")], -// #[view] and #[internal]. However. the order in which this will be evaluated is unknown, which makes combining them +// #[view] and #[only_self]. However. the order in which this will be evaluated is unknown, which makes combining them // tricky. // // Our strategy is to have functions accept at most one #[external(...)] attribute that takes a parameter ("private", @@ -29,10 +26,10 @@ use auth_registry::AUTHORIZE_ONCE_REGISTRY; // the arg of #[external(...)] attribute contains the code for all other attributes, but they only run if // the corresponding marker attribute has been applied to the function. // -// For example, the "private" handler of #[external(...)] knows about #[internal] and what it should do, but it only -// does it if it sees that the private function in question also has the `internal` attribute applied. `#[internal]` +// For example, the "private" handler of #[external(...)] knows about #[only_self] and what it should do, but it only +// does it if it sees that the private function in question also has the `only_self` attribute applied. `#[only_self]` // itself does nothing - it is what we call a 'marker' attribute, that only exists for `#[external("private")]` -// and `#[external(public)]` to check if it's been applied. Therefore, the execution order of `#[internal]` and +// and `#[external(public)]` to check if it's been applied. Therefore, the execution order of `#[only_self]` and // `#[external(...)]` is irrelevant. /// An initializer function is similar to a constructor: @@ -74,8 +71,14 @@ pub comptime fn noinitcheck(f: FunctionDefinition) { } } -/// Internal functions can only be called by the contract itself, typically from private into public. -pub comptime fn internal(f: FunctionDefinition) { +/// Functions marked with #[only_self] attribute can only be called by the contract itself. +/// +/// # Usage +/// This attribute is commonly used when an action starts in private but needs to be completed in public. The public +/// function must be marked with #[only_self] to restrict access to only the contract itself. A typical example is a +/// private token mint operation that needs to enqueue a call to a public function to update the publicly tracked total +/// token supply. +pub comptime fn only_self(f: FunctionDefinition) { // Marker attribute - see the comment above if !is_fn_external(f) { @@ -84,7 +87,7 @@ pub comptime fn internal(f: FunctionDefinition) { // function is run. let name = f.name(); panic( - f"The #[internal] attribute can only be applied to #[external(\"private\")] or #[external(\"public\")] functions - {name} is neither", + f"The #[only_self] attribute can only be applied to #[external(\"private\")] or #[external(\"public\")] functions - {name} is neither", ); } } @@ -244,7 +247,7 @@ comptime fn utility(f: FunctionDefinition) -> Quoted { fn_abi_export } -/// Utility functions cannot be used with the following modifiers: #[authorize_once], #[internal], #[view], +/// Utility functions cannot be used with the following modifiers: #[authorize_once], #[only_self], #[view], /// #[initializer], and #[noinitcheck]. Since we cannot enforce a specific ordering between these modifiers and /// #[external(...)], and we cannot access the #[external(...)] argument from within these modifiers' implementations /// (as accessing EXTERNAL_REGISTRY would require enforcing ordering), we perform these compatibility checks here in @@ -257,10 +260,10 @@ comptime fn post_external_utility_checks(f: FunctionDefinition) { ); } - if is_fn_internal(f) { + if is_fn_only_self(f) { let name = f.name(); panic( - f"The #[internal] attribute cannot be applied to #[external(\"utility\")] functions - {name}", + f"The #[only_self] attribute cannot be applied to #[external(\"utility\")] functions - {name}", ); } diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr index 1b136308bd2e..1187314b3de3 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr @@ -8,7 +8,7 @@ use crate::macros::{ notes::NOTES, utils::{ fn_has_authorize_once, fn_has_noinitcheck, is_fn_contract_library_method, is_fn_external, - is_fn_initializer, is_fn_internal, is_fn_test, is_fn_view, modify_fn_body, + is_fn_initializer, is_fn_only_self, is_fn_test, is_fn_view, modify_fn_body, module_has_initializer, module_has_storage, }, }; @@ -67,8 +67,8 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) { let function_name = f.name(); // Modifications introduced by the different marker attributes. - let internal_check = if is_fn_internal(f) { - let assertion_message = f"Function {function_name} can only be called internally"; + let internal_check = if is_fn_only_self(f) { + let assertion_message = f"Function {function_name} can only be called by the same contract"; quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); } } else { quote {} @@ -233,19 +233,19 @@ pub(crate) comptime fn transform_public(f: FunctionDefinition) { }; }; - let name = f.name(); + let function_name = f.name(); + // Modifications introduced by the different marker attributes. - let internal_check = if is_fn_internal(f) { - let assertion_message = f"Function {name} can only be called internally"; + let internal_check = if is_fn_only_self(f) { + let assertion_message = f"Function {function_name} can only be called by the same contract"; quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); } } else { quote {} }; let view_check = if is_fn_view(f) { - let name = f.name(); let assertion_message = - f"Function {name} can only be called statically".as_ctstring().as_quoted_str(); + f"Function {function_name} can only be called statically".as_ctstring().as_quoted_str(); quote { assert(self.context.is_static_call(), $assertion_message); } } else { quote {} diff --git a/noir-projects/aztec-nr/aztec/src/macros/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/utils.nr index 42f34150b7da..ab7139390e39 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/utils.nr @@ -17,8 +17,8 @@ pub(crate) comptime fn is_fn_view(f: FunctionDefinition) -> bool { f.has_named_attribute("view") } -pub(crate) comptime fn is_fn_internal(f: FunctionDefinition) -> bool { - f.has_named_attribute("internal") +pub(crate) comptime fn is_fn_only_self(f: FunctionDefinition) -> bool { + f.has_named_attribute("only_self") } pub(crate) comptime fn is_fn_initializer(f: FunctionDefinition) -> bool { diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index d1705d140ee5..f04fd6fddb8d 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -46,6 +46,7 @@ members = [ "contracts/test/no_constructor_contract", "contracts/test/note_getter_contract", "contracts/test/offchain_effect_contract", + "contracts/test/only_self_contract", "contracts/test/oracle_version_check_contract", "contracts/test/parent_contract", "contracts/test/pending_note_hashes_contract", diff --git a/noir-projects/noir-contracts/contracts/app/amm_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/amm_contract/src/main.nr index 211f3d2d639a..2b842fe9007e 100644 --- a/noir-projects/noir-contracts/contracts/app/amm_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/amm_contract/src/main.nr @@ -40,7 +40,7 @@ pub contract AMM { lib::{get_amount_in, get_amount_out, get_amounts_on_remove, get_amounts_to_add}, }; use dep::aztec::{ - macros::{functions::{external, initializer, internal}, storage::storage}, + macros::{functions::{external, initializer, only_self}, storage::storage}, protocol_types::address::AztecAddress, state_vars::PublicImmutable, }; @@ -146,7 +146,7 @@ pub contract AMM { } #[external("public")] - #[internal] + #[only_self] fn _add_liquidity( config: Config, // We could read this in public, but it's cheaper to receive from private refund_token0_partial_note: PartialUintNote, @@ -283,7 +283,7 @@ pub contract AMM { } #[external("public")] - #[internal] + #[only_self] fn _remove_liquidity( config: Config, // We could read this in public, but it's cheaper to receive from private liquidity: u128, @@ -357,7 +357,7 @@ pub contract AMM { } #[external("public")] - #[internal] + #[only_self] fn _swap_exact_tokens_for_tokens( token_in: AztecAddress, token_out: AztecAddress, @@ -436,7 +436,7 @@ pub contract AMM { } #[external("public")] - #[internal] + #[only_self] fn _swap_tokens_for_exact_tokens( token_in: AztecAddress, token_out: AztecAddress, diff --git a/noir-projects/noir-contracts/contracts/app/card_game_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/card_game_contract/src/main.nr index 7c2583b7c815..eca0b832b9f1 100644 --- a/noir-projects/noir-contracts/contracts/app/card_game_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/card_game_contract/src/main.nr @@ -12,7 +12,7 @@ pub contract CardGame { use crate::cards::{Card, compute_deck_strength, Deck, get_pack_cards}; use crate::game::{Game, PLAYABLE_CARDS, PlayerEntry}; - use dep::aztec::macros::{functions::{external, internal}, storage::storage}; + use dep::aztec::macros::{functions::{external, only_self}, storage::storage}; use dep::aztec::protocol_types::traits::{FromField, ToField}; @@ -51,7 +51,7 @@ pub contract CardGame { } #[external("public")] - #[internal] + #[only_self] fn on_game_joined(game: u32, player: AztecAddress, deck_strength: u32) { let game_storage = self.storage.games.at(game as Field); @@ -86,7 +86,7 @@ pub contract CardGame { } #[external("public")] - #[internal] + #[only_self] fn on_card_played(game: u32, player: AztecAddress, card_as_field: Field) { let game_storage = self.storage.games.at(game as Field); @@ -113,7 +113,7 @@ pub contract CardGame { } #[external("public")] - #[internal] + #[only_self] fn on_cards_claimed(game: u32, player: AztecAddress, cards_hash: Field) { let game_storage = self.storage.games.at(game as Field); let mut game_data = game_storage.read(); diff --git a/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr index 71a95fbd9b97..a5a76f06863e 100644 --- a/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr @@ -6,7 +6,7 @@ use aztec::macros::aztec; pub contract Crowdfunding { use crate::config::Config; use aztec::{ - macros::{events::event, functions::{external, initializer, internal}, storage::storage}, + macros::{events::event, functions::{external, initializer, only_self}, storage::storage}, messages::message_delivery::MessageDelivery, protocol_types::address::AztecAddress, state_vars::{Map, PrivateSet, PublicImmutable, storage::HasStorageSlot}, @@ -88,7 +88,7 @@ pub contract Crowdfunding { } #[external("public")] - #[internal] + #[only_self] fn _publish_donation_receipts(amount: u128, to: AztecAddress) { self.emit(WithdrawalProcessed { amount, who: to }); } diff --git a/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr index db07d696c62a..21acd5285d37 100644 --- a/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr @@ -23,7 +23,7 @@ pub contract Lending { use dep::price_feed::PriceFeed; use dep::token::Token; - use dep::aztec::macros::{functions::{external, initializer, internal, view}, storage::storage}; + use dep::aztec::macros::{functions::{external, initializer, only_self, view}, storage::storage}; use dep::aztec::protocol_types::traits::{FromField, ToField}; @@ -136,7 +136,7 @@ pub contract Lending { } #[external("public")] - #[internal] + #[only_self] fn _deposit(owner: AztecAddress, amount: u128, collateral_asset: AztecAddress) { let _asset = Lending::at(self.address).update_accumulator().call(self.context); @@ -164,7 +164,7 @@ pub contract Lending { } #[external("public")] - #[internal] + #[only_self] fn _withdraw(owner: AztecAddress, recipient: AztecAddress, amount: u128) { let asset = Lending::at(self.address).update_accumulator().call(self.context); let price = PriceFeed::at(asset.oracle).get_price(0).view(self.context).price; @@ -213,7 +213,7 @@ pub contract Lending { } #[external("public")] - #[internal] + #[only_self] fn _borrow(owner: AztecAddress, to: AztecAddress, amount: u128) { let asset = Lending::at(self.address).update_accumulator().call(self.context); let price = PriceFeed::at(asset.oracle).get_price(0).view(self.context).price; @@ -268,7 +268,7 @@ pub contract Lending { } #[external("public")] - #[internal] + #[only_self] fn _repay(owner: AztecAddress, amount: u128, stable_coin: AztecAddress) { let asset = Lending::at(self.address).update_accumulator().call(self.context); diff --git a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr index 1dbedc384ba4..de753310eceb 100644 --- a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr @@ -13,7 +13,7 @@ pub contract NFT { authwit::auth::compute_authwit_nullifier, context::{PrivateContext, PublicContext}, macros::{ - functions::{authorize_once, external, initializer, internal, view}, + functions::{authorize_once, external, initializer, only_self, view}, storage::storage, }, messages::message_delivery::MessageDelivery, @@ -219,7 +219,7 @@ pub contract NFT { /// to `_finalize_transfer_to_private` can be enqueued. Called unsafe as it does not check `from_and_completer` /// (this has to be done in the calling function). #[external("public")] - #[internal] + #[only_self] fn _finalize_transfer_to_private_unsafe( from_and_completer: AztecAddress, token_id: Field, @@ -302,7 +302,7 @@ pub contract NFT { } #[external("public")] - #[internal] + #[only_self] fn _finish_transfer_to_public(to: AztecAddress, token_id: Field) { self.storage.public_owners.at(token_id).write(to); } diff --git a/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr index 29bbd7b3d5d1..b42529080071 100644 --- a/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr @@ -34,7 +34,7 @@ use aztec::macros::aztec; pub contract Orderbook { use crate::{config::Config, order::Order}; use aztec::{ - macros::{events::event, functions::{external, initializer, internal}, storage::storage}, + macros::{events::event, functions::{external, initializer, only_self}, storage::storage}, oracle::notes::check_nullifier_exists, protocol_types::{address::AztecAddress, traits::{FromField, ToField}}, state_vars::{Map, PublicImmutable}, @@ -105,7 +105,7 @@ pub contract Orderbook { } #[external("public")] - #[internal] + #[only_self] fn _create_order(order_id: Field, order: Order) { // Note that PublicImmutable can be initialized only once so this is a secondary check that the order is // unique. @@ -156,7 +156,7 @@ pub contract Orderbook { } #[external("public")] - #[internal] + #[only_self] fn _fulfill_order( order_id: Field, taker_partial_note: PartialUintNote, diff --git a/noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr index cf79a0012772..bfb707a4797c 100644 --- a/noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr @@ -14,7 +14,7 @@ pub contract PrivateVoting { // docs:start:imports use dep::aztec::{ keys::getters::get_public_keys, - macros::{functions::{external, initializer, internal}, storage::storage}, + macros::{functions::{external, initializer, only_self}, storage::storage}, }; use dep::aztec::protocol_types::{ address::AztecAddress, @@ -58,7 +58,7 @@ pub contract PrivateVoting { } #[external("public")] - #[internal] + #[only_self] fn add_to_tally_public(candidate: Field) { assert(self.storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended let new_tally = self.storage.tally.at(candidate).read() + 1; diff --git a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr index b8c4aac49498..907e08492243 100644 --- a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr @@ -17,7 +17,7 @@ pub contract SimpleToken { context::{PrivateCallInterface, PrivateContext, PublicContext}, macros::{ events::event, - functions::{authorize_once, external, initializer, internal, view}, + functions::{authorize_once, external, initializer, only_self, view}, storage::storage, }, messages::message_delivery::MessageDelivery, @@ -207,7 +207,7 @@ pub contract SimpleToken { } #[external("public")] - #[internal] + #[only_self] fn _finalize_transfer_to_private_unsafe( from_and_completer: AztecAddress, amount: u128, @@ -257,7 +257,7 @@ pub contract SimpleToken { } #[external("public")] - #[internal] + #[only_self] fn _finalize_mint_to_private_unsafe( minter_and_completer: AztecAddress, amount: u128, @@ -287,7 +287,7 @@ pub contract SimpleToken { } #[external("public")] - #[internal] + #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { _increase_public_balance_inner(to, amount, self.storage); } @@ -303,7 +303,7 @@ pub contract SimpleToken { } #[external("public")] - #[internal] + #[only_self] fn _reduce_total_supply(amount: u128) { let new_supply = self.storage.total_supply.read().sub(amount); self.storage.total_supply.write(new_supply); @@ -344,7 +344,7 @@ pub contract SimpleToken { SimpleToken::at(context.this_address())._recurse_subtract_balance(account, remaining) } - #[internal] + #[only_self] #[external("private")] fn _recurse_subtract_balance(account: AztecAddress, amount: u128) -> u128 { subtract_balance( diff --git a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr index 47bcb5171fb6..b0702f094783 100644 --- a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr @@ -20,7 +20,7 @@ pub contract TokenBlacklist { use aztec::{ hash::compute_secret_hash, macros::{ - functions::{authorize_once, external, initializer, internal, view}, + functions::{authorize_once, external, initializer, only_self, view}, storage::storage, }, messages::{ @@ -231,16 +231,15 @@ pub contract TokenBlacklist { TokenBlacklist::at(self.address)._reduce_total_supply(amount).enqueue(self.context); } - /// Internal /// #[external("public")] - #[internal] + #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { let new_balance = self.storage.public_balances.at(to).read().add(amount); self.storage.public_balances.at(to).write(new_balance); } #[external("public")] - #[internal] + #[only_self] fn _reduce_total_supply(amount: u128) { // Only to be called from burn. let new_supply = self.storage.total_supply.read().sub(amount); diff --git a/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr index 4ed1e322e6a3..13f38500d845 100644 --- a/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr @@ -21,7 +21,7 @@ pub contract Token { context::{PrivateCallInterface, PrivateContext, PublicContext}, macros::{ events::event, - functions::{authorize_once, external, initializer, internal, view}, + functions::{authorize_once, external, initializer, only_self, view}, storage::storage, }, messages::message_delivery::MessageDelivery, @@ -300,7 +300,7 @@ pub contract Token { Token::at(context.this_address())._recurse_subtract_balance(account, remaining) } - #[internal] + #[only_self] #[external("private")] fn _recurse_subtract_balance(account: AztecAddress, amount: u128) -> u128 { subtract_balance( @@ -448,7 +448,7 @@ pub contract Token { /// to `_finalize_transfer_to_private` can be enqueued. Called unsafe as it does not check `from_and_completer` /// (this has to be done in the calling function). #[external("public")] - #[internal] + #[only_self] fn _finalize_transfer_to_private_unsafe( from_and_completer: AztecAddress, amount: u128, @@ -526,7 +526,7 @@ pub contract Token { /// to `_finalize_mint_to_private` can be enqueued. Called unsafe as it does not check `minter_and_completer` (this /// has to be done in the calling function). #[external("public")] - #[internal] + #[only_self] fn _finalize_mint_to_private_unsafe( minter_and_completer: AztecAddress, amount: u128, @@ -563,7 +563,7 @@ pub contract Token { /// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal /// function. #[external("public")] - #[internal] + #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { _increase_public_balance_inner(to, amount, self.storage); } @@ -579,7 +579,7 @@ pub contract Token { } #[external("public")] - #[internal] + #[only_self] fn _reduce_total_supply(amount: u128) { // Only to be called from burn. let new_supply = self.storage.total_supply.read().sub(amount); diff --git a/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr index c1d3c0d6369d..3a9f80805566 100644 --- a/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr @@ -13,7 +13,7 @@ pub contract Uniswap { assert_current_call_valid_authwit_public, compute_authwit_message_hash_from_call, set_authorized, }, - macros::{functions::{external, initializer, internal}, storage::storage}, + macros::{functions::{external, initializer, only_self}, storage::storage}, protocol_types::{ abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, @@ -185,10 +185,10 @@ pub contract Uniswap { // This helper method approves the bridge to burn this contract's funds and exits the input asset to L1 // Assumes contract already has funds. // Assume `token` relates to `token_bridge` (ie token_bridge.token == token) - // Note that private can't read public return values so created an internal public that handles everything + // Note that private can't read public return values so created an `only_self` public that handles everything // this method is used for both private and public swaps. #[external("public")] - #[internal] + #[only_self] fn _approve_bridge_and_exit_input_asset_to_L1( token: AztecAddress, token_bridge: AztecAddress, diff --git a/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr index 1145f5a44927..b7f4a571d8cf 100644 --- a/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr @@ -14,7 +14,7 @@ pub contract FPC { use crate::{config::Config, utils::safe_cast_to_u128}; use dep::uint_note::uint_note::PartialUintNote; use aztec::{ - macros::{functions::{external, initializer, internal}, storage::storage}, + macros::{functions::{external, initializer, only_self}, storage::storage}, protocol_types::address::AztecAddress, state_vars::PublicImmutable, }; @@ -108,7 +108,7 @@ pub contract FPC { /// flow. // docs:start:complete_refund #[external("public")] - #[internal] + #[only_self] fn _complete_refund( accepted_asset: AztecAddress, partial_note: PartialUintNote, @@ -175,7 +175,7 @@ pub contract FPC { /// between the `max_fee` and the actual fee. `accepted_asset` is the asset in which the refund is paid. /// It's passed as an argument to avoid the need for another read from public storage. #[external("public")] - #[internal] + #[only_self] fn _pay_refund(refund_recipient: AztecAddress, max_fee: u128, accepted_asset: AztecAddress) { let actual_fee = safe_cast_to_u128(self.context.transaction_fee()); diff --git a/noir-projects/noir-contracts/contracts/protocol/auth_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/protocol/auth_registry_contract/src/main.nr index 397b3cb6cf03..508351e7a75b 100644 --- a/noir-projects/noir-contracts/contracts/protocol/auth_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/protocol/auth_registry_contract/src/main.nr @@ -12,7 +12,7 @@ pub contract AuthRegistry { authwit::auth::{ assert_current_call_valid_authwit, compute_authwit_message_hash, IS_VALID_SELECTOR, }, - macros::{functions::{external, internal, view}, storage::storage}, + macros::{functions::{external, only_self, view}, storage::storage}, protocol_types::address::AztecAddress, state_vars::{Map, PublicMutable}, }; @@ -110,7 +110,7 @@ pub contract AuthRegistry { * @param authorize True if the caller is authorized to perform the message hash, false otherwise */ #[external("public")] - #[internal] + #[only_self] fn _set_authorized(approver: AztecAddress, message_hash: Field, authorize: bool) { self.storage.approved_actions.at(approver).at(message_hash).write(authorize); } diff --git a/noir-projects/noir-contracts/contracts/protocol/fee_juice_contract/src/main.nr b/noir-projects/noir-contracts/contracts/protocol/fee_juice_contract/src/main.nr index cf7514bd7700..c68e7fb874f2 100644 --- a/noir-projects/noir-contracts/contracts/protocol/fee_juice_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/protocol/fee_juice_contract/src/main.nr @@ -6,7 +6,7 @@ use dep::aztec::macros::aztec; pub contract FeeJuice { use dep::aztec::{ context::private_context::PrivateContext, - macros::{functions::{external, internal, view}, storage::storage}, + macros::{functions::{external, only_self, view}, storage::storage}, protocol_types::{ address::{AztecAddress, EthAddress}, constants::FEE_JUICE_ADDRESS, @@ -69,7 +69,7 @@ pub contract FeeJuice { } #[external("public")] - #[internal] + #[only_self] fn _increase_public_balance(to: AztecAddress, amount: u128) { let new_balance = self.storage.balances.at(to).read().add(amount); self.storage.balances.at(to).write(new_balance); diff --git a/noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr index 90846d3c56b9..2500820e333f 100644 --- a/noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr @@ -6,7 +6,7 @@ pub contract Child { use dep::aztec::protocol_types::address::AztecAddress; use dep::aztec::{ - macros::{functions::{external, internal}, storage::storage}, + macros::{functions::external, storage::storage}, messages::message_delivery::MessageDelivery, note::{note_getter_options::NoteGetterOptions, note_interface::NoteProperties}, state_vars::{Map, PrivateSet, PublicMutable}, @@ -25,13 +25,6 @@ pub contract Child { fn value(input: Field) -> Field { input + self.context.chain_id() + self.context.version() } - // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. - // Can only be called from this contract. - #[external("private")] - #[internal] - fn value_internal(input: Field) -> Field { - input + self.context.chain_id() + self.context.version() - } // Returns base_value + chain_id + version + block_number + timestamp #[external("public")] @@ -83,17 +76,6 @@ pub contract Child { new_value } - // Increments `current_value` by `new_value`. Can only be called from this contract. - #[external("public")] - #[internal] - fn pub_inc_value_internal(new_value: Field) -> Field { - let old_value = self.storage.current_value.read(); - self.storage.current_value.write(old_value + new_value); - self.context.emit_public_log(new_value); - - new_value - } - #[external("public")] fn set_value_twice_with_nested_first() { let _result = Child::at(self.address).pub_set_value(10).call(self.context); diff --git a/noir-projects/noir-contracts/contracts/test/only_self_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/only_self_contract/Nargo.toml new file mode 100644 index 000000000000..9014a79bab6e --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/only_self_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "only_self_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } +value_note = { path = "../../../../aztec-nr/value-note" } diff --git a/noir-projects/noir-contracts/contracts/test/only_self_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/only_self_contract/src/main.nr new file mode 100644 index 000000000000..9bec5ae19f75 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/only_self_contract/src/main.nr @@ -0,0 +1,72 @@ +use aztec::macros::aztec; + +/// Contract used to test the #[only_self] attribute restricts the access as expected. +#[aztec] +pub contract OnlySelf { + use aztec::macros::functions::{external, only_self}; + + #[external("private")] + #[only_self] + fn only_self_private() {} + + #[external("public")] + #[only_self] + fn only_self_public() {} + + #[external("private")] + fn call_only_self_private() { + OnlySelf::at(self.address).only_self_private().call(self.context); + } + + #[external("public")] + fn call_only_self_public() { + OnlySelf::at(self.address).only_self_public().call(self.context); + } +} + +mod test { + use super::OnlySelf; + use aztec::{ + protocol_types::address::AztecAddress, test::helpers::test_environment::TestEnvironment, + }; + + unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress) { + let mut env = TestEnvironment::new(); + let caller = env.create_light_account(); + let instance_address = env.deploy("OnlySelf").without_initializer(); + + (env, caller, instance_address) + } + + #[test(should_fail_with = "Function only_self_private can only be called by the same contract")] + unconstrained fn only_self_private_function_fails_if_called_from_other_contract() { + let (mut env, caller, instance_address) = setup(); + let instance = OnlySelf::at(instance_address); + + env.call_private(caller, instance.only_self_private()); + } + + #[test(should_fail_with = "Function only_self_public can only be called by the same contract")] + unconstrained fn only_self_public_function_fails_if_called_from_other_contract() { + let (mut env, caller, instance_address) = setup(); + let instance = OnlySelf::at(instance_address); + + env.call_public(caller, instance.only_self_public()); + } + + #[test] + unconstrained fn only_self_private_function_succeeds_if_called_from_same_contract() { + let (mut env, caller, instance_address) = setup(); + let instance = OnlySelf::at(instance_address); + + env.call_private(caller, instance.call_only_self_private()); + } + + #[test] + unconstrained fn only_self_public_function_succeeds_if_called_from_same_contract() { + let (mut env, caller, instance_address) = setup(); + let instance = OnlySelf::at(instance_address); + + env.call_public(caller, instance.call_only_self_public()); + } +} diff --git a/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr index 0f3c9191881b..e84f6a0f51fa 100644 --- a/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr @@ -6,7 +6,7 @@ use aztec::macros::aztec; pub contract Spam { use aztec::{ - macros::{functions::{external, internal}, storage::storage}, + macros::{functions::{external, only_self}, storage::storage}, messages::message_delivery::MessageDelivery, protocol_types::{ address::AztecAddress, @@ -62,7 +62,7 @@ pub contract Spam { } #[external("public")] - #[internal] + #[only_self] fn public_spam(start: u32, end: u32) { let one = 1 as u128; for i in start..end { diff --git a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr index 42a76ee088ab..f2243746045b 100644 --- a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr @@ -44,7 +44,7 @@ pub contract Test { // Macros macros::{ events::event, - functions::{initializer, internal, noinitcheck, external}, + functions::{initializer, only_self, noinitcheck, external}, storage::storage, }, // Contract instance management @@ -128,7 +128,7 @@ pub contract Test { } #[external("public")] - #[internal] + #[only_self] fn dummy_public_call() {} #[external("private")] diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_call.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_call.test.ts index 44e53bbf7414..e1ccaf248350 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_call.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_call.test.ts @@ -21,12 +21,4 @@ describe('e2e_nested_contract manual', () => { .send({ from: defaultAccountAddress }) .wait(); }); - - it('fails simulation if calling a function not allowed to be called externally', async () => { - await expect( - parentContract.methods - .entry_point(childContract.address, await (childContract.methods as any).value_internal.selector()) - .simulate({ from: defaultAccountAddress }), - ).rejects.toThrow(/Assertion failed: Function value_internal can only be called internally/); - }); }); diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts index a43cd5f1cc4b..dc69a8a53156 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/manual_private_enqueue.test.ts @@ -36,18 +36,6 @@ describe('e2e_nested_contract manual_enqueue', () => { expect(await getChildStoredValue(childContract)).toEqual(new Fr(42n)); }); - it('fails simulation if calling a public function not allowed to be called externally', async () => { - await expect( - parentContract.methods - .enqueue_call_to_child( - childContract.address, - await (childContract.methods as any).pub_inc_value_internal.selector(), - 42n, - ) - .simulate({ from: defaultAccountAddress }), - ).rejects.toThrow(/Assertion failed: Function pub_inc_value_internal can only be called internally/); - }); - it('enqueues multiple public calls', async () => { await parentContract.methods .enqueue_call_to_child_twice(childContract.address, await childContract.methods.pub_inc_value.selector(), 42n)