UNPKG

lisk-framework

Version:

Lisk blockchain application platform

541 lines 25.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenMethod = void 0; const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_chain_1 = require("@liskhq/lisk-chain"); const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_utils_1 = require("@liskhq/lisk-utils"); const base_method_1 = require("../base_method"); const constants_1 = require("./constants"); const schemas_1 = require("./schemas"); const utils_1 = require("./utils"); const user_1 = require("./stores/user"); const escrow_1 = require("./stores/escrow"); const supply_1 = require("./stores/supply"); const transfer_1 = require("./events/transfer"); const initialize_token_1 = require("./events/initialize_token"); const mint_1 = require("./events/mint"); const burn_1 = require("./events/burn"); const lock_1 = require("./events/lock"); const unlock_1 = require("./events/unlock"); const transfer_cross_chain_1 = require("./events/transfer_cross_chain"); const supported_tokens_1 = require("./stores/supported_tokens"); const all_tokens_supported_1 = require("./events/all_tokens_supported"); const all_tokens_supported_removed_1 = require("./events/all_tokens_supported_removed"); const token_id_supported_1 = require("./events/token_id_supported"); const all_tokens_from_chain_supported_1 = require("./events/all_tokens_from_chain_supported"); const all_tokens_from_chain_supported_removed_1 = require("./events/all_tokens_from_chain_supported_removed"); const token_id_supported_removed_1 = require("./events/token_id_supported_removed"); class TokenMethod extends base_method_1.BaseMethod { constructor(stores, events, moduleName) { super(stores, events); this._moduleName = moduleName; } init(config) { this._config = config; } addDependencies(interoperabilityMethod, internalMethod) { this._interoperabilityMethod = interoperabilityMethod; this._internalMethod = internalMethod; } isNativeToken(tokenID) { const [chainID] = (0, utils_1.splitTokenID)(tokenID); return chainID.equals(this._config.ownChainID); } getTokenIDLSK() { const networkID = this._config.ownChainID.slice(0, 1); return Buffer.concat([networkID, Buffer.alloc(3 + constants_1.LOCAL_ID_LENGTH, 0)]); } async userSubstoreExists(methodContext, address, tokenID) { const userStore = this.stores.get(user_1.UserStore); return userStore.has(methodContext, userStore.getKey(address, tokenID)); } async getAvailableBalance(methodContext, address, tokenID) { const userStore = this.stores.get(user_1.UserStore); try { const user = await userStore.get(methodContext, userStore.getKey(address, tokenID)); return user.availableBalance; } catch (error) { if (!(error instanceof lisk_chain_1.NotFoundError)) { throw error; } return BigInt(0); } } async getLockedAmount(methodContext, address, tokenID, module) { var _a, _b; const userStore = this.stores.get(user_1.UserStore); try { const user = await userStore.get(methodContext, userStore.getKey(address, tokenID)); return (_b = (_a = user.lockedBalances.find(lb => lb.module === module)) === null || _a === void 0 ? void 0 : _a.amount) !== null && _b !== void 0 ? _b : BigInt(0); } catch (error) { if (!(error instanceof lisk_chain_1.NotFoundError)) { throw error; } return BigInt(0); } } async getEscrowedAmount(methodContext, escrowChainID, tokenID) { if (!this.isNativeToken(tokenID)) { throw new Error('Only native token can have escrow amount.'); } if (this._config.ownChainID.equals(escrowChainID)) { throw new Error('Escrow is not defined for own chain.'); } const escrowStore = this.stores.get(escrow_1.EscrowStore); const key = escrowStore.getKey(escrowChainID, tokenID); if (!(await escrowStore.has(methodContext, key))) { return BigInt(0); } const escrowAccount = await escrowStore.get(methodContext, key); return escrowAccount.amount; } async isTokenIDAvailable(methodContext, tokenID) { const exist = await this.stores.get(supply_1.SupplyStore).has(methodContext, tokenID); return !exist; } async initializeToken(methodContext, tokenID) { if (!this.isNativeToken(tokenID)) { this.events .get(initialize_token_1.InitializeTokenEvent) .error(methodContext, { tokenID }, 12); throw new Error('Only native token can be initialized.'); } const available = await this.isTokenIDAvailable(methodContext, tokenID); if (!available) { this.events .get(initialize_token_1.InitializeTokenEvent) .error(methodContext, { tokenID }, 11); throw new Error('The specified token ID is not available.'); } await this.stores.get(supply_1.SupplyStore).set(methodContext, tokenID, { totalSupply: BigInt(0) }); this.events.get(initialize_token_1.InitializeTokenEvent).log(methodContext, { tokenID }); } async mint(methodContext, address, tokenID, amount) { if (amount <= BigInt(0)) { return; } const eventData = { address, tokenID, amount, }; if (!this.isNativeToken(tokenID)) { this.events .get(mint_1.MintEvent) .error(methodContext, eventData, 8); throw new Error('Only native token can be minted.'); } const supplyStore = this.stores.get(supply_1.SupplyStore); let supply; try { supply = await supplyStore.get(methodContext, tokenID); } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { this.events .get(mint_1.MintEvent) .error(methodContext, eventData, 10); throw new Error(`TokenID: ${tokenID.toString('hex')} is not initialized.`); } throw error; } if (supply.totalSupply + amount >= BigInt(2) ** BigInt(64)) { this.events .get(mint_1.MintEvent) .error(methodContext, eventData, 9); throw new Error(`TokenID: ${tokenID.toString('hex')} with ${amount.toString()} exceeds maximum range allowed.`); } const availableBalance = await this.getAvailableBalance(methodContext, address, tokenID); if (availableBalance + amount >= BigInt(2) ** BigInt(64)) { this.events .get(mint_1.MintEvent) .error(methodContext, eventData, 9); throw new Error(`TokenID: ${tokenID.toString('hex')} with ${amount.toString()} exceeds maximum range allowed.`); } const userStore = this.stores.get(user_1.UserStore); const userKey = userStore.getKey(address, tokenID); const userExist = await userStore.has(methodContext, userKey); if (!userExist) { await this._internalMethod.initializeUserAccount(methodContext, address, tokenID); } const userAccount = await userStore.get(methodContext, userStore.getKey(address, tokenID)); userAccount.availableBalance += amount; await userStore.save(methodContext, address, tokenID, userAccount); supply.totalSupply += amount; await supplyStore.set(methodContext, tokenID, supply); this.events.get(mint_1.MintEvent).log(methodContext, eventData); } async burn(methodContext, address, tokenID, amount) { if (amount <= BigInt(0)) { return; } const userStore = this.stores.get(user_1.UserStore); const eventData = { address, tokenID, amount, }; let userAccount; try { userAccount = await userStore.get(methodContext, userStore.getKey(address, tokenID)); if (userAccount.availableBalance < amount) { this.events .get(burn_1.BurnEvent) .error(methodContext, eventData, 1); throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(address)} does not have sufficient balance for amount ${amount.toString()}`); } } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { this.events .get(burn_1.BurnEvent) .error(methodContext, eventData, 1); } throw error; } userAccount.availableBalance -= amount; await userStore.set(methodContext, userStore.getKey(address, tokenID), userAccount); if (this.isNativeToken(tokenID)) { const supplyStore = this.stores.get(supply_1.SupplyStore); const supply = await supplyStore.get(methodContext, tokenID); supply.totalSupply -= amount; await supplyStore.set(methodContext, tokenID, supply); } this.events.get(burn_1.BurnEvent).log(methodContext, eventData); } async initializeUserAccount(methodContext, address, tokenID) { const userAccountExist = await this.userSubstoreExists(methodContext, address, tokenID); if (userAccountExist) { return; } await this._internalMethod.initializeUserAccount(methodContext, address, tokenID); } async initializeEscrowAccount(methodContext, chainID, tokenID) { if (!this.isNativeToken(tokenID)) { throw new Error(`TokenID ${tokenID.toString('hex')} is not native token.`); } if (this._config.ownChainID.equals(chainID)) { throw new Error(`Can not initialize escrow account for own chain, ${chainID.toString('hex')}`); } const escrowStore = this.stores.get(escrow_1.EscrowStore); const escrowAccountExist = await escrowStore.has(methodContext, escrowStore.getKey(chainID, tokenID)); if (escrowAccountExist) { return; } await this._internalMethod.initializeEscrowAccount(methodContext, chainID, tokenID); } async transfer(methodContext, senderAddress, recipientAddress, tokenID, amount) { if (amount <= BigInt(0)) { return; } const userStore = this.stores.get(user_1.UserStore); const eventData = { senderAddress, recipientAddress, tokenID, amount, }; let senderAccount; try { senderAccount = await userStore.get(methodContext, userStore.getKey(senderAddress, tokenID)); if (senderAccount.availableBalance < amount) { this.events .get(transfer_1.TransferEvent) .error(methodContext, eventData, 1); throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(senderAddress)} does not have sufficient balance for amount ${amount.toString()}`); } } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { this.events .get(transfer_1.TransferEvent) .error(methodContext, eventData, 1); } throw error; } const recipientExist = await userStore.has(methodContext, userStore.getKey(recipientAddress, tokenID)); if (!recipientExist) { await this._internalMethod.initializeUserAccount(methodContext, recipientAddress, tokenID); } await this._internalMethod.transfer(methodContext, senderAddress, recipientAddress, tokenID, amount); } async transferCrossChain(methodContext, senderAddress, receivingChainID, recipientAddress, tokenID, amount, messageFee, data) { var _a; if (amount <= BigInt(0)) { return; } if (messageFee < BigInt(0)) { return; } const eventData = { senderAddress, recipientAddress, tokenID, amount, receivingChainID, messageFee, }; if (this._config.ownChainID.equals(receivingChainID)) { this.events .get(transfer_cross_chain_1.TransferCrossChainEvent) .error(methodContext, eventData, 14); throw new Error('Receiving chain cannot be the sending chain.'); } if (data.length > constants_1.MAX_DATA_LENGTH) { this.events .get(transfer_cross_chain_1.TransferCrossChainEvent) .error(methodContext, eventData, 2); throw new Error(`Maximum data allowed is ${constants_1.MAX_DATA_LENGTH}, but received ${data.length}`); } const balanceChecks = new lisk_utils_1.dataStructures.BufferMap(); balanceChecks.set(tokenID, amount); const messageFeeTokenID = await this._interoperabilityMethod.getMessageFeeTokenID(methodContext, receivingChainID); const totalMessageFee = ((_a = balanceChecks.get(messageFeeTokenID)) !== null && _a !== void 0 ? _a : BigInt(0)) + messageFee; balanceChecks.set(messageFeeTokenID, totalMessageFee); for (const [checkTokenID, checkAmount] of balanceChecks.entries()) { const availableBalnace = await this.getAvailableBalance(methodContext, senderAddress, checkTokenID); if (availableBalnace < checkAmount) { this.events .get(transfer_cross_chain_1.TransferCrossChainEvent) .error(methodContext, eventData, 1); throw new Error(`Sender ${lisk_cryptography_1.address.getLisk32AddressFromAddress(senderAddress)} does not have sufficient balance ${checkAmount} for token ${checkTokenID.toString('hex')}.`); } } const [tokenChainID] = (0, utils_1.splitTokenID)(tokenID); if (![this._config.ownChainID, receivingChainID].some(id => id.equals(tokenChainID))) { this.events .get(transfer_cross_chain_1.TransferCrossChainEvent) .error(methodContext, eventData, 3); throw new Error(`Invalid token ID ${tokenID.toString('hex')}. Token must be native to either the sending or the receiving chain.`); } const escrowStore = this.stores.get(escrow_1.EscrowStore); if (this.isNativeToken(tokenID)) { const escrowExist = await escrowStore.has(methodContext, escrowStore.getKey(receivingChainID, tokenID)); if (!escrowExist) { await this._internalMethod.initializeEscrowAccount(methodContext, receivingChainID, tokenID); } } await this.stores .get(user_1.UserStore) .addAvailableBalance(methodContext, senderAddress, tokenID, -amount); if (this.isNativeToken(tokenID)) { await this.stores .get(escrow_1.EscrowStore) .addAmount(methodContext, receivingChainID, tokenID, amount); } this.events.get(transfer_cross_chain_1.TransferCrossChainEvent).log(methodContext, eventData); await this._interoperabilityMethod.send(methodContext, senderAddress, this._moduleName, constants_1.CROSS_CHAIN_COMMAND_NAME_TRANSFER, receivingChainID, messageFee, lisk_codec_1.codec.encode(schemas_1.crossChainTransferMessageParams, { tokenID, amount, senderAddress, recipientAddress, data, })); } async lock(methodContext, address, module, tokenID, amount) { if (amount <= BigInt(0)) { return; } const userStore = this.stores.get(user_1.UserStore); const eventData = { address, module, tokenID, amount, }; let account; try { account = await userStore.get(methodContext, userStore.getKey(address, tokenID)); if (account.availableBalance < amount) { this.events .get(lock_1.LockEvent) .error(methodContext, eventData, 1); throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(address)} does not have sufficient balance for amount ${amount.toString()}`); } } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { this.events .get(lock_1.LockEvent) .error(methodContext, eventData, 1); } throw error; } account.availableBalance -= amount; const existingIndex = account.lockedBalances.findIndex(b => b.module === module); if (existingIndex > -1) { const locked = account.lockedBalances[existingIndex].amount + amount; account.lockedBalances[existingIndex] = { module, amount: locked, }; } else { account.lockedBalances.push({ module, amount, }); } await userStore.save(methodContext, address, tokenID, account); this.events.get(lock_1.LockEvent).log(methodContext, eventData); } async unlock(methodContext, address, module, tokenID, amount) { if (amount <= BigInt(0)) { return; } const userStore = this.stores.get(user_1.UserStore); const eventData = { address, module, tokenID, amount, }; let account; try { account = await userStore.get(methodContext, userStore.getKey(address, tokenID)); } catch (error) { if (error instanceof lisk_chain_1.NotFoundError) { this.events .get(unlock_1.UnlockEvent) .error(methodContext, eventData, 1); } throw error; } const existingIndex = account.lockedBalances.findIndex(b => b.module === module); if (existingIndex < 0) { this.events .get(unlock_1.UnlockEvent) .error(methodContext, eventData, 5); throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(address)} does not have locked balance for module ${module}`); } if (account.lockedBalances[existingIndex].amount < amount) { this.events .get(unlock_1.UnlockEvent) .error(methodContext, eventData, 5); throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(address)} does not have sufficient locked balance for amount ${amount.toString()} for module ${module}`); } account.lockedBalances[existingIndex].amount -= amount; account.availableBalance += amount; await userStore.save(methodContext, address, tokenID, account); this.events.get(unlock_1.UnlockEvent).log(methodContext, eventData); } async payMessageFee(methodContext, payFromAddress, receivingChainID, fee) { if (fee < BigInt(0)) { throw new Error('Invalid Message Fee'); } const messageFeeTokenID = await this._interoperabilityMethod.getMessageFeeTokenID(methodContext, receivingChainID); const userStore = this.stores.get(user_1.UserStore); const account = await userStore.get(methodContext, userStore.getKey(payFromAddress, messageFeeTokenID)); if (account.availableBalance < fee) { throw new Error(`Address ${lisk_cryptography_1.address.getLisk32AddressFromAddress(payFromAddress)} does not have sufficient balance ${account.availableBalance.toString()} to pay ${fee.toString()}`); } account.availableBalance -= fee; await userStore.save(methodContext, payFromAddress, messageFeeTokenID, account); if (this.isNativeToken(messageFeeTokenID)) { await this.stores .get(escrow_1.EscrowStore) .addAmount(methodContext, receivingChainID, messageFeeTokenID, fee); } } async isTokenSupported(methodContext, tokenID) { return this.stores.get(supported_tokens_1.SupportedTokensStore).isSupported(methodContext, tokenID); } async supportAllTokens(methodContext) { await this.stores.get(supported_tokens_1.SupportedTokensStore).supportAll(methodContext); this.events.get(all_tokens_supported_1.AllTokensSupportedEvent).log(methodContext); } async removeAllTokensSupport(methodContext) { await this.stores.get(supported_tokens_1.SupportedTokensStore).removeAll(methodContext); this.events.get(all_tokens_supported_removed_1.AllTokensSupportRemovedEvent).log(methodContext); } async supportAllTokensFromChainID(methodContext, chainID) { const allTokensSupported = await this.stores .get(supported_tokens_1.SupportedTokensStore) .has(methodContext, supported_tokens_1.ALL_SUPPORTED_TOKENS_KEY); if (allTokensSupported) { return; } if (chainID.equals(this._config.ownChainID)) { return; } await this.stores .get(supported_tokens_1.SupportedTokensStore) .set(methodContext, chainID, { supportedTokenIDs: [] }); this.events.get(all_tokens_from_chain_supported_1.AllTokensFromChainSupportedEvent).log(methodContext, chainID); } async removeAllTokensSupportFromChainID(methodContext, chainID) { const allTokensSupported = await this.stores .get(supported_tokens_1.SupportedTokensStore) .has(methodContext, supported_tokens_1.ALL_SUPPORTED_TOKENS_KEY); if (allTokensSupported) { throw new Error('Invalid operation. All tokens from all chains are supported.'); } if (chainID.equals(this._config.ownChainID)) { throw new Error('Invalid operation. All tokens from all the specified chain should be supported.'); } const isChainSupported = await this.stores .get(supported_tokens_1.SupportedTokensStore) .has(methodContext, chainID); if (!isChainSupported) { return; } await this.stores.get(supported_tokens_1.SupportedTokensStore).del(methodContext, chainID); this.events.get(all_tokens_from_chain_supported_removed_1.AllTokensFromChainSupportRemovedEvent).log(methodContext, chainID); } async supportTokenID(methodContext, tokenID) { await this.stores.get(supported_tokens_1.SupportedTokensStore).supportToken(methodContext, tokenID); this.events.get(token_id_supported_1.TokenIDSupportedEvent).log(methodContext, tokenID); } async removeSupport(methodContext, tokenID) { const [chainID] = (0, utils_1.splitTokenID)(tokenID); const allTokensSupported = await this.stores .get(supported_tokens_1.SupportedTokensStore) .has(methodContext, supported_tokens_1.ALL_SUPPORTED_TOKENS_KEY); if (allTokensSupported) { throw new Error('All tokens are supported.'); } if (tokenID.equals(this.getTokenIDLSK()) || chainID.equals(this._config.ownChainID)) { throw new Error('Cannot remove support for the specified token.'); } const isChainSupported = await this.stores .get(supported_tokens_1.SupportedTokensStore) .has(methodContext, chainID); if (!isChainSupported) { this.events.get(token_id_supported_removed_1.TokenIDSupportRemovedEvent).log(methodContext, tokenID); return; } const supportedTokens = await this.stores.get(supported_tokens_1.SupportedTokensStore).get(methodContext, chainID); if (supportedTokens.supportedTokenIDs.length === 0) { throw new Error('All tokens from the specified chain are supported.'); } const tokenIndex = supportedTokens.supportedTokenIDs.indexOf(tokenID); if (tokenIndex !== -1) { supportedTokens.supportedTokenIDs.splice(tokenIndex, 1); if (supportedTokens.supportedTokenIDs.length === 0) { await this.stores.get(supported_tokens_1.SupportedTokensStore).del(methodContext, chainID); } } this.events.get(token_id_supported_removed_1.TokenIDSupportRemovedEvent).log(methodContext, tokenID); } async getTotalSupply(context) { const supplyStore = this.stores.get(supply_1.SupplyStore); const supplyData = await supplyStore.getAll(context); return { totalSupply: supplyData.map(({ key: tokenID, value: supply }) => ({ tokenID, totalSupply: supply.totalSupply, })), }; } async escrowSubstoreExists(methodContext, chainID, tokenID) { return this.stores .get(escrow_1.EscrowStore) .has(methodContext, this.stores.get(escrow_1.EscrowStore).getKey(chainID, tokenID)); } } exports.TokenMethod = TokenMethod; //# sourceMappingURL=method.js.map