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

chore: migrate blacklist token to use shared mutable #5885

Merged
merged 7 commits into from
Apr 22, 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
Original file line number Diff line number Diff line change
@@ -1,100 +1,68 @@
mod types;
// Minimal token implementation that supports `AuthWit` accounts and the slow update tree.

// Minimal token implementation that supports `AuthWit` accounts and SharedMutable variables.
// The auth message follows a similar pattern to the cross-chain message and includes a designated caller.
// The designated caller is ALWAYS used here, and not based on a flag as cross-chain.
// message hash = H([caller, contract, selector, ...args])
// To be read as `caller` calls function at `contract` defined by `selector` with `args`
// Including a nonce in the message hash ensures that the message can only be used once.
// The slow updates tree are used for access control related to minters and blacklist.

// TODO's: https://github.com/AztecProtocol/aztec-packages/issues/3210
// We are currently unable to do constructor -> private calls
// The SharedMutables are used for access control related to minters and blacklist.

contract TokenBlacklist {
// Libs

use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress};
use dep::aztec::{
note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader},
hash::compute_secret_hash, state_vars::{Map, PublicMutable, PrivateSet, SharedImmutable}
hash::compute_secret_hash,
state_vars::{Map, PublicMutable, PrivateSet, SharedMutable, SharedImmutable}
};

use dep::field_note::field_note::FieldNote;

use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}};

use crate::types::{transparent_note::TransparentNote, token_note::TokenNote, balances_map::BalancesMap, roles::UserFlags};
// docs:start:interface
use dep::slow_tree::SlowTree;
// docs:end:interface

// Changing an address' roles has a certain block delay before it goes into effect.
global CHANGE_ROLES_DELAY_BLOCKS = 5;

#[aztec(storage)]
struct Storage {
admin: PublicMutable<AztecAddress>,
balances: BalancesMap<TokenNote>,
total_supply: PublicMutable<U128>,
pending_shields: PrivateSet<TransparentNote>,
public_balances: Map<AztecAddress, PublicMutable<U128>>,
slow_update: SharedImmutable<AztecAddress>,
roles: Map<AztecAddress, SharedMutable<UserFlags, CHANGE_ROLES_DELAY_BLOCKS>>,
}

// docs:start:constructor
#[aztec(public)]
#[aztec(initializer)]
fn constructor(admin: AztecAddress, slow_updates_contract: AztecAddress) {
// docs:end:constructor
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
SlowTree::at(slow_updates_contract).initialize().call(&mut 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();
// SlowTree::at(slow_updates_contract).update_at_private(&mut context, admin.to_field(), roles);
}

#[aztec(private)]
fn init_slow_tree(user: AztecAddress) {
let roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false }.get_value().to_field();
// docs:start:get_and_update_private
SlowTree::at(storage.slow_update.read_private()).update_at_private(user.to_field(), roles).call(&mut context);
// docs:end:get_and_update_private
TokenBlacklist::at(context.this_address())._init_slow_tree(context.msg_sender()).enqueue(&mut context);
fn constructor(admin: AztecAddress) {
let admin_roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false };
storage.roles.at(admin).schedule_value_change(admin_roles);
}

#[aztec(public)]
#[aztec(internal)]
fn _init_slow_tree(caller: AztecAddress) {
assert(storage.admin.read().eq(caller), "caller is not admin");
fn get_roles(user: AztecAddress) -> UserFlags {
storage.roles.at(user).get_current_value_in_public()
}

#[aztec(private)]
fn update_roles(user: AztecAddress, roles: Field) {
// docs:start:slowmap_at
let slow = SlowTree::at(storage.slow_update.read_private());
// docs:end:slowmap_at
let role = slow.read_at(context.msg_sender().to_field()).call(&mut context);

let caller_roles = UserFlags::new(U128::from_integer(role));
#[aztec(public)]
fn update_roles(user: AztecAddress, roles: UserFlags) {
let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public();
assert(caller_roles.is_admin, "caller is not admin");

slow.update_at_private(user.to_field(), roles).call(&mut context);
storage.roles.at(user).schedule_value_change(roles);
}

#[aztec(public)]
fn mint_public(to: AztecAddress, amount: Field) {
// docs:start:get_public
let slow = SlowTree::at(storage.slow_update.read_public());
// docs:end:get_public
// docs:start:read_at_pub
let to_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(to.to_field()).call(&mut context)));
// docs:end:read_at_pub
let to_roles = storage.roles.at(to).get_current_value_in_public();
assert(!to_roles.is_blacklisted, "Blacklisted: Recipient");

let caller_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(context.msg_sender().to_field()).call(&mut context)));
let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public();
assert(caller_roles.is_minter, "caller is not minter");

let amount = U128::from_integer(amount);
Expand All @@ -107,8 +75,7 @@ contract TokenBlacklist {

#[aztec(public)]
fn mint_private(amount: Field, secret_hash: Field) {
let slow = SlowTree::at(storage.slow_update.read_public());
let caller_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(context.msg_sender().to_field()).call(&mut context)));
let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public();
assert(caller_roles.is_minter, "caller is not minter");

let pending_shields = storage.pending_shields;
Expand All @@ -121,8 +88,7 @@ contract TokenBlacklist {

#[aztec(public)]
fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_public());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_public();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");

if (!from.eq(context.msg_sender())) {
Expand All @@ -144,10 +110,9 @@ contract TokenBlacklist {

#[aztec(public)]
fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_public());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_public();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");
let to_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(to.to_field()).call(&mut context)));
let to_roles = storage.roles.at(to).get_current_value_in_public();
assert(!to_roles.is_blacklisted, "Blacklisted: Recipient");

if (!from.eq(context.msg_sender())) {
Expand All @@ -166,8 +131,7 @@ contract TokenBlacklist {

#[aztec(public)]
fn burn_public(from: AztecAddress, amount: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_public());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_public();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");

if (!from.eq(context.msg_sender())) {
Expand All @@ -186,10 +150,7 @@ contract TokenBlacklist {

#[aztec(private)]
fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) {
let slow = SlowTree::at(storage.slow_update.read_private());
// docs:start:slowmap_read_at
let to_roles = UserFlags::new(U128::from_integer(slow.read_at(to.to_field()).call(&mut context)));
// docs:end:slowmap_read_at
let to_roles = storage.roles.at(to).get_current_value_in_private();
assert(!to_roles.is_blacklisted, "Blacklisted: Recipient");

let pending_shields = storage.pending_shields;
Expand All @@ -213,10 +174,9 @@ contract TokenBlacklist {

#[aztec(private)]
fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_private());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_private();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");
let to_roles = UserFlags::new(U128::from_integer(slow.read_at(to.to_field()).call(&mut context)));
let to_roles = storage.roles.at(to).get_current_value_in_private();
assert(!to_roles.is_blacklisted, "Blacklisted: Recipient");

if (!from.eq(context.msg_sender())) {
Expand All @@ -234,12 +194,10 @@ contract TokenBlacklist {
// docs:start:transfer_private
#[aztec(private)]
fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_private());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_private();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");
let to_roles = UserFlags::new(U128::from_integer(slow.read_at(to.to_field()).call(&mut context)));
let to_roles = storage.roles.at(to).get_current_value_in_private();
assert(!to_roles.is_blacklisted, "Blacklisted: Recipient");
// docs:end:transfer_private

if (!from.eq(context.msg_sender())) {
assert_current_call_valid_authwit(&mut context, from);
Expand All @@ -254,8 +212,7 @@ contract TokenBlacklist {

#[aztec(private)]
fn burn(from: AztecAddress, amount: Field, nonce: Field) {
let slow = SlowTree::at(storage.slow_update.read_private());
let from_roles = UserFlags::new(U128::from_integer(slow.read_at(from.to_field()).call(&mut context)));
let from_roles = storage.roles.at(from).get_current_value_in_private();
assert(!from_roles.is_blacklisted, "Blacklisted: Sender");

if (!from.eq(context.msg_sender())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
global BLACKLIST_FLAG: U128 = U128::from_integer(1);
global MINTER_FLAG: U128 = U128::from_integer(2);
global ADMIN_FLAG: U128 = U128::from_integer(4);
use dep::aztec::protocol_types::traits::{FromField, ToField, Serialize};

global ADMIN_FLAG: u64 = 1;
global MINTER_FLAG: u64 = 2;
global BLACKLIST_FLAG: u64 = 4;

struct UserFlags {
is_admin: bool,
is_minter: bool,
is_blacklisted: bool,
}

impl UserFlags {

pub fn new(value: U128) -> Self {
impl FromField for UserFlags {
fn from_field(value: Field) -> UserFlags {
let value: u64 = value as u64;
let is_admin = value & ADMIN_FLAG == ADMIN_FLAG;
let is_minter = value & MINTER_FLAG == MINTER_FLAG;
let is_blacklisted = value & BLACKLIST_FLAG == BLACKLIST_FLAG;

Self { is_admin, is_minter, is_blacklisted }
}
}

pub fn get_value(self) -> U128 {
let mut value: U128 = U128::from_integer(0);
impl ToField for UserFlags {
fn to_field(self) -> Field {
let mut value: u64 = 0;

if self.is_admin {
value = value | ADMIN_FLAG;
Expand All @@ -33,6 +37,45 @@ impl UserFlags {
value = value | BLACKLIST_FLAG;
}

value
value.to_field()
}
}

// We don't actually need to implement this trait, but the current macros system requires that all types that are
// contained by a state variable are serializable in order to determine their length.
// Once https://github.com/AztecProtocol/aztec-packages/issues/5736 is closed we'll be able to remove this (unless
// https://github.com/AztecProtocol/aztec-packages/issues/5491 is addressed first, in which case we'd remove the to/from
// field traits instead).
impl Serialize<1> for UserFlags {
fn serialize(self) -> [Field; 1] {
[self.to_field()]
}
}

mod test {
use crate::types::roles::UserFlags;

fn assert_to_from_field(is_minter: bool, is_admin: bool, is_blacklisted: bool) {
let flags = UserFlags { is_minter, is_admin, is_blacklisted };
let converted = UserFlags::from_field(flags.to_field());

assert_eq(converted.is_minter, is_minter);
assert_eq(converted.is_admin, is_admin);
assert_eq(converted.is_blacklisted, is_blacklisted);
}

#[test]
fn test_to_from_field() {
assert_to_from_field(false, false, false);
assert_to_from_field(false, false, true);

assert_to_from_field(false, true, false);
assert_to_from_field(false, true, true);

assert_to_from_field(true, false, false);
assert_to_from_field(true, false, true);

assert_to_from_field(true, true, false);
assert_to_from_field(true, true, true);
}
}
Loading
Loading