Skip to content

Commit

Permalink
Add Native Staking transaction data decoders (#1903)
Browse files Browse the repository at this point in the history
- Adds `KilnDecoder` along with the Kiln Native Staking contract ABIs.
- Adds a fallback to `TransactionsViewService` that tries to decode the data in case the Transaction Service cannot.
  • Loading branch information
hectorgomezv authored Sep 10, 2024
1 parent c24effc commit dd4018b
Show file tree
Hide file tree
Showing 5 changed files with 532 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
KilnAbi,
KilnDecoder,
} from '@/domain/staking/contracts/decoders/kiln-decoder.helper';
import { ILoggingService } from '@/logging/logging.interface';
import { faker } from '@faker-js/faker';
import { encodeFunctionData } from 'viem';

const mockLoggingService = {
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
} as jest.MockedObjectDeep<ILoggingService>;

describe('KilnDecoder', () => {
let kilnDecoder: KilnDecoder;

beforeEach(() => {
kilnDecoder = new KilnDecoder(mockLoggingService);
});

describe('decodeDeposit', () => {
it('decodes a deposit function call correctly', () => {
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'deposit',
args: [],
});
expect(kilnDecoder.decodeDeposit(data)).toEqual({
method: 'deposit',
parameters: [],
});
});

it('returns null if the data is not a deposit function call', () => {
const data = faker.string.hexadecimal({ length: 1 }) as `0x${string}`;
expect(kilnDecoder.decodeDeposit(data)).toBeNull();
});

it('returns null if the data is another Kiln function call', () => {
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'requestValidatorsExit',
args: [faker.string.hexadecimal({ length: 1 }) as `0x${string}`],
});
expect(kilnDecoder.decodeDeposit(data)).toBeNull();
});
});

describe('decodeValidatorsExit', () => {
it('decodes a requestValidatorsExit function call correctly', () => {
const validatorsPublicKeys = faker.string.hexadecimal({
length: 96,
}) as `0x${string}`;
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'requestValidatorsExit',
args: [validatorsPublicKeys],
});
expect(kilnDecoder.decodeValidatorsExit(data)).toEqual({
method: 'requestValidatorsExit',
parameters: [
{
name: '_publicKeys',
type: 'bytes',
value: validatorsPublicKeys.toLocaleLowerCase(),
valueDecoded: null,
},
],
});
});

it('returns null if the data is not a requestValidatorsExit function call', () => {
const data = faker.string.hexadecimal({ length: 1 }) as `0x${string}`;
expect(kilnDecoder.decodeValidatorsExit(data)).toBeNull();
});

it('returns null if the data is another Kiln function call', () => {
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'batchWithdrawCLFee',
args: [faker.string.hexadecimal({ length: 1 }) as `0x${string}`],
});
expect(kilnDecoder.decodeValidatorsExit(data)).toBeNull();
});
});

describe('decodeBatchWithdrawCLFee', () => {
it('decodes a batchWithdrawCLFee function call correctly', () => {
const validatorsPublicKeys = faker.string.hexadecimal({
length: 96,
}) as `0x${string}`;
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'batchWithdrawCLFee',
args: [validatorsPublicKeys],
});
expect(kilnDecoder.decodeBatchWithdrawCLFee(data)).toEqual({
method: 'batchWithdrawCLFee',
parameters: [
{
name: '_publicKeys',
type: 'bytes',
value: validatorsPublicKeys.toLocaleLowerCase(),
valueDecoded: null,
},
],
});
});

it('returns null if the data is not a batchWithdrawCLFee function call', () => {
const data = faker.string.hexadecimal({ length: 1 }) as `0x${string}`;
expect(kilnDecoder.decodeBatchWithdrawCLFee(data)).toBeNull();
});

it('returns null if the data is another Kiln function call', () => {
const data = encodeFunctionData({
abi: KilnAbi,
functionName: 'requestValidatorsExit',
args: [faker.string.hexadecimal({ length: 1 }) as `0x${string}`],
});
expect(kilnDecoder.decodeBatchWithdrawCLFee(data)).toBeNull();
});
});
});
124 changes: 124 additions & 0 deletions src/domain/staking/contracts/decoders/kiln-decoder.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { AbiDecoder } from '@/domain/contracts/decoders/abi-decoder.helper';
import { LoggingService, ILoggingService } from '@/logging/logging.interface';
import { Inject, Injectable } from '@nestjs/common';

export const KilnAbi = [
{
inputs: [],
name: 'deposit',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [{ internalType: 'bytes', name: '_publicKeys', type: 'bytes' }],
name: 'requestValidatorsExit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'bytes', name: '_publicKeys', type: 'bytes' }],
name: 'batchWithdrawCLFee',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const;

export type KilnRequestValidatorsExitParameters = {
name: '_publicKeys';
type: 'bytes';
value: `0x${string}`;
valueDecoded: null;
};
export type KilnBatchWithdrawCLFeeParameters =
KilnRequestValidatorsExitParameters;

@Injectable()
export class KilnDecoder extends AbiDecoder<typeof KilnAbi> {
constructor(
@Inject(LoggingService) private readonly loggingService: ILoggingService,
) {
super(KilnAbi);
}

decodeDeposit(
data: `0x${string}`,
): { method: string; parameters: [] } | null {
if (!this.helpers.isDeposit(data)) {
return null;
}
try {
const decoded = this.decodeFunctionData({ data });
if (decoded.functionName !== 'deposit') {
throw new Error('Data is not of deposit type');
}
return {
method: decoded.functionName,
parameters: [],
};
} catch (e) {
this.loggingService.debug(e);
return null;
}
}

decodeValidatorsExit(data: `0x${string}`): {
method: string;
parameters: KilnRequestValidatorsExitParameters[];
} | null {
if (!this.helpers.isRequestValidatorsExit(data)) {
return null;
}
try {
const decoded = this.decodeFunctionData({ data });
if (decoded.functionName !== 'requestValidatorsExit') {
throw new Error('Data is not of requestValidatorsExit type');
}
return {
method: decoded.functionName,
parameters: [
{
name: '_publicKeys',
type: 'bytes',
value: decoded.args[0],
valueDecoded: null,
},
],
};
} catch (e) {
this.loggingService.debug(e);
return null;
}
}

decodeBatchWithdrawCLFee(data: `0x${string}`): {
method: string;
parameters: KilnBatchWithdrawCLFeeParameters[];
} | null {
if (!this.helpers.isBatchWithdrawCLFee(data)) {
return null;
}
try {
const decoded = this.decodeFunctionData({ data });
if (decoded.functionName !== 'batchWithdrawCLFee') {
throw new Error('Data is not of batchWithdrawCLFee type');
}
return {
method: decoded.functionName,
parameters: [
{
name: '_publicKeys',
type: 'bytes',
value: decoded.args[0],
valueDecoded: null,
},
],
};
} catch (e) {
this.loggingService.debug(e);
return null;
}
}
}
Loading

0 comments on commit dd4018b

Please sign in to comment.