-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new instruction type AssertBucketContains (#1106)
Description --- * New instruction `AssertBucketContains` and associated utils and errors * New workspace action for the engine to handle the new instruction * Updated protobuf definitions with the new instruction type * Updated TypeScript bindings with the new instruction type * Bumped TypeScript bindings package version to trigger a npm publish * Made `tari_dan_wallet_web_ui` npm dependencies with bindings and clients use the npm registry instead of local dependencies, to avoid chicken-and-egg situations. Motivation and Context --- We want a new type of transaction instruction to execute asserts. Asserts can be useful in a variety of ways, making transactions more secure and easier to review by the user. For example, it could allow to implement a maximum slippage in a decentralized exchange. The main use case is to ensure that the return bucket of a component call invocation contains the expected resource and amount of tokens, hence the new instruction is called `AssertBucketContains`. The fields of the new instruction type are: * `key`: workspace reference of the bucket that we want to assert * `resource_address`: the resource address that the bucket must contain * `min_amount`: the minimum amount of tokens that the bucket must contain How Has This Been Tested? --- New engine unit tests that cover all scenarios What process can a PR reviewer use to test or verify this change? --- Send a transaction with the new instruction type Breaking Changes --- - [x] None - [ ] Requires data directory to be deleted - [ ] Other - Please specify
- Loading branch information
Showing
19 changed files
with
1,339 additions
and
587 deletions.
There are no files selected for viewing
1,591 changes: 1,025 additions & 566 deletions
1,591
applications/tari_dan_wallet_web_ui/package-lock.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
// Copyright 2024 The Tari Project | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
use std::vec; | ||
|
||
use tari_crypto::ristretto::RistrettoSecretKey; | ||
use tari_dan_engine::runtime::{AssertError, RuntimeError}; | ||
use tari_template_lib::{ | ||
args, | ||
models::{Amount, ComponentAddress, NonFungibleAddress, ResourceAddress}, | ||
prelude::XTR, | ||
}; | ||
use tari_template_test_tooling::{support::assert_error::assert_reject_reason, TemplateTest}; | ||
use tari_transaction::{Instruction, Transaction}; | ||
|
||
const FAUCET_WITHDRAWAL_AMOUNT: Amount = Amount::new(1000); | ||
|
||
struct AssertTest { | ||
template_test: TemplateTest, | ||
faucet_component: ComponentAddress, | ||
faucet_resource: ResourceAddress, | ||
account: ComponentAddress, | ||
account_proof: NonFungibleAddress, | ||
account_key: RistrettoSecretKey, | ||
} | ||
|
||
fn setup() -> AssertTest { | ||
let mut template_test = TemplateTest::new(vec!["tests/templates/tariswap"]); | ||
|
||
let faucet_template = template_test.get_template_address("TestFaucet"); | ||
|
||
let initial_supply = Amount(1_000_000_000_000); | ||
let result = template_test | ||
.execute_and_commit( | ||
vec![Instruction::CallFunction { | ||
template_address: faucet_template, | ||
function: "mint".to_string(), | ||
args: args![initial_supply], | ||
}], | ||
vec![template_test.get_test_proof()], | ||
) | ||
.unwrap(); | ||
|
||
let faucet_component: ComponentAddress = result.finalize.execution_results[0].decode().unwrap(); | ||
|
||
let faucet_resource = result | ||
.finalize | ||
.result | ||
.expect("Faucet mint failed") | ||
.up_iter() | ||
.find_map(|(address, _)| address.as_resource_address()) | ||
.unwrap(); | ||
|
||
// Create user account to receive faucet tokens | ||
let (account, account_proof, account_key) = template_test.create_funded_account(); | ||
|
||
AssertTest { | ||
template_test, | ||
faucet_component, | ||
faucet_resource, | ||
account, | ||
account_proof, | ||
account_key, | ||
} | ||
} | ||
|
||
#[test] | ||
fn successful_assert() { | ||
let mut test: AssertTest = setup(); | ||
|
||
test.template_test.execute_expect_success( | ||
Transaction::builder() | ||
.call_method(test.faucet_component, "take_free_coins", args![]) | ||
.put_last_instruction_output_on_workspace("faucet_bucket") | ||
.assert_bucket_contains("faucet_bucket", test.faucet_resource, FAUCET_WITHDRAWAL_AMOUNT) | ||
.call_method(test.account, "deposit", args![Workspace("faucet_bucket")]) | ||
.sign(&test.account_key) | ||
.build(), | ||
vec![test.account_proof.clone()], | ||
); | ||
} | ||
|
||
#[test] | ||
fn it_fails_with_invalid_resource() { | ||
let mut test: AssertTest = setup(); | ||
|
||
// we are going to assert a different resource than the faucet resource | ||
let invalid_resource_address = XTR; | ||
|
||
let reason = test.template_test.execute_expect_failure( | ||
Transaction::builder() | ||
.call_method(test.faucet_component, "take_free_coins", args![]) | ||
.put_last_instruction_output_on_workspace("faucet_bucket") | ||
.assert_bucket_contains("faucet_bucket", invalid_resource_address, FAUCET_WITHDRAWAL_AMOUNT) | ||
.call_method(test.account, "deposit", args![Workspace("faucet_bucket")]) | ||
.sign(&test.account_key) | ||
.build(), | ||
vec![test.account_proof.clone()], | ||
); | ||
|
||
assert_reject_reason( | ||
reason, | ||
RuntimeError::AssertError(AssertError::InvalidResource { | ||
expected: invalid_resource_address, | ||
got: test.faucet_resource, | ||
}), | ||
); | ||
} | ||
|
||
#[test] | ||
fn it_fails_with_invalid_amount() { | ||
let mut test: AssertTest = setup(); | ||
|
||
// we are going to assert that the faucet bucket has more tokens that it really has | ||
let min_amount = FAUCET_WITHDRAWAL_AMOUNT + 1; | ||
|
||
let reason = test.template_test.execute_expect_failure( | ||
Transaction::builder() | ||
.call_method(test.faucet_component, "take_free_coins", args![]) | ||
.put_last_instruction_output_on_workspace("faucet_bucket") | ||
.assert_bucket_contains("faucet_bucket", test.faucet_resource, min_amount) | ||
.call_method(test.account, "deposit", args![Workspace("faucet_bucket")]) | ||
.sign(&test.account_key) | ||
.build(), | ||
vec![test.account_proof.clone()], | ||
); | ||
|
||
assert_reject_reason( | ||
reason, | ||
RuntimeError::AssertError(AssertError::InvalidAmount { | ||
expected: min_amount, | ||
got: FAUCET_WITHDRAWAL_AMOUNT, | ||
}), | ||
); | ||
} | ||
|
||
#[test] | ||
fn it_fails_with_invalid_bucket() { | ||
let mut test: AssertTest = setup(); | ||
|
||
let reason = test.template_test.execute_expect_failure( | ||
Transaction::builder() | ||
.call_method(test.faucet_component, "take_free_coins", args![]) | ||
// we are going to assert a workspace value that is NOT a bucket | ||
.call_method(test.account, "get_balances", args![]) | ||
.put_last_instruction_output_on_workspace("invalid_bucket") | ||
.assert_bucket_contains("invalid_bucket", test.faucet_resource, FAUCET_WITHDRAWAL_AMOUNT) | ||
.call_method(test.account, "deposit", args![Workspace("faucet_bucket")]) | ||
.sign(&test.account_key) | ||
.build(), | ||
vec![test.account_proof.clone()], | ||
); | ||
|
||
assert_reject_reason(reason, RuntimeError::AssertError(AssertError::InvalidBucket {})); | ||
} | ||
|
||
#[test] | ||
fn it_fails_with_invalid_workspace_key() { | ||
let mut test: AssertTest = setup(); | ||
|
||
let reason = test.template_test.execute_expect_failure( | ||
Transaction::builder() | ||
.call_method(test.faucet_component, "take_free_coins", args![]) | ||
.put_last_instruction_output_on_workspace("faucet_bucket") | ||
// we are going to assert a key that does not exist in the workspace | ||
.assert_bucket_contains("invalid_key", test.faucet_resource, FAUCET_WITHDRAWAL_AMOUNT) | ||
.call_method(test.account, "deposit", args![Workspace("faucet_bucket")]) | ||
.sign(&test.account_key) | ||
.build(), | ||
vec![test.account_proof.clone()], | ||
); | ||
|
||
assert_reject_reason(reason, RuntimeError::ItemNotOnWorkspace { | ||
key: "invalid_key".to_string(), | ||
}); | ||
} |
Oops, something went wrong.