Skip to content

Commit

Permalink
fix: Update ShimmerEVM txs metadata decoding (#7631)
Browse files Browse the repository at this point in the history
* feat: Output serialization powered by the SDK

* fix

* feat: Improved L2 estimated gas

* feat: Shimmer EVM txs metadata decoding

* it seems to work..

* this actually works, fr this time

* bring back comment

* fix tests

* fix tests

* typo

* remove parseLayer2MetadataForTransferV1

---------

Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
marc2332 and begonaalvarezd authored Oct 24, 2023
1 parent f572490 commit c112755
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 223 deletions.
52 changes: 35 additions & 17 deletions packages/shared/lib/core/layer-2/classes/special-stream.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,27 @@ export class SpecialStream extends WriteStream {

export class ReadSpecialStream extends ReadStream {
readUInt64SpecialEncoding(name: string): number | bigint {
const readValue = this.readBytes(name, 1)
const [value] = size64Decode(readValue)
const [value] = size64Decode(() => this.readUInt8(name))
return value
}

readUInt32SpecialEncoding(name: string): number | bigint {
const readValue = this.readBytes(name, 1)
const [value] = size64Decode(readValue)
const [value] = size64Decode(() => this.readUInt8(name))
return value
}
readUInt16SpecialEncoding(name: string): number | bigint {
const readValue = this.readBytes(name, 1)
const [value] = size64Decode(readValue)
const [value] = size64Decode(() => this.readUInt8(name))
return value
}

readUIntNSpecialEncoding(name: string, length: number): number | bigint {
const readValue = this.readBytes(name, length)
const [value] = size64Decode(readValue)
let index = 0
const [value] = size64Decode(() => {
const val = readValue[index]
index += 1
return val
})
return value
}
}
Expand Down Expand Up @@ -133,19 +136,34 @@ function size64Encode(n: bigint): Buffer {
}
}

function size64Decode(buffer: Uint8Array): [bigint, Error] | [number, null] {
let value = BigInt(0)
let shift = BigInt(0)
let index = 0
// Adapted from WASP golang implementation https://github.com/iotaledger/wasp/blob/7f880a7983d24d0dcd225e994d67b29741b318bc/packages/util/rwutil/convert.go#L76
function size64Decode(readByte: () => number): [number, null | Error] {
let byte = readByte()

if (byte < 0x80) {
return [byte, null]
}

let value = byte & 0x7f

while (index < buffer.length) {
const byte = buffer[index++]
for (let shift = 7; shift < 63; shift += 7) {
byte = readByte()
if (!byte) {
return [0, null]
}
if (byte < 0x80) {
return [Number(value | (BigInt(byte) << shift)), null]
return [Number(value | (byte << shift)), null]
}
value |= BigInt(byte & 0x7f) << shift
shift += BigInt(7)
value |= (byte & 0x7f) << shift
}

byte = readByte()
if (!byte) {
return [0, null]
}
if (byte > 0x01) {
return [0, new Error('size64 overflow')]
}

return [value, new Error('Unexpected end of data')]
return [value | (byte << 63), new Error('Unexpected end of data')]
}
Original file line number Diff line number Diff line change
@@ -1,62 +1,66 @@
import { Converter } from '@iota/util.js'
import { parseLayer2MetadataForTransferV2 } from '../utils/parseLayer2MetadataForTransferV2'
import { parseLayer2MetadataForTransfer } from '../utils/parseLayer2MetadataForTransfer'

describe('Function: parseLayer2MetadataForTransferV2.ts', () => {
describe('Function: parseLayer2MetadataForTransfer.ts', () => {
it('should correctly parse metadata with base token', () => {
const metadata = '0x00000000025e4b3ca1e3f423a0c21e0101611503807d707f59f1345e1063dbb64f2495d1491283a080c0843d'
const metadata =
'0x00025e4b3ca1e3f423914e010161350342f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f8080d293ad03'
const metadataByteArray = Converter.hexToBytes(metadata)
const expected = {
senderContract: '0x0',
targetContract: 'Accounts',
contractFunction: 'transferAllowanceTo',
gasBudget: '500000',
ethereumAddress: '0x807d707f59f1345e1063dbb64f2495d1491283a0',
baseTokens: '1000000',
gasBudget: '10001',
ethereumAddress:
'0x42f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f',
baseTokens: '900000000',
nativeTokens: [],
nfts: [],
}
const parsedMetadata = parseLayer2MetadataForTransferV2(metadataByteArray)
const parsedMetadata = parseLayer2MetadataForTransfer(metadataByteArray)
expect(parsedMetadata).toEqual(expected)
})

it('should correctly parse metadata with native tokens', () => {
const metadata =
'0x00000000025e4b3ca1e3f423a0c21e01016115038cc8112290f8c350a60e1afdb8379c686e2a5bb3400108fcccc313acc182fc2c647dc98864062b163a8ee254231d7f029dc6be3a2de52e01000000000132'
'0x00025e4b3ca1e3f423914e010161350342f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f4001086ac702fcfdc37b437e7ebb7a87d8acfb875be6b1ae3823bc61aa7896b852a6d5010000000001fa'
const metadataByteArray = Converter.hexToBytes(metadata)
const expected = {
senderContract: '0x0',
targetContract: 'Accounts',
contractFunction: 'transferAllowanceTo',
gasBudget: '500000',
ethereumAddress: '0x8cc8112290f8c350a60e1afdb8379c686e2a5bb3',
gasBudget: '10001',
ethereumAddress:
'0x42f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f',
baseTokens: '0',
nativeTokens: [
{
amount: '50',
ID: ['0x08fcccc313acc182fc2c647dc98864062b163a8ee254231d7f029dc6be3a2de52e0100000000'],
amount: '250',
ID: ['0x086ac702fcfdc37b437e7ebb7a87d8acfb875be6b1ae3823bc61aa7896b852a6d50100000000'],
},
],
nfts: [],
}
const parsedMetadata = parseLayer2MetadataForTransferV2(metadataByteArray)
const parsedMetadata = parseLayer2MetadataForTransfer(metadataByteArray)
expect(parsedMetadata).toEqual(expected)
})

it('should correctly parse metadata with nfts', () => {
const metadata =
'0x00000000025e4b3ca1e3f423a0c21e0101611503cbcd6d8659ed1998a452335ae53904dc0af1c99b200166b71141974aa368c9152a24d631494b46172ba05dd998eef553e7fa1218b704'
'0x00025e4b3ca1e3f423e9cd01010161350342f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f2001bf5b7cd4e8ac582e246c25b6a89b4ab4ef0646d3291aa03d9a5313154b714a06'
const metadataByteArray = Converter.hexToBytes(metadata)
const expected = {
senderContract: '0x0',
targetContract: 'Accounts',
contractFunction: 'transferAllowanceTo',
gasBudget: '500000',
ethereumAddress: '0xcbcd6d8659ed1998a452335ae53904dc0af1c99b',
gasBudget: '26345',
ethereumAddress:
'0x42f7da9bdb55b3ec87e5ac1a1e6d88e16768663fde5eca3429eb6f579cc538acb82a77d6f89dae4611b81eac279fbf96d322001f',
baseTokens: '0',
nativeTokens: [],
nfts: ['0x66b71141974aa368c9152a24d631494b46172ba05dd998eef553e7fa1218b704'],
nfts: ['0xbf5b7cd4e8ac582e246c25b6a89b4ab4ef0646d3291aa03d9a5313154b714a06'],
}
const parsedMetadata = parseLayer2MetadataForTransferV2(metadataByteArray)
const parsedMetadata = parseLayer2MetadataForTransfer(metadataByteArray)
expect(parsedMetadata).toEqual(expected)
})
})
2 changes: 0 additions & 2 deletions packages/shared/lib/core/layer-2/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export * from './getLayer2AssetAllowance'
export * from './getLayer2MetadataForTransfer'
export * from './getLayer2NetworkFromAddress'
export * from './parseLayer2Metadata'
export * from './parseLayer2MetadataForTransferV1'
export * from './parseLayer2MetadataForTransferV2'
export * from './parseLayer2MetadataForTransfer'
export * from './specialNativeTokenAmountEncoding'
export * from './outputHexBytes'
Original file line number Diff line number Diff line change
@@ -1,11 +1,89 @@
import { ILayer2TransferAllowanceMetadata } from '../interfaces'
import { parseLayer2MetadataForTransferV1 } from './parseLayer2MetadataForTransferV1'
import { parseLayer2MetadataForTransferV2 } from './parseLayer2MetadataForTransferV2'
import { Converter } from '@core/utils'
import { ILayer2AssetAllowance, ILayer2TransferAllowanceMetadata } from '../interfaces'
import { CONTRACT_FUNCTIONS, TARGET_CONTRACTS } from '../constants'
import { Allowance } from '../enums'
import { ReadSpecialStream } from '../classes'

// Function to parse data from the current metadata, using the new encoding where the shimmer chainId is 1073
export function parseLayer2MetadataForTransfer(metadata: Uint8Array): ILayer2TransferAllowanceMetadata {
try {
return parseLayer2MetadataForTransferV2(metadata)
} catch (err) {
return parseLayer2MetadataForTransferV1(metadata)
const readStream = new ReadSpecialStream(metadata)
const senderContract = readStream.readUInt8('senderContract')
const targetContract = readStream.readUInt32('targetContract')
const contractFunction = readStream.readUInt32('contractFunction')
const gasBudget = readStream.readUInt64SpecialEncoding('gasBudget')
const smartContractParameters = parseSmartContractParameters(readStream)
const ethereumAddress = '0x' + smartContractParameters['a'].substring(4)
const allowance = parseAssetAllowance(readStream)

return {
senderContract: Converter.decimalToHex(senderContract),
targetContract: TARGET_CONTRACTS[targetContract] ?? Converter.decimalToHex(targetContract),
contractFunction: CONTRACT_FUNCTIONS[contractFunction] ?? Converter.decimalToHex(contractFunction),
gasBudget: gasBudget.toString(),
ethereumAddress,
baseTokens: allowance?.baseTokens,
nativeTokens: allowance?.nativeTokens,
nfts: allowance?.nfts,
}
}

function parseSmartContractParameters(readStream: ReadSpecialStream): Record<string, string> {
const smartContractParametersAmount = readStream.readUInt32SpecialEncoding('parametersLength')
const smartContractParameters: Record<string, string> = {}

for (let index = 0; index < smartContractParametersAmount; index++) {
const keyLength = readStream.readUInt32SpecialEncoding('keyLength')
const keyBytes = readStream.readBytes('keyValue', Number(keyLength))

const valueLength = readStream.readUInt32SpecialEncoding('valueLength')
const valueBytes = readStream.readBytes('valueBytes', Number(valueLength))

const key = Converter.bytesToUtf8(keyBytes)
const value = Converter.bytesToHex(valueBytes)

smartContractParameters[key] = value
}

return smartContractParameters
}

function parseAssetAllowance(readStream: ReadSpecialStream): ILayer2AssetAllowance {
const allowance = readStream.readUInt8('encodedAllowance')
const result: ILayer2AssetAllowance = {
baseTokens: '0',
nativeTokens: [],
nfts: [],
}

switch (allowance) {
case Allowance.HasBaseTokens: {
// TODO: This is a temporary fix since now the base token is sent alone in the transfer (without native token and/or nfts)
const baseTokenLength = readStream.length() - readStream.getReadIndex()
result.baseTokens = readStream.readUIntNSpecialEncoding('baseTokenAmount', baseTokenLength).toString()
break
}

case Allowance.HasNativeTokens: {
readStream.readUInt32SpecialEncoding('amountOfDifferentTokens')
const NATIVE_TOKEN_ID_LENGTH = 38
const tokenId = readStream.readBytes('tokenId', NATIVE_TOKEN_ID_LENGTH)
const tokenAmount = readStream.readUInt32SpecialEncoding('nativeTokenAmountLength')
const nativeTokenAmount = readStream.readBytes('nativeTokenAmount', Number(tokenAmount))
result.nativeTokens.push({ ID: [Converter.bytesToHex(tokenId)], amount: nativeTokenAmount.toString() })
break
}

case Allowance.hasNFTs: {
readStream.readUInt16SpecialEncoding('nftAmount')
const NFT_ID_LENGTH = 32
const nftIdBytes = readStream.readBytes('nftId', NFT_ID_LENGTH)
const nftId = Converter.bytesToHex(nftIdBytes)
result.nfts.push(nftId)
break
}

default:
throw new Error('Smart contract call data does not set the asset allowance!')
}
return result
}

This file was deleted.

Loading

0 comments on commit c112755

Please sign in to comment.