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

Align the token support methods to the lip #8631

Merged
merged 7 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions framework/src/modules/token/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { BurnEvent } from './events/burn';
import { LockEvent } from './events/lock';
import { UnlockEvent } from './events/unlock';
import { TransferCrossChainEvent } from './events/transfer_cross_chain';
import { SupportedTokensStore } from './stores/supported_tokens';
import { ALL_SUPPORTED_TOKENS_KEY, SupportedTokensStore } from './stores/supported_tokens';
import { AllTokensSupportedEvent } from './events/all_tokens_supported';
import { AllTokensSupportRemovedEvent } from './events/all_tokens_supported_removed';
import { TokenIDSupportedEvent } from './events/token_id_supported';
Expand Down Expand Up @@ -689,15 +689,55 @@ export class TokenMethod extends BaseMethod {
methodContext: MethodContext,
chainID: Buffer,
): Promise<void> {
await this.stores.get(SupportedTokensStore).supportChain(methodContext, chainID);
const allTokensSupported = await this.stores
.get(SupportedTokensStore)
.has(methodContext, ALL_SUPPORTED_TOKENS_KEY);

if (allTokensSupported) {
return;
}

if (chainID.equals(this._config.ownChainID)) {
return;
}

await this.stores
.get(SupportedTokensStore)
.set(methodContext, chainID, { supportedTokenIDs: [] });

this.events.get(AllTokensFromChainSupportedEvent).log(methodContext, chainID);
}

public async removeAllTokensSupportFromChainID(
methodContext: MethodContext,
chainID: Buffer,
): Promise<void> {
await this.stores.get(SupportedTokensStore).removeSupportForChain(methodContext, chainID);
const allTokensSupported = await this.stores
.get(SupportedTokensStore)
.has(methodContext, 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.',
);
}

try {
await this.stores.get(SupportedTokensStore).get(methodContext, chainID);
} catch (err) {
if (err instanceof NotFoundError) {
return;
}

throw err;
}
mosmartin marked this conversation as resolved.
Show resolved Hide resolved

await this.stores.get(SupportedTokensStore).del(methodContext, chainID);

this.events.get(AllTokensFromChainSupportRemovedEvent).log(methodContext, chainID);
}

Expand All @@ -706,8 +746,49 @@ export class TokenMethod extends BaseMethod {
this.events.get(TokenIDSupportedEvent).log(methodContext, tokenID);
}

public async removeSupportTokenID(methodContext: MethodContext, tokenID: Buffer): Promise<void> {
await this.stores.get(SupportedTokensStore).removeSupportForToken(methodContext, tokenID);
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
public async removeSupport(methodContext: MethodContext, tokenID: Buffer): Promise<void> {
const [chainID] = splitTokenID(tokenID);

const allTokensSupported = await this.stores
.get(SupportedTokensStore)
.has(methodContext, ALL_SUPPORTED_TOKENS_KEY);

if (allTokensSupported) {
throw new Error('All tokens are supported.');
}

if (tokenID.equals(this.getMainchainTokenID()) || chainID.equals(this._config.ownChainID)) {
throw new Error('Cannot remove support for the specified token.');
}

try {
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
const supportedTokens = await this.stores
.get(SupportedTokensStore)
.get(methodContext, chainID);

if (supportedTokens) {
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
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(SupportedTokensStore).del(methodContext, chainID);
}
}
}
} catch (err) {
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
if (err instanceof NotFoundError) {
return;
}

throw err;
}
shuse2 marked this conversation as resolved.
Show resolved Hide resolved

this.events.get(TokenIDSupportRemovedEvent).log(methodContext, tokenID);
}

Expand Down
157 changes: 153 additions & 4 deletions framework/test/unit/modules/token/method.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ import { InternalMethod } from '../../../../src/modules/token/internal_method';
import { crossChainTransferMessageParams } from '../../../../src/modules/token/schemas';
import { EscrowStore } from '../../../../src/modules/token/stores/escrow';
import { SupplyStore } from '../../../../src/modules/token/stores/supply';
import { SupportedTokensStore } from '../../../../src/modules/token/stores/supported_tokens';
import {
ALL_SUPPORTED_TOKENS_KEY,
SupportedTokensStore,
} from '../../../../src/modules/token/stores/supported_tokens';
import { UserStore } from '../../../../src/modules/token/stores/user';
import { MethodContext, createMethodContext, EventQueue } from '../../../../src/state_machine';
import { PrefixedStateReadWriter } from '../../../../src/state_machine/prefixed_state_read_writer';
Expand Down Expand Up @@ -1225,7 +1228,48 @@ describe('token module', () => {
});

describe('supportAllTokensFromChainID', () => {
it('should call support chain', async () => {
it('should return early if all tokens are already supported', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.set(methodContext, ALL_SUPPORTED_TOKENS_KEY, { supportedTokenIDs: [] });

await expect(
method.supportAllTokensFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should return early if the chain ID is the same as the own chain ID', async () => {
const chainID = Buffer.from([1, 2, 3, 4]);
method['_config'].ownChainID = chainID;

await method.supportAllTokensFromChainID(methodContext, chainID);

await expect(
method.supportAllTokensFromChainID(methodContext, chainID),
).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should set an empty array of supported token IDs for the chain ID', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.set(methodContext, ALL_SUPPORTED_TOKENS_KEY, { supportedTokenIDs: [] });

await expect(
method.supportAllTokensFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();

const supportedTokens = await tokenModule.stores
.get(SupportedTokensStore)
.get(methodContext, ALL_SUPPORTED_TOKENS_KEY);

expect(supportedTokens.supportedTokenIDs).toHaveLength(0);
});

it('should log AllTokensFromChainSupportedEvent', async () => {
await expect(
method.supportAllTokensFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();
Expand All @@ -1251,6 +1295,54 @@ describe('token module', () => {
new AllTokensFromChainSupportRemovedEvent('token').name,
);
});

it('should throw an error if all tokens from all chains are supported', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.set(methodContext, ALL_SUPPORTED_TOKENS_KEY, { supportedTokenIDs: [] });

await expect(
method.removeAllTokensSupportFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).rejects.toThrow('Invalid operation. All tokens from all chains are supported.');

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should throw an error if the chain ID is the same as the own chain ID', async () => {
const chainID = Buffer.from([1, 2, 3, 4]);
method['_config'].ownChainID = chainID;

await expect(
method.removeAllTokensSupportFromChainID(methodContext, chainID),
).rejects.toThrow(
'Invalid operation. All tokens from all the specified chain should be supported.',
);

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should return early if there are no supportedTokens', async () => {
await expect(
method.removeAllTokensSupportFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should remove the chain ID from the supported tokens store', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.supportChain(methodContext, Buffer.from([1, 2, 3, 4]));

await expect(
method.removeAllTokensSupportFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(methodContext.eventQueue.getEvents()[0].toObject().name).toEqual(
new AllTokensFromChainSupportRemovedEvent('token').name,
);
});
});

describe('supportTokenID', () => {
Expand All @@ -1266,20 +1358,77 @@ describe('token module', () => {
});
});

describe('removeSupportTokenID', () => {
describe('removeSupport', () => {
it('should call remove support for token', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.supportToken(methodContext, Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]));

await expect(
method.removeSupportTokenID(methodContext, Buffer.from([1, 2, 3, 4, 0, 0, 0, 0])),
method.removeSupport(methodContext, Buffer.from([1, 2, 3, 4, 0, 0, 0, 0])),
).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(methodContext.eventQueue.getEvents()[0].toObject().name).toEqual(
new TokenIDSupportRemovedEvent('token').name,
);
});

it('throws an error if all tokens are supported', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.set(methodContext, ALL_SUPPORTED_TOKENS_KEY, { supportedTokenIDs: [] });

await expect(
method.removeSupport(methodContext, Buffer.from([1, 2, 3, 4, 0, 0, 0, 0])),
).rejects.toThrow('All tokens are supported.');

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('throws an error if the specified token is the mainchain token or the own chain ID', async () => {
const tokenId = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
const chainID = Buffer.from([1, 2, 3, 4]);
method['_config'].ownChainID = chainID;

await expect(method.removeSupport(methodContext, tokenId)).rejects.toThrow(
'Cannot remove support for the specified token.',
);

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('throws an error if all tokens from the specified chain are supported', async () => {
const tokenId = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
const chainID = Buffer.from([1, 2, 3, 4]);
await tokenModule.stores.get(SupportedTokensStore).supportChain(methodContext, chainID);

await expect(method.removeSupport(methodContext, tokenId)).rejects.toThrow(
'All tokens from the specified chain are supported.',
);

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('removes support for the specified token', async () => {
const tokenId = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
await tokenModule.stores.get(SupportedTokensStore).supportToken(methodContext, tokenId);

await expect(method.removeSupport(methodContext, tokenId)).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(methodContext.eventQueue.getEvents()[0].toObject().name).toEqual(
new TokenIDSupportRemovedEvent('token').name,
);
});

it('does nothing if the specified token is not supported', async () => {
const tokenId = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);

await expect(method.removeSupport(methodContext, tokenId)).resolves.toBeUndefined();

expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});
});

describe('getTotalSupply', () => {
Expand Down