Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Update Token module endpoints - Closes #7579 #7660 #7705

Merged
merged 11 commits into from
Oct 29, 2022
66 changes: 43 additions & 23 deletions framework/src/modules/token/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,27 @@ import { validator } from '@liskhq/lisk-validator';
import * as cryptography from '@liskhq/lisk-cryptography';
import { NotFoundError } from '../../state_machine';
import { JSONObject, ModuleEndpointContext } from '../../types';
import { ModuleConfig } from './types';
import { BaseEndpoint } from '../base_endpoint';
import { TokenMethod } from './method';
import { LOCAL_ID_LENGTH, TOKEN_ID_LENGTH } from './constants';
import { CHAIN_ID_LENGTH, TOKEN_ID_LENGTH } from './constants';
import {
getBalanceRequestSchema,
getBalancesRequestSchema,
isSupportedRequestSchema,
SupplyStoreData,
UserStoreData,
} from './schemas';
import { EscrowStore, EscrowStoreData } from './stores/escrow';
import { SupplyStore } from './stores/supply';
import { UserStore } from './stores/user';
import { splitTokenID } from './utils';

const CHAIN_ID_ALIAS_NATIVE = Buffer.from([0, 0, 0, 1]);
import { SupportedTokensStore } from './stores/supported_tokens';

export class TokenEndpoint extends BaseEndpoint {
// eslint-disable-next-line @typescript-eslint/no-empty-function
public init(_tokenMethod: TokenMethod) {}
private _moduleConfig!: ModuleConfig;

public init(moduleConfig: ModuleConfig) {
this._moduleConfig = moduleConfig;
}

public async getBalances(
context: ModuleEndpointContext,
Expand Down Expand Up @@ -93,30 +95,40 @@ export class TokenEndpoint extends BaseEndpoint {
context: ModuleEndpointContext,
): Promise<{ totalSupply: JSONObject<SupplyStoreData & { tokenID: string }>[] }> {
const supplyStore = this.stores.get(SupplyStore);
const supplyData = await supplyStore.iterate(context, {
gte: Buffer.concat([Buffer.alloc(LOCAL_ID_LENGTH, 0)]),
lte: Buffer.concat([Buffer.alloc(LOCAL_ID_LENGTH, 255)]),
});
const supplyData = await supplyStore.getAll(context);

return {
totalSupply: supplyData.map(({ key: localID, value: supply }) => ({
tokenID: Buffer.concat([CHAIN_ID_ALIAS_NATIVE, localID]).toString('hex'),
totalSupply: supplyData.map(({ key: tokenID, value: supply }) => ({
tokenID: tokenID.toString('hex'),
totalSupply: supply.totalSupply.toString(),
})),
};
}

// TODO: Update to use SupportedTokensStore #7579
// eslint-disable-next-line @typescript-eslint/require-await
public async getSupportedTokens(
_context: ModuleEndpointContext,
): Promise<{ tokenIDs: string[] }> {
return {
tokenIDs: [],
};
context: ModuleEndpointContext,
): Promise<{ supportedTokens: string[] }> {
const supportedTokensStore = this.stores.get(SupportedTokensStore);
const supportedTokens: string[] = [];

if (await supportedTokensStore.allSupported(context)) {
return {
supportedTokens: ['*'],
};
}

return { supportedTokens };
}

public async isSupported(context: ModuleEndpointContext) {
validator.validate<{ tokenID: string }>(isSupportedRequestSchema, context.params);

const tokenID = Buffer.from(context.params.tokenID, 'hex');
const supportedTokensStore = this.stores.get(SupportedTokensStore);

return { supported: await supportedTokensStore.isSupported(context, tokenID) };
}

// eslint-disable-next-line @typescript-eslint/require-await
public async getEscrowedAmounts(
context: ModuleEndpointContext,
): Promise<{
Expand All @@ -129,13 +141,21 @@ export class TokenEndpoint extends BaseEndpoint {
});
return {
escrowedAmounts: escrowData.map(({ key, value: escrow }) => {
const [escrowChainID, localID] = splitTokenID(key);
const escrowChainID = key.slice(0, CHAIN_ID_LENGTH);
const tokenID = key.slice(CHAIN_ID_LENGTH);
return {
escrowChainID: escrowChainID.toString('hex'),
amount: escrow.amount.toString(),
tokenID: Buffer.concat([CHAIN_ID_ALIAS_NATIVE, localID]).toString('hex'),
tokenID: tokenID.toString('hex'),
};
}),
};
}

public getInitializationFees() {
return {
userAccount: this._moduleConfig.userAccountInitializationFee.toString(),
escrowAccount: this._moduleConfig.escrowAccountInitializationFee.toString(),
};
}
}
35 changes: 22 additions & 13 deletions framework/src/modules/token/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ import {
getBalanceRequestSchema,
getBalanceResponseSchema,
getBalancesRequestSchema,
getBalancesResponseSchema,
getEscrowedAmountsResponseSchema,
getSupportedTokensResponseSchema,
isSupportedRequestSchema,
isSupportedResponseSchema,
getTotalSupplyResponseSchema,
} from './schemas';
import { TokenMethod } from './method';
import { TokenEndpoint } from './endpoint';
import { GenesisTokenStore, InteroperabilityMethod, ModuleConfigJSON } from './types';
import { GenesisTokenStore, InteroperabilityMethod, ModuleConfig, ModuleConfigJSON } from './types';
import { splitTokenID } from './utils';
import { CCTransferCommand } from './commands/cc_transfer';
import { BaseInteroperableModule } from '../interoperability/base_interoperable_module';
Expand Down Expand Up @@ -126,7 +129,7 @@ export class TokenModule extends BaseInteroperableModule {
{
name: this.endpoint.getBalances.name,
request: getBalancesRequestSchema,
response: getBalancesRequestSchema,
response: getBalancesResponseSchema,
},
{
name: this.endpoint.getTotalSupply.name,
Expand All @@ -136,6 +139,11 @@ export class TokenModule extends BaseInteroperableModule {
name: this.endpoint.getSupportedTokens.name,
response: getSupportedTokensResponseSchema,
},
{
name: this.endpoint.isSupported.name,
request: isSupportedRequestSchema,
response: isSupportedResponseSchema,
},
{
name: this.endpoint.getEscrowedAmounts.name,
response: getEscrowedAmountsResponseSchema,
Expand Down Expand Up @@ -163,29 +171,30 @@ export class TokenModule extends BaseInteroperableModule {
const { moduleConfig, genesisConfig } = args;
this._ownChainID = Buffer.from(genesisConfig.chainID, 'hex');

const config = objects.mergeDeep(
const rawConfig = objects.mergeDeep(
{},
defaultConfig,
{ feeTokenID: Buffer.concat([this._ownChainID, Buffer.alloc(4, 0)]).toString('hex') },
moduleConfig,
) as ModuleConfigJSON;
validator.validate(configSchema, config);
validator.validate(configSchema, rawConfig);

const config: ModuleConfig = {
userAccountInitializationFee: BigInt(rawConfig.userAccountInitializationFee),
escrowAccountInitializationFee: BigInt(rawConfig.escrowAccountInitializationFee),
feeTokenID: Buffer.from(rawConfig.feeTokenID, 'hex'),
};

this.stores.get(SupportedTokensStore).registerOwnChainID(this._ownChainID);
this.crossChainTransferCommand.init({ ownChainID: this._ownChainID });

this.method.init({
ownChainID: this._ownChainID,
escrowAccountInitializationFee: BigInt('50000000'),
feeTokenID: Buffer.from(config.feeTokenID, 'hex'),
userAccountInitializationFee: BigInt('50000000'),
});
this.method.init(Object.assign(config, { ownChainID: this._ownChainID }));
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
this.crossChainMethod.init(this._ownChainID);
this.endpoint.init(this.method);
this.endpoint.init(config);
this._transferCommand.init({
method: this.method,
accountInitializationFee: BigInt(config.userAccountInitializationFee),
feeTokenID: Buffer.from(config.feeTokenID, 'hex'),
accountInitializationFee: config.userAccountInitializationFee,
feeTokenID: config.feeTokenID,
});
}

Expand Down
25 changes: 25 additions & 0 deletions framework/src/modules/token/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,3 +619,28 @@ export const getEscrowedAmountsResponseSchema = {
},
},
};

export const isSupportedRequestSchema = {
$id: '/token/endpoint/isSupportedRequest',
type: 'object',
properties: {
tokenID: {
type: 'string',
format: 'hex',
minLength: TOKEN_ID_LENGTH * 2,
maxLength: TOKEN_ID_LENGTH * 2,
},
},
required: ['tokenID'],
};

export const isSupportedResponseSchema = {
$id: '/token/endpoint/isSupportedResponse',
type: 'object',
properties: {
supported: {
dataType: 'boolean',
},
},
required: ['supported'],
};
77 changes: 64 additions & 13 deletions framework/test/unit/modules/token/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,24 @@
*/
import { address, utils } from '@liskhq/lisk-cryptography';
import { TokenMethod, TokenModule } from '../../../../src/modules/token';
import { CHAIN_ID_LENGTH } from '../../../../src/modules/token/constants';
import {
CHAIN_ID_LENGTH,
USER_SUBSTORE_INITIALIZATION_FEE,
ESCROW_SUBSTORE_INITIALIZATION_FEE,
} from '../../../../src/modules/token/constants';
import { TokenEndpoint } from '../../../../src/modules/token/endpoint';
import { EscrowStore } from '../../../../src/modules/token/stores/escrow';
import { SupplyStore } from '../../../../src/modules/token/stores/supply';
import { UserStore } from '../../../../src/modules/token/stores/user';
import { SupportedTokensStore } from '../../../../src/modules/token/stores/supported_tokens';
import { MethodContext } from '../../../../src/state_machine';
import { PrefixedStateReadWriter } from '../../../../src/state_machine/prefixed_state_read_writer';
import {
createTransientMethodContext,
createTransientModuleEndpointContext,
} from '../../../../src/testing';
import { InMemoryPrefixedStateDB } from '../../../../src/testing/in_memory_prefixed_state';
import { ModuleConfig } from '../../../../src/modules/token/types';

describe('token endpoint', () => {
const tokenModule = new TokenModule();
Expand All @@ -42,7 +48,11 @@ describe('token endpoint', () => {
};
const defaultTotalSupply = BigInt('100000000000000');
const defaultEscrowAmount = BigInt('100000000000');
// const supportedTokenIDs = ['0000000000000000', '0000000200000000'];
const supportedTokenIDs = [
Buffer.from('0000000000000000', 'hex'),
Buffer.from('0000000200000000', 'hex'),
];
let supportedTokensStore: SupportedTokensStore;

let endpoint: TokenEndpoint;
let stateStore: PrefixedStateReadWriter;
Expand All @@ -51,40 +61,41 @@ describe('token endpoint', () => {
beforeEach(async () => {
const method = new TokenMethod(tokenModule.stores, tokenModule.events, tokenModule.name);
endpoint = new TokenEndpoint(tokenModule.stores, tokenModule.offchainStores);
method.init({
ownChainID: Buffer.from([0, 0, 0, 1]),
escrowAccountInitializationFee: BigInt(50000000),
userAccountInitializationFee: BigInt(50000000),
const config: ModuleConfig = {
userAccountInitializationFee: USER_SUBSTORE_INITIALIZATION_FEE,
escrowAccountInitializationFee: ESCROW_SUBSTORE_INITIALIZATION_FEE,
feeTokenID: defaultTokenID,
});
};
method.init(Object.assign(config, { ownChainID: Buffer.from([0, 0, 0, 1]) }));
method.addDependencies({
getOwnChainAccount: jest.fn().mockResolvedValue({ id: Buffer.from([0, 0, 0, 1]) }),
send: jest.fn().mockResolvedValue(true),
error: jest.fn(),
terminateChain: jest.fn(),
getChannel: jest.fn(),
} as never);
endpoint.init(method);
endpoint.init(config);
stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB());
methodContext = createTransientMethodContext({ stateStore });
const userStore = tokenModule.stores.get(UserStore);
await userStore.save(methodContext, defaultAddress, defaultTokenID, defaultAccount);
await userStore.save(methodContext, defaultAddress, defaultForeignTokenID, defaultAccount);

const supplyStore = tokenModule.stores.get(SupplyStore);
await supplyStore.set(methodContext, defaultTokenID.slice(CHAIN_ID_LENGTH), {
await supplyStore.set(methodContext, defaultTokenID, {
totalSupply: defaultTotalSupply,
});

const escrowStore = tokenModule.stores.get(EscrowStore);
await escrowStore.set(
methodContext,
Buffer.concat([
defaultForeignTokenID.slice(0, CHAIN_ID_LENGTH),
defaultTokenID.slice(CHAIN_ID_LENGTH),
]),
Buffer.concat([defaultForeignTokenID.slice(0, CHAIN_ID_LENGTH), defaultTokenID]),
{ amount: defaultEscrowAmount },
);

supportedTokensStore = tokenModule.stores.get(SupportedTokensStore);
supportedTokensStore.registerOwnChainID(defaultTokenID.slice(CHAIN_ID_LENGTH));
await supportedTokensStore.set(methodContext, defaultTokenID, { supportedTokenIDs });
});

describe('getBalances', () => {
Expand Down Expand Up @@ -251,4 +262,44 @@ describe('token endpoint', () => {
});
});
});

describe('isSupported', () => {
it('should return true for a supported token', async () => {
const moduleEndpointContext = createTransientModuleEndpointContext({
stateStore,
params: { tokenID: supportedTokenIDs[0].toString('hex') },
});

expect(await endpoint.isSupported(moduleEndpointContext)).toEqual({ supported: true });
});

it('should return false for a non-supported token', async () => {
const moduleEndpointContext = createTransientModuleEndpointContext({
stateStore,
params: { tokenID: '8888888888888888' },
});

expect(await endpoint.isSupported(moduleEndpointContext)).toEqual({ supported: false });
});

it('should return true for a token from a foreign chain, when all tokens are supported', async () => {
await supportedTokensStore.supportAll(methodContext);

const moduleEndpointContext = createTransientModuleEndpointContext({
stateStore,
params: { tokenID: '8888888888888888' },
});

expect(await endpoint.isSupported(moduleEndpointContext)).toEqual({ supported: true });
});
});

describe('getInitializationFees', () => {
it('should return configured initialization fees for user account and escrow account', () => {
expect(endpoint.getInitializationFees()).toEqual({
userAccount: USER_SUBSTORE_INITIALIZATION_FEE.toString(),
escrowAccount: ESCROW_SUBSTORE_INITIALIZATION_FEE.toString(),
});
});
});
});