mirror of
https://github.com/logos-storage/nim-nitro.git
synced 2026-01-02 13:43:06 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ccdeb46e0 | ||
|
|
fdcea5a2aa | ||
|
|
86d5601b4b | ||
|
|
a15660f70f | ||
|
|
309431a482 | ||
|
|
1e7358c7c4 | ||
|
|
f924a32458 | ||
|
|
f3879ed666 | ||
|
|
9cbecdde20 | ||
|
|
e3719433d5 | ||
|
|
89052f638d | ||
|
|
c13aba9942 | ||
|
|
991d56f7d1 | ||
|
|
7047da19b1 | ||
|
|
6b4c455bf4 | ||
|
|
9611c36c78 | ||
|
|
ad95604c18 | ||
|
|
5977ea47e3 | ||
|
|
39ef902177 | ||
|
|
213551c512 | ||
|
|
a8bd625e3f | ||
|
|
9396fcb7d0 | ||
|
|
adcd534eb5 | ||
|
|
2a8e4e5bf4 | ||
|
|
50d59ce48a | ||
|
|
56f6fac21e | ||
|
|
b096093931 | ||
|
|
85ffad6fb1 | ||
|
|
76cc0a9bcc | ||
|
|
04beab5b91 | ||
|
|
2ac173f87c |
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@ -8,22 +8,15 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
nim: [stable, 1.2.6]
|
||||
nim: [stable, 2.0.14]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: iffy/install-nim@v3
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
version: ${{ matrix.nim }}
|
||||
|
||||
# workaround for https://github.com/iffy/install-nim/issues/11
|
||||
- name: Workaround SSL error with choosenim
|
||||
run: |
|
||||
curl -fO https://curl.se/ca/cacert.pem
|
||||
install cacert.pem ~/.nimble/bin
|
||||
ls ~/.nimble/bin
|
||||
shell: bash
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
submodules: recursive
|
||||
- uses: jiro4989/setup-nim-action@v2
|
||||
with:
|
||||
nim-version: ${{matrix.nim}}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build
|
||||
run: nimble install -y
|
||||
- name: Test
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
*
|
||||
!*/
|
||||
!*.*
|
||||
nimble.develop
|
||||
nimble.paths
|
||||
nimbledeps
|
||||
|
||||
@ -1 +1 @@
|
||||
nim 1.4.4
|
||||
nim 1.6.2
|
||||
|
||||
5
License.md
Normal file
5
License.md
Normal file
@ -0,0 +1,5 @@
|
||||
Licensed and distributed under either of
|
||||
[MIT license](http://opensource.org/licenses/MIT) or
|
||||
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
at your option. These files may not be copied, modified, or distributed except
|
||||
according to those terms.
|
||||
@ -10,3 +10,11 @@ References
|
||||
- [statechannels.org](https://statechannels.org/)
|
||||
- [Nitro paper](https://magmo.com/nitro-protocol.pdf)
|
||||
- [Nitro smart contracts & Javascript code](https://github.com/statechannels/statechannels/tree/master/packages/nitro-protocol)
|
||||
|
||||
# Installation
|
||||
|
||||
To avoid conflicts with previous versions of `contractabi`, use the following command to install dependencies:
|
||||
|
||||
```bash
|
||||
nimble install --maximumtaggedversions=2
|
||||
```
|
||||
5
config.nims
Normal file
5
config.nims
Normal file
@ -0,0 +1,5 @@
|
||||
# begin Nimble config (version 2)
|
||||
--noNimblePath
|
||||
when withDir(thisDir(), system.fileExists("nimble.paths")):
|
||||
include "nimble.paths"
|
||||
# end Nimble config
|
||||
101
nimble.lock
Normal file
101
nimble.lock
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"version": 2,
|
||||
"packages": {
|
||||
"results": {
|
||||
"version": "0.5.1",
|
||||
"vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27",
|
||||
"url": "https://github.com/arnetheduck/nim-results",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [],
|
||||
"checksums": {
|
||||
"sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7"
|
||||
}
|
||||
},
|
||||
"unittest2": {
|
||||
"version": "0.2.5",
|
||||
"vcsRevision": "26f2ef3ae0ec72a2a75bfe557e02e88f6a31c189",
|
||||
"url": "https://github.com/status-im/nim-unittest2",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [],
|
||||
"checksums": {
|
||||
"sha1": "02bb3751ba9ddc3c17bfd89f2e41cb6bfb8fc0c9"
|
||||
}
|
||||
},
|
||||
"stew": {
|
||||
"version": "0.4.2",
|
||||
"vcsRevision": "b66168735d6f3841c5239c3169d3fe5fe98b1257",
|
||||
"url": "https://github.com/status-im/nim-stew",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [
|
||||
"results",
|
||||
"unittest2"
|
||||
],
|
||||
"checksums": {
|
||||
"sha1": "928e82cb8d2f554e8f10feb2349ee9c32fee3a8c"
|
||||
}
|
||||
},
|
||||
"stint": {
|
||||
"version": "0.8.2",
|
||||
"vcsRevision": "470b7892561b5179ab20bd389a69217d6213fe58",
|
||||
"url": "https://github.com/status-im/nim-stint",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [
|
||||
"stew",
|
||||
"unittest2"
|
||||
],
|
||||
"checksums": {
|
||||
"sha1": "d8f871fd617e7857192d4609fe003b48942a8ae5"
|
||||
}
|
||||
},
|
||||
"nimcrypto": {
|
||||
"version": "0.6.4",
|
||||
"vcsRevision": "721fb99ee099b632eb86dfad1f0d96ee87583774",
|
||||
"url": "https://github.com/cheatfate/nimcrypto",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [],
|
||||
"checksums": {
|
||||
"sha1": "f9ab24fa940ed03d0fb09729a7303feb50b7eaec"
|
||||
}
|
||||
},
|
||||
"questionable": {
|
||||
"version": "0.10.15",
|
||||
"vcsRevision": "82d90b67bcfb7f2e918b61dace2ff1a4ced60935",
|
||||
"url": "https://github.com/codex-storage/questionable",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [],
|
||||
"checksums": {
|
||||
"sha1": "3238ff637c7b44d2fa8fcb839a8ded968e389de3"
|
||||
}
|
||||
},
|
||||
"contractabi": {
|
||||
"version": "0.7.3",
|
||||
"vcsRevision": "0a7b4cecce725bcb11ad8648035a92704a8854d3",
|
||||
"url": "https://github.com/status-im/nim-contract-abi",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [
|
||||
"stint",
|
||||
"stew",
|
||||
"nimcrypto",
|
||||
"questionable"
|
||||
],
|
||||
"checksums": {
|
||||
"sha1": "1bb9af15f02a77b44af02ec94b0e392b1ec88438"
|
||||
}
|
||||
},
|
||||
"secp256k1": {
|
||||
"version": "0.6.0.3.2",
|
||||
"vcsRevision": "b526c4b436809aa1cfe650026d796cf7b8328b91",
|
||||
"url": "https://github.com/status-im/nim-secp256k1",
|
||||
"downloadMethod": "git",
|
||||
"dependencies": [
|
||||
"stew",
|
||||
"results",
|
||||
"nimcrypto"
|
||||
],
|
||||
"checksums": {
|
||||
"sha1": "e6e50bd4a29cb473b070eb5359d87d8946d96075"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tasks": {}
|
||||
}
|
||||
16
nitro.nimble
16
nitro.nimble
@ -1,12 +1,12 @@
|
||||
version = "0.1.0"
|
||||
version = "0.6.1"
|
||||
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 "questionable >= 0.4.3 & < 0.5.0"
|
||||
requires "upraises >= 0.1.0 & < 0.2.0"
|
||||
requires "secp256k1"
|
||||
requires "stint"
|
||||
requires "stew"
|
||||
requires "nim >= 2.0.14 & < 3.0.0"
|
||||
requires "nimcrypto >= 0.6.0 & < 0.7.0"
|
||||
requires "questionable >= 0.10.10 & < 0.11.0"
|
||||
requires "contractabi >= 0.7.1 & < 0.8.0"
|
||||
requires "secp256k1 >= 0.6.0 & < 0.7.0"
|
||||
requires "stint >= 0.8.0 & < 0.9.0"
|
||||
requires "stew >= 0.2.0"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/upraises
|
||||
import pkg/stint
|
||||
import ./basics/uint48
|
||||
import ./basics/ethaddress
|
||||
@ -8,7 +7,6 @@ import ./basics/destination
|
||||
|
||||
export questionable
|
||||
export results
|
||||
export upraises
|
||||
export stint
|
||||
export uint48
|
||||
export ethaddress
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import std/hashes
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/upraises
|
||||
import pkg/stew/byteutils
|
||||
import ./ethaddress
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
type Destination* = distinct array[32, byte]
|
||||
|
||||
|
||||
@ -1,21 +1,12 @@
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/stew/byteutils
|
||||
import std/hashes
|
||||
import pkg/contractabi/address
|
||||
|
||||
export questionable
|
||||
export address
|
||||
|
||||
type EthAddress* = distinct array[20, byte]
|
||||
type EthAddress* = Address
|
||||
|
||||
func zero*(_: type EthAddress): EthAddress =
|
||||
EthAddress.default
|
||||
|
||||
func toArray*(address: EthAddress): array[20, byte] =
|
||||
array[20, byte](address)
|
||||
|
||||
func `$`*(a: EthAddress): string =
|
||||
a.toArray().toHex()
|
||||
|
||||
func parse*(_: type EthAddress, hex: string): ?EthAddress =
|
||||
EthAddress(array[20, byte].fromHex(hex)).catch.option
|
||||
|
||||
proc `==`*(a, b: EthAddress): bool {.borrow.}
|
||||
proc `hash`*(a: EthAddress): Hash =
|
||||
hash(a.toArray)
|
||||
|
||||
@ -8,7 +8,7 @@ import ./wallet/signedstate
|
||||
|
||||
export signedstate
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
func `%`(value: Outcome | Allocation): JsonNode =
|
||||
type Base = distinctBase(typeof value)
|
||||
@ -37,7 +37,7 @@ func toJson*(payment: SignedState): string =
|
||||
|
||||
{.pop.}
|
||||
|
||||
push: {.upraises: [ValueError].}
|
||||
{.push raises: [ValueError].}
|
||||
|
||||
func expectKind(node: JsonNode, kind: JsonNodeKind) =
|
||||
if node.kind != kind:
|
||||
@ -46,27 +46,21 @@ func expectKind(node: JsonNode, kind: JsonNodeKind) =
|
||||
|
||||
func initFromJson*(bytes: var seq[byte], node: JsonNode, _: var string) =
|
||||
node.expectKind(JString)
|
||||
let parsed = seq[byte].fromHex(node.getStr)
|
||||
if parsed.isOk:
|
||||
bytes = parsed.get
|
||||
else:
|
||||
without parsed =? seq[byte].fromHex(node.getStr):
|
||||
raise newException(ValueError, "invalid hex string")
|
||||
bytes = parsed
|
||||
|
||||
func initFromJson*(address: var EthAddress, node: JsonNode, _: var string) =
|
||||
node.expectKind(JString)
|
||||
let parsed = EthAddress.parse(node.getStr)
|
||||
if parsed.isSome:
|
||||
address = parsed.get
|
||||
else:
|
||||
without parsed =? EthAddress.init(node.getStr):
|
||||
raise newException(ValueError, "invalid ethereum address")
|
||||
address = parsed
|
||||
|
||||
func initFromJson*(dest: var Destination, node: JsonNode, _: var string) =
|
||||
node.expectKind(JString)
|
||||
let parsed = Destination.parse(node.getStr)
|
||||
if parsed.isSome:
|
||||
dest = parsed.get
|
||||
else:
|
||||
without parsed =? Destination.parse(node.getStr):
|
||||
raise newException(ValueError, "invalid nitro destination")
|
||||
dest = parsed
|
||||
|
||||
func initFromJson*(number: var UInt256, node: JsonNode, _: var string) =
|
||||
node.expectKind(JString)
|
||||
@ -74,15 +68,13 @@ func initFromJson*(number: var UInt256, node: JsonNode, _: var string) =
|
||||
|
||||
func initFromJson*(signature: var Signature, node: JsonNode, _: var string) =
|
||||
node.expectKind(JString)
|
||||
let parsed = Signature.parse(node.getStr)
|
||||
if parsed.isSome:
|
||||
signature = parsed.get
|
||||
else:
|
||||
without parsed =? Signature.parse(node.getStr):
|
||||
raise newException(ValueError, "invalid signature")
|
||||
signature = parsed
|
||||
|
||||
{.pop.}
|
||||
|
||||
push: {.upraises: [].}
|
||||
{.push raises: [].}
|
||||
|
||||
proc fromJson*(_: type SignedState, json: string): ?SignedState =
|
||||
try:
|
||||
|
||||
@ -5,7 +5,7 @@ import ./basics
|
||||
export basics
|
||||
export toPublicKey
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
type
|
||||
EthPrivateKey* = SkSecretKey
|
||||
|
||||
@ -1,132 +1,10 @@
|
||||
import pkg/stew/endians2
|
||||
import pkg/contractabi
|
||||
import ../basics
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
export contractabi
|
||||
|
||||
type
|
||||
AbiEncoder* = object
|
||||
stack: seq[Tuple]
|
||||
Tuple = object
|
||||
bytes: seq[byte]
|
||||
postponed: seq[Split]
|
||||
dynamic: bool
|
||||
Split = object
|
||||
head: Slice[int]
|
||||
tail: seq[byte]
|
||||
|
||||
func write*[T](encoder: var AbiEncoder, value: T)
|
||||
func encode*[T](_: type AbiEncoder, value: T): seq[byte]
|
||||
|
||||
func init*(_: type AbiEncoder): AbiEncoder =
|
||||
AbiEncoder(stack: @[Tuple()])
|
||||
|
||||
func append(tupl: var Tuple, bytes: openArray[byte]) =
|
||||
tupl.bytes.add(bytes)
|
||||
|
||||
func 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)
|
||||
|
||||
func 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
|
||||
|
||||
func append(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.stack[^1].append(bytes)
|
||||
|
||||
func postpone(encoder: var AbiEncoder, bytes: seq[byte]) =
|
||||
if encoder.stack.len > 1:
|
||||
encoder.stack[^1].postpone(bytes)
|
||||
else:
|
||||
encoder.stack[0].append(bytes)
|
||||
|
||||
func setDynamic(encoder: var AbiEncoder) =
|
||||
encoder.stack[^1].dynamic = true
|
||||
|
||||
func startTuple*(encoder: var AbiEncoder) =
|
||||
encoder.stack.add(Tuple())
|
||||
|
||||
func encode(encoder: var AbiEncoder, tupl: Tuple) =
|
||||
if tupl.dynamic:
|
||||
encoder.postpone(tupl.finish())
|
||||
encoder.setDynamic()
|
||||
else:
|
||||
encoder.append(tupl.finish())
|
||||
|
||||
func finishTuple*(encoder: var AbiEncoder) =
|
||||
encoder.encode(encoder.stack.pop())
|
||||
|
||||
func pad(encoder: var AbiEncoder, len: int) =
|
||||
let padlen = (32 - len mod 32) mod 32
|
||||
for _ in 0..<padlen:
|
||||
encoder.append([0'u8])
|
||||
|
||||
func padleft(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.pad(bytes.len)
|
||||
encoder.append(bytes)
|
||||
|
||||
func padright(encoder: var AbiEncoder, bytes: openArray[byte]) =
|
||||
encoder.append(bytes)
|
||||
encoder.pad(bytes.len)
|
||||
|
||||
func encode(encoder: var AbiEncoder, value: SomeUnsignedInt | StUint) =
|
||||
encoder.padleft(value.toBytesBE)
|
||||
|
||||
func encode(encoder: var AbiEncoder, value: bool) =
|
||||
encoder.encode(cast[uint8](value))
|
||||
|
||||
func encode(encoder: var AbiEncoder, value: enum) =
|
||||
encoder.encode(uint64(ord(value)))
|
||||
|
||||
func encode[I](encoder: var AbiEncoder, bytes: array[I, byte]) =
|
||||
encoder.padright(bytes)
|
||||
|
||||
func encode(encoder: var AbiEncoder, bytes: seq[byte]) =
|
||||
encoder.encode(bytes.len.uint64)
|
||||
encoder.padright(bytes)
|
||||
encoder.setDynamic()
|
||||
|
||||
func encode(encoder: var AbiEncoder, address: EthAddress) =
|
||||
encoder.padleft(address.toArray)
|
||||
|
||||
func encode(encoder: var AbiEncoder, destination: Destination) =
|
||||
encoder.encode(destination.toArray)
|
||||
|
||||
func encode[I, T](encoder: var AbiEncoder, value: array[I, T]) =
|
||||
encoder.startTuple()
|
||||
for element in value:
|
||||
encoder.write(element)
|
||||
encoder.finishTuple()
|
||||
|
||||
func 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()
|
||||
|
||||
func write*[T](encoder: var AbiEncoder, value: T) =
|
||||
var writer = AbiEncoder.init()
|
||||
writer.encode(value)
|
||||
encoder.encode(writer.stack[0])
|
||||
|
||||
func 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
|
||||
|
||||
func encode*[T](_: type AbiEncoder, value: T): seq[byte] =
|
||||
var encoder = AbiEncoder.init()
|
||||
encoder.write(value)
|
||||
encoder.finish()
|
||||
func encode*(encoder: var AbiEncoder, destination: Destination) =
|
||||
encoder.write(destination.toArray)
|
||||
|
||||
@ -2,7 +2,7 @@ import pkg/nimcrypto
|
||||
import ../basics
|
||||
import ./abi
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
|
||||
@ -13,10 +13,6 @@ type
|
||||
chainId*: UInt256
|
||||
|
||||
func getChannelId*(channel: ChannelDefinition): Destination =
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(channel.chainId)
|
||||
encoder.write(channel.participants)
|
||||
encoder.write(channel.nonce)
|
||||
encoder.finishTuple()
|
||||
Destination(keccak256.digest(encoder.finish()).data)
|
||||
let encoding = AbiEncoder.encode:
|
||||
(channel.chainId, channel.participants, channel.nonce)
|
||||
Destination(keccak256.digest(encoding).data)
|
||||
|
||||
@ -2,7 +2,7 @@ import pkg/nimcrypto
|
||||
import ../basics
|
||||
import ./abi
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
|
||||
@ -52,46 +52,25 @@ func `==`*(a, b: AssetOutcome): bool =
|
||||
proc `==`*(a, b: Outcome): bool {.borrow.}
|
||||
|
||||
func encode*(encoder: var AbiEncoder, guarantee: Guarantee) =
|
||||
encoder.startTuple()
|
||||
encoder.startTuple()
|
||||
encoder.write(guarantee.targetChannelId)
|
||||
encoder.write(guarantee.destinations)
|
||||
encoder.finishTuple()
|
||||
encoder.finishTuple()
|
||||
|
||||
func encode*(encoder: var AbiEncoder, item: AllocationItem) =
|
||||
encoder.startTuple()
|
||||
encoder.write(item.destination)
|
||||
encoder.write(item.amount)
|
||||
encoder.finishTuple()
|
||||
encoder.write:
|
||||
( (guarantee.targetChannelId, guarantee.destinations), )
|
||||
|
||||
func encode*(encoder: var AbiEncoder, allocation: Allocation) =
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AllocationItem](allocation))
|
||||
encoder.finishTuple()
|
||||
encoder.write: (seq[AllocationItem](allocation),)
|
||||
|
||||
func encode*(encoder: var AbiEncoder, assetOutcome: AssetOutcome) =
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(assetOutcome.kind)
|
||||
var content: seq[byte]
|
||||
case assetOutcome.kind:
|
||||
of allocationType:
|
||||
content.write(AbiEncoder.encode(assetOutcome.allocation))
|
||||
content = AbiEncoder.encode:
|
||||
( (assetOutcome.kind, ABiEncoder.encode(assetOutcome.allocation)), )
|
||||
of guaranteeType:
|
||||
content.write(AbiEncoder.encode(assetOutcome.guarantee))
|
||||
content.finishTuple()
|
||||
content.finishTuple()
|
||||
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
content = AbiEncoder.encode:
|
||||
( (assetOutcome.kind, AbiEncoder.encode(assetOutcome.guarantee)), )
|
||||
encoder.write( (assetOutcome.assetHolder, content) )
|
||||
|
||||
func encode*(encoder: var AbiEncoder, outcome: Outcome) =
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AssetOutcome](outcome))
|
||||
encoder.finishTuple()
|
||||
encoder.write: (seq[AssetOutcome](outcome),)
|
||||
|
||||
func hashOutcome*(outcome: Outcome): array[32, byte] =
|
||||
keccak256.digest(AbiEncoder.encode(outcome)).data
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import pkg/secp256k1
|
||||
import pkg/nimcrypto
|
||||
import pkg/nimcrypto/keccak
|
||||
import pkg/stew/byteutils
|
||||
import ../basics
|
||||
import ../keys
|
||||
import ./state
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
export keys
|
||||
@ -32,7 +32,7 @@ func recover(signature: Signature, hash: array[32, byte]): ?EthPublicKey =
|
||||
|
||||
func recover*(signature: Signature, state: State): ?EthAddress =
|
||||
let hash = hashMessage(hashState(state))
|
||||
recover(signature, hash)?.toAddress
|
||||
recover(signature, hash).?toAddress
|
||||
|
||||
func verify*(signature: Signature, state: State, signer: EthAddress): bool =
|
||||
recover(signature, state) == signer.some
|
||||
@ -43,8 +43,7 @@ func `$`*(signature: Signature): string =
|
||||
bytes.toHex()
|
||||
|
||||
func parse*(_: type Signature, s: string): ?Signature =
|
||||
let signature = catch:
|
||||
var bytes = array[65, byte].fromHex(s)
|
||||
bytes[64] = bytes[64] - 27
|
||||
SkRecoverableSignature.fromRaw(bytes).get()
|
||||
signature.option
|
||||
without var bytes =? array[65, byte].fromHex(s).catch:
|
||||
return Signature.none
|
||||
bytes[64] = bytes[64] - 27
|
||||
SkRecoverableSignature.fromRaw(bytes).option
|
||||
|
||||
@ -4,7 +4,7 @@ import ./channel
|
||||
import ./outcome
|
||||
import ./abi
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
export channel
|
||||
@ -45,21 +45,17 @@ func variablePart*(state: State): VariablePart =
|
||||
)
|
||||
|
||||
func hashAppPart*(state: State): array[32, byte] =
|
||||
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
|
||||
let encoding = AbiEncoder.encode:
|
||||
(state.challengeDuration, state.appDefinition, state.appData)
|
||||
keccak256.digest(encoding).data
|
||||
|
||||
func hashState*(state: State): array[32, byte] =
|
||||
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
|
||||
let encoding = AbiEncoder.encode:
|
||||
(
|
||||
state.turnNum,
|
||||
state.isFinal,
|
||||
getChannelId(state.channel),
|
||||
hashAppPart(state),
|
||||
hashOutcome(state.outcome)
|
||||
)
|
||||
keccak256.digest(encoding).data
|
||||
|
||||
@ -3,7 +3,7 @@ import std/sequtils
|
||||
import ../basics
|
||||
import ../protocol
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export tables
|
||||
|
||||
@ -31,7 +31,7 @@ func move*(balances: var Balances,
|
||||
amount: UInt256): ?!void =
|
||||
try:
|
||||
if balances[source] < amount:
|
||||
return void.failure "insufficient funds"
|
||||
return failure "insufficient funds"
|
||||
|
||||
balances[source] -= amount
|
||||
if (balances.contains(destination)):
|
||||
@ -39,6 +39,6 @@ func move*(balances: var Balances,
|
||||
else:
|
||||
balances[destination] = amount
|
||||
|
||||
ok()
|
||||
success()
|
||||
except KeyError:
|
||||
void.failure "no funds"
|
||||
failure "no funds"
|
||||
|
||||
46
nitro/wallet/deref.nim
Normal file
46
nitro/wallet/deref.nim
Normal file
@ -0,0 +1,46 @@
|
||||
import std/macros
|
||||
|
||||
func identName(identDefs: NimNode): NimNode =
|
||||
identDefs.expectKind(nnkIdentDefs)
|
||||
identDefs[0]
|
||||
|
||||
func identType(identDefs: NimNode): NimNode =
|
||||
identDefs.expectKind(nnkIdentDefs)
|
||||
identDefs[^2]
|
||||
|
||||
func `identType=`(identDefs: NimNode, identType: NimNode) =
|
||||
identDefs.expectKind(nnkIdentDefs)
|
||||
identDefs[^2] = identType
|
||||
|
||||
func insertRef(function: NimNode) =
|
||||
function.expectKind(nnkFuncDef)
|
||||
var paramType = function.params[1].identType
|
||||
if paramType.kind == nnkVarTy:
|
||||
paramType = paramType[0]
|
||||
function.params[1].identType = newNimNode(nnkRefTy, paramType).add(paramType)
|
||||
|
||||
func paramNames(function: NimNode): seq[NimNode] =
|
||||
function.expectKind(nnkFuncDef)
|
||||
for i in 1..<function.params.len:
|
||||
result.add(function.params[i].identName)
|
||||
|
||||
func insertDeref(params: var seq[NimNode]) =
|
||||
params[0] = newNimNode(nnkBracketExpr, params[0]).add(params[0])
|
||||
|
||||
func derefOverload(function: NimNode): NimNode =
|
||||
function.expectKind(nnkFuncDef)
|
||||
var arguments = function.paramNames
|
||||
arguments.insertDeref()
|
||||
result = function.copyNimTree()
|
||||
result.insertRef()
|
||||
result.body = newCall(function.name, arguments)
|
||||
|
||||
macro deref*(function: untyped{nkFuncDef}): untyped =
|
||||
## Creates an overload that dereferences the first argument of the function
|
||||
## call. Roughly equivalent to the `implicitDeref` experimental feature of
|
||||
## Nim.
|
||||
|
||||
let overload = derefOverload(function)
|
||||
quote do:
|
||||
`function`
|
||||
`overload`
|
||||
34
nitro/wallet/nonces.nim
Normal file
34
nitro/wallet/nonces.nim
Normal file
@ -0,0 +1,34 @@
|
||||
import std/tables
|
||||
import std/sets
|
||||
import std/hashes
|
||||
import ../basics
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
type
|
||||
Nonces* = object
|
||||
next: Table[NonceKey, UInt48]
|
||||
NonceKey = object
|
||||
chainId: UInt256
|
||||
participants: HashSet[EthAddress]
|
||||
|
||||
func hash(key: NonceKey): Hash =
|
||||
var h: Hash
|
||||
h = h !& key.chainId.hash
|
||||
h = h !& key.participants.hash
|
||||
!$h
|
||||
|
||||
func key(chainId: UInt256, participants: openArray[EthAddress]): NonceKey =
|
||||
NonceKey(chainId: chainId, participants: participants.toHashSet)
|
||||
|
||||
func getNonce*(nonces: var Nonces,
|
||||
chainId: UInt256,
|
||||
participants: varargs[EthAddress]): UInt48 =
|
||||
nonces.next.?[key(chainId, participants)] |? 0
|
||||
|
||||
func incNonce*(nonces: var Nonces,
|
||||
oldNonce: UInt48,
|
||||
chainId: UInt256,
|
||||
participants: varargs[EthAddress]) =
|
||||
let next = max(oldNonce, nonces.getNonce(chainId, participants)) + 1
|
||||
nonces.next[key(chainId, participants)] = next
|
||||
@ -1,7 +1,7 @@
|
||||
import ../basics
|
||||
import ../protocol
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
type
|
||||
SignedState* = object
|
||||
|
||||
@ -5,8 +5,10 @@ import ../protocol
|
||||
import ./signedstate
|
||||
import ./ledger
|
||||
import ./balances
|
||||
import ./nonces
|
||||
import ./deref
|
||||
|
||||
push: {.upraises:[].}
|
||||
{.push raises: [].}
|
||||
|
||||
export basics
|
||||
export keys
|
||||
@ -17,21 +19,26 @@ type
|
||||
Wallet* = object
|
||||
key: EthPrivateKey
|
||||
channels: Table[ChannelId, SignedState]
|
||||
nonces: Nonces
|
||||
WalletRef* = ref Wallet
|
||||
ChannelId* = Destination
|
||||
Payment* = tuple
|
||||
destination: Destination
|
||||
amount: UInt256
|
||||
|
||||
func init*(_: type Wallet, key: EthPrivateKey): Wallet =
|
||||
result.key = key
|
||||
Wallet(key: key)
|
||||
|
||||
func publicKey*(wallet: Wallet): EthPublicKey =
|
||||
func new*(_: type WalletRef, key: EthPrivateKey): WalletRef =
|
||||
WalletRef(key: key)
|
||||
|
||||
func publicKey*(wallet: Wallet): EthPublicKey {.deref.} =
|
||||
wallet.key.toPublicKey
|
||||
|
||||
func address*(wallet: Wallet): EthAddress =
|
||||
func address*(wallet: Wallet): EthAddress {.deref.} =
|
||||
wallet.publicKey.toAddress
|
||||
|
||||
func destination*(wallet: Wallet): Destination =
|
||||
func destination*(wallet: Wallet): Destination {.deref.}=
|
||||
wallet.address.toDestination
|
||||
|
||||
func sign(wallet: Wallet, state: SignedState): SignedState =
|
||||
@ -39,12 +46,17 @@ func sign(wallet: Wallet, state: SignedState): SignedState =
|
||||
signed.signatures &= wallet.key.sign(state.state)
|
||||
signed
|
||||
|
||||
func incNonce(wallet: var Wallet, state: SignedState) =
|
||||
let channel = state.state.channel
|
||||
wallet.nonces.incNonce(channel.nonce, channel.chainId, channel.participants)
|
||||
|
||||
func createChannel(wallet: var Wallet, state: SignedState): ?!ChannelId =
|
||||
let id = getChannelId(state.state.channel)
|
||||
if wallet.channels.contains(id):
|
||||
return ChannelId.failure("channel with id " & $id & " already exists")
|
||||
return failure "channel with id " & $id & " already exists"
|
||||
wallet.channels[id] = wallet.sign(state)
|
||||
ok id
|
||||
wallet.incNonce(state)
|
||||
success id
|
||||
|
||||
func updateChannel(wallet: var Wallet, state: SignedState) =
|
||||
let signed = wallet.sign(state)
|
||||
@ -56,31 +68,43 @@ func openLedgerChannel*(wallet: var Wallet,
|
||||
chainId: UInt256,
|
||||
nonce: UInt48,
|
||||
asset: EthAddress,
|
||||
amount: UInt256): ?!ChannelId =
|
||||
amount: UInt256): ?!ChannelId {.deref.} =
|
||||
let state = startLedger(wallet.address, hub, chainId, nonce, asset, amount)
|
||||
wallet.createChannel(state)
|
||||
|
||||
func acceptChannel*(wallet: var Wallet, signed: SignedState): ?!ChannelId =
|
||||
func openLedgerChannel*(wallet: var Wallet,
|
||||
hub: EthAddress,
|
||||
chainId: UInt256,
|
||||
asset: EthAddress,
|
||||
amount: UInt256): ?!ChannelId {.deref.} =
|
||||
let nonce = wallet.nonces.getNonce(chainId, wallet.address, hub)
|
||||
openLedgerChannel(wallet, hub, chainId, nonce, asset, amount)
|
||||
|
||||
func acceptChannel*(wallet: var Wallet,
|
||||
signed: SignedState): ?!ChannelId {.deref.} =
|
||||
if not signed.hasParticipant(wallet.address):
|
||||
return ChannelId.failure "wallet owner is not a participant"
|
||||
return failure "wallet owner is not a participant"
|
||||
|
||||
if not verifySignatures(signed):
|
||||
return ChannelId.failure "incorrect signatures"
|
||||
return failure "incorrect signatures"
|
||||
|
||||
wallet.createChannel(signed)
|
||||
|
||||
func latestSignedState*(wallet: Wallet, channel: ChannelId): ?SignedState =
|
||||
wallet.channels?[channel]
|
||||
func latestSignedState*(wallet: Wallet,
|
||||
channel: ChannelId): ?SignedState {.deref.} =
|
||||
wallet.channels.?[channel]
|
||||
|
||||
func state*(wallet: Wallet, channel: ChannelId): ?State =
|
||||
wallet.latestSignedState(channel)?.state
|
||||
func state*(wallet: Wallet,
|
||||
channel: ChannelId): ?State {.deref.} =
|
||||
wallet.latestSignedState(channel).?state
|
||||
|
||||
func signatures*(wallet: Wallet, channel: ChannelId): ?seq[Signature] =
|
||||
wallet.latestSignedState(channel)?.signatures
|
||||
func signatures*(wallet: Wallet,
|
||||
channel: ChannelId): ?seq[Signature] {.deref.} =
|
||||
wallet.latestSignedState(channel).?signatures
|
||||
|
||||
func signature*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
address: EthAddress): ?Signature =
|
||||
address: EthAddress): ?Signature {.deref.} =
|
||||
if signed =? wallet.latestSignedState(channel):
|
||||
for signature in signed.signatures:
|
||||
if signer =? signature.recover(signed.state):
|
||||
@ -91,30 +115,27 @@ func signature*(wallet: Wallet,
|
||||
func balance(state: State,
|
||||
asset: EthAddress,
|
||||
destination: Destination): UInt256 =
|
||||
if balances =? state.outcome.balances(asset):
|
||||
if balance =? (balances?[destination]):
|
||||
balance
|
||||
else:
|
||||
0.u256
|
||||
else:
|
||||
0.u256
|
||||
without balance =? state.outcome.balances(asset).?[destination]:
|
||||
return 0.u256
|
||||
balance
|
||||
|
||||
func balance*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
destination: Destination): UInt256 =
|
||||
if state =? wallet.state(channel):
|
||||
state.balance(asset, destination)
|
||||
else:
|
||||
0.u256
|
||||
destination: Destination): UInt256 {.deref.} =
|
||||
without state =? wallet.state(channel):
|
||||
return 0.u256
|
||||
state.balance(asset, destination)
|
||||
|
||||
func balance*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
address: EthAddress): UInt256 =
|
||||
address: EthAddress): UInt256 {.deref.} =
|
||||
wallet.balance(channel, asset, address.toDestination)
|
||||
|
||||
func balance*(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 =
|
||||
func balance*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress): UInt256 {.deref.} =
|
||||
wallet.balance(channel, asset, wallet.address)
|
||||
|
||||
func total(state: State, asset: EthAddress): UInt256 =
|
||||
@ -125,65 +146,64 @@ func total(state: State, asset: EthAddress): UInt256 =
|
||||
total
|
||||
|
||||
func total(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 =
|
||||
if state =? wallet.state(channel):
|
||||
state.total(asset)
|
||||
else:
|
||||
0.u256
|
||||
without state =? wallet.state(channel):
|
||||
return 0.u256
|
||||
state.total(asset)
|
||||
|
||||
func pay*(wallet: var Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
receiver: Destination,
|
||||
amount: UInt256): ?!SignedState =
|
||||
if var state =? wallet.state(channel):
|
||||
if var balances =? state.outcome.balances(asset):
|
||||
?balances.move(wallet.destination, receiver, amount)
|
||||
state.outcome.update(asset, balances)
|
||||
wallet.updateChannel(SignedState(state: state))
|
||||
ok(wallet.channels?[channel].get)
|
||||
else:
|
||||
SignedState.failure "asset not found"
|
||||
else:
|
||||
SignedState.failure "channel not found"
|
||||
amount: UInt256): ?!SignedState {.deref.} =
|
||||
without var state =? wallet.state(channel):
|
||||
return failure "channel not found"
|
||||
|
||||
without var balances =? state.outcome.balances(asset):
|
||||
return failure "asset not found"
|
||||
|
||||
?balances.move(wallet.destination, receiver, amount)
|
||||
state.outcome.update(asset, balances)
|
||||
wallet.updateChannel(SignedState(state: state))
|
||||
success !wallet.latestSignedState(channel)
|
||||
|
||||
func pay*(wallet: var Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
receiver: EthAddress,
|
||||
amount: UInt256): ?!SignedState =
|
||||
amount: UInt256): ?!SignedState {.deref.} =
|
||||
wallet.pay(channel, asset, receiver.toDestination, amount)
|
||||
|
||||
func acceptPayment*(wallet: var Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
sender: EthAddress,
|
||||
payment: SignedState): ?!void =
|
||||
payment: SignedState): ?!void {.deref.} =
|
||||
if not wallet.channels.contains(channel):
|
||||
return void.failure "unknown channel"
|
||||
return failure "unknown channel"
|
||||
|
||||
if not (getChannelId(payment.state.channel) == channel):
|
||||
return void.failure "payment does not match channel"
|
||||
return failure "payment does not match channel"
|
||||
|
||||
let currentBalance = wallet.balance(channel, asset)
|
||||
let futureBalance = payment.state.balance(asset, wallet.destination)
|
||||
if futureBalance <= currentBalance:
|
||||
return void.failure "payment should not decrease balance"
|
||||
return failure "payment should not decrease balance"
|
||||
|
||||
let currentTotal = wallet.total(channel, asset)
|
||||
let futureTotal = payment.state.total(asset)
|
||||
if futureTotal != currentTotal:
|
||||
return void.failure "total supply of asset should not change"
|
||||
return failure "total supply of asset should not change"
|
||||
|
||||
if not payment.isSignedBy(sender):
|
||||
return void.failure "missing signature on payment"
|
||||
return failure "missing signature on payment"
|
||||
|
||||
if updatedBalances =? payment.state.outcome.balances(asset):
|
||||
var expectedState: State = wallet.channels?[channel]?.state.get
|
||||
expectedState.outcome.update(asset, updatedBalances)
|
||||
if payment.state != expectedState:
|
||||
return void.failure "payment has unexpected changes in state"
|
||||
else:
|
||||
return void.failure "payment misses balances for asset"
|
||||
without updatedBalances =? payment.state.outcome.balances(asset):
|
||||
return failure "payment misses balances for asset"
|
||||
|
||||
var expectedState: State = !wallet.state(channel)
|
||||
expectedState.outcome.update(asset, updatedBalances)
|
||||
if payment.state != expectedState:
|
||||
return failure "payment has unexpected changes in state"
|
||||
|
||||
wallet.channels[channel] = payment
|
||||
ok()
|
||||
success()
|
||||
|
||||
@ -19,10 +19,10 @@ proc example*[T](_: type seq[T], len = 0..5): seq[T] =
|
||||
newSeqWith(chosenlen, T.example)
|
||||
|
||||
proc example*(_: type UInt256): UInt256 =
|
||||
UInt256.fromBytes(array[32, byte].example)
|
||||
UInt256.fromBytesBE(array[32, byte].example)
|
||||
|
||||
proc example*(_: type UInt128): UInt128 =
|
||||
UInt128.fromBytes(array[16, byte].example)
|
||||
UInt128.fromBytesBE(array[16, byte].example)
|
||||
|
||||
proc example*(_: type EthAddress): EthAddress =
|
||||
EthAddress(array[20, byte].example)
|
||||
|
||||
@ -6,154 +6,11 @@ suite "ABI encoding":
|
||||
proc zeroes(amount: int): seq[byte] =
|
||||
newSeq[byte](amount)
|
||||
|
||||
test "encodes uint8":
|
||||
check AbiEncoder.encode(42'u8) == 31.zeroes & 42'u8
|
||||
|
||||
test "encodes booleans":
|
||||
check AbiEncoder.encode(false) == 31.zeroes & 0'u8
|
||||
check AbiEncoder.encode(true) == 31.zeroes & 1'u8
|
||||
|
||||
test "encodes uint16, 32, 64":
|
||||
check AbiEncoder.encode(0xABCD'u16) ==
|
||||
30.zeroes & 0xAB'u8 & 0xCD'u8
|
||||
check AbiEncoder.encode(0x11223344'u32) ==
|
||||
28.zeroes & 0x11'u8 & 0x22'u8 & 0x33'u8 & 0x44'u8
|
||||
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 AbiEncoder.encode(SomeRange(0x1122)) == 30.zeroes & 0x11'u8 & 0x22'u8
|
||||
|
||||
test "encodes enums":
|
||||
type SomeEnum = enum
|
||||
one = 1
|
||||
two = 2
|
||||
check AbiEncoder.encode(one) == 31.zeroes & 1'u8
|
||||
check AbiEncoder.encode(two) == 31.zeroes & 2'u8
|
||||
|
||||
test "encodes stints":
|
||||
let uint256 = UInt256.example
|
||||
check AbiEncoder.encode(uint256) == @(uint256.toBytesBE)
|
||||
let uint128 = UInt128.example
|
||||
check AbiEncoder.encode(uint128) == 16.zeroes & @(uint128.toBytesBE)
|
||||
|
||||
test "encodes byte arrays":
|
||||
let bytes3 = [1'u8, 2'u8, 3'u8]
|
||||
check AbiEncoder.encode(bytes3) == @bytes3 & 29.zeroes
|
||||
let bytes32 = array[32, byte].example
|
||||
check AbiEncoder.encode(bytes32) == @bytes32
|
||||
let bytes33 = array[33, byte].example
|
||||
check AbiEncoder.encode(bytes33) == @bytes33 & 31.zeroes
|
||||
|
||||
test "encodes byte sequences":
|
||||
let bytes3 = @[1'u8, 2'u8, 3'u8]
|
||||
let bytes3len = AbiEncoder.encode(bytes3.len.uint64)
|
||||
check AbiEncoder.encode(bytes3) == bytes3len & bytes3 & 29.zeroes
|
||||
let bytes32 = @(array[32, byte].example)
|
||||
let bytes32len = AbiEncoder.encode(bytes32.len.uint64)
|
||||
check AbiEncoder.encode(bytes32) == bytes32len & bytes32
|
||||
let bytes33 = @(array[33, byte].example)
|
||||
let bytes33len = AbiEncoder.encode(bytes33.len.uint64)
|
||||
check AbiEncoder.encode(bytes33) == bytes33len & bytes33 & 31.zeroes
|
||||
|
||||
test "encodes ethereum addresses":
|
||||
let address = EthAddress.example
|
||||
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 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 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= AbiEncoder.init()
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check AbiEncoder.encode([element1, element2]) == expected.finish()
|
||||
|
||||
test "encodes sequences":
|
||||
let element1 = seq[byte].example
|
||||
let element2 = seq[byte].example
|
||||
var expected= AbiEncoder.init()
|
||||
expected.write(2'u8)
|
||||
expected.startTuple()
|
||||
expected.write(element1)
|
||||
expected.write(element2)
|
||||
expected.finishTuple()
|
||||
check AbiEncoder.encode(@[element1, element2]) == expected.finish()
|
||||
|
||||
test "encodes sequence as dynamic element":
|
||||
let s = @[42.u256, 43.u256]
|
||||
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 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 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
|
||||
test "encodes nitro destinations":
|
||||
let destination = Destination.example
|
||||
check:
|
||||
AbiEncoder.encode(destination) == AbiEncoder.encode(destination.toArray)
|
||||
|
||||
@ -7,13 +7,8 @@ suite "channel definition":
|
||||
let channel = ChannelDefinition.example
|
||||
|
||||
test "calculates channel id":
|
||||
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 encoded = AbiEncoder.encode:
|
||||
(channel.chainId, channel.participants, channel.nonce)
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check getChannelId(channel) == Destination(hashed)
|
||||
|
||||
@ -22,10 +17,10 @@ suite "channel definition":
|
||||
chainId: 9001.u256,
|
||||
nonce: 1,
|
||||
participants: @[
|
||||
EthAddress.parse("24b905Dcc8A11C0FE57C2592f3D25f0447402C10").get()
|
||||
!EthAddress.init("24b905Dcc8A11C0FE57C2592f3D25f0447402C10")
|
||||
]
|
||||
)
|
||||
let expected = Destination.parse(
|
||||
let expected = !Destination.parse(
|
||||
"4f8cce57e9fe88edaab05234972eaf0c2d183e4f6b175aff293375fbe4d5d7cc"
|
||||
).get()
|
||||
)
|
||||
check getChannelId(channel) == expected
|
||||
|
||||
@ -6,31 +6,20 @@ suite "outcome":
|
||||
|
||||
test "encodes guarantees":
|
||||
let guarantee = Guarantee.example
|
||||
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()
|
||||
let expected = AbiEncoder.encode:
|
||||
((guarantee.targetChannelId, guarantee.destinations),)
|
||||
check AbiEncoder.encode(guarantee) == expected
|
||||
|
||||
test "encodes allocation items":
|
||||
let item = AllocationItem.example
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(item.destination)
|
||||
encoder.write(item.amount)
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(item) == encoder.finish()
|
||||
let expected = AbiEncoder.encode: (item.destination, item.amount)
|
||||
check AbiEncoder.encode(item) == expected
|
||||
|
||||
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()
|
||||
let expected = AbiEncoder.encode:
|
||||
(seq[AllocationItem](allocation),)
|
||||
check AbiEncoder.encode(allocation) == expected
|
||||
|
||||
test "encodes allocation outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
@ -38,19 +27,11 @@ suite "outcome":
|
||||
assetHolder: EthAddress.example,
|
||||
allocation: Allocation.example
|
||||
)
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(allocationType)
|
||||
content.write(AbiEncoder.encode(assetOutcome.allocation))
|
||||
content.finishTuple()
|
||||
content.finishTuple()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(assetOutcome) == encoder.finish()
|
||||
let content = AbiEncoder.encode:
|
||||
((allocationType, AbiEncoder.encode(assetOutcome.allocation)),)
|
||||
let expected = AbiEncoder.encode:
|
||||
(assetOutcome.assetHolder, content)
|
||||
check AbiEncoder.encode(assetOutcome) == expected
|
||||
|
||||
test "encodes guarantee outcome":
|
||||
let assetOutcome = AssetOutcome(
|
||||
@ -58,27 +39,17 @@ suite "outcome":
|
||||
assetHolder: EthAddress.example,
|
||||
guarantee: Guarantee.example
|
||||
)
|
||||
var content= AbiEncoder.init()
|
||||
content.startTuple()
|
||||
content.startTuple()
|
||||
content.write(guaranteeType)
|
||||
content.write(AbiEncoder.encode(assetOutcome.guarantee))
|
||||
content.finishTuple()
|
||||
content.finishTuple()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(assetOutcome.assetHolder)
|
||||
encoder.write(content.finish())
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(assetOutcome) == encoder.finish()
|
||||
let content = AbiEncoder.encode:
|
||||
((guaranteeType, AbiEncoder.encode(assetOutcome.guarantee)),)
|
||||
let expected = AbiEncoder.encode:
|
||||
(assetOutcome.assetHolder, content)
|
||||
check AbiEncoder.encode(assetOutcome) == expected
|
||||
|
||||
test "encodes outcomes":
|
||||
let outcome = Outcome.example()
|
||||
var encoder= AbiEncoder.init()
|
||||
encoder.startTuple()
|
||||
encoder.write(seq[AssetOutcome](outcome))
|
||||
encoder.finishTuple()
|
||||
check AbiEncoder.encode(outcome) == encoder.finish()
|
||||
let expected = AbiEncoder.encode:
|
||||
(seq[AssetOutcome](outcome),)
|
||||
check AbiEncoder.encode(outcome) == expected
|
||||
|
||||
test "hashes outcomes":
|
||||
let outcome = Outcome.example
|
||||
@ -90,31 +61,31 @@ suite "outcome":
|
||||
let outcome = Outcome(@[
|
||||
AssetOutcome(
|
||||
kind: allocationType,
|
||||
assetHolder: EthAddress.parse(
|
||||
assetHolder: !EthAddress.init(
|
||||
"1E90B49563da16D2537CA1Ddd9b1285279103D93"
|
||||
).get(),
|
||||
),
|
||||
allocation: Allocation(@[
|
||||
(
|
||||
destination: Destination.parse(
|
||||
destination: !Destination.parse(
|
||||
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
|
||||
).get(),
|
||||
),
|
||||
amount: 0x05.u256
|
||||
)
|
||||
])
|
||||
),
|
||||
AssetOutcome(
|
||||
kind: guaranteeType,
|
||||
assetHolder: EthAddress.parse(
|
||||
assetHolder: !EthAddress.init(
|
||||
"1E90B49563da16D2537CA1Ddd9b1285279103D93"
|
||||
).get(),
|
||||
),
|
||||
guarantee: Guarantee(
|
||||
targetChannelId: Destination.parse(
|
||||
targetChannelId: !Destination.parse(
|
||||
"cac1bb71f0a97c8ac94ca9546b43178a9ad254c7b757ac07433aa6df35cd8089"
|
||||
).get(),
|
||||
),
|
||||
destinations: @[
|
||||
Destination.parse(
|
||||
!Destination.parse(
|
||||
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
|
||||
).get()
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@ -40,7 +40,7 @@ suite "signature":
|
||||
chainId: 0x1.u256,
|
||||
nonce: 1,
|
||||
participants: @[
|
||||
EthAddress.parse("0x8a64E10FF40Bc9C90EA5750313dB5e036495c10E").get()
|
||||
!EthAddress.init("0x8a64E10FF40Bc9C90EA5750313dB5e036495c10E")
|
||||
]
|
||||
),
|
||||
outcome: Outcome(@[]),
|
||||
@ -50,12 +50,12 @@ suite "signature":
|
||||
appDefinition: EthAddress.default,
|
||||
challengeDuration: 5
|
||||
)
|
||||
let seckey = EthPrivateKey.parse(
|
||||
let seckey = !EthPrivateKey.parse(
|
||||
"41b0f5f91967dded8af487277874f95116094cc6004ac2b2169b5b6a87608f3e"
|
||||
).get()
|
||||
let expected = Signature.parse(
|
||||
)
|
||||
let expected = !Signature.parse(
|
||||
"9b966cf0065586d59c8b9eb475ac763c96ad8316b81061238f32968a631f9e21" &
|
||||
"251363c193c78c89b3eb2fec23f0ea5c3c72acff7d1f27430cfb84b9da9831fb" &
|
||||
"1c"
|
||||
).get()
|
||||
)
|
||||
check seckey.sign(state) == expected
|
||||
|
||||
@ -23,26 +23,20 @@ suite "state":
|
||||
)
|
||||
|
||||
test "hashes app part of state":
|
||||
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 encoded = AbiEncoder.encode:
|
||||
(state.challengeDuration, state.appDefinition, state.appData)
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashAppPart(state) == hashed
|
||||
|
||||
test "hashes state":
|
||||
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 encoded = AbiEncoder.encode:
|
||||
(
|
||||
state.turnNum,
|
||||
state.isFinal,
|
||||
getChannelId(state.channel),
|
||||
hashAppPart(state),
|
||||
hashOutcome(state.outcome)
|
||||
)
|
||||
let hashed = keccak256.digest(encoded).data
|
||||
check hashState(state) == hashed
|
||||
|
||||
@ -52,7 +46,7 @@ suite "state":
|
||||
chainId: 0x1.u256,
|
||||
nonce: 1,
|
||||
participants: @[
|
||||
EthAddress.parse("DBE821484648c73C1996Da25f2355342B9803eBD").get()
|
||||
!EthAddress.init("DBE821484648c73C1996Da25f2355342B9803eBD")
|
||||
]
|
||||
),
|
||||
outcome: Outcome(@[]),
|
||||
|
||||
@ -23,29 +23,35 @@ suite "wallet: opening ledger channel":
|
||||
|
||||
setup:
|
||||
wallet = Wallet.init(key)
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).get
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, amount)
|
||||
|
||||
test "sets correct channel definition":
|
||||
let definition = wallet.state(channel).get.channel
|
||||
let definition = (!wallet.state(channel)).channel
|
||||
check definition.chainId == chainId
|
||||
check definition.nonce == nonce
|
||||
check definition.participants == @[wallet.address, hub]
|
||||
|
||||
test "uses consecutive nonces when none is provided":
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, asset, amount)
|
||||
check (!wallet.state(channel)).channel.nonce == nonce + 1
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, asset, amount)
|
||||
check (!wallet.state(channel)).channel.nonce == nonce + 2
|
||||
|
||||
test "provides correct outcome":
|
||||
let outcome = wallet.state(channel).get.outcome
|
||||
let outcome = (!wallet.state(channel)).outcome
|
||||
check outcome == Outcome.init(asset, {wallet.destination: amount})
|
||||
|
||||
test "signs the state":
|
||||
let state = wallet.state(channel).get
|
||||
let signatures = wallet.signatures(channel).get
|
||||
let state = !wallet.state(channel)
|
||||
let signatures = !wallet.signatures(channel)
|
||||
check signatures == @[key.sign(state)]
|
||||
|
||||
test "sets app definition and app data to zero":
|
||||
check wallet.state(channel).get.appDefinition == EthAddress.zero
|
||||
check wallet.state(channel).get.appData.len == 0
|
||||
check (!wallet.state(channel)).appDefinition == EthAddress.zero
|
||||
check (!wallet.state(channel)).appData.len == 0
|
||||
|
||||
test "does not allow opening a channel that already exists":
|
||||
check wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).isErr
|
||||
check wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).isFailure
|
||||
|
||||
suite "wallet: accepting incoming channel":
|
||||
|
||||
@ -59,26 +65,26 @@ suite "wallet: accepting incoming channel":
|
||||
signed.state.channel.participants &= @[wallet.address]
|
||||
|
||||
test "returns the new channel id":
|
||||
let channel = wallet.acceptChannel(signed).get
|
||||
check wallet.state(channel).get == signed.state
|
||||
let channel = !wallet.acceptChannel(signed)
|
||||
check !wallet.state(channel) == signed.state
|
||||
|
||||
test "signs the channel state":
|
||||
let channel = wallet.acceptChannel(signed).get
|
||||
let channel = !wallet.acceptChannel(signed)
|
||||
let expectedSignatures = @[key.sign(signed.state)]
|
||||
check wallet.signatures(channel).get == expectedSignatures
|
||||
check !wallet.signatures(channel) == expectedSignatures
|
||||
|
||||
test "fails when wallet address is not a participant":
|
||||
let wrongParticipants = seq[EthAddress].example
|
||||
signed.state.channel.participants = wrongParticipants
|
||||
check wallet.acceptChannel(signed).isErr
|
||||
check wallet.acceptChannel(signed).isFailure
|
||||
|
||||
test "fails when signatures are incorrect":
|
||||
signed.signatures = @[key.sign(State.example)]
|
||||
check wallet.acceptChannel(signed).isErr
|
||||
check wallet.acceptChannel(signed).isFailure
|
||||
|
||||
test "fails when channel with this id already exists":
|
||||
check wallet.acceptChannel(signed).isOk
|
||||
check wallet.acceptChannel(signed).isErr
|
||||
check wallet.acceptChannel(signed).isSuccess
|
||||
check wallet.acceptChannel(signed).isFailure
|
||||
|
||||
suite "wallet: making payments":
|
||||
|
||||
@ -93,54 +99,54 @@ suite "wallet: making payments":
|
||||
|
||||
test "paying updates the channel state":
|
||||
wallet = Wallet.init(key)
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256).get
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256)
|
||||
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isOk
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isSuccess
|
||||
check wallet.balance(channel, asset) == 99.u256
|
||||
check wallet.balance(channel, asset, hub) == 1.u256
|
||||
|
||||
check wallet.pay(channel, asset, hub, 2.u256).isOk
|
||||
check wallet.pay(channel, asset, hub, 2.u256).isSuccess
|
||||
check wallet.balance(channel, asset) == 97.u256
|
||||
check wallet.balance(channel, asset, hub) == 3.u256
|
||||
|
||||
test "paying updates signatures":
|
||||
wallet = Wallet.init(key)
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256).get
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isOk
|
||||
let expectedSignature = key.sign(wallet.state(channel).get)
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256)
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isSuccess
|
||||
let expectedSignature = key.sign(!wallet.state(channel))
|
||||
check wallet.signature(channel, wallet.address) == expectedSignature.some
|
||||
|
||||
test "pay returns the updated signed state":
|
||||
wallet = Wallet.init(key)
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 42.u256).get
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 42.u256)
|
||||
let updated = wallet.pay(channel, asset, hub, 1.u256).option
|
||||
check updated?.state == wallet.state(channel)
|
||||
check updated?.signatures == wallet.signatures(channel)
|
||||
check updated.?state == wallet.state(channel)
|
||||
check updated.?signatures == wallet.signatures(channel)
|
||||
|
||||
test "payment fails when channel not found":
|
||||
wallet = Wallet.init(key)
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isErr
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isFailure
|
||||
|
||||
test "payment fails when asset not found":
|
||||
wallet = Wallet.init(key)
|
||||
var state = State.example
|
||||
state.channel.participants &= wallet.address
|
||||
channel = wallet.acceptChannel(SignedState(state: state)).get
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isErr
|
||||
channel = !wallet.acceptChannel(SignedState(state: state))
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isFailure
|
||||
|
||||
test "payment fails when payer has no allocation":
|
||||
wallet = Wallet.init(key)
|
||||
var state: State
|
||||
state.channel = ChannelDefinition(participants: @[wallet.address])
|
||||
state.outcome = Outcome.init(asset, @[])
|
||||
channel = wallet.acceptChannel(SignedState(state: state)).get
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isErr
|
||||
channel = !wallet.acceptChannel(SignedState(state: state))
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isFailure
|
||||
|
||||
test "payment fails when payer has insufficient funds":
|
||||
wallet = Wallet.init(key)
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 1.u256).get
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isOk
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isErr
|
||||
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 1.u256)
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isSuccess
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isFailure
|
||||
|
||||
suite "wallet: accepting payments":
|
||||
|
||||
@ -155,55 +161,71 @@ suite "wallet: accepting payments":
|
||||
setup:
|
||||
payer = Wallet.init(payerKey)
|
||||
receiver = Wallet.init(receiverKey)
|
||||
channel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce, asset, 100.u256).get
|
||||
let update = payer.latestSignedState(channel).get
|
||||
channel = !payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce, asset, 100.u256)
|
||||
let update = !payer.latestSignedState(channel)
|
||||
discard receiver.acceptChannel(update)
|
||||
|
||||
test "updates channel state":
|
||||
let payment = payer.pay(channel, asset, receiver.address, 42.u256).get
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isOk
|
||||
let payment = !payer.pay(channel, asset, receiver.address, 42.u256)
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isSuccess
|
||||
check receiver.balance(channel, asset) == 42.u256
|
||||
|
||||
test "fails when receiver balance is decreased":
|
||||
let payment1 = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
let payment2 = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment1).isOk
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment2).isOk
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment1).isErr
|
||||
let payment1 = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
let payment2 = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment1).isSuccess
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment2).isSuccess
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment1).isFailure
|
||||
check receiver.balance(channel, asset) == 20
|
||||
|
||||
test "fails when the total supply of the asset changes":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
var balances = payment.state.outcome.balances(asset).get
|
||||
var payment = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
var balances = !payment.state.outcome.balances(asset)
|
||||
balances[payer.destination] += 10.u256
|
||||
payment.state.outcome.update(asset, balances)
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure
|
||||
|
||||
test "fails without a signature":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
var payment = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
payment.signatures = @[]
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure
|
||||
|
||||
test "fails with an incorrect signature":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
var payment = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
payment.signatures = @[Signature.example]
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure
|
||||
|
||||
test "fails when channel is unknown":
|
||||
let newChannel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256).get
|
||||
let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
|
||||
check receiver.acceptPayment(newChannel, asset, payer.address, payment).isErr
|
||||
let newChannel = !payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256)
|
||||
let payment = !payer.pay(newChannel, asset, receiver.address, 10.u256)
|
||||
check receiver.acceptPayment(newChannel, asset, payer.address, payment).isFailure
|
||||
|
||||
test "fails when payment does not match channel":
|
||||
let newChannel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256).get
|
||||
let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
let newChannel = !payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256)
|
||||
let payment = !payer.pay(newChannel, asset, receiver.address, 10.u256)
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure
|
||||
|
||||
test "fails when state is updated in unrelated areas":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
var payment = !payer.pay(channel, asset, receiver.address, 10.u256)
|
||||
payment.state.appDefinition = EthAddress.example
|
||||
payment.signatures = @[payerKey.sign(payment.state)]
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure
|
||||
|
||||
suite "wallet reference type":
|
||||
|
||||
let asset = EthAddress.example
|
||||
let amount = 42.u256
|
||||
let chainId = UInt256.example
|
||||
|
||||
test "wallet can also be used as a reference type":
|
||||
let wallet1 = WalletRef.new(EthPrivateKey.random())
|
||||
let wallet2 = WalletRef.new(EthPrivateKey.random())
|
||||
let address1 = wallet1.address
|
||||
let address2 = wallet2.address
|
||||
let channel = !wallet1.openLedgerChannel(address2, chainId, asset, amount)
|
||||
check !wallet2.acceptChannel(!wallet1.latestSignedState(channel)) == channel
|
||||
let payment = !wallet1.pay(channel, asset, address2, amount)
|
||||
check wallet2.acceptPayment(channel, asset, address1, payment).isSuccess
|
||||
|
||||
37
tests/nitro/wallet/testNonces.nim
Normal file
37
tests/nitro/wallet/testNonces.nim
Normal file
@ -0,0 +1,37 @@
|
||||
import ../basics
|
||||
import pkg/nitro/wallet/nonces
|
||||
|
||||
suite "nonces":
|
||||
|
||||
let chainId = UInt256.example
|
||||
let participants = seq[EthAddress].example(1..5)
|
||||
|
||||
var nonces: Nonces
|
||||
|
||||
setup:
|
||||
nonces = Nonces()
|
||||
|
||||
test "nonces start at 0":
|
||||
check nonces.getNonce(chainId, participants) == 0
|
||||
|
||||
test "nonces increase by 1":
|
||||
nonces.incNonce(0, chainId, participants)
|
||||
check nonces.getNonce(chainId, participants) == 1
|
||||
nonces.incNonce(1, chainId, participants)
|
||||
check nonces.getNonce(chainId, participants) == 2
|
||||
|
||||
test "nonces do not decrease":
|
||||
nonces.incNonce(100, chainId, participants)
|
||||
check nonces.getNonce(chainId, participants) == 101
|
||||
nonces.incNonce(0, chainId, participants)
|
||||
check nonces.getNonce(chainId, participants) == 102
|
||||
|
||||
test "nonces are different when participants differ":
|
||||
let otherParticipants = seq[EthAddress].example(1..5)
|
||||
nonces.incNonce(0, chainId, participants)
|
||||
check nonces.getNonce(chainId, otherParticipants) == 0
|
||||
|
||||
test "nonces are different when chain ids differ":
|
||||
let otherChainId = UInt256.example
|
||||
nonces.incNonce(0, chainId, participants)
|
||||
check nonces.getNonce(otherChainId, participants) == 0
|
||||
@ -3,6 +3,7 @@ import ./nitro/protocol/testChannel
|
||||
import ./nitro/protocol/testOutcome
|
||||
import ./nitro/protocol/testState
|
||||
import ./nitro/protocol/testSignature
|
||||
import ./nitro/wallet/testNonces
|
||||
import ./nitro/testWallet
|
||||
import ./nitro/testJson
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user