mirror of
https://github.com/logos-storage/nim-nitro.git
synced 2026-01-02 13:43:06 +00:00
Nitro state, including ABI encoding and hashing
This commit is contained in:
commit
ab2a39a704
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
[*]
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*
|
||||
!*/
|
||||
!*.*
|
||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
nim 1.4.2
|
||||
9
nitro.nimble
Normal file
9
nitro.nimble
Normal file
@ -0,0 +1,9 @@
|
||||
version = "0.1.0"
|
||||
author = "Nim Nitro developers"
|
||||
license = "MIT"
|
||||
description = "Nitro state channels"
|
||||
|
||||
requires "nim >= 1.2.6 & < 2.0.0"
|
||||
requires "nimcrypto >= 0.5.4 & < 0.6.0"
|
||||
requires "stint"
|
||||
requires "stew"
|
||||
101
nitro/abi.nim
Normal file
101
nitro/abi.nim
Normal file
@ -0,0 +1,101 @@
|
||||
import pkg/stew/endians2
|
||||
import pkg/stint
|
||||
|
||||
type
|
||||
Abi* = object
|
||||
AbiWriter* = object
|
||||
bytes: seq[byte]
|
||||
tuples: seq[Tuple]
|
||||
Tuple = object
|
||||
postponed: seq[Split]
|
||||
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 encode*[T](_: type Abi, value: T): seq[byte]
|
||||
|
||||
proc pad(writer: var AbiWriter, len: int) =
|
||||
let padlen = (32 - len mod 32) mod 32
|
||||
for _ in 0..<padlen:
|
||||
writer.bytes.add(0'u8)
|
||||
|
||||
proc padleft(writer: var AbiWriter, bytes: openArray[byte]) =
|
||||
writer.pad(bytes.len)
|
||||
writer.bytes.add(bytes)
|
||||
|
||||
proc padright(writer: var AbiWriter, bytes: openArray[byte]) =
|
||||
writer.bytes.add(bytes)
|
||||
writer.pad(bytes.len)
|
||||
|
||||
proc write*(writer: var AbiWriter, value: SomeUnsignedInt | StUint) =
|
||||
writer.padleft(value.toBytesBE)
|
||||
|
||||
proc write*(writer: var AbiWriter, value: bool) =
|
||||
writer.write(cast[uint8](value))
|
||||
|
||||
proc write*(writer: var AbiWriter, value: enum) =
|
||||
writer.write(uint64(ord(value)))
|
||||
|
||||
proc write*[I](writer: var AbiWriter, bytes: array[I, byte]) =
|
||||
writer.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 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 startTuple*(writer: var AbiWriter) =
|
||||
writer.tuples.add(Tuple())
|
||||
|
||||
proc finishTuple*(writer: var AbiWriter) =
|
||||
let tupl = writer.tuples.pop()
|
||||
for split in tupl.postponed:
|
||||
let offset = writer.bytes.len - split.head.a
|
||||
writer.bytes[split.head] = Abi.encode(offset.uint64)
|
||||
writer.bytes.add(split.tail)
|
||||
|
||||
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 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()
|
||||
18
nitro/channel.nim
Normal file
18
nitro/channel.nim
Normal file
@ -0,0 +1,18 @@
|
||||
import pkg/nimcrypto
|
||||
import ./abi
|
||||
import ./types
|
||||
|
||||
export types
|
||||
|
||||
type
|
||||
Channel* = object
|
||||
nonce*: UInt48
|
||||
participants*: seq[EthAddress]
|
||||
chainId*: UInt256
|
||||
|
||||
proc getChannelId*(channel: Channel): array[32, byte] =
|
||||
var writer: AbiWriter
|
||||
writer.write(channel.chainId)
|
||||
writer.write(channel.participants)
|
||||
writer.write(channel.nonce)
|
||||
keccak256.digest(writer.finish()).data
|
||||
60
nitro/outcome.nim
Normal file
60
nitro/outcome.nim
Normal file
@ -0,0 +1,60 @@
|
||||
import pkg/nimcrypto
|
||||
import ./abi
|
||||
import ./types
|
||||
|
||||
export types
|
||||
export abi
|
||||
|
||||
type
|
||||
Outcome* = seq[AssetOutcome]
|
||||
AssetOutcomeType* = enum
|
||||
allocationType = 0
|
||||
guaranteeType = 1
|
||||
AssetOutcome* = object
|
||||
assetHolder*: EthAddress
|
||||
case kind*: AssetOutcomeType
|
||||
of allocationType:
|
||||
allocation*: Allocation
|
||||
of guaranteeType:
|
||||
guarantee*: Guarantee
|
||||
Allocation* = seq[AllocationItem]
|
||||
AllocationItem* = object
|
||||
destination*: array[32, byte]
|
||||
amount*: UInt256
|
||||
Guarantee* = object
|
||||
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 write*(writer: var AbiWriter, guarantee: Guarantee) =
|
||||
writer.startTuple()
|
||||
writer.write(guarantee.targetChannelId)
|
||||
writer.write(guarantee.destinations)
|
||||
writer.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, item: AllocationItem) =
|
||||
writer.startTuple()
|
||||
writer.write(item.destination)
|
||||
writer.write(item.amount)
|
||||
writer.finishTuple()
|
||||
|
||||
proc write*(writer: var AbiWriter, assetOutcome: AssetOutcome) =
|
||||
var content: AbiWriter
|
||||
content.startTuple()
|
||||
content.write(assetOutcome.kind)
|
||||
case assetOutcome.kind:
|
||||
of allocationType:
|
||||
content.write(Abi.encode(assetOutcome.allocation))
|
||||
of guaranteeType:
|
||||
content.write(Abi.encode(assetOutcome.guarantee))
|
||||
content.finishTuple()
|
||||
writer.startTuple()
|
||||
writer.write(assetOutcome.assetHolder)
|
||||
writer.write(content.finish())
|
||||
writer.finishTuple()
|
||||
|
||||
proc hashOutcome*(outcome: Outcome): array[32, byte] =
|
||||
keccak256.digest(Abi.encode(outcome)).data
|
||||
61
nitro/state.nim
Normal file
61
nitro/state.nim
Normal file
@ -0,0 +1,61 @@
|
||||
import pkg/nimcrypto
|
||||
import ./types
|
||||
import ./channel
|
||||
import ./outcome
|
||||
import ./abi
|
||||
|
||||
export types
|
||||
export channel
|
||||
export outcome
|
||||
|
||||
type
|
||||
State* = object
|
||||
turnNum*: UInt48
|
||||
isFinal*: bool
|
||||
channel*: Channel
|
||||
challengeDuration*: UInt48
|
||||
outcome*: Outcome
|
||||
appDefinition*: EthAddress
|
||||
appData*: seq[byte]
|
||||
FixedPart* = object
|
||||
chainId*: UInt256
|
||||
participants*: seq[EthAddress]
|
||||
channelNonce*: UInt48
|
||||
appDefinition*: EthAddress
|
||||
challengeDuration*: UInt48
|
||||
VariablePart* = object
|
||||
outcome*: seq[byte]
|
||||
appdata*: seq[byte]
|
||||
|
||||
proc fixedPart*(state: State): FixedPart =
|
||||
FixedPart(
|
||||
chainId: state.channel.chainId,
|
||||
participants: state.channel.participants,
|
||||
channelNonce: state.channel.nonce,
|
||||
appDefinition: state.appDefinition,
|
||||
challengeDuration: state.challengeDuration
|
||||
)
|
||||
|
||||
proc variablePart*(state: State): VariablePart =
|
||||
VariablePart(
|
||||
outcome: Abi.encode(state.outcome),
|
||||
appData: state.appData
|
||||
)
|
||||
|
||||
proc hashAppPart*(state: State): array[32, byte] =
|
||||
var writer: AbiWriter
|
||||
writer.write(state.challengeDuration)
|
||||
writer.write(state.appDefinition)
|
||||
writer.write(state.appData)
|
||||
keccak256.digest(writer.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
|
||||
8
nitro/types.nim
Normal file
8
nitro/types.nim
Normal file
@ -0,0 +1,8 @@
|
||||
import std/math
|
||||
import pkg/stint
|
||||
|
||||
export stint
|
||||
|
||||
type
|
||||
UInt48* = range[0'u64..2'u64^48-1]
|
||||
EthAddress* = array[20, byte]
|
||||
71
tests/nitro/examples.nim
Normal file
71
tests/nitro/examples.nim
Normal file
@ -0,0 +1,71 @@
|
||||
import std/random
|
||||
import std/sequtils
|
||||
import pkg/nitro
|
||||
|
||||
randomize()
|
||||
|
||||
proc example*(_: type bool): bool =
|
||||
rand(0'u8..1'u8) == 1
|
||||
|
||||
proc example*[T: SomeInteger](_: type T): T =
|
||||
rand(T)
|
||||
|
||||
proc example*[I: static int, T](_: type array[I, T]): array[I, T] =
|
||||
for i in 0..<I:
|
||||
result[i] = T.example
|
||||
|
||||
proc example*[T](_: type seq[T], len = 0..5): seq[T] =
|
||||
let chosenlen = rand(len)
|
||||
newSeqWith(chosenlen, T.example)
|
||||
|
||||
proc example*(_: type UInt256): UInt256 =
|
||||
UInt256.fromBytes(array[32, byte].example)
|
||||
|
||||
proc example*(_: type UInt128): UInt128 =
|
||||
UInt128.fromBytes(array[16, byte].example)
|
||||
|
||||
proc example*(_: type Channel): Channel =
|
||||
Channel(
|
||||
nonce: UInt48.example,
|
||||
participants: seq[EthAddress].example(2..5),
|
||||
chainId: UInt256.example
|
||||
)
|
||||
|
||||
proc example*(_: type AllocationItem): AllocationItem =
|
||||
AllocationItem(
|
||||
destination: array[32, byte].example,
|
||||
amount: UInt256.example
|
||||
)
|
||||
|
||||
proc example*(_: type Guarantee): Guarantee =
|
||||
Guarantee(
|
||||
targetChannelId: array[32, byte].example,
|
||||
destinations: seq[array[32, byte]].example
|
||||
)
|
||||
|
||||
proc example*(_: type AssetOutcome): AssetOutcome =
|
||||
let kind = rand(AssetOutcomeType.low..AssetOutcomeType.high)
|
||||
case kind:
|
||||
of allocationType:
|
||||
AssetOutcome(
|
||||
kind: allocationType,
|
||||
assetHolder: EthAddress.example,
|
||||
allocation: Allocation.example
|
||||
)
|
||||
of guaranteeType:
|
||||
AssetOutcome(
|
||||
kind: guaranteeType,
|
||||
assetHolder: EthAddress.example,
|
||||
guarantee: Guarantee.example
|
||||
)
|
||||
|
||||
proc example*(_: type State): State =
|
||||
State(
|
||||
turnNum: UInt48.example,
|
||||
isFinal: bool.example,
|
||||
channel: Channel.example,
|
||||
challengeDuration: UInt48.example,
|
||||
outcome: Outcome.example,
|
||||
appDefinition: EthAddress.example,
|
||||
appData: seq[byte].example
|
||||
)
|
||||
1
tests/nitro/nim.cfg
Normal file
1
tests/nitro/nim.cfg
Normal file
@ -0,0 +1 @@
|
||||
--path:"../.."
|
||||
156
tests/nitro/testAbi.nim
Normal file
156
tests/nitro/testAbi.nim
Normal file
@ -0,0 +1,156 @@
|
||||
import std/unittest
|
||||
import pkg/nitro/abi
|
||||
import pkg/stint
|
||||
import ./examples
|
||||
|
||||
suite "ABI encoding":
|
||||
|
||||
proc zeroes(amount: int): seq[byte] =
|
||||
newSeq[byte](amount)
|
||||
|
||||
test "encodes uint8":
|
||||
check Abi.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
|
||||
|
||||
test "encodes uint16, 32, 64":
|
||||
check Abi.encode(0xABCD'u16) ==
|
||||
30.zeroes & 0xAB'u8 & 0xCD'u8
|
||||
check Abi.encode(0x11223344'u32) ==
|
||||
28.zeroes & 0x11'u8 & 0x22'u8 & 0x33'u8 & 0x44'u8
|
||||
check Abi.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
|
||||
|
||||
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
|
||||
|
||||
test "encodes stints":
|
||||
let uint256 = UInt256.example
|
||||
check Abi.encode(uint256) == @(uint256.toBytesBE)
|
||||
let uint128 = UInt128.example
|
||||
check Abi.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
|
||||
let bytes32 = array[32, byte].example
|
||||
check Abi.encode(bytes32) == @bytes32
|
||||
let bytes33 = array[33, byte].example
|
||||
check Abi.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 bytes32 = @(array[32, byte].example)
|
||||
let bytes32len = Abi.encode(bytes32.len.uint64)
|
||||
check Abi.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
|
||||
|
||||
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(3 * 32'u8) & # offset in tuple
|
||||
Abi.encode(c) &
|
||||
Abi.encode(3 * 32'u8) & # offset in tuple
|
||||
Abi.encode(b) &
|
||||
Abi.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(5 * 32'u8) & # offset in tuple
|
||||
Abi.encode(c) &
|
||||
Abi.encode(1 * 32'u8) & # offset in tuple
|
||||
Abi.encode(d) &
|
||||
Abi.encode(b)
|
||||
|
||||
test "encodes arrays":
|
||||
let element1 = seq[byte].example
|
||||
let element2 = seq[byte].example
|
||||
var expected: AbiWriter
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check Abi.encode([element1, element2]) == expected.finish()
|
||||
|
||||
test "encodes sequences":
|
||||
let element1 = seq[byte].example
|
||||
let element2 = seq[byte].example
|
||||
var expected: AbiWriter
|
||||
expected.write(2'u8)
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check Abi.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 in tuple
|
||||
Abi.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)
|
||||
|
||||
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 in tuple
|
||||
Abi.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
|
||||
19
tests/nitro/testChannel.nim
Normal file
19
tests/nitro/testChannel.nim
Normal file
@ -0,0 +1,19 @@
|
||||
import std/unittest
|
||||
import pkg/nitro/channel
|
||||
import pkg/nitro/abi
|
||||
import pkg/nimcrypto
|
||||
import ./examples
|
||||
|
||||
suite "channel":
|
||||
|
||||
let channel = Channel.example
|
||||
|
||||
test "calculates channel id":
|
||||
var writer: AbiWriter
|
||||
writer.write(channel.chainId)
|
||||
writer.write(channel.participants)
|
||||
writer.write(channel.nonce)
|
||||
let encoded = writer.finish()
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check getChannelId(channel) == hashed
|
||||
|
||||
66
tests/nitro/testOutcome.nim
Normal file
66
tests/nitro/testOutcome.nim
Normal file
@ -0,0 +1,66 @@
|
||||
import std/unittest
|
||||
import pkg/nimcrypto
|
||||
import pkg/nitro/outcome
|
||||
import ./examples
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
test "encodes allocation outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
kind: allocationType,
|
||||
assetHolder: EthAddress.example,
|
||||
allocation: Allocation.example
|
||||
)
|
||||
var content: AbiWriter
|
||||
content.startTuple()
|
||||
content.write(allocationType)
|
||||
content.write(Abi.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()
|
||||
|
||||
test "encodes guarantee outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
kind: guaranteeType,
|
||||
assetHolder: EthAddress.example,
|
||||
guarantee: Guarantee.example
|
||||
)
|
||||
var content: AbiWriter
|
||||
content.startTuple()
|
||||
content.write(guaranteeType)
|
||||
content.write(Abi.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()
|
||||
|
||||
test "hashes outcomes":
|
||||
let outcome = Outcome.example
|
||||
let encoded = Abi.encode(outcome)
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashOutcome(outcome) == hashed
|
||||
48
tests/nitro/testState.nim
Normal file
48
tests/nitro/testState.nim
Normal file
@ -0,0 +1,48 @@
|
||||
import std/unittest
|
||||
import pkg/nimcrypto
|
||||
import pkg/nitro
|
||||
import pkg/nitro/state
|
||||
import pkg/nitro/abi
|
||||
import ./examples
|
||||
|
||||
suite "state":
|
||||
|
||||
let state = State.example
|
||||
|
||||
test "has a fixed part":
|
||||
check state.fixedPart == FixedPart(
|
||||
chainId: state.channel.chainId,
|
||||
participants: state.channel.participants,
|
||||
channelNonce: state.channel.nonce,
|
||||
appDefinition: state.appDefinition,
|
||||
challengeDuration: state.challengeDuration
|
||||
)
|
||||
|
||||
test "has a variable part":
|
||||
check state.variablePart == VariablePart(
|
||||
outcome: Abi.encode(state.outcome),
|
||||
appData: state.appData
|
||||
)
|
||||
|
||||
test "hashes app part of state":
|
||||
var writer: AbiWriter
|
||||
writer.write(state.challengeDuration)
|
||||
writer.write(state.appDefinition)
|
||||
writer.write(state.appData)
|
||||
let encoded = writer.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()
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashState(state) == hashed
|
||||
|
||||
6
tests/testAll.nim
Normal file
6
tests/testAll.nim
Normal file
@ -0,0 +1,6 @@
|
||||
import ./nitro/testAbi
|
||||
import ./nitro/testChannel
|
||||
import ./nitro/testOutcome
|
||||
import ./nitro/testState
|
||||
|
||||
{.warning[UnusedImport]: off.}
|
||||
Loading…
x
Reference in New Issue
Block a user