Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Use public constructors where possible #4937

Merged
merged 4 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions docs/docs/developers/tutorials/writing_private_voting_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,26 +95,10 @@ In this contract, we will store three vars:

The next step is to initialize the contract with a constructor. The constructor will take an address as a parameter and set the admin.

All constructors must be private, and because the admin is in public storage, we cannot directly update it from the constructor. You can find more information about this [here](../../learn/concepts/communication/public_private_calls/main.md).

Therefore our constructor must call a public function by using `context.call_public_function()`. Paste this under the `impl` storage block:

#include_code constructor noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust

`context.call_public_function()` takes three arguments:

1. The contract address whose method we want to call
2. The selector of the function to call (we can use `FunctionSelector::from_signature(...)` for this)
3. The arguments of the function (we pass the `admin`)

We now need to write the `_initialize()` function:

#include_code initialize noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust

This function takes the admin argument and writes it to the storage. We are also using this function to set the `voteEnded` boolean as false in the same way.

This function is set as `internal` so that it can only be called from within the contract. This stops anyone from setting a new admin.

## Casting a vote privately

For the sake of simplicity, we will have three requirements:
Expand Down
12 changes: 1 addition & 11 deletions docs/docs/developers/tutorials/writing_token_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,10 @@ Copy and paste the body of each function into the appropriate place in your proj

### Constructor

In the source code, the constructor logic is commented out due to some limitations of the current state of the development.
This function sets the creator of the contract (passed as `msg_sender` from the constructor) as the admin and makes them a minter, and sets name, symbol, and decimals.

#include_code constructor /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

The constructor is a private function. There isn't any private state to set up in this function, but there is public state to set up. The `context` is a global variable that is available to private and public functions, but the available methods differ based on the context. You can see the implementation details [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/context.nr). The `context.call_public_function` allows a private function to call a public function on any contract. In this case, the constructor is passing the `msg_sender` as the argument to the `_initialize` function, which is also defined in this contract.

### Public function implementations

Public functions are declared with the `#[aztec(public)]` macro above the function name like so:
Expand Down Expand Up @@ -375,14 +373,6 @@ After initializing storage, the function checks that the `msg_sender` is authori

Internal functions are functions that can only be called by this contract. The following 3 functions are public functions that are called from the [private execution context](#execution-contexts). Marking these as `internal` ensures that only the desired private functions in this contract are able to call them. Private functions defer execution to public functions because private functions cannot update public state directly.

#### `_initialize`

This function is called via the [constructor](#constructor).

This function sets the creator of the contract (passed as `msg_sender` from the constructor) as the admin and makes them a minter.

#include_code initialize /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `_increase_public_balance`

This function is called from [`unshield`](#unshield). The account's private balance is decremented in `shield` and the public balance is increased in this function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,6 @@ contract AppSubscription {
global SUBSCRIPTION_TXS = 5;
// global GAS_TOKEN_ADDRESS = AztecAddress::from_field(0x08c0e8041f92758ca49ccb62a77318b46090019d380552ddaec5cd0b54804636);

// Constructs the contract
#[aztec(private)]
#[aztec(initializer)]
fn constructor(
target_address: AztecAddress,
subscription_recipient_address: AztecAddress,
subscription_token_address: AztecAddress,
subscription_price: Field,
gas_token_address: AztecAddress
) {
context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("init((Field),(Field),(Field),Field,(Field))"),
[
target_address.to_field(), subscription_token_address.to_field(), subscription_recipient_address.to_field(), subscription_price, gas_token_address.to_field()
]
);
}

#[aztec(private)]
fn entrypoint(payload: pub DAppPayload, user_address: pub AztecAddress) {
assert(context.msg_sender().to_field() == 0);
Expand Down Expand Up @@ -86,12 +67,11 @@ contract AppSubscription {
}

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn init(
#[aztec(initializer)]
fn constructor(
target_address: AztecAddress,
subscription_token_address: AztecAddress,
subscription_recipient_address: AztecAddress,
subscription_token_address: AztecAddress,
subscription_price: Field,
gas_token_address: AztecAddress
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,14 @@ contract EasyPrivateVoting {
// docs:end:storage_struct

// docs:start:constructor
#[aztec(private)]
#[aztec(initializer)] // annotation to mark function as private and expose private context
fn constructor(admin: AztecAddress) { // called when contract is deployed
context.call_public_function(
// we cannot update public state directly from private function but we can call public function (which queues it)
context.this_address(),// contract address whose method we want to call
FunctionSelector::from_signature("_initialize((Field))"), // function selector
[admin.to_field()] // parameters
);
}
// docs:end:constructor
// docs:start:initialize
#[aztec(public)] // annotation to mark function as public and expose public context
#[aztec(internal)] // internal - can only be called by contract
#[aztec(noinitcheck)]
fn _initialize(admin: AztecAddress) {
#[aztec(public)]
#[aztec(initializer)] // annotation to mark function as a constructor
fn constructor(admin: AztecAddress) {
storage.admin.write(admin);
storage.voteEnded.write(false);
}
// docs:end:initialize
// docs:end:constructor

// docs:start:cast_vote
#[aztec(private)] // annotation to mark function as private and expose private context
fn cast_vote(candidate: Field) {
Expand Down
14 changes: 1 addition & 13 deletions noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,9 @@ contract FPC {
fee_asset: SharedImmutable<AztecAddress>,
}

#[aztec(private)]
#[aztec(public)]
#[aztec(initializer)]
fn constructor(other_asset: AztecAddress, fee_asset: AztecAddress) {
let selector = FunctionSelector::from_signature("_initialize((Field),(Field))");
context.call_public_function(
context.this_address(),
selector,
[other_asset.to_field(), fee_asset.to_field()]
);
}

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn _initialize(other_asset: AztecAddress, fee_asset: AztecAddress) {
storage.other_asset.initialize(other_asset);
storage.fee_asset.initialize(fee_asset);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,10 @@ contract InclusionProofs {
public_unused_value: PublicMutable<Field>,
}

#[aztec(private)]
#[aztec(public)]
#[aztec(initializer)]
fn constructor(public_value: Field) {
let selector = FunctionSelector::from_signature("_initialize(Field)");
context.call_public_function(context.this_address(), selector, [public_value]);
}

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn _initialize(value: Field) {
storage.public_value.write(value);
storage.public_value.write(public_value);
}

// docs:start:create_note
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,18 @@ contract TokenBlacklist {
}

// docs:start:constructor
#[aztec(private)]
#[aztec(public)]
#[aztec(initializer)]
fn constructor(admin: AztecAddress, slow_updates_contract: AztecAddress) {
// docs:end:constructor
let selector = FunctionSelector::from_signature("_initialize((Field),(Field))");
context.call_public_function(
context.this_address(),
selector,
[admin.to_field(), slow_updates_contract.to_field()]
);
assert(!admin.is_zero(), "invalid admin");
storage.admin.write(admin);
// docs:start:write_slow_update_public
storage.slow_update.initialize(slow_updates_contract);
// docs:end:write_slow_update_public
// docs:start:slowmap_initialize
SlowMap::at(slow_updates_contract).initialize(context);
// docs:end:slowmap_initialize
// We cannot do the following atm
// let roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false }.get_value().to_field();
// SlowMap::at(slow_updates_contract).update_at_private(&mut context, admin.to_field(), roles);
Expand All @@ -75,21 +77,6 @@ contract TokenBlacklist {
assert(storage.admin.read().eq(caller), "caller is not admin");
}

///////
#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn _initialize(new_admin: AztecAddress, slow_updates_contract: AztecAddress) {
assert(!new_admin.is_zero(), "invalid admin");
storage.admin.write(new_admin);
// docs:start:write_slow_update_public
storage.slow_update.initialize(slow_updates_contract);
// docs:end:write_slow_update_public
// docs:start:slowmap_initialize
SlowMap::at(slow_updates_contract).initialize(context);
// docs:end:slowmap_initialize
}

#[aztec(private)]
fn update_roles(user: AztecAddress, roles: Field) {
// docs:start:slowmap_at
Expand Down
39 changes: 9 additions & 30 deletions noir-projects/noir-contracts/contracts/token_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ contract Token {
// docs:end:storage_struct

// docs:start:constructor
#[aztec(private)]
#[aztec(public)]
#[aztec(initializer)]
fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) {
let selector = FunctionSelector::from_signature("_initialize((Field),(Field),(Field),u8)");
let name_s = FieldCompressedString::from_string(name);
let symbol_s = FieldCompressedString::from_string(symbol);
context.call_public_function(
context.this_address(),
selector,
[admin.to_field(), name_s.serialize()[0], symbol_s.serialize()[0], decimals as Field]
);
assert(!admin.is_zero(), "invalid admin");
storage.admin.write(admin);
storage.minters.at(admin).write(true);
storage.name.initialize(FieldCompressedString::from_string(name));
storage.symbol.initialize(FieldCompressedString::from_string(symbol));
// docs:start:initialize_decimals
storage.decimals.initialize(decimals);
// docs:end:initialize_decimals
}
// docs:end:constructor

Expand Down Expand Up @@ -311,27 +311,6 @@ contract Token {
}
// docs:end:burn

// docs:start:initialize
#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn _initialize(
new_admin: AztecAddress,
name: FieldCompressedString,
symbol: FieldCompressedString,
decimals: u8
) {
assert(!new_admin.is_zero(), "invalid admin");
storage.admin.write(new_admin);
storage.minters.at(new_admin).write(true);
storage.name.initialize(name);
storage.symbol.initialize(symbol);
// docs:start:initialize_decimals
storage.decimals.initialize(decimals);
// docs:end:initialize_decimals
}
// docs:end:initialize

/// Internal ///

// docs:start:increase_public_balance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ pub fn validate_inputs(public_call: PublicCallData) {
!this_call_stack_item.contract_address.eq(AztecAddress::zero()), "Contract address cannot be zero"
);
assert(this_call_stack_item.function_data.selector.to_field() != 0, "Function signature cannot be zero");
assert(
this_call_stack_item.function_data.is_constructor == false, "Constructors cannot be public functions"
);
assert(
this_call_stack_item.function_data.is_private == false, "Cannot execute a private function with the public kernel circuit"
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,22 +199,6 @@ mod tests {
// assert_eq_public_data_update_requests(public_inputs.end.public_data_update_requests, expected);
// }

#[test(should_fail_with="Constructors cannot be public functions")]
fn constructor_should_fail() {
let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new();
builder.public_call.function_data.is_constructor = true;

builder.failed();
}

#[test(should_fail_with="Contract deployment cannot be a public function")]
fn constructor_should_fail_2() {
let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new();
builder.public_call.public_inputs.call_context.is_contract_deployment = true;

builder.failed();
}

#[test(should_fail_with="Bytecode hash cannot be zero")]
fn no_bytecode_hash_should_fail() {
let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,6 @@ mod tests {
}
}

#[test(should_fail_with="Constructors cannot be public functions")]
fn constructor_should_fail() {
let mut builder = PublicKernelTeardownCircuitPrivateInputsBuilder::new();
builder.public_call.function_data.is_constructor = true;

builder.failed();
}

#[test(should_fail_with="Contract deployment cannot be a public function")]
fn constructor_should_fail_2() {
let mut builder = PublicKernelTeardownCircuitPrivateInputsBuilder::new();
builder.public_call.public_inputs.call_context.is_contract_deployment = true;

builder.failed();
}

#[test(should_fail_with="Bytecode hash cannot be zero")]
fn no_bytecode_hash_should_fail() {
let mut builder = PublicKernelTeardownCircuitPrivateInputsBuilder::new();
Expand Down
Loading