1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::{api::PreparedTransactionData, secret::SecretManage},
types::{
api::plugins::participation::types::{Participations, PARTICIPATION_TAG},
block::{
output::{
feature::{MetadataFeature, TagFeature},
unlock_condition::AddressUnlockCondition,
BasicOutput, BasicOutputBuilder, Output,
},
payload::TaggedDataPayload,
},
},
wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Error, Result, Wallet},
};
impl<S: 'static + SecretManage> Wallet<S>
where
crate::wallet::Error: From<S::Error>,
crate::client::Error: From<S::Error>,
{
/// Returns an account's total voting power (voting or NOT voting).
pub async fn get_voting_power(&self) -> Result<u64, WalletError> {
Ok(self
.get_voting_output()
.await?
// If no voting output exists, return 0
.map_or(0, |v| v.output.amount()))
}
/// Designates a given amount of tokens towards an account's "voting power" by creating a
/// special output, which is really a basic one with some metadata.
///
/// If not enough funds, throws an error.
/// If voting, use voting output (should only ever have one unless more space for more votes is needed).
/// This will stop voting in most cases (if there is a remainder), but the voting data isn't lost and calling `Vote`
/// without parameters will revote. Removes metadata for any events that have expired (uses event IDs to get
/// cached event information, checks event milestones in there against latest network milestone).
/// Prioritizes consuming outputs that are designated for voting but don't have any metadata (only possible if user
/// increases voting power then increases again immediately after).
pub async fn increase_voting_power(&self, amount: u64) -> Result<TransactionWithMetadata, WalletError> {
let prepared = self.prepare_increase_voting_power(amount).await?;
self.sign_and_submit_transaction(prepared, None, None).await
}
/// Prepares the transaction for [Wallet::increase_voting_power()].
pub async fn prepare_increase_voting_power(&self, amount: u64) -> Result<PreparedTransactionData, WalletError> {
let (new_output, tx_options) = match self.get_voting_output().await? {
Some(current_output_data) => {
let output = current_output_data.output.as_basic();
let new_amount = output.amount().checked_add(amount).ok_or(Error::InvalidVotingPower)?;
let (new_output, tagged_data_payload) =
self.new_voting_output_and_tagged_data(output, new_amount).await?;
(
new_output,
Some(TransactionOptions {
// Use the previous voting output and additionally other for the additional amount.
required_inputs: Some(vec![current_output_data.output_id]),
tagged_data_payload: Some(tagged_data_payload),
..Default::default()
}),
)
}
None => (
BasicOutputBuilder::new_with_amount(amount)
.add_unlock_condition(AddressUnlockCondition::new(self.address().await))
.add_feature(TagFeature::new(PARTICIPATION_TAG)?)
.finish_output()?,
None,
),
};
self.prepare_send_outputs([new_output], tx_options).await
}
/// Reduces an account's "voting power" by a given amount.
/// This will stop voting, but the voting data isn't lost and calling `Vote` without parameters will revote.
///
/// If amount is higher than actual voting power, throws an error.
/// If voting and amount is equal to voting power, removes tagged data payload and output metadata.
/// Removes metadata for any events that have expired (uses event IDs to get cached event information, checks event
/// milestones in there against latest network milestone).
/// Prioritizes consuming outputs that are designated for voting but don't have any metadata (only possible if user
/// increases voting power then decreases immediately after).
pub async fn decrease_voting_power(&self, amount: u64) -> Result<TransactionWithMetadata, WalletError> {
let prepared = self.prepare_decrease_voting_power(amount).await?;
self.sign_and_submit_transaction(prepared, None, None).await
}
/// Prepares the transaction for [Wallet::decrease_voting_power()].
pub async fn prepare_decrease_voting_power(&self, amount: u64) -> Result<PreparedTransactionData, WalletError> {
let current_output_data = self
.get_voting_output()
.await?
.ok_or_else(|| crate::wallet::Error::Voting("No unspent voting output found".to_string()))?;
let output = current_output_data.output.as_basic();
// If the amount to decrease is the amount of the output, then we just remove the features.
let (new_output, tagged_data_payload) = if amount == output.amount() {
(BasicOutputBuilder::from(output).clear_features().finish_output()?, None)
} else {
let new_amount = output.amount().checked_sub(amount).ok_or(Error::InvalidVotingPower)?;
let (new_output, tagged_data_payload) = self.new_voting_output_and_tagged_data(output, new_amount).await?;
(new_output, Some(tagged_data_payload))
};
self.prepare_send_outputs(
[new_output],
Some(TransactionOptions {
// Use the previous voting output and additionally others for possible additional required amount for
// the remainder to reach the minimum required storage deposit.
required_inputs: Some(vec![current_output_data.output_id]),
tagged_data_payload,
..Default::default()
}),
)
.await
}
async fn new_voting_output_and_tagged_data(
&self,
output: &BasicOutput,
amount: u64,
) -> Result<(Output, TaggedDataPayload)> {
let mut output_builder = BasicOutputBuilder::from(output).with_amount(amount);
let mut participation_bytes = output.features().metadata().map(|m| m.data()).unwrap_or(&[]);
let participation_bytes = if let Ok(mut participations) = Participations::from_bytes(&mut participation_bytes) {
// Remove ended participations.
self.remove_ended_participation_events(&mut participations).await?;
let participation_bytes = participations.to_bytes()?;
output_builder = output_builder.replace_feature(MetadataFeature::new(participation_bytes.clone())?);
participation_bytes
} else {
Vec::new()
};
Ok((
output_builder.finish_output()?,
TaggedDataPayload::new(PARTICIPATION_TAG.as_bytes().to_vec(), participation_bytes.to_vec())?,
))
}
}