Skip to content

Commit a601098

Browse files
jvffafck
andauthored
Allow applications to hold native tokens (#2978)
* Use `AccountOwner` as the key for `balances` Allow applications to hold native tokens. * Use `AccountOwner` in `ChainInfoRequest` Allow requesting the balance of an application's account. * Allow client to query application balances Update `ChainClient` methods to be able to query an application's balance. * Use `AccountOwner` in `OwnerBalance` request Allow the runtime to request the balance of application accounts. * Update parameter in runtime's `read_owner_balance` Prepare to update the WIT API to be able to query an application's balance. * Update `read_owner_balance` WIT interface Use `AccountOwner` as the parameter instead of `Owner`, in order to support application accounts. * Derive `Hash` for `AccountOwner` Prepare to use the type in `SystemMessage`, which has `Hash` derived for it. * Use `AccountOwner` inside `MockContractRuntime` Prepare to change the runtime interface to use `AccountOwner`. * Use `AccountOwner` inside `MockServiceRuntime` Prepare to change the runtime interface to use `AccountOwner`. * Use `AccountOwner` in runtime's `owner_balance` Update the SDK's runtime API to support querying an application's balance. * Use `AccountOwner` in cached `owner_balances` Prepare the `ServiceRuntime` to use an updated WIT interface that allows querying the balance of application accounts. * Use `AccountOwner` in `OwnerBalances` request Allow the runtime to get a list the balances of application accounts. * Update return type of `read_owner_balances` Prepare to update the WIT API to be able to list application balances. * Update `read_owner_balances` WIT interface Return `AccountOwner` in order to include application accounts. * Update runtime's `owner_balances` signature Include application balances in the result. * Use `AccountOwner` in cached `balance_owners` Prepare the `ServiceRuntime` to use an updated WIT interface that allows querying the balance of application accounts. * Use `AccountOwner` in `BalanceOwners` request Allow the runtime to get a list the accounts including application accounts. * Update return type of `read_balance_owners` Prepare to update the WIT API to be able to list application accounts. * Update `read_balance_owners` WIT interface Return `AccountOwner` in order to include application accounts. * Update runtime's `balance_owners` signature Include application accounts in the result. * Remove redundant authentication check The authentication is checked before the message is sent, so it does not need to be checked again when the message is received, because only the system application can send its own messages. Also prepares to allow applications to initiate transfers and claims, which can't be authorized by the message receiving code. * Use `AccountOwner` in `SystemMessage` Prepare to allow applications to transfer native tokens. * Change `transfer` parameter into `AccountOwner` Prepare to allow applications to transfer native tokens. * Verify application token transfers Ensure that moving an application's account can only be performed by the application that owns the account. * Send application ID to execution state actor Allow it to be used to authenticate transfers. * Accept application transfers in execution actor Update the request message to use `AccountOwner` so that the runtime can request transfers from application accounts. * Update the runtime trait to support app. transfers Replace the `Owner` parameter with `AccountOwner`, so that applications can request transfers from their account. * Update `transfer` WIT interface Use `AccountOwner` instead of `Owner` as the source parameter in order to allow applications to request transfers from their own accounts. * Update `ContractRuntime::transfer` SDK interface Use `AccountOwner` to allow applications to transfer native tokens using the SDK. * Send authenticated application ID to `claim` func. Prepare to allow claiming from application accounts. * Verify application claims Allow the source account to be an application account, and ensure only the application that owns that account can access it. * Send application ID in `ExecutionRequest::Claim` Allow it to be used to authenticate claims from applications. * Tweak `Account` constructors documentation Add documentation links and rephrase a little to provide more details. * Refactor `FromStr` implementation for `Account` Prepare to handle more than one colon in the input string. * Use `AccountOwner` in `Account` type Update `Account` to also represent application accounts. * Box some futures to reduce their size Prevent excessive stack usage by nested futures. * Support app. accounts in native token example Remove normalization of owners, and use `AccountOwner` directly. * Use `AccountOwner` in `SystemExecuteState` helper Update the `balances` map to support applications. * Refactor to move test helpers to `test_utils` Prepare to allow the helpers to be used by a new integration test module. Also, rename the test helpers for consistency. * Test `transfer` contract runtime API Ensure that applications can transfer from their accounts, from the shared chain account, and from the signer's account. * Test `claim` contract runtime API Ensure that applications can claim from their accounts or from the signer account in remote chains. * Derive `Arbitrary` for `Amount` Prepare to use it in property tests. * Test `read_chain_balance` contract runtime API Ensure applications can read the chain balance. * Test `read_owner_balance` contract runtime API Ensure applications can read the balances of individual accounts. * Test `read_owner_balances` contract runtime API Ensure applications can read a list of all account balances. * Test `read_balance_owners` contract runtime API Ensure applications can read a list of all accounts. * Test reading the balance of a missing account The system API should fallback and return zero in that case. * Test unauthorized transfers from applications Ensure that execution fails with the appropriate error. * Test unauthorized claims from applications Ensure that execution fails with the appropriate error. * Add a `create_dummy_query_context` helper function Allow tests to create a dummy context to test queries. * Test `read_chain_balance` service runtime API Ensure applications can read the chain balance. * Move `test_accounts_strategy` to `test_utils` Allow the helper function to be used in other tests. * Test `read_owner_balance` service runtime API Ensure applications can read the balances of individual accounts. * Test `read_owner_balances` service runtime API Ensure applications can read a list of all account balances. * Test `read_balance_owners` service runtime API Ensure applications can read a list of all accounts. * Test reading the balance of a missing account The system API should fallback and return zero in that case. * Replace `ok_or_else` with `context` Simplify the code to generate the error. Co-authored-by: Andreas Fackler <[email protected]> Signed-off-by: Janito Vaqueiro Ferreira Filho <[email protected]> * Expect returned accounts to be sorted The runtime API should be deterministic, so the returned lists of accounts must be sorted. Co-authored-by: Andreas Fackler <[email protected]> Signed-off-by: Janito Vaqueiro Ferreira Filho <[email protected]> --------- Signed-off-by: Janito Vaqueiro Ferreira Filho <[email protected]> Co-authored-by: Andreas Fackler <[email protected]>
1 parent 30092c1 commit a601098

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1511
-330
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/native-fungible/src/contract.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use fungible::{FungibleResponse, FungibleTokenAbi, InitialState, Operation, Parameters};
77
use linera_sdk::{
8-
base::{Account, AccountOwner, ChainId, Owner, WithContractAbi},
8+
base::{Account, AccountOwner, ChainId, WithContractAbi},
99
Contract, ContractRuntime,
1010
};
1111
use native_fungible::{Message, TICKER_SYMBOL};
@@ -36,7 +36,6 @@ impl Contract for NativeFungibleTokenContract {
3636
"Only NAT is accepted as ticker symbol"
3737
);
3838
for (owner, amount) in state.accounts {
39-
let owner = self.normalize_owner(owner);
4039
let account = Account {
4140
chain_id: self.runtime.chain_id(),
4241
owner: Some(owner),
@@ -48,8 +47,6 @@ impl Contract for NativeFungibleTokenContract {
4847
async fn execute_operation(&mut self, operation: Self::Operation) -> Self::Response {
4948
match operation {
5049
Operation::Balance { owner } => {
51-
let owner = self.normalize_owner(owner);
52-
5350
let balance = self.runtime.owner_balance(owner);
5451
FungibleResponse::Balance(balance)
5552
}
@@ -62,7 +59,6 @@ impl Contract for NativeFungibleTokenContract {
6259
target_account,
6360
} => {
6461
self.check_account_authentication(owner);
65-
let owner = self.normalize_owner(owner);
6662

6763
let fungible_target_account = target_account;
6864
let target_account = self.normalize_account(target_account);
@@ -130,18 +126,10 @@ impl NativeFungibleTokenContract {
130126
}
131127
}
132128

133-
fn normalize_owner(&self, account_owner: AccountOwner) -> Owner {
134-
match account_owner {
135-
AccountOwner::User(owner) => owner,
136-
AccountOwner::Application(_) => panic!("Applications not supported yet!"),
137-
}
138-
}
139-
140129
fn normalize_account(&self, account: fungible::Account) -> Account {
141-
let owner = self.normalize_owner(account.owner);
142130
Account {
143131
chain_id: account.chain_id,
144-
owner: Some(owner),
132+
owner: Some(account.owner),
145133
}
146134
}
147135

examples/native-fungible/src/service.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,14 @@ struct Accounts {
4949
impl Accounts {
5050
// Define a field that lets you query by key
5151
async fn entry(&self, key: AccountOwner) -> AccountEntry {
52-
let owner = match key {
53-
AccountOwner::User(owner) => owner,
54-
AccountOwner::Application(_) => panic!("Applications not supported yet"),
55-
};
5652
let runtime = self
5753
.runtime
5854
.try_lock()
5955
.expect("Services only run in a single thread");
6056

61-
AccountEntry {
62-
key,
63-
value: runtime.owner_balance(owner),
64-
}
57+
let value = runtime.owner_balance(key);
58+
59+
AccountEntry { key, value }
6560
}
6661

6762
async fn entries(&self) -> Vec<AccountEntry> {
@@ -73,7 +68,7 @@ impl Accounts {
7368
.owner_balances()
7469
.into_iter()
7570
.map(|(owner, amount)| AccountEntry {
76-
key: AccountOwner::User(owner),
71+
key: owner,
7772
value: amount,
7873
})
7974
.collect()
@@ -84,11 +79,7 @@ impl Accounts {
8479
.runtime
8580
.try_lock()
8681
.expect("Services only run in a single thread");
87-
runtime
88-
.balance_owners()
89-
.into_iter()
90-
.map(AccountOwner::User)
91-
.collect()
82+
runtime.balance_owners()
9283
}
9384
}
9485

linera-base/src/data_types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ use crate::{
4848
#[derive(
4949
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
5050
)]
51+
#[cfg_attr(
52+
all(with_testing, not(target_arch = "wasm32")),
53+
derive(test_strategy::Arbitrary)
54+
)]
5155
pub struct Amount(u128);
5256

5357
#[derive(Serialize, Deserialize)]

linera-base/src/identifiers.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ use crate::{
3030
pub struct Owner(pub CryptoHash);
3131

3232
/// An account owner.
33-
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
33+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, WitLoad, WitStore, WitType)]
34+
#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
3435
pub enum AccountOwner {
3536
/// An account owned by a user.
3637
User(Owner),
@@ -47,23 +48,23 @@ pub struct Account {
4748
pub chain_id: ChainId,
4849
/// The owner of the account, or `None` for the chain balance.
4950
#[debug(skip_if = Option::is_none)]
50-
pub owner: Option<Owner>,
51+
pub owner: Option<AccountOwner>,
5152
}
5253

5354
impl Account {
54-
/// Creates an Account with a ChainId
55+
/// Creates an [`Account`] representing the balance shared by a chain's owners.
5556
pub fn chain(chain_id: ChainId) -> Self {
5657
Account {
5758
chain_id,
5859
owner: None,
5960
}
6061
}
6162

62-
/// Creates an Account with a ChainId and an Owner
63-
pub fn owner(chain_id: ChainId, owner: Owner) -> Self {
63+
/// Creates an [`Account`] for a specific [`Owner`] on a chain.
64+
pub fn owner(chain_id: ChainId, owner: impl Into<AccountOwner>) -> Self {
6465
Account {
6566
chain_id,
66-
owner: Some(owner),
67+
owner: Some(owner.into()),
6768
}
6869
}
6970
}
@@ -80,18 +81,21 @@ impl Display for Account {
8081
impl FromStr for Account {
8182
type Err = anyhow::Error;
8283

83-
fn from_str(s: &str) -> Result<Self, Self::Err> {
84-
let parts = s.split(':').collect::<Vec<_>>();
85-
anyhow::ensure!(
86-
parts.len() <= 2,
87-
"Expecting format `chain-id:address` or `chain-id`"
88-
);
89-
if parts.len() == 1 {
90-
Ok(Account::chain(s.parse()?))
91-
} else {
92-
let chain_id = parts[0].parse()?;
93-
let owner = parts[1].parse()?;
84+
fn from_str(string: &str) -> Result<Self, Self::Err> {
85+
let mut parts = string.splitn(2, ':');
86+
87+
let chain_id = parts
88+
.next()
89+
.context(
90+
"Expecting an account formatted as `chain-id` or `chain-id:owner-type:address`",
91+
)?
92+
.parse()?;
93+
94+
if let Some(owner_string) = parts.next() {
95+
let owner = owner_string.parse::<AccountOwner>()?;
9496
Ok(Account::owner(chain_id, owner))
97+
} else {
98+
Ok(Account::chain(chain_id))
9599
}
96100
}
97101
}
@@ -292,7 +296,7 @@ impl<'a> Deserialize<'a> for BlobId {
292296
WitStore,
293297
WitType,
294298
)]
295-
#[cfg_attr(with_testing, derive(Default))]
299+
#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
296300
pub struct MessageId {
297301
/// The chain ID that created the message.
298302
pub chain_id: ChainId,
@@ -304,7 +308,7 @@ pub struct MessageId {
304308

305309
/// A unique identifier for a user application.
306310
#[derive(Debug, WitLoad, WitStore, WitType)]
307-
#[cfg_attr(with_testing, derive(Default))]
311+
#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
308312
pub struct ApplicationId<A = ()> {
309313
/// The bytecode to use for the application.
310314
pub bytecode_id: BytecodeId<A>,
@@ -358,7 +362,7 @@ impl From<ApplicationId> for GenericApplicationId {
358362

359363
/// A unique identifier for an application bytecode.
360364
#[derive(Debug, WitLoad, WitStore, WitType)]
361-
#[cfg_attr(with_testing, derive(Default))]
365+
#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
362366
pub struct BytecodeId<Abi = (), Parameters = (), InstantiationArgument = ()> {
363367
/// The hash of the blob containing the contract bytecode.
364368
pub contract_blob_hash: CryptoHash,

linera-base/src/unit_tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use crate::{
1212
crypto::{CryptoHash, PublicKey},
1313
data_types::{Amount, BlockHeight, Resources, SendMessageRequest, TimeDelta, Timestamp},
1414
identifiers::{
15-
Account, ApplicationId, BytecodeId, ChainId, ChannelName, Destination, MessageId, Owner,
15+
Account, AccountOwner, ApplicationId, BytecodeId, ChainId, ChannelName, Destination,
16+
MessageId, Owner,
1617
},
1718
ownership::{ChainOwnership, TimeoutConfig},
1819
};
@@ -83,7 +84,7 @@ fn send_message_request_test_case() -> SendMessageRequest<Vec<u8>> {
8384
fn account_test_case() -> Account {
8485
Account {
8586
chain_id: ChainId::root(10),
86-
owner: Some(Owner(CryptoHash::test_hash("account"))),
87+
owner: Some(AccountOwner::User(Owner(CryptoHash::test_hash("account")))),
8788
}
8889
}
8990

linera-chain/src/chain.rs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ where
767767
.track_executed_block_size_of(&incoming_bundle)
768768
.with_execution_context(chain_execution_context)?;
769769
for (message_id, posted_message) in incoming_bundle.messages_and_ids() {
770-
self.execute_message_in_block(
770+
Box::pin(self.execute_message_in_block(
771771
message_id,
772772
posted_message,
773773
incoming_bundle,
@@ -776,7 +776,7 @@ where
776776
local_time,
777777
&mut txn_tracker,
778778
&mut resource_controller,
779-
)
779+
))
780780
.await?;
781781
}
782782
}
@@ -793,16 +793,15 @@ where
793793
authenticated_signer: block.authenticated_signer,
794794
authenticated_caller_id: None,
795795
};
796-
self.execution_state
797-
.execute_operation(
798-
context,
799-
local_time,
800-
operation.clone(),
801-
&mut txn_tracker,
802-
&mut resource_controller,
803-
)
804-
.await
805-
.with_execution_context(chain_execution_context)?;
796+
Box::pin(self.execution_state.execute_operation(
797+
context,
798+
local_time,
799+
operation.clone(),
800+
&mut txn_tracker,
801+
&mut resource_controller,
802+
))
803+
.await
804+
.with_execution_context(chain_execution_context)?;
806805
resource_controller
807806
.with_state(&mut self.execution_state)
808807
.await?
@@ -942,17 +941,16 @@ where
942941
// Once a chain is closed, accepting incoming messages is not allowed.
943942
ensure!(!self.is_closed(), ChainError::ClosedChain);
944943

945-
self.execution_state
946-
.execute_message(
947-
context,
948-
local_time,
949-
posted_message.message.clone(),
950-
(grant > Amount::ZERO).then_some(&mut grant),
951-
txn_tracker,
952-
resource_controller,
953-
)
954-
.await
955-
.with_execution_context(ChainExecutionContext::IncomingBundle(txn_index))?;
944+
Box::pin(self.execution_state.execute_message(
945+
context,
946+
local_time,
947+
posted_message.message.clone(),
948+
(grant > Amount::ZERO).then_some(&mut grant),
949+
txn_tracker,
950+
resource_controller,
951+
))
952+
.await
953+
.with_execution_context(ChainExecutionContext::IncomingBundle(txn_index))?;
956954
if grant > Amount::ZERO {
957955
if let Some(refund_grant_to) = posted_message.refund_grant_to {
958956
self.execution_state

linera-core/src/chain_worker/state/temporary_changes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use linera_base::{
77
data_types::{ArithmeticError, Blob, BlobContent, Timestamp, UserApplicationDescription},
88
ensure,
9-
identifiers::{GenericApplicationId, UserApplicationId},
9+
identifiers::{AccountOwner, GenericApplicationId, UserApplicationId},
1010
};
1111
use linera_chain::{
1212
data_types::{
@@ -139,7 +139,7 @@ where
139139
.execution_state
140140
.system
141141
.balances
142-
.get(&signer)
142+
.get(&AccountOwner::User(signer))
143143
.await?;
144144
}
145145

0 commit comments

Comments
 (0)