Skip to content

Commit 3c75243

Browse files
authored
Llama support 18 decimals (#4060)
* Llama support 18 decimals * Comments * Comment
1 parent 1ab955a commit 3c75243

File tree

5 files changed

+98
-17
lines changed

5 files changed

+98
-17
lines changed

.changeset/funny-chairs-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/llama-guard-adapter': minor
3+
---
4+
5+
Support 18 decimals

packages/composites/llama-guard/src/transport/ea.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
AdapterError,
44
AdapterInputError,
55
} from '@chainlink/external-adapter-framework/validation/error'
6+
import { trimDecimals } from './nav'
67

78
export const getRawNav = async (source: string, sourceInput: string, requester: Requester) => {
89
const requestConfig = {
@@ -48,15 +49,21 @@ export const getEAUrl = (ea: string) => {
4849
return url
4950
}
5051

51-
const parse = (response: any): number => {
52+
const WEI = 18
53+
const parse = (response: any): string => {
5254
if (typeof response === 'number') {
53-
return response
55+
return response.toString()
5456
}
5557

5658
if (typeof response === 'string') {
57-
const num = Number(response)
58-
if (!isNaN(num)) {
59-
return num
59+
if (!isNaN(Number(response))) {
60+
if (response.indexOf('.') === -1) {
61+
// Response may already be scaled, use BigInt to avoid overflow
62+
return BigInt(response).toString()
63+
} else {
64+
// Avoid having too many decimals here
65+
return trimDecimals(response.trim(), WEI)
66+
}
6067
}
6168
}
6269

packages/composites/llama-guard/src/transport/nav.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const getNav = async (
2525

2626
const rawNavScaled = sourceScaled
2727
? BigInt(rawNav)
28-
: parseUnits(rawNav.toFixed(bounds.decimals).toString(), bounds.decimals)
28+
: parseUnits(trimDecimals(rawNav, bounds.decimals), bounds.decimals)
2929

3030
const now = Date.now() / 1000
3131

@@ -93,6 +93,16 @@ export const getNav = async (
9393

9494
const mulBigInt = (value: bigint, multiplier: number, decimals: number) => {
9595
const scale = 10n ** BigInt(decimals)
96-
const scaledMultiplier = BigInt(Math.floor(multiplier * Number(scale)))
96+
const scaledMultiplier = parseUnits(trimDecimals(multiplier.toString(), decimals), decimals)
97+
9798
return (value * scaledMultiplier) / scale
9899
}
100+
101+
export const trimDecimals = (number: string, decimals: number) => {
102+
if (number.indexOf('.') === -1) {
103+
return number
104+
}
105+
const arr = number.split('.')
106+
const fraction = arr[1].substring(0, decimals)
107+
return arr[0] + '.' + fraction
108+
}

packages/composites/llama-guard/test/unit/ea.test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,29 @@ describe('ea.ts', () => {
4646
const requester = { request: jest.fn() } as any
4747

4848
requester.request.mockResolvedValueOnce({ response: { data: { result: 10 } } })
49-
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(10)
49+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual('10')
50+
51+
const decimals = '1.234456123123'
52+
requester.request.mockResolvedValueOnce({ response: { data: { result: decimals } } })
53+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(decimals)
5054

5155
requester.request.mockResolvedValueOnce({ response: { data: { result: ' 11 ' } } })
52-
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(11)
56+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual('11')
5357

5458
requester.request.mockResolvedValueOnce({ response: { data: { result: ' 0xC ' } } })
55-
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(12)
59+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual('12')
5660

5761
requester.request.mockResolvedValueOnce({ response: { data: { result: ' 0XD ' } } })
58-
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(13)
62+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual('13')
63+
64+
const large = '999999999999999999999999999'
65+
requester.request.mockResolvedValueOnce({ response: { data: { result: large } } })
66+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual(large)
67+
68+
// Limit to 18 decimals
69+
const largeDecimals = ' 9.99999999999999999999999999 '
70+
requester.request.mockResolvedValueOnce({ response: { data: { result: largeDecimals } } })
71+
await expect(getRawNav('ea', '{}', requester)).resolves.toEqual('9.999999999999999999')
5972
})
6073

6174
it('should throw if empty', async () => {

packages/composites/llama-guard/test/unit/nav.test.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('getNav', () => {
3434

3535
describe('no bounds', () => {
3636
it('should return rawNav', async () => {
37-
const nav = 1.2301
37+
const nav = '1.2301'
3838
const scaledNav = '123'
3939
const decimals = 2
4040

@@ -84,7 +84,7 @@ describe('getNav', () => {
8484
})
8585

8686
it('should return rawNav - already scaled', async () => {
87-
const nav = 123
87+
const nav = '123'
8888
const scaledNav = '123'
8989
const decimals = 2
9090

@@ -136,7 +136,7 @@ describe('getNav', () => {
136136

137137
describe('lowerBound', () => {
138138
it('should return lowerBound', async () => {
139-
mockGetRawNav.mockResolvedValue(1.4)
139+
mockGetRawNav.mockResolvedValue('1.4')
140140
mockGetBounds.mockResolvedValue({
141141
lower: {
142142
isLowerBoundEnabled: true,
@@ -181,7 +181,7 @@ describe('getNav', () => {
181181
})
182182

183183
it('should return lowerBound with out maxDiscount', async () => {
184-
mockGetRawNav.mockResolvedValue(1.4)
184+
mockGetRawNav.mockResolvedValue('1.4')
185185
mockGetBounds.mockResolvedValue({
186186
lower: {
187187
isLowerBoundEnabled: true,
@@ -228,7 +228,7 @@ describe('getNav', () => {
228228

229229
describe('upperBound', () => {
230230
it('should return upperBound', async () => {
231-
mockGetRawNav.mockResolvedValue(1.006)
231+
mockGetRawNav.mockResolvedValue('1.006')
232232
mockGetBounds.mockResolvedValue({
233233
lower: {
234234
isLowerBoundEnabled: false,
@@ -276,7 +276,7 @@ describe('getNav', () => {
276276

277277
describe('with in bound', () => {
278278
it('should return rawNav', async () => {
279-
mockGetRawNav.mockResolvedValue(1.001)
279+
mockGetRawNav.mockResolvedValue('1.001')
280280
mockGetBounds.mockResolvedValue({
281281
lower: {
282282
isLowerBoundEnabled: true,
@@ -320,5 +320,51 @@ describe('getNav', () => {
320320
isBounded: true,
321321
})
322322
})
323+
324+
it('should return rawNav - 18 decimals', async () => {
325+
mockGetRawNav.mockResolvedValue('1.001000000000000000000000000000001')
326+
mockGetBounds.mockResolvedValue({
327+
lower: {
328+
isLowerBoundEnabled: true,
329+
latestNav: 10n ** 18n,
330+
latestTime: now / 1000 - 86400 / 2,
331+
maxDiscount: 25,
332+
lowerBoundTolerance: 50,
333+
},
334+
upper: {
335+
isUpperBoundEnabled: true,
336+
lookbackNav: 10n ** 18n,
337+
lookbackTime: now / 1000 - 86400 / 2,
338+
maxExpectedApy: 25,
339+
upperBoundTolerance: 50,
340+
},
341+
decimals: 18,
342+
})
343+
344+
const result = await getNav(
345+
defaultParams.source,
346+
defaultParams.sourceInput,
347+
defaultParams.sourceScaled,
348+
defaultParams.requester,
349+
defaultParams.asset,
350+
defaultParams.registry,
351+
defaultParams.provider,
352+
)
353+
354+
expect(result).toEqual({
355+
rawNav: '1001000000000000000',
356+
adjustedNav: '1001000000000000000',
357+
lowerBound: '993755471683049500',
358+
upperBound: '1005003437491631700',
359+
bases: {
360+
lookback: { nav: '1000000000000000000', ts: now / 1000 - 86400 / 2 },
361+
previous: { nav: '1000000000000000000', ts: now / 1000 - 86400 / 2 },
362+
},
363+
decimals: 18,
364+
riskFlag: false,
365+
breachDirection: '',
366+
isBounded: true,
367+
})
368+
})
323369
})
324370
})

0 commit comments

Comments
 (0)