Skip to content

Commit

Permalink
Allow applications to hold native tokens (#2978)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
jvff and afck authored Nov 28, 2024
1 parent 30092c1 commit a601098
Show file tree
Hide file tree
Showing 46 changed files with 1,511 additions and 330 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 2 additions & 14 deletions examples/native-fungible/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use fungible::{FungibleResponse, FungibleTokenAbi, InitialState, Operation, Parameters};
use linera_sdk::{
base::{Account, AccountOwner, ChainId, Owner, WithContractAbi},
base::{Account, AccountOwner, ChainId, WithContractAbi},
Contract, ContractRuntime,
};
use native_fungible::{Message, TICKER_SYMBOL};
Expand Down Expand Up @@ -36,7 +36,6 @@ impl Contract for NativeFungibleTokenContract {
"Only NAT is accepted as ticker symbol"
);
for (owner, amount) in state.accounts {
let owner = self.normalize_owner(owner);
let account = Account {
chain_id: self.runtime.chain_id(),
owner: Some(owner),
Expand All @@ -48,8 +47,6 @@ impl Contract for NativeFungibleTokenContract {
async fn execute_operation(&mut self, operation: Self::Operation) -> Self::Response {
match operation {
Operation::Balance { owner } => {
let owner = self.normalize_owner(owner);

let balance = self.runtime.owner_balance(owner);
FungibleResponse::Balance(balance)
}
Expand All @@ -62,7 +59,6 @@ impl Contract for NativeFungibleTokenContract {
target_account,
} => {
self.check_account_authentication(owner);
let owner = self.normalize_owner(owner);

let fungible_target_account = target_account;
let target_account = self.normalize_account(target_account);
Expand Down Expand Up @@ -130,18 +126,10 @@ impl NativeFungibleTokenContract {
}
}

fn normalize_owner(&self, account_owner: AccountOwner) -> Owner {
match account_owner {
AccountOwner::User(owner) => owner,
AccountOwner::Application(_) => panic!("Applications not supported yet!"),
}
}

fn normalize_account(&self, account: fungible::Account) -> Account {
let owner = self.normalize_owner(account.owner);
Account {
chain_id: account.chain_id,
owner: Some(owner),
owner: Some(account.owner),
}
}

Expand Down
19 changes: 5 additions & 14 deletions examples/native-fungible/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,14 @@ struct Accounts {
impl Accounts {
// Define a field that lets you query by key
async fn entry(&self, key: AccountOwner) -> AccountEntry {
let owner = match key {
AccountOwner::User(owner) => owner,
AccountOwner::Application(_) => panic!("Applications not supported yet"),
};
let runtime = self
.runtime
.try_lock()
.expect("Services only run in a single thread");

AccountEntry {
key,
value: runtime.owner_balance(owner),
}
let value = runtime.owner_balance(key);

AccountEntry { key, value }
}

async fn entries(&self) -> Vec<AccountEntry> {
Expand All @@ -73,7 +68,7 @@ impl Accounts {
.owner_balances()
.into_iter()
.map(|(owner, amount)| AccountEntry {
key: AccountOwner::User(owner),
key: owner,
value: amount,
})
.collect()
Expand All @@ -84,11 +79,7 @@ impl Accounts {
.runtime
.try_lock()
.expect("Services only run in a single thread");
runtime
.balance_owners()
.into_iter()
.map(AccountOwner::User)
.collect()
runtime.balance_owners()
}
}

Expand Down
4 changes: 4 additions & 0 deletions linera-base/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ use crate::{
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
)]
#[cfg_attr(
all(with_testing, not(target_arch = "wasm32")),
derive(test_strategy::Arbitrary)
)]
pub struct Amount(u128);

#[derive(Serialize, Deserialize)]
Expand Down
44 changes: 24 additions & 20 deletions linera-base/src/identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ use crate::{
pub struct Owner(pub CryptoHash);

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

impl Account {
/// Creates an Account with a ChainId
/// Creates an [`Account`] representing the balance shared by a chain's owners.
pub fn chain(chain_id: ChainId) -> Self {
Account {
chain_id,
owner: None,
}
}

/// Creates an Account with a ChainId and an Owner
pub fn owner(chain_id: ChainId, owner: Owner) -> Self {
/// Creates an [`Account`] for a specific [`Owner`] on a chain.
pub fn owner(chain_id: ChainId, owner: impl Into<AccountOwner>) -> Self {
Account {
chain_id,
owner: Some(owner),
owner: Some(owner.into()),
}
}
}
Expand All @@ -80,18 +81,21 @@ impl Display for Account {
impl FromStr for Account {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split(':').collect::<Vec<_>>();
anyhow::ensure!(
parts.len() <= 2,
"Expecting format `chain-id:address` or `chain-id`"
);
if parts.len() == 1 {
Ok(Account::chain(s.parse()?))
} else {
let chain_id = parts[0].parse()?;
let owner = parts[1].parse()?;
fn from_str(string: &str) -> Result<Self, Self::Err> {
let mut parts = string.splitn(2, ':');

let chain_id = parts
.next()
.context(
"Expecting an account formatted as `chain-id` or `chain-id:owner-type:address`",
)?
.parse()?;

if let Some(owner_string) = parts.next() {
let owner = owner_string.parse::<AccountOwner>()?;
Ok(Account::owner(chain_id, owner))
} else {
Ok(Account::chain(chain_id))
}
}
}
Expand Down Expand Up @@ -292,7 +296,7 @@ impl<'a> Deserialize<'a> for BlobId {
WitStore,
WitType,
)]
#[cfg_attr(with_testing, derive(Default))]
#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
pub struct MessageId {
/// The chain ID that created the message.
pub chain_id: ChainId,
Expand All @@ -304,7 +308,7 @@ pub struct MessageId {

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

/// A unique identifier for an application bytecode.
#[derive(Debug, WitLoad, WitStore, WitType)]
#[cfg_attr(with_testing, derive(Default))]
#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
pub struct BytecodeId<Abi = (), Parameters = (), InstantiationArgument = ()> {
/// The hash of the blob containing the contract bytecode.
pub contract_blob_hash: CryptoHash,
Expand Down
5 changes: 3 additions & 2 deletions linera-base/src/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use crate::{
crypto::{CryptoHash, PublicKey},
data_types::{Amount, BlockHeight, Resources, SendMessageRequest, TimeDelta, Timestamp},
identifiers::{
Account, ApplicationId, BytecodeId, ChainId, ChannelName, Destination, MessageId, Owner,
Account, AccountOwner, ApplicationId, BytecodeId, ChainId, ChannelName, Destination,
MessageId, Owner,
},
ownership::{ChainOwnership, TimeoutConfig},
};
Expand Down Expand Up @@ -83,7 +84,7 @@ fn send_message_request_test_case() -> SendMessageRequest<Vec<u8>> {
fn account_test_case() -> Account {
Account {
chain_id: ChainId::root(10),
owner: Some(Owner(CryptoHash::test_hash("account"))),
owner: Some(AccountOwner::User(Owner(CryptoHash::test_hash("account")))),
}
}

Expand Down
44 changes: 21 additions & 23 deletions linera-chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ where
.track_executed_block_size_of(&incoming_bundle)
.with_execution_context(chain_execution_context)?;
for (message_id, posted_message) in incoming_bundle.messages_and_ids() {
self.execute_message_in_block(
Box::pin(self.execute_message_in_block(
message_id,
posted_message,
incoming_bundle,
Expand All @@ -776,7 +776,7 @@ where
local_time,
&mut txn_tracker,
&mut resource_controller,
)
))
.await?;
}
}
Expand All @@ -793,16 +793,15 @@ where
authenticated_signer: block.authenticated_signer,
authenticated_caller_id: None,
};
self.execution_state
.execute_operation(
context,
local_time,
operation.clone(),
&mut txn_tracker,
&mut resource_controller,
)
.await
.with_execution_context(chain_execution_context)?;
Box::pin(self.execution_state.execute_operation(
context,
local_time,
operation.clone(),
&mut txn_tracker,
&mut resource_controller,
))
.await
.with_execution_context(chain_execution_context)?;
resource_controller
.with_state(&mut self.execution_state)
.await?
Expand Down Expand Up @@ -942,17 +941,16 @@ where
// Once a chain is closed, accepting incoming messages is not allowed.
ensure!(!self.is_closed(), ChainError::ClosedChain);

self.execution_state
.execute_message(
context,
local_time,
posted_message.message.clone(),
(grant > Amount::ZERO).then_some(&mut grant),
txn_tracker,
resource_controller,
)
.await
.with_execution_context(ChainExecutionContext::IncomingBundle(txn_index))?;
Box::pin(self.execution_state.execute_message(
context,
local_time,
posted_message.message.clone(),
(grant > Amount::ZERO).then_some(&mut grant),
txn_tracker,
resource_controller,
))
.await
.with_execution_context(ChainExecutionContext::IncomingBundle(txn_index))?;
if grant > Amount::ZERO {
if let Some(refund_grant_to) = posted_message.refund_grant_to {
self.execution_state
Expand Down
4 changes: 2 additions & 2 deletions linera-core/src/chain_worker/state/temporary_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use linera_base::{
data_types::{ArithmeticError, Blob, BlobContent, Timestamp, UserApplicationDescription},
ensure,
identifiers::{GenericApplicationId, UserApplicationId},
identifiers::{AccountOwner, GenericApplicationId, UserApplicationId},
};
use linera_chain::{
data_types::{
Expand Down Expand Up @@ -139,7 +139,7 @@ where
.execution_state
.system
.balances
.get(&signer)
.get(&AccountOwner::User(signer))
.await?;
}

Expand Down
Loading

0 comments on commit a601098

Please sign in to comment.