mirror of
https://github.com/logos-storage/nim-nitro.git
synced 2026-01-05 07:03:07 +00:00
Fix ABI encoding of Nitro state to match javascript implementation
- rewrite ABI encoding to properly handle dynamic tuples - surround Nitro types with extra tuples to match javascript - test with examples of encoding extracted from javascript
This commit is contained in:
parent
3867258fe6
commit
34072f4749
176
nitro/abi.nim
176
nitro/abi.nim
@ -3,104 +3,124 @@ import pkg/stint
|
||||
import ./types
|
||||
|
||||
type
|
||||
Abi* = object
|
||||
AbiWriter* = object
|
||||
bytes: seq[byte]
|
||||
tuples: seq[Tuple]
|
||||
AbiEncoder* = object
|
||||
stack: seq[Tuple]
|
||||
Tuple = object
|
||||
start: int
|
||||
bytes: seq[byte]
|
||||
postponed: seq[Split]
|
||||
dynamic: bool
|
||||
Split = object
|
||||
head: Slice[int]
|
||||
tail: seq[byte]
|
||||
|
||||
proc isStatic*(_: type Abi, t: type SomeUnsignedInt): bool = true
|
||||
proc isStatic*(_: type Abi, t: type StUint): bool = true
|
||||
proc isStatic*(_: type Abi, t: type bool): bool = true
|
||||
proc isStatic*(_: type Abi, t: type enum): bool = true
|
||||
proc isStatic*[T](_: type Abi, t: type seq[T]): bool = false
|
||||
proc isStatic*[I, T](_: type Abi, t: type array[I, T]): bool = Abi.isStatic(T)
|
||||
proc write*[T](encoder: var AbiEncoder, value: T)
|
||||
proc encode*[T](_: type AbiEncoder, value: T): seq[byte]
|
||||
|
||||
proc encode*[T](_: type Abi, value: T): seq[byte]
|
||||
proc init*(_: type AbiEncoder): AbiEncoder =
|
||||
AbiEncoder(stack: @[Tuple()])
|
||||
|
||||
proc pad(writer: var AbiWriter, len: int) =
|
||||
proc append(tupl: var Tuple, bytes: openArray[byte]) =
|
||||
tupl.bytes.add(bytes)
|
||||
|
||||
proc postpone(tupl: var Tuple, bytes: seq[byte]) =
|
||||
var split: Split
|
||||
split.head.a = tupl.bytes.len
|
||||
tupl.append(AbiEncoder.encode(0'u64))
|
||||
split.head.b = tupl.bytes.high
|
||||
split.tail = bytes
|
||||
tupl.postponed.add(split)
|
||||
|
||||
proc finish(tupl: Tuple): seq[byte] =
|
||||
var bytes = tupl.bytes
|
||||
for split in tupl.postponed:
|
||||
let offset = bytes.len
|
||||
bytes[split.head] = AbiEncoder.encode(offset.uint64)
|
||||
bytes.add(split.tail)
|
||||
bytes
|
||||
|
||||
proc append(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.stack[^1].append(bytes)
|
||||
|
||||
proc postpone(encoder: var AbiEncoder, bytes: seq[byte]) =
|
||||
if encoder.stack.len > 1:
|
||||
encoder.stack[^1].postpone(bytes)
|
||||
else:
|
||||
encoder.stack[0].append(bytes)
|
||||
|
||||
proc setDynamic(encoder: var AbiEncoder) =
|
||||
encoder.stack[^1].dynamic = true
|
||||
|
||||
proc startTuple*(encoder: var AbiEncoder) =
|
||||
encoder.stack.add(Tuple())
|
||||
|
||||
proc encode(encoder: var AbiEncoder, tupl: Tuple) =
|
||||
if tupl.dynamic:
|
||||
encoder.postpone(tupl.finish())
|
||||
encoder.setDynamic()
|
||||
else:
|
||||
encoder.append(tupl.finish())
|
||||
|
||||
proc finishTuple*(encoder: var AbiEncoder) =
|
||||
encoder.encode(encoder.stack.pop())
|
||||
|
||||
proc pad(encoder: var AbiEncoder, len: int) =
|
||||
let padlen = (32 - len mod 32) mod 32
|
||||
for _ in 0..<padlen:
|
||||
writer.bytes.add(0'u8)
|
||||
encoder.append([0'u8])
|
||||
|
||||
proc padleft(writer: var AbiWriter, bytes: openArray[byte]) =
|
||||
writer.pad(bytes.len)
|
||||
writer.bytes.add(bytes)
|
||||
proc padleft(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.pad(bytes.len)
|
||||
encoder.append(bytes)
|
||||
|
||||
proc padright(writer: var AbiWriter, bytes: openArray[byte]) =
|
||||
writer.bytes.add(bytes)
|
||||
writer.pad(bytes.len)
|
||||
proc padright(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.append(bytes)
|
||||
encoder.pad(bytes.len)
|
||||
|
||||
proc write*(writer: var AbiWriter, value: SomeUnsignedInt | StUint) =
|
||||
writer.padleft(value.toBytesBE)
|
||||
proc encode(encoder: var AbiEncoder, value: SomeUnsignedInt | StUint) =
|
||||
encoder.padleft(value.toBytesBE)
|
||||
|
||||
proc write*(writer: var AbiWriter, value: bool) =
|
||||
writer.write(cast[uint8](value))
|
||||
proc encode(encoder: var AbiEncoder, value: bool) =
|
||||
encoder.encode(cast[uint8](value))
|
||||
|
||||
proc write*(writer: var AbiWriter, value: enum) =
|
||||
writer.write(uint64(ord(value)))
|
||||
proc encode(encoder: var AbiEncoder, value: enum) =
|
||||
encoder.encode(uint64(ord(value)))
|
||||
|
||||
proc write*[I](writer: var AbiWriter, bytes: array[I, byte]) =
|
||||
writer.padright(bytes)
|
||||
proc encode[I](encoder: var AbiEncoder, bytes: array[I, byte]) =
|
||||
encoder.padright(bytes)
|
||||
|
||||
proc writeLater[T](writer: var AbiWriter, value: T) =
|
||||
var split: Split
|
||||
split.head.a = writer.bytes.high + 1
|
||||
writer.write(0'u64)
|
||||
split.head.b = writer.bytes.high
|
||||
split.tail = Abi.encode(value)
|
||||
writer.tuples[^1].postponed.add(split)
|
||||
proc encode(encoder: var AbiEncoder, bytes: seq[byte]) =
|
||||
encoder.encode(bytes.len.uint64)
|
||||
encoder.padright(bytes)
|
||||
encoder.setDynamic()
|
||||
|
||||
proc write*(writer: var AbiWriter, bytes: seq[byte]) =
|
||||
if writer.tuples.len == 0:
|
||||
writer.write(bytes.len.uint64)
|
||||
writer.padright(bytes)
|
||||
else:
|
||||
writer.writeLater(bytes)
|
||||
proc encode(encoder: var AbiEncoder, address: EthAddress) =
|
||||
encoder.padleft(address.toArray)
|
||||
|
||||
proc write*(writer: var AbiWriter, address: EthAddress) =
|
||||
writer.padleft(address.toArray)
|
||||
proc encode[I, T](encoder: var AbiEncoder, value: array[I, T]) =
|
||||
encoder.startTuple()
|
||||
for element in value:
|
||||
encoder.write(element)
|
||||
encoder.finishTuple()
|
||||
|
||||
proc startTuple*(writer: var AbiWriter) =
|
||||
writer.tuples.add(Tuple(start: writer.bytes.len))
|
||||
proc encode[T](encoder: var AbiEncoder, value: seq[T]) =
|
||||
encoder.encode(value.len.uint64)
|
||||
encoder.startTuple()
|
||||
for element in value:
|
||||
encoder.write(element)
|
||||
encoder.finishTuple()
|
||||
encoder.setDynamic()
|
||||
|
||||
proc finishTuple*(writer: var AbiWriter) =
|
||||
let tupl = writer.tuples.pop()
|
||||
for split in tupl.postponed:
|
||||
let offset = writer.bytes.len - tupl.start
|
||||
writer.bytes[split.head] = Abi.encode(offset.uint64)
|
||||
writer.bytes.add(split.tail)
|
||||
proc write*[T](encoder: var AbiEncoder, value: T) =
|
||||
var writer = AbiEncoder.init()
|
||||
writer.encode(value)
|
||||
encoder.encode(writer.stack[0])
|
||||
|
||||
proc write*[I, T](writer: var AbiWriter, value: array[I, T]) =
|
||||
if writer.tuples.len == 0 or Abi.isStatic(T):
|
||||
writer.startTuple()
|
||||
for element in value:
|
||||
writer.write(element)
|
||||
writer.finishTuple()
|
||||
else:
|
||||
writer.writeLater(value)
|
||||
proc finish*(encoder: var AbiEncoder): seq[byte] =
|
||||
doAssert encoder.stack.len == 1, "not all tuples were finished"
|
||||
doAssert encoder.stack[0].bytes.len mod 32 == 0, "encoding invariant broken"
|
||||
encoder.stack[0].bytes
|
||||
|
||||
proc write*[T](writer: var AbiWriter, value: seq[T]) =
|
||||
if writer.tuples.len == 0:
|
||||
writer.write(value.len.uint64)
|
||||
writer.startTuple()
|
||||
for element in value:
|
||||
writer.write(element)
|
||||
writer.finishTuple()
|
||||
else:
|
||||
writer.writeLater(value)
|
||||
|
||||
proc finish*(writer: var AbiWriter): seq[byte] =
|
||||
doAssert writer.tuples.len == 0, "not all tuples were finished"
|
||||
doAssert writer.bytes.len mod 32 == 0, "encoding invariant broken"
|
||||
writer.bytes
|
||||
|
||||
proc encode*[T](_: type Abi, value: T): seq[byte] =
|
||||
var writer: AbiWriter
|
||||
writer.write(value)
|
||||
writer.finish()
|
||||
proc encode*[T](_: type AbiEncoder, value: T): seq[byte] =
|
||||
var encoder = AbiEncoder.init()
|
||||
encoder.write(value)
|
||||
encoder.finish()
|
||||
|
||||
@ -11,10 +11,10 @@ type
|
||||
chainId*: UInt256
|
||||
|
||||
proc getChannelId*(channel: Channel): array[32, byte] =
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(channel.chainId)
|
||||
writer.write(channel.participants)
|
||||
writer.write(channel.nonce)
|
||||
writer.finishTuple()
|
||||
keccak256.digest(writer.finish()).data
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(channel.chainId)
|
||||
encoder.write(channel.participants)
|
||||
encoder.write(channel.nonce)
|
||||
encoder.finishTuple()
|
||||
keccak256.digest(encoder.finish()).data
|
||||
|
||||
@ -17,7 +17,7 @@ type
|
||||
allocation*: Allocation
|
||||
of guaranteeType:
|
||||
guarantee*: Guarantee
|
||||
Allocation* = seq[AllocationItem]
|
||||
Allocation* = distinct seq[AllocationItem]
|
||||
AllocationItem* = object
|
||||
destination*: array[32, byte]
|
||||
amount*: UInt256
|
||||
@ -25,41 +25,47 @@ type
|
||||
targetChannelId*: array[32, byte]
|
||||
destinations*: seq[array[32, byte]]
|
||||
|
||||
proc isStatic*(_: type Abi, t: type AssetOutcome): bool = false
|
||||
proc isStatic*(_: type Abi, t: type AllocationItem): bool = true
|
||||
proc isStatic*(_: type Abi, t: type Guarantee): bool = false
|
||||
proc encode*(encoder: var AbiEncoder, guarantee: Guarantee) =
|
||||
encoder.startTuple()
|
||||
encoder.startTuple()
|
||||
encoder.write(guarantee.targetChannelId)
|
||||
encoder.write(guarantee.destinations)
|
||||
encoder.finishTuple()
|
||||
encoder.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, guarantee: Guarantee) =
|
||||
writer.startTuple()
|
||||
writer.write(guarantee.targetChannelId)
|
||||
writer.write(guarantee.destinations)
|
||||
writer.finishTuple()
|
||||
proc encode*(encoder: var AbiEncoder, item: AllocationItem) =
|
||||
encoder.startTuple()
|
||||
encoder.write(item.destination)
|
||||
encoder.write(item.amount)
|
||||
encoder.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, item: AllocationItem) =
|
||||
writer.startTuple()
|
||||
writer.write(item.destination)
|
||||
writer.write(item.amount)
|
||||
writer.finishTuple()
|
||||
proc encode*(encoder: var AbiEncoder, allocation: Allocation) =
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AllocationItem](allocation))
|
||||
encoder.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, assetOutcome: AssetOutcome) =
|
||||
var content: AbiWriter
|
||||
proc encode*(encoder: var AbiEncoder, assetOutcome: AssetOutcome) =
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(assetOutcome.kind)
|
||||
case assetOutcome.kind:
|
||||
of allocationType:
|
||||
content.write(Abi.encode(assetOutcome.allocation))
|
||||
content.write(AbiEncoder.encode(assetOutcome.allocation))
|
||||
of guaranteeType:
|
||||
content.write(Abi.encode(assetOutcome.guarantee))
|
||||
content.write(AbiEncoder.encode(assetOutcome.guarantee))
|
||||
content.finishTuple()
|
||||
content.finishTuple()
|
||||
writer.startTuple()
|
||||
writer.write(assetOutcome.assetHolder)
|
||||
writer.write(content.finish())
|
||||
writer.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, outcome: Outcome) =
|
||||
writer.startTuple()
|
||||
writer.write(seq[AssetOutcome](outcome))
|
||||
writer.finishTuple()
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
|
||||
proc encode*(encoder: var AbiEncoder, outcome: Outcome) =
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AssetOutcome](outcome))
|
||||
encoder.finishTuple()
|
||||
|
||||
proc hashOutcome*(outcome: Outcome): array[32, byte] =
|
||||
keccak256.digest(Abi.encode(outcome)).data
|
||||
keccak256.digest(AbiEncoder.encode(outcome)).data
|
||||
|
||||
@ -38,26 +38,26 @@ proc fixedPart*(state: State): FixedPart =
|
||||
|
||||
proc variablePart*(state: State): VariablePart =
|
||||
VariablePart(
|
||||
outcome: Abi.encode(state.outcome),
|
||||
outcome: AbiEncoder.encode(state.outcome),
|
||||
appData: state.appData
|
||||
)
|
||||
|
||||
proc hashAppPart*(state: State): array[32, byte] =
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(state.challengeDuration)
|
||||
writer.write(state.appDefinition)
|
||||
writer.write(state.appData)
|
||||
writer.finishTuple()
|
||||
keccak256.digest(writer.finish).data
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(state.challengeDuration)
|
||||
encoder.write(state.appDefinition)
|
||||
encoder.write(state.appData)
|
||||
encoder.finishTuple()
|
||||
keccak256.digest(encoder.finish).data
|
||||
|
||||
proc hashState*(state: State): array[32, byte] =
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(state.turnNum)
|
||||
writer.write(state.isFinal)
|
||||
writer.write(getChannelId(state.channel))
|
||||
writer.write(hashAppPart(state))
|
||||
writer.write(hashOutcome(state.outcome))
|
||||
writer.finishTuple()
|
||||
keccak256.digest(writer.finish).data
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(state.turnNum)
|
||||
encoder.write(state.isFinal)
|
||||
encoder.write(getChannelId(state.channel))
|
||||
encoder.write(hashAppPart(state))
|
||||
encoder.write(hashOutcome(state.outcome))
|
||||
encoder.finishTuple()
|
||||
keccak256.digest(encoder.finish).data
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import std/math
|
||||
import pkg/stint
|
||||
import pkg/stew/byteutils
|
||||
|
||||
export stint
|
||||
|
||||
@ -10,4 +11,7 @@ type
|
||||
proc toArray*(address: EthAddress): array[20, byte] =
|
||||
array[20, byte](address)
|
||||
|
||||
proc fromHex*(_: type EthAddress, hex: string): EthAddress =
|
||||
EthAddress(array[20, byte].fromHex(hex))
|
||||
|
||||
proc `==`*(a, b: EthAddress): bool {.borrow.}
|
||||
|
||||
@ -46,6 +46,9 @@ proc example*(_: type Guarantee): Guarantee =
|
||||
destinations: seq[array[32, byte]].example
|
||||
)
|
||||
|
||||
proc example*(_: type Allocation): Allocation =
|
||||
Allocation(seq[AllocationItem].example)
|
||||
|
||||
proc example*(_: type AssetOutcome): AssetOutcome =
|
||||
let kind = rand(AssetOutcomeType.low..AssetOutcomeType.high)
|
||||
case kind:
|
||||
|
||||
@ -10,152 +10,153 @@ suite "ABI encoding":
|
||||
newSeq[byte](amount)
|
||||
|
||||
test "encodes uint8":
|
||||
check Abi.encode(42'u8) == 31.zeroes & 42'u8
|
||||
check AbiEncoder.encode(42'u8) == 31.zeroes & 42'u8
|
||||
|
||||
test "encodes booleans":
|
||||
check Abi.encode(false) == 31.zeroes & 0'u8
|
||||
check Abi.encode(true) == 31.zeroes & 1'u8
|
||||
check AbiEncoder.encode(false) == 31.zeroes & 0'u8
|
||||
check AbiEncoder.encode(true) == 31.zeroes & 1'u8
|
||||
|
||||
test "encodes uint16, 32, 64":
|
||||
check Abi.encode(0xABCD'u16) ==
|
||||
check AbiEncoder.encode(0xABCD'u16) ==
|
||||
30.zeroes & 0xAB'u8 & 0xCD'u8
|
||||
check Abi.encode(0x11223344'u32) ==
|
||||
check AbiEncoder.encode(0x11223344'u32) ==
|
||||
28.zeroes & 0x11'u8 & 0x22'u8 & 0x33'u8 & 0x44'u8
|
||||
check Abi.encode(0x1122334455667788'u64) ==
|
||||
check AbiEncoder.encode(0x1122334455667788'u64) ==
|
||||
24.zeroes &
|
||||
0x11'u8 & 0x22'u8 & 0x33'u8 & 0x44'u8 &
|
||||
0x55'u8 & 0x66'u8 & 0x77'u8 & 0x88'u8
|
||||
|
||||
test "encodes ranges":
|
||||
type SomeRange = range[0x0000'u16..0xAAAA'u16]
|
||||
check Abi.encode(SomeRange(0x1122)) == 30.zeroes & 0x11'u8 & 0x22'u8
|
||||
check AbiEncoder.encode(SomeRange(0x1122)) == 30.zeroes & 0x11'u8 & 0x22'u8
|
||||
|
||||
test "encodes enums":
|
||||
type SomeEnum = enum
|
||||
one = 1
|
||||
two = 2
|
||||
check Abi.encode(one) == 31.zeroes & 1'u8
|
||||
check Abi.encode(two) == 31.zeroes & 2'u8
|
||||
check AbiEncoder.encode(one) == 31.zeroes & 1'u8
|
||||
check AbiEncoder.encode(two) == 31.zeroes & 2'u8
|
||||
|
||||
test "encodes stints":
|
||||
let uint256 = UInt256.example
|
||||
check Abi.encode(uint256) == @(uint256.toBytesBE)
|
||||
check AbiEncoder.encode(uint256) == @(uint256.toBytesBE)
|
||||
let uint128 = UInt128.example
|
||||
check Abi.encode(uint128) == 16.zeroes & @(uint128.toBytesBE)
|
||||
check AbiEncoder.encode(uint128) == 16.zeroes & @(uint128.toBytesBE)
|
||||
|
||||
test "encodes byte arrays":
|
||||
let bytes3 = [1'u8, 2'u8, 3'u8]
|
||||
check Abi.encode(bytes3) == @bytes3 & 29.zeroes
|
||||
check AbiEncoder.encode(bytes3) == @bytes3 & 29.zeroes
|
||||
let bytes32 = array[32, byte].example
|
||||
check Abi.encode(bytes32) == @bytes32
|
||||
check AbiEncoder.encode(bytes32) == @bytes32
|
||||
let bytes33 = array[33, byte].example
|
||||
check Abi.encode(bytes33) == @bytes33 & 31.zeroes
|
||||
check AbiEncoder.encode(bytes33) == @bytes33 & 31.zeroes
|
||||
|
||||
test "encodes byte sequences":
|
||||
let bytes3 = @[1'u8, 2'u8, 3'u8]
|
||||
let bytes3len = Abi.encode(bytes3.len.uint64)
|
||||
check Abi.encode(bytes3) == bytes3len & bytes3 & 29.zeroes
|
||||
let bytes3len = AbiEncoder.encode(bytes3.len.uint64)
|
||||
check AbiEncoder.encode(bytes3) == bytes3len & bytes3 & 29.zeroes
|
||||
let bytes32 = @(array[32, byte].example)
|
||||
let bytes32len = Abi.encode(bytes32.len.uint64)
|
||||
check Abi.encode(bytes32) == bytes32len & bytes32
|
||||
let bytes32len = AbiEncoder.encode(bytes32.len.uint64)
|
||||
check AbiEncoder.encode(bytes32) == bytes32len & bytes32
|
||||
let bytes33 = @(array[33, byte].example)
|
||||
let bytes33len = Abi.encode(bytes33.len.uint64)
|
||||
check Abi.encode(bytes33) == bytes33len & bytes33 & 31.zeroes
|
||||
let bytes33len = AbiEncoder.encode(bytes33.len.uint64)
|
||||
check AbiEncoder.encode(bytes33) == bytes33len & bytes33 & 31.zeroes
|
||||
|
||||
test "encodes ethereum addresses":
|
||||
let address = EthAddress.example
|
||||
check Abi.encode(address) == 12.zeroes & @(address.toArray)
|
||||
check AbiEncoder.encode(address) == 12.zeroes & @(address.toArray)
|
||||
|
||||
test "encodes tuples":
|
||||
let a = true
|
||||
let b = @[1'u8, 2'u8, 3'u8]
|
||||
let c = 0xAABBCCDD'u32
|
||||
let d = @[4'u8, 5'u8, 6'u8]
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(a)
|
||||
writer.write(b)
|
||||
writer.write(c)
|
||||
writer.write(d)
|
||||
writer.finishTuple()
|
||||
check writer.finish() ==
|
||||
Abi.encode(a) &
|
||||
Abi.encode(4 * 32'u8) & # offset from start of tuple
|
||||
Abi.encode(c) &
|
||||
Abi.encode(6 * 32'u8) & # offset from start of tuple
|
||||
Abi.encode(b) &
|
||||
Abi.encode(d)
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(a)
|
||||
encoder.write(b)
|
||||
encoder.write(c)
|
||||
encoder.write(d)
|
||||
encoder.finishTuple()
|
||||
check encoder.finish() ==
|
||||
AbiEncoder.encode(a) &
|
||||
AbiEncoder.encode(4 * 32'u8) & # offset in tuple
|
||||
AbiEncoder.encode(c) &
|
||||
AbiEncoder.encode(6 * 32'u8) & # offset in tuple
|
||||
AbiEncoder.encode(b) &
|
||||
AbiEncoder.encode(d)
|
||||
|
||||
test "encodes nested tuples":
|
||||
let a = true
|
||||
let b = @[1'u8, 2'u8, 3'u8]
|
||||
let c = 0xAABBCCDD'u32
|
||||
let d = @[4'u8, 5'u8, 6'u8]
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(a)
|
||||
writer.write(b)
|
||||
writer.startTuple()
|
||||
writer.write(c)
|
||||
writer.write(d)
|
||||
writer.finishTuple()
|
||||
writer.finishTuple()
|
||||
check writer.finish() ==
|
||||
Abi.encode(a) &
|
||||
Abi.encode(6 * 32'u8) & # offset from start of tuple
|
||||
Abi.encode(c) &
|
||||
Abi.encode(2 * 32'u8) & # offset from start of tuple
|
||||
Abi.encode(d) &
|
||||
Abi.encode(b)
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(a)
|
||||
encoder.write(b)
|
||||
encoder.startTuple()
|
||||
encoder.write(c)
|
||||
encoder.write(d)
|
||||
encoder.finishTuple()
|
||||
encoder.finishTuple()
|
||||
check encoder.finish() ==
|
||||
AbiEncoder.encode(a) &
|
||||
AbiEncoder.encode(3 * 32'u8) & # offset of b in outer tuple
|
||||
AbiEncoder.encode(5 * 32'u8) & # offset of inner tuple in outer tuple
|
||||
AbiEncoder.encode(b) &
|
||||
AbiEncoder.encode(c) &
|
||||
AbiEncoder.encode(2 * 32'u8) & # offset of d in inner tuple
|
||||
AbiEncoder.encode(d)
|
||||
|
||||
test "encodes arrays":
|
||||
let element1 = seq[byte].example
|
||||
let element2 = seq[byte].example
|
||||
var expected: AbiWriter
|
||||
var expected= AbiEncoder.init()
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check Abi.encode([element1, element2]) == expected.finish()
|
||||
check AbiEncoder.encode([element1, element2]) == expected.finish()
|
||||
|
||||
test "encodes sequences":
|
||||
let element1 = seq[byte].example
|
||||
let element2 = seq[byte].example
|
||||
var expected: AbiWriter
|
||||
var expected= AbiEncoder.init()
|
||||
expected.write(2'u8)
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check Abi.encode(@[element1, element2]) == expected.finish()
|
||||
check AbiEncoder.encode(@[element1, element2]) == expected.finish()
|
||||
|
||||
test "encodes sequence as dynamic element":
|
||||
let s = @[42.u256, 43.u256]
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(s)
|
||||
writer.finishTuple()
|
||||
check writer.finish() ==
|
||||
Abi.encode(32'u8) & # offset from start of tuple
|
||||
Abi.encode(s)
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(s)
|
||||
encoder.finishTuple()
|
||||
check encoder.finish() ==
|
||||
AbiEncoder.encode(32'u8) & # offset in tuple
|
||||
AbiEncoder.encode(s)
|
||||
|
||||
test "encodes array of static elements as static element":
|
||||
let a = [[42'u8], [43'u8]]
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(a)
|
||||
writer.finishTuple()
|
||||
check writer.finish() == Abi.encode(a)
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(a)
|
||||
encoder.finishTuple()
|
||||
check encoder.finish() == AbiEncoder.encode(a)
|
||||
|
||||
test "encodes array of dynamic elements as dynamic element":
|
||||
let a = [@[42'u8], @[43'u8]]
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(a)
|
||||
writer.finishTuple()
|
||||
check writer.finish() ==
|
||||
Abi.encode(32'u8) & # offset from start of tuple
|
||||
Abi.encode(a)
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(a)
|
||||
encoder.finishTuple()
|
||||
check encoder.finish() ==
|
||||
AbiEncoder.encode(32'u8) & # offset in tuple
|
||||
AbiEncoder.encode(a)
|
||||
|
||||
# https://medium.com/b2expand/abi-encoding-explanation-4f470927092d
|
||||
# https://docs.soliditylang.org/en/v0.8.1/abi-spec.html#formal-specification-of-the-encoding
|
||||
|
||||
@ -2,6 +2,7 @@ import std/unittest
|
||||
import pkg/nitro/channel
|
||||
import pkg/nitro/abi
|
||||
import pkg/nimcrypto
|
||||
import pkg/stew/byteutils
|
||||
import ./examples
|
||||
|
||||
suite "channel":
|
||||
@ -9,13 +10,25 @@ suite "channel":
|
||||
let channel = Channel.example
|
||||
|
||||
test "calculates channel id":
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(channel.chainId)
|
||||
writer.write(channel.participants)
|
||||
writer.write(channel.nonce)
|
||||
writer.finishTuple()
|
||||
let encoded = writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(channel.chainId)
|
||||
encoder.write(channel.participants)
|
||||
encoder.write(channel.nonce)
|
||||
encoder.finishTuple()
|
||||
let encoded = encoder.finish()
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check getChannelId(channel) == hashed
|
||||
|
||||
test "produces same id as javascript implementation":
|
||||
let channel = Channel(
|
||||
chainId: 9001.u256,
|
||||
nonce: 1,
|
||||
participants: @[
|
||||
EthAddress.fromHex("24b905Dcc8A11C0FE57C2592f3D25f0447402C10")
|
||||
]
|
||||
)
|
||||
let expected = array[32, byte].fromHex(
|
||||
"4f8cce57e9fe88edaab05234972eaf0c2d183e4f6b175aff293375fbe4d5d7cc"
|
||||
)
|
||||
check getChannelId(channel) == expected
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import std/unittest
|
||||
import pkg/nimcrypto
|
||||
import pkg/stew/byteutils
|
||||
import pkg/nitro/outcome
|
||||
import ./examples
|
||||
|
||||
@ -7,21 +8,31 @@ suite "outcome":
|
||||
|
||||
test "encodes guarantees":
|
||||
let guarantee = Guarantee.example
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(guarantee.targetChannelId)
|
||||
writer.write(guarantee.destinations)
|
||||
writer.finishTuple()
|
||||
check Abi.encode(guarantee) == writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.startTuple()
|
||||
encoder.write(guarantee.targetChannelId)
|
||||
encoder.write(guarantee.destinations)
|
||||
encoder.finishTuple()
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(guarantee) == encoder.finish()
|
||||
|
||||
test "encodes allocation items":
|
||||
let item = AllocationItem.example
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(item.destination)
|
||||
writer.write(item.amount)
|
||||
writer.finishTuple()
|
||||
check Abi.encode(item) == writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(item.destination)
|
||||
encoder.write(item.amount)
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(item) == encoder.finish()
|
||||
|
||||
test "encodes allocation":
|
||||
let allocation = Allocation.example
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AllocationItem](allocation))
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(allocation) == encoder.finish()
|
||||
|
||||
test "encodes allocation outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
@ -29,17 +40,19 @@ suite "outcome":
|
||||
assetHolder: EthAddress.example,
|
||||
allocation: Allocation.example
|
||||
)
|
||||
var content: AbiWriter
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(allocationType)
|
||||
content.write(Abi.encode(assetOutcome.allocation))
|
||||
content.write(AbiEncoder.encode(assetOutcome.allocation))
|
||||
content.finishTuple()
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(assetOutcome.assetHolder)
|
||||
writer.write(content.finish())
|
||||
writer.finishTuple()
|
||||
check Abi.encode(assetOutcome) == writer.finish()
|
||||
content.finishTuple()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(assetOutcome) == encoder.finish()
|
||||
|
||||
test "encodes guarantee outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
@ -47,28 +60,68 @@ suite "outcome":
|
||||
assetHolder: EthAddress.example,
|
||||
guarantee: Guarantee.example
|
||||
)
|
||||
var content: AbiWriter
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(guaranteeType)
|
||||
content.write(Abi.encode(assetOutcome.guarantee))
|
||||
content.write(AbiEncoder.encode(assetOutcome.guarantee))
|
||||
content.finishTuple()
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(assetOutcome.assetHolder)
|
||||
writer.write(content.finish())
|
||||
writer.finishTuple()
|
||||
check Abi.encode(assetOutcome) == writer.finish()
|
||||
content.finishTuple()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(assetOutcome) == encoder.finish()
|
||||
|
||||
test "encodes outcomes":
|
||||
let outcome = Outcome.example()
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(seq[AssetOutcome](outcome))
|
||||
writer.finishTuple()
|
||||
check Abi.encode(outcome) == writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AssetOutcome](outcome))
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(outcome) == encoder.finish()
|
||||
|
||||
test "hashes outcomes":
|
||||
let outcome = Outcome.example
|
||||
let encoded = Abi.encode(outcome)
|
||||
let encoded = AbiEncoder.encode(outcome)
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashOutcome(outcome) == hashed
|
||||
|
||||
test "produces the same encoding as the javascript implementation":
|
||||
let outcome = Outcome(@[
|
||||
AssetOutcome(
|
||||
kind: allocationType,
|
||||
assetHolder: EthAddress.fromHex(
|
||||
"1E90B49563da16D2537CA1Ddd9b1285279103D93"
|
||||
),
|
||||
allocation: Allocation(@[
|
||||
AllocationItem(
|
||||
destination: array[32, byte].fromHex(
|
||||
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
|
||||
),
|
||||
amount: 0x05.u256
|
||||
)
|
||||
])
|
||||
),
|
||||
AssetOutcome(
|
||||
kind: guaranteeType,
|
||||
assetHolder: EthAddress.fromHex(
|
||||
"1E90B49563da16D2537CA1Ddd9b1285279103D93"
|
||||
),
|
||||
guarantee: Guarantee(
|
||||
targetChannelId: array[32, byte].fromHex(
|
||||
"cac1bb71f0a97c8ac94ca9546b43178a9ad254c7b757ac07433aa6df35cd8089"
|
||||
),
|
||||
destinations: @[
|
||||
array[32, byte].fromHex(
|
||||
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
])
|
||||
let expected = fromHex(
|
||||
"53993a1bc1de832c2e04bd59491a18d43b6546ec5c611f13dc5dc56d678d228d"
|
||||
)
|
||||
check hashOutcome(outcome) == expected
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import std/unittest
|
||||
import pkg/nimcrypto
|
||||
import pkg/stew/byteutils
|
||||
import pkg/nitro
|
||||
import pkg/nitro/state
|
||||
import pkg/nitro/abi
|
||||
@ -20,31 +21,51 @@ suite "state":
|
||||
|
||||
test "has a variable part":
|
||||
check state.variablePart == VariablePart(
|
||||
outcome: Abi.encode(state.outcome),
|
||||
outcome: AbiEncoder.encode(state.outcome),
|
||||
appData: state.appData
|
||||
)
|
||||
|
||||
test "hashes app part of state":
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(state.challengeDuration)
|
||||
writer.write(state.appDefinition)
|
||||
writer.write(state.appData)
|
||||
writer.finishTuple()
|
||||
let encoded = writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(state.challengeDuration)
|
||||
encoder.write(state.appDefinition)
|
||||
encoder.write(state.appData)
|
||||
encoder.finishTuple()
|
||||
let encoded = encoder.finish()
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashAppPart(state) == hashed
|
||||
|
||||
test "hashes state":
|
||||
var writer: AbiWriter
|
||||
writer.startTuple()
|
||||
writer.write(state.turnNum)
|
||||
writer.write(state.isFinal)
|
||||
writer.write(getChannelId(state.channel))
|
||||
writer.write(hashAppPart(state))
|
||||
writer.write(hashOutcome(state.outcome))
|
||||
writer.finishTuple()
|
||||
let encoded = writer.finish()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(state.turnNum)
|
||||
encoder.write(state.isFinal)
|
||||
encoder.write(getChannelId(state.channel))
|
||||
encoder.write(hashAppPart(state))
|
||||
encoder.write(hashOutcome(state.outcome))
|
||||
encoder.finishTuple()
|
||||
let encoded = encoder.finish()
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashState(state) == hashed
|
||||
|
||||
test "produces the same hash as the javascript implementation":
|
||||
let state = State(
|
||||
channel: Channel(
|
||||
chainId: 0x1.u256,
|
||||
nonce: 1,
|
||||
participants: @[
|
||||
EthAddress.fromHex("DBE821484648c73C1996Da25f2355342B9803eBD")
|
||||
]
|
||||
),
|
||||
outcome: Outcome(@[]),
|
||||
turnNum: 1,
|
||||
isFinal: false,
|
||||
appData: @[0'u8],
|
||||
appDefinition: EthAddress.default,
|
||||
challengeDuration: 5
|
||||
)
|
||||
let expected = array[32, byte].fromHex(
|
||||
"8f515b04e6120bffadc159b5e117297bb7c135337d4ec9c0468bcf298292f46d"
|
||||
)
|
||||
check hashState(state) == expected
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user