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

Commit

Permalink
Align the token support methods to the lip
Browse files Browse the repository at this point in the history
Cleanup the comments

Refactor the updates
  • Loading branch information
mosmartin committed Jun 23, 2023
1 parent 4709bdd commit fd4c805
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 17 deletions.
95 changes: 90 additions & 5 deletions framework/src/modules/token/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ 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,
SupportedTokensStoreData,
} 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 +693,47 @@ export class TokenMethod extends BaseMethod {
methodContext: MethodContext,
chainID: Buffer,
): Promise<void> {
await this.stores.get(SupportedTokensStore).supportChain(methodContext, chainID);
const allSupportedTokenEntries = await this._isAllTokensSupported(methodContext);

if (allSupportedTokenEntries) {
return;
}

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

const supportedTokens = await this._getSupportedTokens(methodContext, chainID);

if (supportedTokens) {
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 allSupportedTokenEntries = await this._isAllTokensSupported(methodContext);

if (allSupportedTokenEntries) {
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.',
);
}

await this._getSupportedTokens(methodContext, chainID);

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

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

Expand All @@ -706,8 +742,37 @@ 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);
public async removeSupport(methodContext: MethodContext, tokenID: Buffer): Promise<void> {
const [chainID] = splitTokenID(tokenID);

const allSupportedTokenEntries = await this._isAllTokensSupported(methodContext);

if (allSupportedTokenEntries) {
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.');
}

const supportedTokens = await this._getSupportedTokens(methodContext, chainID);

if (supportedTokens) {
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);
}
}
}

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

Expand All @@ -734,4 +799,24 @@ export class TokenMethod extends BaseMethod {
.get(EscrowStore)
.has(methodContext, this.stores.get(EscrowStore).getKey(chainID, tokenID));
}

private async _isAllTokensSupported(methodContext: MethodContext): Promise<boolean> {
const supportedTokensStore = this.stores.get(SupportedTokensStore);

return supportedTokensStore.has(methodContext, ALL_SUPPORTED_TOKENS_KEY);
}

private async _getSupportedTokens(
methodContext: MethodContext,
chainID: Buffer,
): Promise<SupportedTokensStoreData> {
const supportedTokensStore = this.stores.get(SupportedTokensStore);
const supportedTokens = await supportedTokensStore.get(methodContext, chainID);

if (!supportedTokens) {
throw new Error('No supported tokens found.');
}

return supportedTokens;
}
}
166 changes: 154 additions & 12 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 {
SupportedTokensStore,
supportedTokensStoreSchema,
} 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 @@ -68,6 +71,7 @@ describe('token module', () => {

let method: TokenMethod;
let methodContext: MethodContext;
let chainID: Buffer;

const checkEventResult = (
eventQueue: EventQueue,
Expand All @@ -88,6 +92,7 @@ describe('token module', () => {

beforeEach(async () => {
method = new TokenMethod(tokenModule.stores, tokenModule.events, tokenModule.name);
chainID = Buffer.from([1, 2, 3, 4]);
const internalMethod = new InternalMethod(tokenModule.stores, tokenModule.events);
const config = {
ownChainID: Buffer.from([0, 0, 0, 1]),
Expand Down Expand Up @@ -1225,11 +1230,61 @@ describe('token module', () => {
});

describe('supportAllTokensFromChainID', () => {
it('should call support chain', async () => {
await expect(
method.supportAllTokensFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).resolves.toBeUndefined();
it('should return early if all supported token entries is truthy', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(true);

await method.supportAllTokensFromChainID(methodContext, chainID);

expect((method as any)._isAllTokensSupported).toHaveBeenCalledWith(methodContext);
expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should return early if chainID equals ownChainID', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
method['_config'].ownChainID = chainID;

await method.supportAllTokensFromChainID(methodContext, chainID);

expect((method as any)._isAllTokensSupported).toHaveBeenCalledWith(methodContext);
expect(methodContext.eventQueue.getEvents()).toHaveLength(0);
});

it('should set supportedTokenIDs to an empty array if supported tokens exist', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue(true);
const setSpy = jest.spyOn(tokenModule.stores.get(SupportedTokensStore), 'set');

await method.supportAllTokensFromChainID(methodContext, chainID);

expect((method as any)._isAllTokensSupported).toHaveBeenCalledWith(methodContext);
expect((method as any)._getSupportedTokens).toHaveBeenCalledWith(methodContext, chainID);
expect(setSpy).toHaveBeenCalledWith(methodContext, chainID, { supportedTokenIDs: [] });
expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
});

it('should set supportedTokenIDs to an empty array if supported tokens do not exist', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue([]);
const encodeSpy = jest.spyOn(codec, 'encode');

await method.supportAllTokensFromChainID(methodContext, chainID);

expect((method as any)._isAllTokensSupported).toHaveBeenCalledWith(methodContext);
expect((method as any)._getSupportedTokens).toHaveBeenCalledWith(methodContext, chainID);
expect(encodeSpy).toHaveBeenCalledWith(supportedTokensStoreSchema, { supportedTokenIDs: [] });
expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
});

it('should log AllTokensFromChainSupportedEvent', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue([]);
const encodeSpy = jest.spyOn(codec, 'encode');

await method.supportAllTokensFromChainID(methodContext, chainID);

expect((method as any)._isAllTokensSupported).toHaveBeenCalledWith(methodContext);
expect((method as any)._getSupportedTokens).toHaveBeenCalledWith(methodContext, chainID);
expect(encodeSpy).toHaveBeenCalledWith(supportedTokensStoreSchema, { supportedTokenIDs: [] });
expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(methodContext.eventQueue.getEvents()[0].toObject().name).toEqual(
new AllTokensFromChainSupportedEvent('token').name,
Expand All @@ -1238,13 +1293,41 @@ describe('token module', () => {
});

describe('removeAllTokensSupportFromChainID', () => {
it('should call remove support from chain', async () => {
await tokenModule.stores
.get(SupportedTokensStore)
.supportChain(methodContext, Buffer.from([1, 2, 3, 4]));
it('should throw an error if allSupportedTokenEntries is truthy', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(true);

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

it('should throw an error if chainID equals ownChainID', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
method['_config'].ownChainID = chainID;

await expect(
method.removeAllTokensSupportFromChainID(methodContext, Buffer.from([1, 2, 3, 4])),
).rejects.toThrow(
'Invalid operation. All tokens from all the specified chain should be supported.',
);
});

it('should delete supported tokens from stores when supportedTokens is truthy', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue(true);
const deleteSpy = jest.spyOn(tokenModule.stores.get(SupportedTokensStore), 'del');

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

expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(deleteSpy).toHaveBeenCalledWith(methodContext, Buffer.from([1, 2, 3, 4]));
});

it('should log an event when supportedTokens is truthy', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue(true);

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

expect(methodContext.eventQueue.getEvents()).toHaveLength(1);
expect(methodContext.eventQueue.getEvents()[0].toObject().name).toEqual(
Expand All @@ -1266,20 +1349,79 @@ 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('should throw an error if allSupportedTokenEntries is truthy', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(true);

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

it('should throw an error if tokenID is the mainchainTokenID', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);

await expect(
method.removeSupport(methodContext, Buffer.from([0, 0, 0, 0, 0, 0, 0, 0])),
).rejects.toThrow('Cannot remove support for the specified token.');
});

it('should throw an error if chainID is equal to ownChainID', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);

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

it('should throw an error when supportedTokens returns truthy value with empty supportedTokenIDs', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest.spyOn(method as any, '_getSupportedTokens').mockResolvedValue({ supportedTokenIDs: [] });

await expect(
method.removeSupport(methodContext, Buffer.from([1, 0, 0, 0, 0, 0, 0, 0])),
).rejects.toThrow('All tokens from the specified chain are supported.');
});

it('should delete the SupportedTokensStore entry if all supportedTokenIDs are removed', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest
.spyOn(method as any, '_getSupportedTokens')
.mockResolvedValue({ supportedTokenIDs: [Buffer.from([1, 0, 0, 0, 0, 0, 0, 0])] });
const deleteSpy = jest.spyOn(tokenModule.stores.get(SupportedTokensStore), 'del');

await method.removeSupport(methodContext, Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]));

expect(deleteSpy).toHaveBeenCalledWith(methodContext, Buffer.from([1, 0, 0, 0]));
});

it('logs TokenIDSupportRemovedEvent', async () => {
jest.spyOn(method as any, '_isAllTokensSupported').mockResolvedValue(false);
jest
.spyOn(method as any, '_getSupportedTokens')
.mockResolvedValue({ supportedTokenIDs: [Buffer.from([1, 0, 0, 0, 0, 0, 0, 0])] });

await method.removeSupport(methodContext, Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]));

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

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

0 comments on commit fd4c805

Please sign in to comment.