Skip to content

Commit

Permalink
CCIP-4474 manual exec lbtc (#8)
Browse files Browse the repository at this point in the history
Add lbtc manual exec feature.
If destTokenData is more than 32 bytes - attestation is disabled onchain
else - call lombard attestation api for a proof and include it as
offchain token data
  • Loading branch information
bukata-sa authored Dec 10, 2024
2 parents c92e135 + ee4e9d7 commit 9249c1d
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
- Allow `parseBytes` command to parse EVMExtraArgs bytearrays, both standalone and in structs (#7)
- Support Lombard attestation for LBTC transfers (#8)

## [0.1.2] - 2024-11-25
- Add public `recursiveParseErrors` function to lib, to return nested/inner ABI errors
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/ccip-tools-ts",
"version": "0.1.2",
"version": "0.1.3",
"description": "CLI and library to interact with CCIP",
"author": "Chainlink devs",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { logParsedError } from './utils.js'
util.inspect.defaultOptions.depth = 6 // print down to tokenAmounts in requests
// generate:nofail
// `const VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'`
const VERSION = '0.1.2-15d5cc5'
const VERSION = '0.1.2-c55978f'
// generate:end

async function main() {
Expand Down
205 changes: 201 additions & 4 deletions src/lib/offchain.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { getAddress, hexlify, id, keccak256, randomBytes } from 'ethers'

import { fetchOffchainTokenData } from './offchain.js'
import { type CCIPRequest, defaultAbiCoder } from './types.js'
import { LBTC_EVENT, fetchOffchainTokenData } from './offchain.js'
import { type CCIPRequest, defaultAbiCoder, encodeSourceTokenData } from './types.js'

const origFetch = global.fetch

beforeEach(() => {
jest.clearAllMocks()
Expand All @@ -12,13 +14,14 @@ describe('fetchOffchainTokenData', () => {
const TRANSFER_TOPIC0 = id('Transfer(address,address,uint256)')
const usdcToken = getAddress(hexlify(randomBytes(20)))

const origFetch = global.fetch
const mockedFetchJson = jest.fn<any, [], any>(() => ({
status: 'complete',
attestation: '0xa77e57a71090',
}))
const mockedFetch = jest.fn(() => ({ json: mockedFetchJson }))
global.fetch = mockedFetch as any
beforeAll(() => {
global.fetch = mockedFetch as any
})
afterAll(() => {
global.fetch = origFetch
})
Expand Down Expand Up @@ -116,3 +119,197 @@ describe('fetchOffchainTokenData', () => {
expect(mockedFetch).toHaveBeenCalledWith(expect.stringContaining(keccak256('0xbeef02')))
})
})

describe('fetchLbtcOffchainTokenData', () => {
const lbtcToken = getAddress(hexlify(randomBytes(20)))
const approvedPayloadHash1 = '0x111114eb42fd24b59b6edf6c5aa6b9357be7dcaf91f1d62da303f1fad100762e'
const approvedPayloadAttestation1 = hexlify(randomBytes(20))
const approvedPayloadHash2 = '0x222224eb42fd24b59b6edf6c5aa6b9357be7dcaf91f1d62da303f1fad100762e'
const approvedPayloadAttestation2 = hexlify(randomBytes(20))
const pendingPayloadHash = '0x333334eb42fd24b59b6edf6c5aa6b9357be7dcaf91f1d62da303f1fad100762e'

const mockedFetchJson = jest.fn<any, [], any>(() => ({
attestations: [
{
message_hash: approvedPayloadHash1,
status: 'NOTARIZATION_STATUS_SESSION_APPROVED',
attestation: approvedPayloadAttestation1,
},
{
message_hash: approvedPayloadHash2,
status: 'NOTARIZATION_STATUS_SESSION_APPROVED',
attestation: approvedPayloadAttestation2,
},
{ message_hash: pendingPayloadHash, status: 'NOTARIZATION_STATUS_SESSION_PENDING' },
],
}))
const mockedFetch = jest.fn(() => ({ json: mockedFetchJson }))
beforeAll(() => {
global.fetch = mockedFetch as any
})
afterAll(() => {
global.fetch = origFetch
})

it('should skip if has no LBTC Deposit Event', async () => {
const mockRequest = {
message: {
sourceChainSelector: 16015286601757825753n,
tokenAmounts: [{ token: lbtcToken, amount: 100n }],
sourceTokenData: [
encodeSourceTokenData({
sourcePoolAddress: '0x',
destTokenAddress: '0x',
extraData: approvedPayloadHash1,
destGasAmount: 0n,
}),
],
},
log: { topics: ['0x123'], index: 7 },
tx: {
logs: [],
},
}
const result = await fetchOffchainTokenData(mockRequest as unknown as CCIPRequest)
expect(result).toHaveLength(1)
expect(result[0]).toBe('0x')
})

it('should return offchain token data', async () => {
const mockRequest = {
message: {
sourceChainSelector: 16015286601757825753n,
tokenAmounts: [{ token: lbtcToken, amount: 100n }],
sourceTokenData: [
encodeSourceTokenData({
sourcePoolAddress: '0x',
destTokenAddress: '0x',
extraData: approvedPayloadHash1,
destGasAmount: 0n,
}),
],
},
log: { topics: ['0x123'], index: 7 },
tx: {
logs: [
{
topics: [LBTC_EVENT.topicHash, '0x', '0x', approvedPayloadHash1],
index: 6,
data: '0x',
},
],
},
}
const result = await fetchOffchainTokenData(mockRequest as unknown as CCIPRequest)
expect(mockedFetch).toHaveBeenCalledTimes(1)
expect(result).toHaveLength(1)
expect(result[0]).toBe(approvedPayloadAttestation1)
})

it('should fallback if attestation is not found', async () => {
const randomExtraData = '0x0000000000000000000000000000000000000000000000000000000000000000'
const mockRequest = {
message: {
sourceChainSelector: 16015286601757825753n,
tokenAmounts: [{ token: lbtcToken, amount: 100n }],
sourceTokenData: [
encodeSourceTokenData({
sourcePoolAddress: '0x1234',
destTokenAddress: '0x5678',
extraData: randomExtraData,
destGasAmount: 100n,
}),
],
},
log: { topics: ['0x123'], index: 7 },
tx: {
logs: [
{
topics: [LBTC_EVENT.topicHash, '0x', '0x', randomExtraData],
index: 6,
data: '0x',
},
],
},
}
await expect(fetchOffchainTokenData(mockRequest as unknown as CCIPRequest)).resolves.toEqual([
'0x',
])
})

it('should fallback if attestation is not approved', async () => {
const mockRequest = {
message: {
sourceChainSelector: 16015286601757825753n,
tokenAmounts: [{ token: lbtcToken, amount: 100n }],
sourceTokenData: [
encodeSourceTokenData({
sourcePoolAddress: '0x1234',
destTokenAddress: '0x5678',
extraData: pendingPayloadHash,
destGasAmount: 100n,
}),
],
},
log: { topics: ['0x123'], index: 7 },
tx: {
logs: [
{
topics: [LBTC_EVENT.topicHash, '0x', '0x', pendingPayloadHash],
index: 6,
data: '0x',
},
],
},
}
await expect(fetchOffchainTokenData(mockRequest as unknown as CCIPRequest)).resolves.toEqual([
'0x',
])
})

it('should return offchain token data multiple transfers', async () => {
const mockRequest = {
message: {
sourceChainSelector: 16015286601757825753n,
tokenAmounts: [
{ token: lbtcToken, amount: 100n },
{ token: lbtcToken, amount: 200n },
],
sourceTokenData: [
encodeSourceTokenData({
sourcePoolAddress: '0x',
destTokenAddress: '0x',
extraData: approvedPayloadHash1,
destGasAmount: 100n,
}),
encodeSourceTokenData({
sourcePoolAddress: '0x',
destTokenAddress: '0x',
extraData: approvedPayloadHash2,
destGasAmount: 100n,
}),
],
},
log: { topics: ['0x123'], index: 7 },
tx: {
logs: [
{
topics: [LBTC_EVENT.topicHash, '0x', '0x', approvedPayloadHash1],
index: 6,
data: '0x',
},
{
topics: [LBTC_EVENT.topicHash, '0x', '0x', approvedPayloadHash2],
index: 7,
data: '0x',
},
],
},
}

const result = await fetchOffchainTokenData(mockRequest as unknown as CCIPRequest)
expect(result).toHaveLength(2)
expect(result[0]).toBe(approvedPayloadAttestation1)
expect(result[1]).toBe(approvedPayloadAttestation2)
})
})
Loading

0 comments on commit 9249c1d

Please sign in to comment.