Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:

env:
UBUNTU_VERSION: '22.04'
STATIC_LIBRARIES_IMAGE_TAG: 'rust-1.94_ghc-9.10.2'
STATIC_LIBRARIES_IMAGE_TAG: 'rust-1.95.0_ghc-9.10.2'
STACK_VERSION: '3.7.1'
FLATBUFFERS_VERSION: '23.5.26'
GHC_VERSION: '9.10.2'
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## Unreleased changes

- Add support for the following "meta update" operations:
- `lockFund`: move protocol-level tokens from an account's available balance into a PLT Lock.
- `lockSend`: move protocol-level tokens from a PLT Lock to a recipient account's available balance.
- `lockReturn`: release protocol-level tokens from a PLT Lock back to the owner's available balance.
- `lockCancel`: cancel a PLT Lock, releasing all funds to their owners.
- Populate the protocol-level token account `module_state` returned by `GetAccountInfo` with available balance and lock details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ testTransfer ::
(IsProtocolVersion pv, PVSupportsPLT pv) =>
SProtocolVersion pv ->
Property
testTransfer _ = property (ioProperty . theTest)
testTransfer spv = property (ioProperty . theTest)
where
theTest TransferConfig{..} = do
let govAcct = CBOR.accountTokenHolder dummyAddress
Expand Down Expand Up @@ -769,6 +769,14 @@ testTransfer _ = property (ioProperty . theTest)
-- The full supplied energy will be used in the case of an
-- out-of-energy failure.
postCheck False
| tcRecvInvalid && demoteProtocolVersion spv >= Types.P11 -> do
assertTokenReject
CBOR.AddressNotFound
{ trrOperationIndex = 0,
trrAddress = CBOR.accountTokenHolder actualRecipientAddress
}
result
postCheck False
| tcPaused -> do
assertTokenReject
CBOR.OperationNotPermitted
Expand Down
27 changes: 27 additions & 0 deletions plt/plt-block-state/src/block_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ impl<C: EntityContextTypes> BlockStateOperations for ExecutionTimeBlockStateP9<C
) {
panic!("no locks on P9")
}

fn remove_lock_balance_ref(
&mut self,
_lock: &LockId,
_account: &Self::Account,
_token: &Self::Token,
) {
panic!("no locks on P9")
}
}

/// Runtime/execution state relevant for providing an implementation of
Expand Down Expand Up @@ -567,4 +576,22 @@ impl<C: EntityContextTypes> BlockStateOperations for ExecutionTimeBlockStateP11<
lock.add_lock_balance_ref(account.account_index(), *token);
self.block_state.update_lock(&self.context, lock).unwrap();
}

fn remove_lock_balance_ref(
&mut self,
lock: &LockId,
account: &Self::Account,
token: &Self::Token,
) {
let mut lock = self
.block_state
.lock_by_id(&self.context, lock)
.unwrap()
.unwrap();
let removed = lock.remove_lock_balance_ref(account.account_index(), *token);
if removed {
// Only update on a change.
self.block_state.update_lock(&self.context, lock).unwrap();
}
}
}
22 changes: 22 additions & 0 deletions plt/plt-block-state/src/block_state_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,26 @@ pub trait BlockStateOperations: BlockStateQuery {
/// - The `lock` MUST already exist in the block state, i.e.
/// `s.lock_by_id(lock_id).expect("lock exists")`.
fn add_lock_balance_ref(&mut self, lock: &LockId, account: &Self::Account, token: &Self::Token);

/// Stop tracking that a lock holds a balance for the given account and token.
///
/// This removes the account/token pair from the lock state, so it will no longer be
/// returned by [`BlockStateQuery::lock_balances`].
///
/// # Arguments
/// - `lock` The lock to update.
/// - `account` The account whose locked balance is no longer tracked.
/// - `token` The token whose locked balance is no longer tracked.
///
/// The caller must ensure the following conditions are true, and failing to do so results in
/// undefined behavior.
///
/// - The `lock` MUST already exist in the block state, i.e.
/// `s.lock_by_id(lock_id).expect("lock exists")`.
fn remove_lock_balance_ref(
Comment thread
soerenbf marked this conversation as resolved.
&mut self,
lock: &LockId,
account: &Self::Account,
token: &Self::Token,
);
}
22 changes: 22 additions & 0 deletions plt/plt-block-state/src/entity/protocol_level_locks/p11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,26 @@ impl LockP11 {
.locked_balances
.insert((account_index, token_index));
}

/// Stop tracking that the lock holds a balance for the given account and token.
/// This removes the account/token pair from the lock state, so it will no longer be
/// returned by [`Self::lock_balance_refs`].
///
/// # Arguments
///
/// - `account_index` The index of the account whose locked balance is no longer tracked.
/// - `token_index` Index of the token whose locked balance is no longer tracked.
///
/// # Returns
/// `true` if the account/token pair was previously tracked and has been removed,
/// `false` if the account/token pair was not previously tracked.
pub fn remove_lock_balance_ref(
&mut self,
account_index: AccountIndex,
token_index: TokenIndex,
) -> bool {
self.persistent
.locked_balances
.remove(&(account_index, token_index))
}
}
10 changes: 10 additions & 0 deletions plt/plt-scheduler-types/src/types/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ pub struct RawTokenAmount(pub u64);
impl RawTokenAmount {
/// Maximum representable raw token amount.
pub const MAX: Self = Self(u64::MAX);

/// Checked addition of raw token amounts. Returns `None` if the result would overflow.
pub fn checked_add(self, other: RawTokenAmount) -> Option<RawTokenAmount> {
self.0.checked_add(other.0).map(RawTokenAmount)
}

/// Checked subtraction of raw token amounts. Returns `None` if the result would overflow.
pub fn checked_sub(self, other: RawTokenAmount) -> Option<RawTokenAmount> {
self.0.checked_sub(other.0).map(RawTokenAmount)
}
}

/// Serialization of 'TokenRawAmount' is as a variable length quantity (VLQ).
Expand Down
12 changes: 8 additions & 4 deletions plt/plt-scheduler/src/locks/lock_controller.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Runtime interface for protocol-level lock controllers.

use concordium_base::contracts_common::AccountAddress;
use concordium_base::protocol_level_tokens::meta_operations::{
MetaLockCancelDetails, MetaLockFundDetails, MetaLockReturnDetails, MetaLockSendDetails,
};
Expand All @@ -23,17 +24,19 @@ pub enum LockOperation {

/// Runtime interface implemented by protocol-level locks.
pub trait LockController {
/// Approve or reject a lock operation. Returns `true` if the operation is authorized.
/// Approve or reject a lock operation. Returns `Ok(())` if the operation is authorized, or
/// a `TransactionRejectReason` if it is not.
///
/// * `bsq`: the block state to query on
/// * `sender`: the transaction sender reference
/// * `operation`: the lock operation to approve/reject.
fn validate_operation<BSQ: BlockStateQuery>(
&self,
bsq: &BSQ,
sender_address: AccountAddress,
sender: &BSQ::Account,
operation: &LockOperation,
) -> bool;
) -> Result<(), TransactionRejectReason>;

/// Convert this controller configuration to its canonical CBOR
/// [`concordium_base::protocol_level_locks::LockController`] representation, used by the
Expand Down Expand Up @@ -67,12 +70,13 @@ impl LockController for LockControllerConfig {
fn validate_operation<BSQ: BlockStateQuery>(
&self,
bsq: &BSQ,
sender_address: AccountAddress,
sender: &BSQ::Account,
operation: &LockOperation,
) -> bool {
) -> Result<(), TransactionRejectReason> {
match self {
LockControllerConfig::SimpleV0(lock_controller_simple_v0) => {
lock_controller_simple_v0.validate_operation(bsq, sender, operation)
lock_controller_simple_v0.validate_operation(bsq, sender_address, sender, operation)
}
}
}
Expand Down
52 changes: 44 additions & 8 deletions plt/plt-scheduler/src/locks/lock_controller_simple.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use concordium_base::base::AccountIndex;
use concordium_base::contracts_common::AccountAddress;
use concordium_base::protocol_level_locks::LockControllerSimpleV0Capability;
use concordium_base::protocol_level_tokens::{CborHolderAccount, TokenId};
use plt_block_state::block_state_interface::BlockStateQuery;
Expand All @@ -15,17 +16,52 @@ impl LockController for LockControllerSimpleV0 {
fn validate_operation<BSQ: BlockStateQuery>(
&self,
bsq: &BSQ,
sender_address: AccountAddress,
sender: &BSQ::Account,
operation: &LockOperation,
) -> bool {
) -> Result<(), TransactionRejectReason> {
let sender_index = bsq.account_index(sender);
let role = match operation {
LockOperation::Fund(_) => LockControllerSimpleV0Capability::Fund,
LockOperation::Send(_) => LockControllerSimpleV0Capability::Send,
LockOperation::Return(_) => LockControllerSimpleV0Capability::Return,
LockOperation::Cancel(_) => LockControllerSimpleV0Capability::Cancel,
};
self.has_role(sender_index, role)
match operation {
LockOperation::Fund(fund_details) => {
if !self.has_role(sender_index, LockControllerSimpleV0Capability::Fund) {
return Err(TransactionRejectReason::LockFundNotAuthorized(
fund_details.lock.clone(),
sender_address,
));
}
if !self.tokens.contains(&fund_details.token) {
return Err(TransactionRejectReason::LockTokenNotPermitted(
fund_details.lock.clone(),
fund_details.token.clone(),
));
}
}
LockOperation::Send(send_details) => {
if !self.has_role(sender_index, LockControllerSimpleV0Capability::Send) {
return Err(TransactionRejectReason::LockSendNotAuthorized(
send_details.lock.clone(),
sender_address,
));
}
}
LockOperation::Return(return_details) => {
if !self.has_role(sender_index, LockControllerSimpleV0Capability::Return) {
return Err(TransactionRejectReason::LockReturnNotAuthorized(
return_details.lock.clone(),
sender_address,
));
}
}
LockOperation::Cancel(cancel_details) => {
if !self.has_role(sender_index, LockControllerSimpleV0Capability::Cancel) {
return Err(TransactionRejectReason::LockCancelNotAuthorized(
cancel_details.lock.clone(),
sender_address,
));
}
}
}
Ok(())
}

fn to_cbor_controller<BSQ: BlockStateQuery>(
Expand Down
Loading
Loading