lisk-framework
Version:
Lisk blockchain application platform
541 lines • 25.5 kB
JavaScript
"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