Skip to content

Commit

Permalink
Merge pull request #46 from kaleido-io/decimals
Browse files Browse the repository at this point in the history
Expose ERC20 "decimals" field in event back to FireFly
  • Loading branch information
peterbroadhurst authored Apr 19, 2022
2 parents 959b871 + ad57de5 commit 8055c63
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 37 deletions.
3 changes: 3 additions & 0 deletions src/tokens/tokens.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ export class TokenPoolEvent extends tokenEventBase {
@ApiProperty()
symbol: string;

@ApiProperty({ type: 'integer' })
decimals: number;

@ApiProperty()
info: TokenPoolEventInfo;
}
Expand Down
51 changes: 30 additions & 21 deletions src/tokens/tokens.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,30 +85,22 @@ const ERC721_TRANSFER_NO_DATA = 'safeTransferFrom';
const BURN_NO_DATA = 'burn';
const APPROVE_NO_DATA = 'approve';
const APPROVE_ALL_NO_DATA = 'setApprovalForAll';
const DECIMALS = 'decimals';

const MINT_WITH_DATA = 'mintWithData';
const TRANSFER_WITH_DATA = 'transferWithData';
const BURN_WITH_DATA = 'burnWithData';
const APPROVE_WITH_DATA = 'approveWithData';
const APPROVE_ALL_WITH_DATA = 'setApprovalForAllWithData';

const METHODS_NO_DATA = [
MINT_NO_DATA,
BURN_NO_DATA,
APPROVE_NO_DATA,
APPROVE_ALL_NO_DATA,
'name',
'symbol',
];
const METHODS_NO_DATA = [MINT_NO_DATA, BURN_NO_DATA, APPROVE_NO_DATA, APPROVE_ALL_NO_DATA];

const METHODS_WITH_DATA = [
MINT_WITH_DATA,
BURN_WITH_DATA,
TRANSFER_WITH_DATA,
APPROVE_WITH_DATA,
APPROVE_ALL_WITH_DATA,
'name',
'symbol',
];

const TRANSFER_EVENT = 'Transfer';
Expand Down Expand Up @@ -148,7 +140,7 @@ describe('TokensService', () => {
getOrCreateSubscription: jest.fn(),
};

const mockNameAndSymbolQuery = () => {
const mockPoolQuery = (withDecimals: boolean) => {
http.post
.mockReturnValueOnce(
new FakeObservable(<EthConnectReturn>{
Expand All @@ -160,6 +152,13 @@ describe('TokensService', () => {
output: SYMBOL,
}),
);
if (withDecimals) {
http.post.mockReturnValueOnce(
new FakeObservable(<EthConnectReturn>{
output: '18',
}),
);
}
};

beforeEach(async () => {
Expand Down Expand Up @@ -213,7 +212,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(true);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -222,6 +221,7 @@ describe('TokensService', () => {
standard: 'ERC20',
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -246,14 +246,15 @@ describe('TokensService', () => {
standard: 'ERC20',
type: TokenType.FUNGIBLE,
symbol: SYMBOL,
decimals: 18,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
schema: ERC20_NO_DATA_SCHEMA,
},
};

mockNameAndSymbolQuery();
mockPoolQuery(true);

eventstream.createOrUpdateStream = jest.fn(() => mockEventStream);
eventstream.getOrCreateSubscription = jest.fn(() => new FakeObservable(undefined));
Expand Down Expand Up @@ -382,7 +383,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(true);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -391,6 +392,7 @@ describe('TokensService', () => {
standard: 'ERC20',
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -411,7 +413,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(true);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -420,6 +422,7 @@ describe('TokensService', () => {
standard: 'ERC20',
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -444,14 +447,15 @@ describe('TokensService', () => {
standard: 'ERC20',
type: TokenType.FUNGIBLE,
symbol: SYMBOL,
decimals: 18,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
schema: ERC20_WITH_DATA_SCHEMA,
},
};

mockNameAndSymbolQuery();
mockPoolQuery(true);

eventstream.createOrUpdateStream = jest.fn(() => mockEventStream);
eventstream.getOrCreateSubscription = jest.fn(() => new FakeObservable(undefined));
Expand Down Expand Up @@ -576,7 +580,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(false);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -585,6 +589,7 @@ describe('TokensService', () => {
standard: 'ERC721',
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -609,14 +614,15 @@ describe('TokensService', () => {
standard: 'ERC721',
type: TokenType.NONFUNGIBLE,
symbol: SYMBOL,
decimals: 0,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
schema: ERC721_NO_DATA_SCHEMA,
},
};

mockNameAndSymbolQuery();
mockPoolQuery(false);

eventstream.createOrUpdateStream = jest.fn(() => mockEventStream);
eventstream.getOrCreateSubscription = jest.fn(() => new FakeObservable(undefined));
Expand Down Expand Up @@ -758,7 +764,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(false);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -767,6 +773,7 @@ describe('TokensService', () => {
standard: 'ERC721',
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -787,7 +794,7 @@ describe('TokensService', () => {
symbol: SYMBOL,
};

mockNameAndSymbolQuery();
mockPoolQuery(false);

await service.createPool(request).then(resp => {
expect(resp).toEqual({
Expand All @@ -796,6 +803,7 @@ describe('TokensService', () => {
standard: 'ERC721',
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
Expand All @@ -820,14 +828,15 @@ describe('TokensService', () => {
standard: 'ERC721',
type: TokenType.NONFUNGIBLE,
symbol: SYMBOL,
decimals: 0,
info: {
name: NAME,
address: CONTRACT_ADDRESS,
schema: ERC721_WITH_DATA_SCHEMA,
},
};

mockNameAndSymbolQuery();
mockPoolQuery(false);

eventstream.createOrUpdateStream = jest.fn(() => mockEventStream);
eventstream.getOrCreateSubscription = jest.fn(() => new FakeObservable(undefined));
Expand Down
61 changes: 51 additions & 10 deletions src/tokens/tokens.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export interface AbiMethods {
SYMBOL: string;
APPROVE: string;
APPROVEFORALL: string | null;
DECIMALS: string | null;
}

export interface AbiEvents {
Expand All @@ -94,6 +95,7 @@ abiMethodMap.set('ERC20NoData', {
APPROVEFORALL: null,
NAME: 'name',
SYMBOL: 'symbol',
DECIMALS: 'decimals',
});
abiMethodMap.set('ERC20WithData', {
MINT: 'mintWithData',
Expand All @@ -103,6 +105,7 @@ abiMethodMap.set('ERC20WithData', {
APPROVEFORALL: null,
NAME: 'name',
SYMBOL: 'symbol',
DECIMALS: 'decimals',
});
abiMethodMap.set('ERC721WithData', {
MINT: 'mintWithData',
Expand All @@ -112,6 +115,7 @@ abiMethodMap.set('ERC721WithData', {
APPROVEFORALL: 'setApprovalForAllWithData',
NAME: 'name',
SYMBOL: 'symbol',
DECIMALS: null,
});
abiMethodMap.set('ERC721NoData', {
MINT: 'mint',
Expand All @@ -121,6 +125,7 @@ abiMethodMap.set('ERC721NoData', {
APPROVEFORALL: 'setApprovalForAll',
NAME: 'name',
SYMBOL: 'symbol',
DECIMALS: null,
});

const abiEventMap = new Map<ContractSchemaStrings, AbiEvents>();
Expand Down Expand Up @@ -197,7 +202,11 @@ export class TokensService {
if (contractAbi === undefined || abiMethods === undefined) {
return undefined;
}
return contractAbi.find(abi => abi.name === abiMethods[operation]);
const name = abiMethods[operation] ?? undefined;
if (name === undefined) {
return undefined;
}
return contractAbi.find(abi => abi.name === name);
}

private getEventAbi(
Expand Down Expand Up @@ -350,6 +359,7 @@ export class TokensService {

private async queryPool(poolLocator: IValidPoolLocator) {
const schema = poolLocator.schema as ContractSchemaStrings;

const nameResponse = await lastValueFrom(
this.http.post<EthConnectReturn>(
`${this.baseUrl}`,
Expand All @@ -364,6 +374,7 @@ export class TokensService {
this.queryOptions(),
),
);

const symbolResponse = await lastValueFrom(
this.http.post<EthConnectReturn>(
`${this.baseUrl}`,
Expand All @@ -378,9 +389,34 @@ export class TokensService {
this.queryOptions(),
),
);

let decimals = 0;
const decimalsMethod = this.getMethodAbi(schema, 'DECIMALS');
if (decimalsMethod !== undefined) {
const decimalsResponse = await lastValueFrom(
this.http.post<EthConnectReturn>(
`${this.baseUrl}`,
{
headers: {
type: queryHeader,
},
to: poolLocator.address,
method: decimalsMethod,
params: [],
} as EthConnectMsgRequest,
this.queryOptions(),
),
);
decimals = parseInt(decimalsResponse.data.output);
if (isNaN(decimals)) {
decimals = 0;
}
}

return {
name: nameResponse.data.output,
symbol: symbolResponse.data.output,
decimals,
};
}

Expand All @@ -394,10 +430,11 @@ export class TokensService {
if (!this.validatePoolLocator(poolLocator)) {
throw new BadRequestException('Invalid pool locator');
}
const nameAndSymbol = await this.queryPool(poolLocator);
if (dto.symbol !== undefined && dto.symbol !== '' && dto.symbol !== nameAndSymbol.symbol) {

const poolInfo = await this.queryPool(poolLocator);
if (dto.symbol !== undefined && dto.symbol !== '' && dto.symbol !== poolInfo.symbol) {
throw new BadRequestException(
`Supplied symbol '${dto.symbol}' does not match expected '${nameAndSymbol.symbol}'`,
`Supplied symbol '${dto.symbol}' does not match expected '${poolInfo.symbol}'`,
);
}

Expand All @@ -406,9 +443,10 @@ export class TokensService {
poolLocator: packPoolLocator(poolLocator),
standard: dto.type === TokenType.FUNGIBLE ? 'ERC20' : 'ERC721',
type: dto.type,
symbol: nameAndSymbol.symbol,
symbol: poolInfo.symbol,
decimals: poolInfo.decimals,
info: {
name: nameAndSymbol.name,
name: poolInfo.name,
address: dto.config.address,
schema,
},
Expand Down Expand Up @@ -449,7 +487,9 @@ export class TokensService {
throw new BadRequestException(`Unknown schema: ${poolLocator.schema}`);
}

const possibleMethods: string[] = Object.values(abiMethods);
const possibleMethods: string[] = Object.values(abiMethods).filter(
m => !['name', 'symbol', 'decimals'].includes(m),
);
const methodsToSubTo: IAbiMethod[] = contractAbi.filter(
method => method.name !== undefined && possibleMethods.includes(method.name),
);
Expand Down Expand Up @@ -496,14 +536,15 @@ export class TokensService {
}
await Promise.all(promises);

const nameAndSymbol = await this.queryPool(poolLocator);
const poolInfo = await this.queryPool(poolLocator);
const tokenPoolEvent: TokenPoolEvent = {
poolLocator: dto.poolLocator,
standard: poolLocator.type === TokenType.FUNGIBLE ? 'ERC20' : 'ERC721',
type: poolLocator.type,
symbol: nameAndSymbol.symbol,
symbol: poolInfo.symbol,
decimals: poolInfo.decimals,
info: {
name: nameAndSymbol.name,
name: poolInfo.name,
address: poolLocator.address,
schema: poolLocator.schema,
},
Expand Down
Loading

0 comments on commit 8055c63

Please sign in to comment.