From 72f465652c906e41a19d251cc417bb73d0277539 Mon Sep 17 00:00:00 2001 From: svetoslav-nikol0v Date: Fri, 14 Jun 2024 13:36:47 +0300 Subject: [PATCH] feature: change or remove existing keys from a token [HIP-540] (#2299) * feature: change or remove token keys + tests + example Signed-off-by: svetoslav-nikol0v * chore: remove import Signed-off-by: svetoslav-nikol0v * fix: unit test Signed-off-by: svetoslav-nikol0v * chore: remove only Signed-off-by: svetoslav-nikol0v * update: public key class and tests Signed-off-by: svetoslav-nikol0v * update: example Signed-off-by: svetoslav-nikol0v --------- Signed-off-by: svetoslav-nikol0v --- examples/change-or-remove-token-keys.js | 214 ++ package.json | 2 +- src/PublicKey.js | 13 + src/Status.js | 8 +- src/exports.js | 1 + src/token/TokenKeyValidation.js | 88 + src/token/TokenUpdateTransaction.js | 42 + .../integration/TokenUpdateIntegrationTest.js | 2012 ++++++++++++++++- test/unit/TokenUpdateTransaction.js | 3 + 9 files changed, 2376 insertions(+), 7 deletions(-) create mode 100644 examples/change-or-remove-token-keys.js create mode 100644 src/token/TokenKeyValidation.js diff --git a/examples/change-or-remove-token-keys.js b/examples/change-or-remove-token-keys.js new file mode 100644 index 000000000..9940f099d --- /dev/null +++ b/examples/change-or-remove-token-keys.js @@ -0,0 +1,214 @@ +import { + AccountId, + Client, + PrivateKey, + Logger, + LogLevel, + PublicKey, + KeyList, + TokenUpdateTransaction, + TokenKeyValidation, + TokenCreateTransaction, + TokenType, + TokenInfoQuery, +} from "@hashgraph/sdk"; +import dotenv from "dotenv"; + +/** + * @description Change ot remove token keys + */ + +async function main() { + // Ensure required environment variables are available + dotenv.config(); + if ( + !process.env.OPERATOR_KEY || + !process.env.OPERATOR_ID || + !process.env.HEDERA_NETWORK + ) { + throw new Error("Please set required keys in .env file."); + } + + const network = process.env.HEDERA_NETWORK; + + // Configure client using environment variables + const operatorId = AccountId.fromString(process.env.OPERATOR_ID); + const operatorKey = PrivateKey.fromStringED25519(process.env.OPERATOR_KEY); + + const client = Client.forName(network).setOperator(operatorId, operatorKey); + + // Set logger + const infoLogger = new Logger(LogLevel.Info); + client.setLogger(infoLogger); + + + const adminKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const newSupplyKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + // This HIP introduces ability to remove lower-privilege keys (Wipe, KYC, Freeze, Pause, Supply, Fee Schedule, Metadata) from a Token: + // - using an update with the empty KeyList; + const emptyKeyList = KeyList.of(); + // - updating with an “invalid” key such as an Ed25519 0x0000000000000000000000000000000000000000000000000000000000000000 public key, + // since it is (presumably) impossible to find the 32-byte string whose SHA-512 hash begins with 32 bytes of zeros. + const unusableKey = PublicKey.unusableKey(); + + console.log("====================================================="); + console.log("Initializing token keys..."); + console.log("-----------------------------------------------------"); + console.log('Admin key:', adminKey.publicKey.toString()); + console.log('Supply key:', supplyKey.publicKey.toString()); + console.log('New supply key:', newSupplyKey.publicKey.toString()); + console.log('Wipe key:', wipeKey.publicKey.toString()); + console.log('Freeze key:', freezeKey.publicKey.toString()); + console.log('Pause key:', pauseKey.publicKey.toString()); + console.log('Fee schedule key:', feeScheduleKey.publicKey.toString()); + console.log('Metadata key:', metadataKey.publicKey.toString()); + console.log("Unusable key:", unusableKey.toString()); + console.log("====================================================="); + console.log("\n"); + + let token, tokenInfo, response, receipt, update; + + console.log("====================================================="); + console.log("Creating token..."); + console.log("-----------------------------------------------------"); + token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(client); + + response = await ( + await token.sign(adminKey) + ).execute(client); + + receipt = await response.getReceipt(client) + console.log('Token create transction status:', receipt.status.toString()); + + const tokenId = receipt.tokenId; + console.log('Token id:', tokenId.toString()); + console.log("====================================================="); + console.log("\n"); + console.log("====================================================="); + console.log("Token keys:"); + console.log("-----------------------------------------------------"); + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(client); + + console.log('Token admin key:', tokenInfo.adminKey.toString()); + console.log('Token pause key:', tokenInfo.pauseKey.toString()); + console.log('Token freeze key:', tokenInfo.freezeKey.toString()); + console.log('Token wipe key:', tokenInfo.wipeKey.toString()); + console.log('Token supply key:', tokenInfo.supplyKey.toString()); + console.log('Token fee schedule key:', tokenInfo.feeScheduleKey.toString()); + console.log('Token metadata key:', tokenInfo.metadataKey.toString()); + console.log("====================================================="); + console.log("\n"); + console.log("====================================================="); + console.log("Removing Wipe Key..."); + console.log("-----------------------------------------------------"); + update = new TokenUpdateTransaction() + .setTokenId(tokenId) + .setWipeKey(emptyKeyList) + .setKeyVerificationMode(TokenKeyValidation.FullValidation) + .freezeWith(client) + response = await ( + await update.sign(adminKey) + ).execute(client); + + receipt = await response.getReceipt(client) + console.log("Token update transaction status:", receipt.status.toString()); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(client); + console.log('Token wipeKey is', tokenInfo.wipeKey); + console.log("====================================================="); + console.log("\n"); + console.log("====================================================="); + console.log("Removing Admin Key..."); + console.log("-----------------------------------------------------"); + update = new TokenUpdateTransaction() + .setTokenId(tokenId) + .setAdminKey(emptyKeyList) + .setKeyVerificationMode(TokenKeyValidation.NoValidation) + .freezeWith(client) + response = await ( + await update.sign(adminKey) + ).execute(client); + + receipt = await response.getReceipt(client) + console.log("Token update transaction status:", receipt.status.toString()); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(client); + console.log('Token adminKey is', tokenInfo.adminKey); + console.log("====================================================="); + console.log("\n"); + console.log("====================================================="); + console.log("Updating Supply Key..."); + console.log("-----------------------------------------------------"); + console.log('Token supplyKey (before update) is', tokenInfo.supplyKey.toString()) + update = new TokenUpdateTransaction() + .setTokenId(tokenId) + .setSupplyKey(newSupplyKey) + .setKeyVerificationMode(TokenKeyValidation.FullValidation) + .freezeWith(client) + response = await ( + await( + await update.sign(supplyKey) + ).sign(newSupplyKey) + ).execute(client); + + receipt = await response.getReceipt(client) + console.log("Token update transaction status:", receipt.status.toString()); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(client); + console.log('Token suppleyKey (after update) is', tokenInfo.supplyKey.toString()); + console.log("====================================================="); + console.log("\n"); + console.log("====================================================="); + console.log("Updating Supply Key to unusable format..."); + console.log("-----------------------------------------------------"); + console.log('Token supplyKey (before update) is', tokenInfo.supplyKey.toString()) + update = new TokenUpdateTransaction() + .setTokenId(tokenId) + .setSupplyKey(unusableKey) + .setKeyVerificationMode(TokenKeyValidation.NoValidation) + .freezeWith(client); + response = await( + await update.sign(newSupplyKey) + ).execute(client); + + receipt = await response.getReceipt(client) + console.log("Token update transaction status:", receipt.status.toString()); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(client); + + console.log('Token suppleyKey (after update) is', tokenInfo.supplyKey.toString()); + console.log("====================================================="); + + client.close(); +} + +main(); \ No newline at end of file diff --git a/package.json b/package.json index 26368325d..a64f33360 100644 --- a/package.json +++ b/package.json @@ -130,4 +130,4 @@ "optional": true } } -} \ No newline at end of file +} diff --git a/src/PublicKey.js b/src/PublicKey.js index bda8869ba..47905a76d 100644 --- a/src/PublicKey.js +++ b/src/PublicKey.js @@ -282,6 +282,19 @@ export default class PublicKey extends Key { toAccountId(shard, realm) { return CACHE.accountIdConstructor(shard, realm, this); } + + /** + * Returns an "unusable" public key. + * “Unusable” refers to a key such as an Ed25519 0x00000... public key, + * since it is (presumably) impossible to find the 32-byte string whose SHA-512 hash begins with 32 bytes of zeros. + * + * @returns {PublicKey} + */ + static unusableKey() { + return PublicKey.fromStringED25519( + "0000000000000000000000000000000000000000000000000000000000000000", + ); + } } CACHE.setPublicKeyED25519((key) => PublicKey.fromBytesED25519(key)); diff --git a/src/Status.js b/src/Status.js index 3eb8897cc..596228165 100644 --- a/src/Status.js +++ b/src/Status.js @@ -2919,11 +2919,11 @@ Status.InvalidEndpoint = new Status(351); Status.GossipEndpointsExceededLimit = new Status(352); /** - * The number of service endpoints exceeds the limit - */ -Status.ServiceEndpointsExceededLimit = new Status(356) + * The number of service endpoints exceeds the limit + */ +Status.ServiceEndpointsExceededLimit = new Status(356); /* * The IPv4 address is invalid */ -Status.InvalidIpv4Address = new Status(357) \ No newline at end of file +Status.InvalidIpv4Address = new Status(357); diff --git a/src/exports.js b/src/exports.js index e1c8e448f..6c1359424 100644 --- a/src/exports.js +++ b/src/exports.js @@ -175,6 +175,7 @@ export { default as LogLevel } from "./logger/LogLevel.js"; export { EntityIdHelper }; export { default as Long } from "long"; export { default as FreezeType } from "./FreezeType.js"; +export { default as TokenKeyValidation } from "./token/TokenKeyValidation.js"; export { default as StatusError } from "./StatusError.js"; export { default as PrecheckStatusError } from "./PrecheckStatusError.js"; diff --git a/src/token/TokenKeyValidation.js b/src/token/TokenKeyValidation.js new file mode 100644 index 000000000..031567e5e --- /dev/null +++ b/src/token/TokenKeyValidation.js @@ -0,0 +1,88 @@ +/*- + * ‌ + * Hedera JavaScript SDK + * ​ + * Copyright (C) 2020 - 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. + * ‍ + */ + +/** + * @namespace proto + * @typedef {import("@hashgraph/proto").proto.TokenKeyValidation} HashgraphProto.proto.TokenKeyValidation + */ + +/** Types of validation strategies for token keys. */ +export default class TokenKeyValidation { + /** + * @hideconstructor + * @internal + * @param {number} code + */ + constructor(code) { + /** @readonly */ + this._code = code; + + Object.freeze(this); + } + + /** + * @returns {string} + */ + toString() { + switch (this) { + case TokenKeyValidation.FullValidation: + return "FULL_VALIDATION"; + case TokenKeyValidation.NoValidation: + return "NO_VALIDATION"; + default: + return `UNKNOWN (${this._code})`; + } + } + + /** + * @internal + * @param {number} code + * @returns {TokenKeyValidation} + */ + static _fromCode(code) { + switch (code) { + case 0: + return TokenKeyValidation.FullValidation; + case 1: + return TokenKeyValidation.NoValidation; + } + + throw new Error( + `(BUG) TokenKeyValidation.fromCode() does not handle code: ${code}`, + ); + } + + /** + * @returns {HashgraphProto.proto.TokenKeyValidation} + */ + valueOf() { + return this._code; + } +} + +/** + * Currently the default behaviour. It will perform all token key validations. + */ +TokenKeyValidation.FullValidation = new TokenKeyValidation(0); + +/** + * Perform no validations at all for all passed token keys. + */ +TokenKeyValidation.NoValidation = new TokenKeyValidation(1); diff --git a/src/token/TokenUpdateTransaction.js b/src/token/TokenUpdateTransaction.js index 8c73e30c0..bcf2b9f64 100644 --- a/src/token/TokenUpdateTransaction.js +++ b/src/token/TokenUpdateTransaction.js @@ -26,6 +26,7 @@ import AccountId from "../account/AccountId.js"; import Timestamp from "../Timestamp.js"; import Duration from "../Duration.js"; import Key from "../Key.js"; +import TokenKeyValidation from "./TokenKeyValidation.js"; /** * @namespace proto @@ -68,6 +69,7 @@ export default class TokenUpdateTransaction extends Transaction { * @param {Key} [props.pauseKey] * @param {Key} [props.metadataKey] * @param {Uint8Array} [props.metadata] + * @param {TokenKeyValidation} [props.keyVerificationMode] */ constructor(props = {}) { super(); @@ -174,6 +176,14 @@ export default class TokenUpdateTransaction extends Transaction { */ this._metadata = null; + /** + * @private + * @type {?TokenKeyValidation} + * Determines whether the system should check the validity of the passed keys for update. + * Defaults to FULL_VALIDATION + */ + this._keyVerificationMode = TokenKeyValidation.FullValidation; + if (props.tokenId != null) { this.setTokenId(props.tokenId); } @@ -241,6 +251,10 @@ export default class TokenUpdateTransaction extends Transaction { if (props.metadata != null) { this.setMetadata(props.metadata); } + + if (props.keyVerificationMode != null) { + this.setKeyVerificationMode(props.keyVerificationMode); + } } /** @@ -333,6 +347,12 @@ export default class TokenUpdateTransaction extends Transaction { ? update.metadata.value : undefined : undefined, + keyVerificationMode: + update.keyVerificationMode != null + ? TokenKeyValidation._fromCode( + update.keyVerificationMode, + ) + : undefined, }), transactions, signedTransactions, @@ -670,6 +690,24 @@ export default class TokenUpdateTransaction extends Transaction { return this; } + /** + * @returns {?TokenKeyValidation} + */ + get keyVerificationMode() { + return this._keyVerificationMode; + } + + /** + * @param {TokenKeyValidation} keyVerificationMode + * @returns {this} + */ + setKeyVerificationMode(keyVerificationMode) { + this._requireNotFrozen(); + this._keyVerificationMode = keyVerificationMode; + + return this; + } + /** * @returns {this} */ @@ -778,6 +816,10 @@ export default class TokenUpdateTransaction extends Transaction { value: this._metadata, } : null, + keyVerificationMode: + this._keyVerificationMode != null + ? this._keyVerificationMode._code + : undefined, }; } diff --git a/test/integration/TokenUpdateIntegrationTest.js b/test/integration/TokenUpdateIntegrationTest.js index d97e9a71d..83ba700c3 100644 --- a/test/integration/TokenUpdateIntegrationTest.js +++ b/test/integration/TokenUpdateIntegrationTest.js @@ -11,6 +11,9 @@ import { TokenType, TokenUpdateTransaction, TransferTransaction, + KeyList, + TokenKeyValidation, + PublicKey, } from "../../src/exports.js"; import IntegrationTestEnv from "./client/NodeIntegrationTestEnv.js"; @@ -259,7 +262,7 @@ describe("TokenUpdate", function () { ).getReceipt(env.client); }); - it.only("should error updating immutable token", async function () { + it("should error updating immutable token", async function () { this.timeout(120000); const operatorId = env.operatorId; @@ -1121,7 +1124,2012 @@ describe("TokenUpdate", function () { }); }); - after(function () { + describe("[HIP-540] Change or remove existing keys from a token", function () { + it("Can make the token immutable when updating all of its keys to an empty KeyList, signing with an Admin Key, and setting the key verification mode to NO_VALIDATION.", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newKey = KeyList.of(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode(TokenKeyValidation.NoValidation) + .setAdminKey(newKey) + .setWipeKey(newKey) + .setFreezeKey(newKey) + .setPauseKey(newKey) + .setSupplyKey(newKey) + .setFeeScheduleKey(newKey) + .setMetadataKey(newKey) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey).to.be.null; + expect(tokenInfo.wipeKey).to.be.null; + expect(tokenInfo.freezeKey).to.be.null; + expect(tokenInfo.pauseKey).to.be.null; + expect(tokenInfo.supplyKey).to.be.null; + expect(tokenInfo.feeScheduleKey).to.be.null; + expect(tokenInfo.metadataKey).to.be.null; + }); + + it("Can remove all of token's lower-privilege keys when updating them to an empty KeyList, signing with an Admin Key, and setting the key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const emptyKeyList = KeyList.of(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(emptyKeyList) + .setFreezeKey(emptyKeyList) + .setPauseKey(emptyKeyList) + .setSupplyKey(emptyKeyList) + .setFeeScheduleKey(emptyKeyList) + .setMetadataKey(emptyKeyList) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey).to.be.null; + expect(tokenInfo.freezeKey).to.be.null; + expect(tokenInfo.pauseKey).to.be.null; + expect(tokenInfo.supplyKey).to.be.null; + expect(tokenInfo.feeScheduleKey).to.be.null; + expect(tokenInfo.metadataKey).to.be.null; + }); + + it("Can update all of token's lower-privilege keys to an unusable key (i.e. all-zeros key) when signing with an Admin Key, and setting the key verification mode to FULL_VALIDATION and then set all lower-privilege keys back by signing with an Admin Key and setting key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(unusableKey) + .setFreezeKey(unusableKey) + .setPauseKey(unusableKey) + .setSupplyKey(unusableKey) + .setFeeScheduleKey(unusableKey) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql(unusableKey.toString()); + expect(tokenInfo.freezeKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + unusableKey.toString(), + ); + + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode(TokenKeyValidation.NoValidation) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + }); + + it("Can update all of token's lower-privilege keys when signing with an Admin Key and new respective lower-privilege key, and setting key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newWipeKey = PrivateKey.generateED25519(); + const newFreezeKey = PrivateKey.generateED25519(); + const newPauseKey = PrivateKey.generateED25519(); + const newSupplyKey = PrivateKey.generateED25519(); + const newFeeScheduleKey = PrivateKey.generateED25519(); + const newMetadataKey = PrivateKey.generateED25519(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(newWipeKey) + .setFreezeKey(newFreezeKey) + .setPauseKey(newPauseKey) + .setSupplyKey(newSupplyKey) + .setFeeScheduleKey(newFeeScheduleKey) + .setMetadataKey(newMetadataKey) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + newWipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + newFreezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + newPauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + newSupplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + newFeeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + newMetadataKey.publicKey.toString(), + ); + }); + + it("Cannot make the token immutable when updating all of its keys to an empty KeyList, signing with a key that is different from an Admin Key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newKey = KeyList.of(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setAdminKey(newKey) + .setWipeKey(newKey) + .setFreezeKey(newKey) + .setPauseKey(newKey) + .setSupplyKey(newKey) + .setFeeScheduleKey(newKey) + .setMetadataKey(newKey) + .freezeWith(env.client) + .sign(env.operatorKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot make a token immutable when updating all of its keys to an unusable key (i.e. all-zeros key), signing with a key that is different from an Admin Key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setAdminKey(unusableKey) + .setWipeKey(unusableKey) + .setFreezeKey(unusableKey) + .setPauseKey(unusableKey) + .setSupplyKey(unusableKey) + .setFeeScheduleKey(unusableKey) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(env.operatorKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot update the Admin Key to an unusable key (i.e. all-zeros key), signing with an Admin Key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const adminKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setAdminKey(adminKey) + .setSupplyKey(supplyKey) + .freezeWith(env.client); + + let response = await ( + await token.sign(adminKey) + ).execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.adminKey.toString()).to.eql( + adminKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setAdminKey(unusableKey) + .freezeWith(env.client) + .sign(adminKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Can update all of token’s lower-privilege keys to an unusable key (i.e. all-zeros key), when signing with a respective lower-privilege key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setWipeKey(unusableKey) + .setFreezeKey(unusableKey) + .setPauseKey(unusableKey) + .setSupplyKey(unusableKey) + .setFeeScheduleKey(unusableKey) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(wipeKey) + ).sign(freezeKey) + ).sign(pauseKey) + ).sign(supplyKey) + ).sign(feeScheduleKey) + ).sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql(unusableKey.toString()); + expect(tokenInfo.freezeKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + unusableKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + unusableKey.toString(), + ); + }); + + it("Can update all of token’s lower-privilege keys when signing with an old respective lower-privilege key and a new respective lower-privilege key, and setting key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newWipeKey = PrivateKey.generateED25519(); + const newFreezeKey = PrivateKey.generateED25519(); + const newPauseKey = PrivateKey.generateED25519(); + const newSupplyKey = PrivateKey.generateED25519(); + const newFeeScheduleKey = PrivateKey.generateED25519(); + const newMetadataKey = PrivateKey.generateED25519(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId( + tokenId, + ) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey( + newWipeKey, + ) + .setFreezeKey( + newFreezeKey, + ) + .setPauseKey( + newPauseKey, + ) + .setSupplyKey( + newSupplyKey, + ) + .setFeeScheduleKey( + newFeeScheduleKey, + ) + .setMetadataKey( + newMetadataKey, + ) + .freezeWith( + env.client, + ) + .sign( + wipeKey, + ) + ).sign(newWipeKey) + ).sign(freezeKey) + ).sign(newFreezeKey) + ).sign(pauseKey) + ).sign(newPauseKey) + ).sign(supplyKey) + ).sign(newSupplyKey) + ).sign(feeScheduleKey) + ).sign(newFeeScheduleKey) + ).sign(metadataKey) + ).sign(newMetadataKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + newWipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + newFreezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + newPauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + newSupplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + newFeeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + newMetadataKey.publicKey.toString(), + ); + }); + + it("Can update all of token's lower-privilege keys when signing ONLY with an old respective lower-privilege key and setting key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newWipeKey = PrivateKey.generateED25519(); + const newFreezeKey = PrivateKey.generateED25519(); + const newPauseKey = PrivateKey.generateED25519(); + const newSupplyKey = PrivateKey.generateED25519(); + const newFeeScheduleKey = PrivateKey.generateED25519(); + const newMetadataKey = PrivateKey.generateED25519(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setWipeKey(newWipeKey) + .setFreezeKey(newFreezeKey) + .setPauseKey(newPauseKey) + .setSupplyKey(newSupplyKey) + .setFeeScheduleKey( + newFeeScheduleKey, + ) + .setMetadataKey(newMetadataKey) + .freezeWith(env.client) + .sign(wipeKey) + ).sign(freezeKey) + ).sign(pauseKey) + ).sign(supplyKey) + ).sign(feeScheduleKey) + ).sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + + tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + newWipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + newFreezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + newPauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + newSupplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + newFeeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + newMetadataKey.publicKey.toString(), + ); + }); + + it("Cannot remove all of token's lower-privilege keys when updating them to an empty KeyList, signing with a respective lower-privilege key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newKey = KeyList.of(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await ( + await ( + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setWipeKey(newKey) + .setFreezeKey(newKey) + .setPauseKey(newKey) + .setSupplyKey(newKey) + .setFeeScheduleKey(newKey) + .setMetadataKey(newKey) + .freezeWith(env.client) + .sign(wipeKey) + ).sign(freezeKey) + ).sign(pauseKey) + ).sign(supplyKey) + ).sign(feeScheduleKey) + ).sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.TokenIsImmutable); + }); + + it("Cannot update all of token’s lower-privilege keys to an unusable key (i.e. all-zeros key), when signing with a key that is different from a respective lower-privilege key, and setting the key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setWipeKey(unusableKey) + .setFreezeKey(unusableKey) + .setPauseKey(unusableKey) + .setSupplyKey(unusableKey) + .setFeeScheduleKey(unusableKey) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(env.operatorKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot update all of token's lower-privilege keys to an unusable key (i.e. all-zeros key), when signing ONLY with an old respective lower-privilege key, and setting key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(unusableKey) + .freezeWith(env.client) + .sign(wipeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFreezeKey(unusableKey) + .freezeWith(env.client) + .sign(freezeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setPauseKey(unusableKey) + .freezeWith(env.client) + .sign(pauseKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setSupplyKey(unusableKey) + .freezeWith(env.client) + .sign(supplyKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFeeScheduleKey(unusableKey) + .freezeWith(env.client) + .sign(feeScheduleKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot update all of token's lower-privilege to an unusable key (i.e. all-zeros key), when signing with an old respective lower-privilege key and a new respective lower-privilege key, and setting key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const newWipeKey = PrivateKey.generateED25519(); + const newFreezeKey = PrivateKey.generateED25519(); + const newPauseKey = PrivateKey.generateED25519(); + const newSupplyKey = PrivateKey.generateED25519(); + const newFeeScheduleKey = PrivateKey.generateED25519(); + const newMetadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(unusableKey) + .freezeWith(env.client) + .sign(wipeKey) + ).sign(newWipeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFreezeKey(unusableKey) + .freezeWith(env.client) + .sign(freezeKey) + ).sign(newFreezeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setPauseKey(unusableKey) + .freezeWith(env.client) + .sign(pauseKey) + ).sign(newPauseKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setSupplyKey(unusableKey) + .freezeWith(env.client) + .sign(supplyKey) + ).sign(newSupplyKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFeeScheduleKey(unusableKey) + .freezeWith(env.client) + .sign(feeScheduleKey) + ).sign(newFeeScheduleKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(metadataKey) + ).sign(newMetadataKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot update all of token's lower-privilege keys when signing ONLY with an old respective lower-privilege key and setting key verification mode to FULL_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const unusableKey = PublicKey.unusableKey(); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setWipeKey(unusableKey) + .freezeWith(env.client) + .sign(wipeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFreezeKey(unusableKey) + .freezeWith(env.client) + .sign(freezeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setPauseKey(unusableKey) + .freezeWith(env.client) + .sign(pauseKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setSupplyKey(unusableKey) + .freezeWith(env.client) + .sign(supplyKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setFeeScheduleKey(unusableKey) + .freezeWith(env.client) + .sign(feeScheduleKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.FullValidation, + ) + .setMetadataKey(unusableKey) + .freezeWith(env.client) + .sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSignature); + }); + + it("Cannot update all of token's lower-privilege keys when updating them to a keys with an invalid structure and signing with an old respective lower-privilege and setting key verification mode to NO_VALIDATION", async function () { + this.timeout(120000); + + const wipeKey = PrivateKey.generateED25519(); + const freezeKey = PrivateKey.generateED25519(); + const pauseKey = PrivateKey.generateED25519(); + const supplyKey = PrivateKey.generateED25519(); + const feeScheduleKey = PrivateKey.generateED25519(); + const metadataKey = PrivateKey.generateED25519(); + + const structurallyInvalidKey = PublicKey.fromString( + "000000000000000000000000000000000000000000000000000000000000000000", + ); + + let token = new TokenCreateTransaction() + .setTokenName("Token") + .setTokenSymbol("T") + .setTokenType(TokenType.NonFungibleUnique) + .setTreasuryAccountId(env.operatorId) + .setWipeKey(wipeKey) + .setFreezeKey(freezeKey) + .setPauseKey(pauseKey) + .setSupplyKey(supplyKey) + .setFeeScheduleKey(feeScheduleKey) + .setMetadataKey(metadataKey) + .freezeWith(env.client); + + let response = await token.execute(env.client); + const tokenId = (await response.getReceipt(env.client)).tokenId; + + let tokenInfo = await new TokenInfoQuery() + .setTokenId(tokenId) + .execute(env.client); + + expect(tokenInfo.name).to.eql(token.tokenName); + expect(tokenInfo.symbol).to.eql(token.tokenSymbol); + expect(tokenInfo.tokenType).to.eql(token.tokenType); + expect(tokenInfo.treasuryAccountId.toString()).to.eql( + token.treasuryAccountId.toString(), + ); + expect(tokenInfo.wipeKey.toString()).to.eql( + wipeKey.publicKey.toString(), + ); + expect(tokenInfo.freezeKey.toString()).to.eql( + freezeKey.publicKey.toString(), + ); + expect(tokenInfo.pauseKey.toString()).to.eql( + pauseKey.publicKey.toString(), + ); + expect(tokenInfo.supplyKey.toString()).to.eql( + supplyKey.publicKey.toString(), + ); + expect(tokenInfo.feeScheduleKey.toString()).to.eql( + feeScheduleKey.publicKey.toString(), + ); + expect(tokenInfo.metadataKey.toString()).to.eql( + metadataKey.publicKey.toString(), + ); + + let status; + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setWipeKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(wipeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidWipeKey); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setFreezeKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(freezeKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidFreezeKey); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setPauseKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(pauseKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidPauseKey); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setSupplyKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(supplyKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidSupplyKey); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setFeeScheduleKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(feeScheduleKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidCustomFeeScheduleKey); + + try { + await ( + await ( + await new TokenUpdateTransaction() + .setTokenId(tokenId) + .setKeyVerificationMode( + TokenKeyValidation.NoValidation, + ) + .setMetadataKey(structurallyInvalidKey) + .freezeWith(env.client) + .sign(metadataKey) + ).execute(env.client) + ).getReceipt(env.client); + } catch (error) { + status = error.status; + } + + expect(status).to.be.eql(Status.InvalidMetadataKey); + }); + }); + + after(async function () { if (env != null) { env.close(); } diff --git a/test/unit/TokenUpdateTransaction.js b/test/unit/TokenUpdateTransaction.js index 303821ef6..d1b4f145a 100644 --- a/test/unit/TokenUpdateTransaction.js +++ b/test/unit/TokenUpdateTransaction.js @@ -4,6 +4,7 @@ import { TransactionId, AccountId, Timestamp, + TokenKeyValidation, } from "../../src/index.js"; import Long from "long"; @@ -81,6 +82,8 @@ describe("TokenUpdateTransaction", function () { autoRenewPeriod: null, expiry: null, treasury: treasuryAccountId._toProtobuf(), + keyVerificationMode: + TokenKeyValidation.FullValidation.valueOf(), adminKey: { ed25519: key1.publicKey.toBytesRaw(), },