-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Native Staking transaction data decoders (#1903)
- 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
1 parent
c24effc
commit dd4018b
Showing
5 changed files
with
532 additions
and
9 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
src/domain/staking/contracts/decoders/__tests__/kiln-decoder.helper.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
124
src/domain/staking/contracts/decoders/kiln-decoder.helper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.