Skip to content

Commit

Permalink
feat(HIP-540): Change or Remove existing keys from tokens
Browse files Browse the repository at this point in the history
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
RickyLB and dependabot[bot] authored Jun 13, 2024
1 parent d28dbdc commit ce4d743
Show file tree
Hide file tree
Showing 7 changed files with 1,801 additions and 8 deletions.
183 changes: 183 additions & 0 deletions examples/modify_token_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* ‌
* Hedera Rust SDK
* ​
* Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

use clap::Parser;
use hedera::{
AccountId, Client, KeyList, PrivateKey, PublicKey, TokenCreateTransaction, TokenInfoQuery, TokenKeyValidation, TokenUpdateTransaction
};
use time::{Duration, OffsetDateTime};

#[derive(Parser, Debug)]
struct Args {
#[clap(long, env)]
operator_id: AccountId,

#[clap(long, env)]
operator_key: PrivateKey,

#[clap(long, env, default_value = "testnet")]
hedera_network: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = dotenvy::dotenv();
let args = Args::parse();

let client = Client::for_name(&args.hedera_network)?;

client.set_operator(args.operator_id, args.operator_key.clone());

// Generate a higher-privileged key.
let admin_key = PrivateKey::generate_ed25519();

// Generate the lower-privileged keys that will be modified.
// Note: Lower-privileged keys are KYC, Freeze, Wipe, and Supply, Fee Schedule, Metadata key.
let supply_key = PrivateKey::generate_ed25519();
let wipe_key = PrivateKey::generate_ed25519();
let new_supply_key = PrivateKey::generate_ed25519();

// Generate an invalid key to update the supply key.
let unusable = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();

// Create the NFT token with admin, wipe, and supply keys.
let token_id = TokenCreateTransaction::new()
.name("Example NFT")
.symbol("ENFT")
.token_type(hedera::TokenType::NonFungibleUnique)
.treasury_account_id(client.get_operator_account_id().unwrap())
.admin_key(admin_key.public_key())
.wipe_key(wipe_key.public_key())
.supply_key(supply_key.public_key())
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
.freeze_with(&client)?
.sign(admin_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?
.token_id
.unwrap();

let token_info = TokenInfoQuery::new()
.token_id(token_id)
.execute(&client)
.await?;

println!("Admin Key: {:?}", token_info.admin_key);
println!("Supply Key: {:?}", token_info.supply_key);
println!("Wipe Key: {:?}", token_info.wipe_key);

println!("------");
println!("Removing Wipe Key...");

// Remove the wipe key with an empty key list, signing with the admin key.
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(KeyList::new())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(admin_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;

let token_info = TokenInfoQuery::new()
.token_id(token_id)
.execute(&client)
.await?;

println!("Wipe Key (after removal): {:?}", token_info.wipe_key);

println!("------");
println!("Removing Admin Key...");

// Remove the admin key with an empty key list, signing with the admin key.
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(KeyList::new())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(admin_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;

let token_info = TokenInfoQuery::new()
.token_id(token_id)
.execute(&client)
.await?;

println!("Admin Key (after removal): {:?}", token_info.admin_key);

println!("------");
println!("Updating Supply Key...");

// Update the supply key with a new key, signing with the old supply key and the new supply key.
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(new_supply_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(supply_key)
.sign(new_supply_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;

let token_info = TokenInfoQuery::new()
.token_id(token_id)
.execute(&client)
.await?;

println!("Supply Key (after update): {:?}", token_info.supply_key);

println!("------");
println!("Removing Supply Key...");

// Remove the supply key with an invalid key, signing with the new supply key.
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(unusable)
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(new_supply_key)
.execute(&client)
.await?
.get_receipt(&client)
.await?;

let token_info = TokenInfoQuery::new()
.token_id(token_id)
.execute(&client)
.await?;

let supply_key = token_info.supply_key.unwrap();

println!("Supply Key (after removal): {:?}", supply_key);

Ok(())
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ pub use token::{
TokenId,
TokenInfo,
TokenInfoQuery,
TokenKeyValidation,
TokenMintTransaction,
TokenNftInfo,
TokenNftInfoQuery,
Expand Down
1 change: 1 addition & 0 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub use token_info_query::{
TokenInfoQuery,
TokenInfoQueryData,
};
pub use token_key_validation_type::TokenKeyValidation;
pub use token_mint_transaction::{
TokenMintTransaction,
TokenMintTransactionData,
Expand Down
2 changes: 1 addition & 1 deletion src/token/snapshots/token_update_transaction/serialize.txt
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,6 @@ TokenUpdate(
),
},
),
key_verification_mode: FullValidation,
key_verification_mode: NoValidation,
},
)
16 changes: 16 additions & 0 deletions src/token/token_key_validation_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
* ‍
*/

use std::fmt::{
self,
Debug,
Display,
Formatter,
};

use hedera_proto::services;

use crate::{
Expand All @@ -38,6 +45,15 @@ pub enum TokenKeyValidation {
NoValidation = 1,
}

impl Display for TokenKeyValidation {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::FullValidation => write!(f, "FULL_VALIDATION"),
Self::NoValidation => write!(f, "NO_VALIDATION"),
}
}
}

impl FromProtobuf<services::TokenKeyValidation> for TokenKeyValidation {
fn from_protobuf(pb: services::TokenKeyValidation) -> crate::Result<Self> {
Ok(match pb {
Expand Down
44 changes: 37 additions & 7 deletions src/token/token_update_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ pub struct TokenUpdateTransactionData {
pause_key: Option<Key>,

/// Metadata of the created token definition.
metadata: Vec<u8>,
metadata: Option<Vec<u8>>,

/// The key which can change the metadata of a token
/// (token definition, partition definition, and individual NFTs).
Expand Down Expand Up @@ -349,13 +349,13 @@ impl TokenUpdateTransaction {

/// Returns the new metadata of the created token definition.
#[must_use]
pub fn get_metadata(&self) -> Vec<u8> {
pub fn get_metadata(&self) -> Option<Vec<u8>> {
self.data().metadata.clone()
}

/// Sets the new metadata of the token definition.
pub fn metadata(&mut self, metadata: Vec<u8>) -> &mut Self {
self.data_mut().metadata = metadata;
self.data_mut().metadata = Some(metadata);
self
}

Expand All @@ -370,6 +370,21 @@ impl TokenUpdateTransaction {
self.data_mut().metadata_key = Some(metadata_key.into());
self
}

/// Returns key verification mode.
#[must_use]
pub fn get_key_verification_mode(&self) -> TokenKeyValidation {
self.data().key_verification_mode
}

/// Assignss key verification mode.
pub fn key_verification_mode(
&mut self,
key_verification_mode: TokenKeyValidation,
) -> &mut Self {
self.data_mut().key_verification_mode = key_verification_mode;
self
}
}

impl TransactionData for TokenUpdateTransactionData {}
Expand Down Expand Up @@ -439,7 +454,7 @@ impl FromProtobuf<services::TokenUpdateTransactionBody> for TokenUpdateTransacti
token_memo: pb.memo,
fee_schedule_key: Option::from_protobuf(pb.fee_schedule_key)?,
pause_key: Option::from_protobuf(pb.pause_key)?,
metadata: pb.metadata.unwrap_or_default(),
metadata: pb.metadata,
metadata_key: Option::from_protobuf(pb.metadata_key)?,
key_verification_mode: TokenKeyValidation::from_protobuf(key_verification_mode)?,
})
Expand All @@ -466,7 +481,7 @@ impl ToProtobuf for TokenUpdateTransactionData {
memo: self.token_memo.clone(),
fee_schedule_key: self.fee_schedule_key.to_protobuf(),
pause_key: self.pause_key.to_protobuf(),
metadata: Some(self.metadata.clone()),
metadata: self.metadata.clone(),
metadata_key: self.metadata_key.to_protobuf(),
key_verification_mode: self.key_verification_mode.to_protobuf().into(),
}
Expand Down Expand Up @@ -575,6 +590,7 @@ mod tests {
.token_memo(Some(TEST_TOKEN_MEMO))
.metadata(TEST_METADATA.as_bytes().to_vec())
.metadata_key(test_metadata_key())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze()
.unwrap();

Expand Down Expand Up @@ -644,7 +660,7 @@ mod tests {
assert_eq!(tx.token_memo, Some(TEST_TOKEN_MEMO.into()));
assert_eq!(tx.fee_schedule_key, Some(test_fee_schedule_key().into()));
assert_eq!(tx.pause_key, Some(test_pause_key().into()));
assert_eq!(tx.metadata, TEST_METADATA.as_bytes());
assert_eq!(tx.metadata, Some(TEST_METADATA.as_bytes().into()));
assert_eq!(tx.metadata_key, Some(test_metadata_key().into()));
assert_eq!(tx.key_verification_mode, TEST_KEY_VERIFICATION_MODE);
}
Expand Down Expand Up @@ -863,7 +879,7 @@ mod tests {
fn get_set_metadata() {
let mut tx = TokenUpdateTransaction::new();
tx.metadata(TEST_METADATA.as_bytes().to_vec());
assert_eq!(tx.get_metadata(), TEST_METADATA.as_bytes().to_vec());
assert_eq!(tx.get_metadata(), Some(TEST_METADATA.as_bytes().to_vec()));
}

#[test]
Expand All @@ -886,4 +902,18 @@ mod tests {
let mut tx = make_transaction();
tx.metadata_key(test_metadata_key());
}

#[test]
fn get_set_key_verification_mode() {
let mut tx = TokenUpdateTransaction::new();
tx.key_verification_mode(TokenKeyValidation::NoValidation);
assert_eq!(tx.get_key_verification_mode(), TokenKeyValidation::NoValidation);
}

#[test]
#[should_panic]
fn get_set_key_verification_mode_frozen_panic() {
let mut tx = make_transaction();
tx.key_verification_mode(TokenKeyValidation::NoValidation);
}
}
Loading

0 comments on commit ce4d743

Please sign in to comment.