Skip to content

Commit

Permalink
feat!: new account constructor for custom access rules (#1138)
Browse files Browse the repository at this point in the history
Description
---
* Updated the account template constructor to allow custom owner and
access rules
* Updated the `CreateAccount` instruction type
* Updated the transaction builder for the new account creation fields
* Updated the transaction processor to use the new fields
* Updated the protobuf definitions with the new fields in the account
creation instruction type

Motivation and Context
---

When creating accounts we want to be able to specify custom access
rules.

This PR refactors the account template constructor (`create` function)
to allow specifying the following fields:
* `public_key_token`: mandatory field. Needed always for component
creation. If the `owner_rule` is not set, it will be used to set up the
default owner rule
* `owner_rule`: optional field. Used to specify custom logic for the
account component ownership (e.g. update the access rules after
creation)
* `access_rules`: optional field. Used to specify custom access control
rules over the account component methods
* `bucket`: optional field. Initial funds of the account

How Has This Been Tested?
---
* New unit test for accounts: `custom_access_rules`
* Manually spawning a network with `tari_spawn` and checking it works
* Existing unit and integration tests pass

What process can a PR reviewer use to test or verify this change?
---
See previous section

Breaking Changes
---

- [ ] None
- [ ] Requires data directory to be deleted
- [x] Other - Requires network reset as account creation (including the
related instruction type) has changed
  • Loading branch information
mrnaveira committed Sep 13, 2024
1 parent 552b7ed commit 5431162
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 65 deletions.
8 changes: 6 additions & 2 deletions applications/tari_dan_wallet_daemon/src/handlers/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,9 @@ async fn finish_claiming<T: WalletStore>(
});
} else {
instructions.push(Instruction::CreateAccount {
owner_public_key: account_public_key.clone(),
public_key_address: account_public_key.clone(),
owner_rule: None,
access_rules: None,
workspace_bucket: Some("bucket".to_string()),
});
}
Expand Down Expand Up @@ -884,7 +886,9 @@ pub async fn handle_transfer(
inputs.push(address);
} else {
instructions.push(Instruction::CreateAccount {
owner_public_key: req.destination_public_key,
public_key_address: req.destination_public_key,
owner_rule: None,
access_rules: None,
workspace_bucket: None,
});
}
Expand Down
4 changes: 3 additions & 1 deletion applications/tari_validator_node_cli/src/command/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ pub async fn handle_create(
.ok_or_else(|| anyhow::anyhow!("No active key"))?;

let instruction = Instruction::CreateAccount {
owner_public_key: key.public_key,
public_key_address: key.public_key,
owner_rule: None,
access_rules: None,
workspace_bucket: None,
};

Expand Down
11 changes: 10 additions & 1 deletion bindings/src/types/Instruction.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Amount } from "./Amount";
import type { Arg } from "./Arg";
import type { ComponentAccessRules } from "./ComponentAccessRules";
import type { ComponentAddress } from "./ComponentAddress";
import type { ConfidentialClaim } from "./ConfidentialClaim";
import type { LogLevel } from "./LogLevel";
import type { OwnerRule } from "./OwnerRule";
import type { ResourceAddress } from "./ResourceAddress";

export type Instruction =
| { CreateAccount: { owner_public_key: string; workspace_bucket: string | null } }
| {
CreateAccount: {
public_key_address: string;
owner_rule: OwnerRule | null;
access_rules: ComponentAccessRules | null;
workspace_bucket: string | null;
};
}
| { CallFunction: { template_address: Uint8Array; function: string; args: Array<Arg> } }
| { CallMethod: { component_address: ComponentAddress; method: string; args: Array<string> } }
| { PutLastInstructionOutputOnWorkspace: { key: Array<number> } }
Expand Down
66 changes: 49 additions & 17 deletions dan_layer/engine/src/transaction/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ use tari_template_lib::{
arg,
args,
args::{Arg, WorkspaceAction},
auth::OwnerRule,
crypto::RistrettoPublicKeyBytes,
invoke_args,
models::{ComponentAddress, NonFungibleAddress},
prelude::TemplateAddress,
models::{Bucket, ComponentAddress, NonFungibleAddress},
prelude::{AccessRules, TemplateAddress},
};
use tari_transaction::Transaction;
use tari_utilities::ByteArray;
Expand All @@ -70,6 +71,7 @@ use crate::{

const LOG_TARGET: &str = "tari::dan::engine::instruction_processor";
pub const MAX_CALL_DEPTH: usize = 10;
const ACCOUNT_CONSTRUCTOR_FUNCTION: &str = "create";

pub struct TransactionProcessor<TTemplateProvider> {
template_provider: Arc<TTemplateProvider>,
Expand Down Expand Up @@ -240,9 +242,18 @@ impl<TTemplateProvider: TemplateProvider<Template = LoadedTemplate> + 'static> T
debug!(target: LOG_TARGET, "instruction = {:?}", instruction);
match instruction {
Instruction::CreateAccount {
owner_public_key,
public_key_address,
owner_rule,
access_rules,
workspace_bucket,
} => Self::create_account(template_provider, runtime, &owner_public_key, workspace_bucket),
} => Self::create_account(
template_provider,
runtime,
&public_key_address,
owner_rule,
access_rules,
workspace_bucket,
),
Instruction::CallFunction {
template_address,
function,
Expand Down Expand Up @@ -312,7 +323,9 @@ impl<TTemplateProvider: TemplateProvider<Template = LoadedTemplate> + 'static> T
pub fn create_account(
template_provider: &TTemplateProvider,
runtime: &Runtime,
owner_public_key: &PublicKey,
public_key_address: &PublicKey,
owner_rule: Option<OwnerRule>,
access_rules: Option<AccessRules>,
workspace_bucket: Option<String>,
) -> Result<InstructionResult, TransactionError> {
let template = template_provider
Expand All @@ -325,23 +338,42 @@ impl<TTemplateProvider: TemplateProvider<Template = LoadedTemplate> + 'static> T
address: ACCOUNT_TEMPLATE_ADDRESS,
})?;

let function = if workspace_bucket.is_some() {
"create_with_bucket"
let function_def = template
.template_def()
.get_function(ACCOUNT_CONSTRUCTOR_FUNCTION)
.cloned()
.ok_or_else(|| TransactionError::FunctionNotFound {
name: ACCOUNT_CONSTRUCTOR_FUNCTION.to_string(),
})?;

let account_address = new_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, public_key_address);

// the publick key is the first argument of the Account template constructor
let public_key = RistrettoPublicKeyBytes::from_bytes(public_key_address.as_bytes()).unwrap();
let mut args = args![NonFungibleAddress::from_public_key(public_key)];

// add the optional owner rule if specified
if let Some(owner_rule) = owner_rule {
args.push(arg![Literal(owner_rule)]);
} else {
"create"
};
let none: Option<OwnerRule> = None;
args.push(arg![Literal(none)]);
}

let function_def = template.template_def().get_function(function).cloned().ok_or_else(|| {
TransactionError::FunctionNotFound {
name: function.to_string(),
}
})?;
let owner_pk = RistrettoPublicKeyBytes::from_bytes(owner_public_key.as_bytes()).unwrap();
let account_address = new_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, owner_public_key);
// add the optional access rules if specified
if let Some(access_rules) = access_rules {
args.push(arg![Literal(access_rules)]);
} else {
let none: Option<AccessRules> = None;
args.push(arg![Literal(none)]);
}

let mut args = args![NonFungibleAddress::from_public_key(owner_pk)];
// add the optional workspace bucket with the initial funds of the account
if let Some(workspace_bucket) = workspace_bucket {
args.push(arg![Workspace(workspace_bucket)]);
} else {
let none: Option<Bucket> = None;
args.push(arg![Literal(none)]);
}

let args = runtime.resolve_args(args)?;
Expand Down
52 changes: 51 additions & 1 deletion dan_layer/engine/tests/account.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// Copyright 2023 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey};
use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey, tari_utilities::ByteArray};
use tari_dan_engine::runtime::{ActionIdent, RuntimeError};
use tari_engine_types::instruction::Instruction;
use tari_template_lib::{
args,
auth::AccessRule,
constants::XTR,
models::{Amount, ComponentAddress, ResourceAddress},
prelude::AccessRules,
};
use tari_template_test_tooling::{
support::assert_error::{assert_access_denied_for_action, assert_reject_reason},
test_faucet_component,
TemplateTest,
};
use tari_transaction::Transaction;
Expand Down Expand Up @@ -252,3 +255,50 @@ fn gasless() {
let balance = result.expect_return::<Vec<(ResourceAddress, Amount)>>(3);
assert_eq!(balance[0].1, 100);
}

#[test]
fn custom_access_rules() {
let mut template_test = TemplateTest::new::<_, &str>([]);

// First we create a account with a custom rule that anyone can withdraw
let (owner_proof, public_key, secret_key) = template_test.create_owner_proof();

let access_rules = AccessRules::new()
.add_method_rule("balance", AccessRule::AllowAll)
.add_method_rule("get_balances", AccessRule::AllowAll)
.add_method_rule("deposit", AccessRule::AllowAll)
.add_method_rule("deposit_all", AccessRule::AllowAll)
.add_method_rule("get_non_fungible_ids", AccessRule::AllowAll)
// We are going to make it so anyone can withdraw
.default(AccessRule::AllowAll);

let result = template_test.execute_expect_success(
Transaction::builder()
.call_method(test_faucet_component(), "take_free_coins", args![])
.put_last_instruction_output_on_workspace("bucket")
// Create component with the same ID
.create_account_with_custom_rules(
public_key,
None,
Some(access_rules),
Some("bucket"),
)
// Signed by source account so that it can pay the fees for the new account creation
.sign(&secret_key)
.build(),
vec![owner_proof],
);
let user_account = result.finalize.execution_results[2].decode().unwrap();

// We create another account and we we will withdraw from the custom one
let (user2_account, user2_account_proof, user2_secret_key) = template_test.create_funded_account();
template_test.execute_expect_success(
Transaction::builder()
.call_method(user_account, "withdraw", args![XTR, Amount(100)])
.put_last_instruction_output_on_workspace("b")
.call_method(user2_account, "deposit", args![Workspace("b")])
.build()
.sign(&user2_secret_key),
vec![user2_account_proof],
);
}
4 changes: 3 additions & 1 deletion dan_layer/engine/tests/airdrop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ fn airdrop() {
let instructions = iter::repeat_with(|| {
let (_, owner_public_key, _) = template_test.create_owner_proof();
Instruction::CreateAccount {
owner_public_key,
public_key_address: owner_public_key,
owner_rule: None,
access_rules: None,
workspace_bucket: None,
}
})
Expand Down
17 changes: 13 additions & 4 deletions dan_layer/engine_types/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use tari_common_types::types::PublicKey;
use tari_crypto::tari_utilities::hex::Hex;
use tari_template_lib::{
args::{Arg, LogLevel},
auth::OwnerRule,
models::{ComponentAddress, ResourceAddress, TemplateAddress},
prelude::Amount,
prelude::{AccessRules, Amount},
};
#[cfg(feature = "ts")]
use ts_rs::TS;
Expand All @@ -21,7 +22,9 @@ use crate::{confidential::ConfidentialClaim, serde_with};
pub enum Instruction {
CreateAccount {
#[cfg_attr(feature = "ts", ts(type = "string"))]
owner_public_key: PublicKey,
public_key_address: PublicKey,
owner_rule: Option<OwnerRule>,
access_rules: Option<AccessRules>,
#[cfg_attr(feature = "ts", ts(type = "string | null"))]
workspace_bucket: Option<String>,
},
Expand Down Expand Up @@ -71,10 +74,16 @@ impl Display for Instruction {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::CreateAccount {
owner_public_key,
public_key_address,
owner_rule,
access_rules,
workspace_bucket,
} => {
write!(f, "CreateAccount {{ owner_public_key: {}, bucket: ", owner_public_key,)?;
write!(
f,
"CreateAccount {{ public_key_address: {}, owner_rule: {:?}, acces_rules: {:?}, bucket: ",
public_key_address, owner_rule, access_rules
)?;
match workspace_bucket {
Some(bucket) => write!(f, "{}", bucket)?,
None => write!(f, "None")?,
Expand Down
19 changes: 15 additions & 4 deletions dan_layer/p2p/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,17 @@ message Instruction {
bytes claim_validator_fees_validator_public_key = 15;
uint64 claim_validator_fees_epoch = 16;

bytes create_account_owner_public_key = 17;
string create_account_workspace_bucket = 18;
bytes create_account_public_key = 17;
OwnerRule create_account_owner_rule = 18;
AccessRules create_account_access_rules = 19;
string create_account_workspace_bucket = 20;

// AssertBucketContains
bytes resource_address = 19;
int64 min_amount = 20;
bytes resource_address = 21;
int64 min_amount = 22;
}


message Arg {
enum ArgType {
LITERAL = 0;
Expand Down Expand Up @@ -136,3 +139,11 @@ message ViewableBalanceProof {
bytes s_m = 7;
bytes s_r = 8;
}

message OwnerRule {
bytes encoded_owner_rule = 1;
}

message AccessRules {
bytes encoded_access_rules = 1;
}
Loading

0 comments on commit 5431162

Please sign in to comment.