Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove option from output unlock conditions #2192

Merged
merged 2 commits into from
Mar 20, 2024
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
41 changes: 19 additions & 22 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,25 +601,24 @@ pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> {
println_log_info!(" + {} {}", native_token.amount(), native_token.token_id());
}

if let Some(unlock_conditions) = output.unlock_conditions() {
let deposit_return = unlock_conditions
.storage_deposit_return()
.map(|deposit_return| deposit_return.amount())
.unwrap_or(0);
let amount = output.amount() - deposit_return;
println_log_info!(" - base coin amount: {}", amount);

if let Some(expiration) = unlock_conditions.expiration() {
let slot_index = wallet.client().get_slot_index().await?;

if *expiration.slot_index() > *slot_index {
println_log_info!(" - expires in {} slot indices", *expiration.slot_index() - *slot_index);
} else {
println_log_info!(
" - expired {} slot indices ago",
*slot_index - *expiration.slot_index()
);
}
let deposit_return = output
.unlock_conditions()
.storage_deposit_return()
.map(|deposit_return| deposit_return.amount())
.unwrap_or(0);
let amount = output.amount() - deposit_return;
println_log_info!(" - base coin amount: {}", amount);

if let Some(expiration) = output.unlock_conditions().expiration() {
let slot_index = wallet.client().get_slot_index().await?;

if *expiration.slot_index() > *slot_index {
println_log_info!(" - expires in {} slot indices", *expiration.slot_index() - *slot_index);
} else {
println_log_info!(
" - expired {} slot indices ago",
*slot_index - *expiration.slot_index()
);
}
}
}
Expand Down Expand Up @@ -1342,11 +1341,9 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> {
Output::Delegation(delegation) => delegations.push(delegation.delegation_id_non_null(&output_id)),
Output::Anchor(anchor) => anchors.push(anchor.anchor_id_non_null(&output_id)),
}
let unlock_conditions = output_data
let sdr_amount = output_data
.output
.unlock_conditions()
.expect("output must have unlock conditions");
let sdr_amount = unlock_conditions
.storage_deposit_return()
.map(|sdr| sdr.amount())
.unwrap_or(0);
Expand Down
17 changes: 7 additions & 10 deletions sdk/examples/wallet/17_check_unlock_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.add_unlock_condition(AddressUnlockCondition::new(wallet_address.clone()))
.finish_output()?;

let controlled_by_account = if let [UnlockCondition::Address(address_unlock_condition)] = output
.unlock_conditions()
.expect("output needs to have unlock conditions")
.as_ref()
{
// Check that the address in the unlock condition belongs to the wallet
wallet_address.inner() == address_unlock_condition.address()
} else {
false
};
let controlled_by_account =
if let [UnlockCondition::Address(address_unlock_condition)] = output.unlock_conditions().as_ref() {
// Check that the address in the unlock condition belongs to the wallet
wallet_address.inner() == address_unlock_condition.address()
} else {
false
};

println!(
"The output has only an address unlock condition and the address is from the account: {controlled_by_account:?}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ impl TransactionBuilder {
if input
.output
.unlock_conditions()
.map_or(false, |u| u.iter().any(|u| u.is_timelock() || u.is_expiration()))
.iter()
.any(|u| u.is_timelock() || u.is_expiration())
{
log::debug!("Adding commitment context input for timelocked or expiring output");
needs_commitment_context = true;
Expand Down
5 changes: 1 addition & 4 deletions sdk/src/client/api/block_builder/transaction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,10 +640,7 @@ impl TransactionBuilder {
return false;
}

// PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out.
let unlock_conditions = input.output.unlock_conditions().unwrap();

if unlock_conditions.is_timelocked(
if input.output.unlock_conditions().is_timelocked(
self.latest_slot_commitment_id.slot_index(),
self.protocol_parameters.min_committable_age(),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ impl TransactionBuilder {
// Find the first value that matches the remainder address
self.non_remainder_outputs().any(|o| {
(o.is_basic() || o.is_account() || o.is_anchor() || o.is_nft())
&& o.unlock_conditions()
.map_or(true, |uc| uc.expiration().is_none() && uc.timelock().is_none())
&& o.unlock_conditions().expiration().is_none()
&& o.unlock_conditions().timelock().is_none()
&& matches!(o.required_address(
self.latest_slot_commitment_id.slot_index(),
self.protocol_parameters.committable_age_range(),
Expand All @@ -156,10 +156,7 @@ impl TransactionBuilder {
.provided_outputs
.iter_mut()
.chain(&mut self.added_outputs)
.filter(|o| {
o.unlock_conditions()
.map_or(true, |uc| uc.expiration().is_none() && uc.timelock().is_none())
})
.filter(|o| o.unlock_conditions().expiration().is_none() && o.unlock_conditions().timelock().is_none())
.filter_map(|o| sort_order.get(&o.kind()).map(|order| (*order, o)))
.collect::<BTreeMap<_, _>>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@ pub(crate) fn sdruc_not_expired(
output: &Output,
slot_index: SlotIndex,
) -> Option<&StorageDepositReturnUnlockCondition> {
// PANIC: safe to unwrap as outputs without unlock conditions have been filtered out already.
let unlock_conditions = output.unlock_conditions().unwrap();

unlock_conditions.storage_deposit_return().and_then(|sdr| {
let expired = unlock_conditions
output.unlock_conditions().storage_deposit_return().filter(|_| {
output
.unlock_conditions()
.expiration()
.map_or(false, |expiration| slot_index >= expiration.slot_index());

// We only have to send the storage deposit return back if the output is not expired
(!expired).then_some(sdr)
.map_or(true, |expiration| slot_index < expiration.slot_index())
})
}

Expand Down
19 changes: 8 additions & 11 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,14 @@ impl Output {
}

/// Returns the unlock conditions of an [`Output`], if any.
pub fn unlock_conditions(&self) -> Option<&UnlockConditions> {
pub fn unlock_conditions(&self) -> &UnlockConditions {
match self {
Self::Basic(output) => Some(output.unlock_conditions()),
Self::Account(output) => Some(output.unlock_conditions()),
Self::Anchor(output) => Some(output.unlock_conditions()),
Self::Foundry(output) => Some(output.unlock_conditions()),
Self::Nft(output) => Some(output.unlock_conditions()),
Self::Delegation(output) => Some(output.unlock_conditions()),
Self::Basic(output) => output.unlock_conditions(),
Self::Account(output) => output.unlock_conditions(),
Self::Anchor(output) => output.unlock_conditions(),
Self::Foundry(output) => output.unlock_conditions(),
Self::Nft(output) => output.unlock_conditions(),
Self::Delegation(output) => output.unlock_conditions(),
}
}

Expand Down Expand Up @@ -340,10 +340,7 @@ impl Output {
});
}

if let Some(return_condition) = self
.unlock_conditions()
.and_then(UnlockConditions::storage_deposit_return)
{
if let Some(return_condition) = self.unlock_conditions().storage_deposit_return() {
// We can't return more tokens than were originally contained in the output.
// `Return Amount` ≤ `Amount`.
if return_condition.amount() > self.amount() {
Expand Down
38 changes: 20 additions & 18 deletions sdk/src/types/block/semantic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,25 +355,27 @@ impl<'a> SemanticValidationContext<'a> {
}
}

if let Some(unlock_conditions) = created_output.unlock_conditions() {
if let (Some(address), Some(timelock)) = (unlock_conditions.address(), unlock_conditions.timelock()) {
if let Address::Account(account_address) = address.address() {
if let Some(entry) = self.block_issuer_mana.get_mut(account_address.account_id()) {
if let Some(commitment_context_input) = self.commitment_context_input {
let past_bounded_slot =
self.protocol_parameters.past_bounded_slot(commitment_context_input);

if timelock.slot_index()
>= past_bounded_slot + self.protocol_parameters.max_committable_age()
{
entry.1 = entry
.1
.checked_add(created_output.mana())
.ok_or(TransactionFailureReason::SemanticValidationFailed)?;
}
} else {
return Err(TransactionFailureReason::BlockIssuerCommitmentInputMissing);
if let Some((address, timelock)) = created_output
.unlock_conditions()
.address()
.zip(created_output.unlock_conditions().timelock())
{
if let Address::Account(account_address) = address.address() {
if let Some(entry) = self.block_issuer_mana.get_mut(account_address.account_id()) {
if let Some(commitment_context_input) = self.commitment_context_input {
let past_bounded_slot =
self.protocol_parameters.past_bounded_slot(commitment_context_input);

if timelock.slot_index()
>= past_bounded_slot + self.protocol_parameters.max_committable_age()
{
entry.1 = entry
.1
.checked_add(created_output.mana())
.ok_or(TransactionFailureReason::SemanticValidationFailed)?;
}
} else {
return Err(TransactionFailureReason::BlockIssuerCommitmentInputMissing);
}
}
}
Expand Down
39 changes: 14 additions & 25 deletions sdk/src/wallet/operations/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,7 @@ impl<S: 'static + SecretManage> Wallet<S> {
_ => {
// If there is only an [AddressUnlockCondition], then we can spend the output at any time
// without restrictions
if let [UnlockCondition::Address(_)] = output
.unlock_conditions()
.expect("output needs to have unlock conditions")
.as_ref()
{
if let [UnlockCondition::Address(_)] = output.unlock_conditions().as_ref() {
// add nft_id for nft outputs
if let Output::Nft(nft) = &output {
let nft_id = nft.nft_id_non_null(output_id);
Expand Down Expand Up @@ -185,21 +181,18 @@ impl<S: 'static + SecretManage> Wallet<S> {
// If output has a StorageDepositReturnUnlockCondition, the amount of it should
// be subtracted, because this part
// needs to be sent back
let amount = output
.unlock_conditions()
.and_then(|u| u.storage_deposit_return())
.map_or_else(
|| output.amount(),
|sdr| {
if wallet_address.inner() == sdr.return_address() {
// sending to ourself, we get the full amount
output.amount()
} else {
// Sending to someone else
output.amount() - sdr.amount()
}
},
);
let amount = output.unlock_conditions().storage_deposit_return().map_or_else(
|| output.amount(),
|sdr| {
if wallet_address.inner() == sdr.return_address() {
// sending to ourself, we get the full amount
output.amount()
} else {
// Sending to someone else
output.amount() - sdr.amount()
}
},
);

// add nft_id for nft outputs
if let Output::Nft(output) = &output {
Expand Down Expand Up @@ -243,11 +236,7 @@ impl<S: 'static + SecretManage> Wallet<S> {
}
} else {
// Don't add expired outputs that can't ever be unlocked by us
if let Some(expiration) = output
.unlock_conditions()
.expect("output needs to have unlock conditions")
.expiration()
{
if let Some(expiration) = output.unlock_conditions().expiration() {
// Not expired, could get unlockable when it's expired, so we insert it
if slot_index < expiration.slot_index() {
balance.potentially_locked_outputs.insert(*output_id, false);
Expand Down
51 changes: 26 additions & 25 deletions sdk/src/wallet/operations/helpers/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ pub(crate) fn can_output_be_unlocked_now(
commitment_slot_index: impl Into<SlotIndex> + Copy,
committable_age_range: CommittableAgeRange,
) -> Result<bool, WalletError> {
if let Some(unlock_conditions) = output_data.output.unlock_conditions() {
if unlock_conditions.is_timelocked(commitment_slot_index, committable_age_range.min) {
return Ok(false);
}
if output_data
.output
.unlock_conditions()
.is_timelocked(commitment_slot_index, committable_age_range.min)
{
return Ok(false);
}

let required_address = output_data
Expand All @@ -40,30 +42,29 @@ pub(crate) fn can_output_be_unlocked_from_now_on(
slot_index: impl Into<SlotIndex> + Copy,
committable_age_range: CommittableAgeRange,
) -> bool {
if let Some(unlock_conditions) = output.unlock_conditions() {
if unlock_conditions.is_timelocked(slot_index, committable_age_range.min) {
return false;
}
if output
.unlock_conditions()
.is_timelocked(slot_index, committable_age_range.min)
{
return false;
}

// If there is an expiration unlock condition, we can only unlock it forever from now on, if it's expired and
// the return address belongs to the wallet
if let Some(expiration) = unlock_conditions.expiration() {
if let Some(address) = expiration.return_address_expired(
// Safe to unwrap, if there is an expiration, then there also needs to be an address unlock condition
unlock_conditions.address().unwrap().address(),
slot_index,
committable_age_range,
) {
if address != expiration.return_address() || !controlled_addresses.contains(address) {
return false;
}
} else {
// If there is an expiration unlock condition, we can only unlock it forever from now on, if it's expired and
// the return address belongs to the wallet
if let Some(expiration) = output.unlock_conditions().expiration() {
if let Some(address) = expiration.return_address_expired(
// Safe to unwrap, if there is an expiration, then there also needs to be an address unlock condition
output.unlock_conditions().address().unwrap().address(),
slot_index,
committable_age_range,
) {
if address != expiration.return_address() || !controlled_addresses.contains(address) {
return false;
}
} else {
return false;
}

true
} else {
false
}

true
}
Loading
Loading