Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

32 changed files with 622 additions and 542 deletions

View File

@ -8,15 +8,22 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
nim: [stable, 2.0.14] nim: [stable, 1.2.6]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v2
- uses: iffy/install-nim@v3
with: with:
submodules: recursive version: ${{ matrix.nim }}
- uses: jiro4989/setup-nim-action@v2
with: # workaround for https://github.com/iffy/install-nim/issues/11
nim-version: ${{matrix.nim}} - name: Workaround SSL error with choosenim
repo-token: ${{ secrets.GITHUB_TOKEN }} run: |
curl -fO https://curl.se/ca/cacert.pem
install cacert.pem ~/.nimble/bin
ls ~/.nimble/bin
shell: bash
if: runner.os == 'Windows'
- name: Build - name: Build
run: nimble install -y run: nimble install -y
- name: Test - name: Test

3
.gitignore vendored
View File

@ -1,6 +1,3 @@
* *
!*/ !*/
!*.* !*.*
nimble.develop
nimble.paths
nimbledeps

View File

@ -1 +1 @@
nim 1.6.2 nim 1.4.4

View File

@ -1,5 +0,0 @@
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.

View File

@ -10,11 +10,3 @@ References
- [statechannels.org](https://statechannels.org/) - [statechannels.org](https://statechannels.org/)
- [Nitro paper](https://magmo.com/nitro-protocol.pdf) - [Nitro paper](https://magmo.com/nitro-protocol.pdf)
- [Nitro smart contracts & Javascript code](https://github.com/statechannels/statechannels/tree/master/packages/nitro-protocol) - [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
```

View File

@ -1,5 +0,0 @@
# begin Nimble config (version 2)
--noNimblePath
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

View File

@ -1,101 +0,0 @@
{
"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": {}
}

View File

@ -1,12 +1,12 @@
version = "0.6.1" version = "0.1.0"
author = "Nim Nitro developers" author = "Nim Nitro developers"
license = "MIT" license = "MIT"
description = "Nitro state channels" description = "Nitro state channels"
requires "nim >= 2.0.14 & < 3.0.0" requires "nim >= 1.2.6 & < 2.0.0"
requires "nimcrypto >= 0.6.0 & < 0.7.0" requires "nimcrypto >= 0.5.4 & < 0.6.0"
requires "questionable >= 0.10.10 & < 0.11.0" requires "questionable >= 0.4.3 & < 0.5.0"
requires "contractabi >= 0.7.1 & < 0.8.0" requires "upraises >= 0.1.0 & < 0.2.0"
requires "secp256k1 >= 0.6.0 & < 0.7.0" requires "secp256k1"
requires "stint >= 0.8.0 & < 0.9.0" requires "stint"
requires "stew >= 0.2.0" requires "stew"

View File

@ -1,5 +1,6 @@
import pkg/questionable import pkg/questionable
import pkg/questionable/results import pkg/questionable/results
import pkg/upraises
import pkg/stint import pkg/stint
import ./basics/uint48 import ./basics/uint48
import ./basics/ethaddress import ./basics/ethaddress
@ -7,6 +8,7 @@ import ./basics/destination
export questionable export questionable
export results export results
export upraises
export stint export stint
export uint48 export uint48
export ethaddress export ethaddress

View File

@ -1,10 +1,11 @@
import std/hashes import std/hashes
import pkg/questionable import pkg/questionable
import pkg/questionable/results import pkg/questionable/results
import pkg/upraises
import pkg/stew/byteutils import pkg/stew/byteutils
import ./ethaddress import ./ethaddress
{.push raises: [].} push: {.upraises:[].}
type Destination* = distinct array[32, byte] type Destination* = distinct array[32, byte]

View File

@ -1,12 +1,21 @@
import std/hashes import pkg/questionable
import pkg/contractabi/address import pkg/questionable/results
import pkg/stew/byteutils
export address export questionable
type EthAddress* = Address type EthAddress* = distinct array[20, byte]
func zero*(_: type EthAddress): EthAddress = func zero*(_: type EthAddress): EthAddress =
EthAddress.default EthAddress.default
proc `hash`*(a: EthAddress): Hash = func toArray*(address: EthAddress): array[20, byte] =
hash(a.toArray) 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.}

View File

@ -8,7 +8,7 @@ import ./wallet/signedstate
export signedstate export signedstate
{.push raises: [].} push: {.upraises:[].}
func `%`(value: Outcome | Allocation): JsonNode = func `%`(value: Outcome | Allocation): JsonNode =
type Base = distinctBase(typeof value) type Base = distinctBase(typeof value)
@ -37,7 +37,7 @@ func toJson*(payment: SignedState): string =
{.pop.} {.pop.}
{.push raises: [ValueError].} push: {.upraises: [ValueError].}
func expectKind(node: JsonNode, kind: JsonNodeKind) = func expectKind(node: JsonNode, kind: JsonNodeKind) =
if node.kind != kind: if node.kind != kind:
@ -46,21 +46,27 @@ func expectKind(node: JsonNode, kind: JsonNodeKind) =
func initFromJson*(bytes: var seq[byte], node: JsonNode, _: var string) = func initFromJson*(bytes: var seq[byte], node: JsonNode, _: var string) =
node.expectKind(JString) node.expectKind(JString)
without parsed =? seq[byte].fromHex(node.getStr): let parsed = seq[byte].fromHex(node.getStr)
if parsed.isOk:
bytes = parsed.get
else:
raise newException(ValueError, "invalid hex string") raise newException(ValueError, "invalid hex string")
bytes = parsed
func initFromJson*(address: var EthAddress, node: JsonNode, _: var string) = func initFromJson*(address: var EthAddress, node: JsonNode, _: var string) =
node.expectKind(JString) node.expectKind(JString)
without parsed =? EthAddress.init(node.getStr): let parsed = EthAddress.parse(node.getStr)
if parsed.isSome:
address = parsed.get
else:
raise newException(ValueError, "invalid ethereum address") raise newException(ValueError, "invalid ethereum address")
address = parsed
func initFromJson*(dest: var Destination, node: JsonNode, _: var string) = func initFromJson*(dest: var Destination, node: JsonNode, _: var string) =
node.expectKind(JString) node.expectKind(JString)
without parsed =? Destination.parse(node.getStr): let parsed = Destination.parse(node.getStr)
if parsed.isSome:
dest = parsed.get
else:
raise newException(ValueError, "invalid nitro destination") raise newException(ValueError, "invalid nitro destination")
dest = parsed
func initFromJson*(number: var UInt256, node: JsonNode, _: var string) = func initFromJson*(number: var UInt256, node: JsonNode, _: var string) =
node.expectKind(JString) node.expectKind(JString)
@ -68,13 +74,15 @@ func initFromJson*(number: var UInt256, node: JsonNode, _: var string) =
func initFromJson*(signature: var Signature, node: JsonNode, _: var string) = func initFromJson*(signature: var Signature, node: JsonNode, _: var string) =
node.expectKind(JString) node.expectKind(JString)
without parsed =? Signature.parse(node.getStr): let parsed = Signature.parse(node.getStr)
if parsed.isSome:
signature = parsed.get
else:
raise newException(ValueError, "invalid signature") raise newException(ValueError, "invalid signature")
signature = parsed
{.pop.} {.pop.}
{.push raises: [].} push: {.upraises: [].}
proc fromJson*(_: type SignedState, json: string): ?SignedState = proc fromJson*(_: type SignedState, json: string): ?SignedState =
try: try:

View File

@ -5,7 +5,7 @@ import ./basics
export basics export basics
export toPublicKey export toPublicKey
{.push raises: [].} push: {.upraises:[].}
type type
EthPrivateKey* = SkSecretKey EthPrivateKey* = SkSecretKey

View File

@ -1,10 +1,132 @@
import pkg/contractabi import pkg/stew/endians2
import ../basics import ../basics
{.push raises: [].} push: {.upraises:[].}
export basics export basics
export contractabi
func encode*(encoder: var AbiEncoder, destination: Destination) = type
encoder.write(destination.toArray) 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()

View File

@ -2,7 +2,7 @@ import pkg/nimcrypto
import ../basics import ../basics
import ./abi import ./abi
{.push raises: [].} push: {.upraises:[].}
export basics export basics
@ -13,6 +13,10 @@ type
chainId*: UInt256 chainId*: UInt256
func getChannelId*(channel: ChannelDefinition): Destination = func getChannelId*(channel: ChannelDefinition): Destination =
let encoding = AbiEncoder.encode: var encoder= AbiEncoder.init()
(channel.chainId, channel.participants, channel.nonce) encoder.startTuple()
Destination(keccak256.digest(encoding).data) encoder.write(channel.chainId)
encoder.write(channel.participants)
encoder.write(channel.nonce)
encoder.finishTuple()
Destination(keccak256.digest(encoder.finish()).data)

View File

@ -2,7 +2,7 @@ import pkg/nimcrypto
import ../basics import ../basics
import ./abi import ./abi
{.push raises: [].} push: {.upraises:[].}
export basics export basics
@ -52,25 +52,46 @@ func `==`*(a, b: AssetOutcome): bool =
proc `==`*(a, b: Outcome): bool {.borrow.} proc `==`*(a, b: Outcome): bool {.borrow.}
func encode*(encoder: var AbiEncoder, guarantee: Guarantee) = func encode*(encoder: var AbiEncoder, guarantee: Guarantee) =
encoder.write: encoder.startTuple()
( (guarantee.targetChannelId, guarantee.destinations), ) 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()
func encode*(encoder: var AbiEncoder, allocation: Allocation) = func encode*(encoder: var AbiEncoder, allocation: Allocation) =
encoder.write: (seq[AllocationItem](allocation),) encoder.startTuple()
encoder.write(seq[AllocationItem](allocation))
encoder.finishTuple()
func encode*(encoder: var AbiEncoder, assetOutcome: AssetOutcome) = func encode*(encoder: var AbiEncoder, assetOutcome: AssetOutcome) =
var content: seq[byte] var content= AbiEncoder.init()
content.startTuple()
content.startTuple()
content.write(assetOutcome.kind)
case assetOutcome.kind: case assetOutcome.kind:
of allocationType: of allocationType:
content = AbiEncoder.encode: content.write(AbiEncoder.encode(assetOutcome.allocation))
( (assetOutcome.kind, ABiEncoder.encode(assetOutcome.allocation)), )
of guaranteeType: of guaranteeType:
content = AbiEncoder.encode: content.write(AbiEncoder.encode(assetOutcome.guarantee))
( (assetOutcome.kind, AbiEncoder.encode(assetOutcome.guarantee)), ) content.finishTuple()
encoder.write( (assetOutcome.assetHolder, content) ) content.finishTuple()
encoder.startTuple()
encoder.write(assetOutcome.assetHolder)
encoder.write(content.finish())
encoder.finishTuple()
func encode*(encoder: var AbiEncoder, outcome: Outcome) = func encode*(encoder: var AbiEncoder, outcome: Outcome) =
encoder.write: (seq[AssetOutcome](outcome),) encoder.startTuple()
encoder.write(seq[AssetOutcome](outcome))
encoder.finishTuple()
func hashOutcome*(outcome: Outcome): array[32, byte] = func hashOutcome*(outcome: Outcome): array[32, byte] =
keccak256.digest(AbiEncoder.encode(outcome)).data keccak256.digest(AbiEncoder.encode(outcome)).data

View File

@ -1,11 +1,11 @@
import pkg/secp256k1 import pkg/secp256k1
import pkg/nimcrypto/keccak import pkg/nimcrypto
import pkg/stew/byteutils import pkg/stew/byteutils
import ../basics import ../basics
import ../keys import ../keys
import ./state import ./state
{.push raises: [].} push: {.upraises:[].}
export basics export basics
export keys export keys
@ -32,7 +32,7 @@ func recover(signature: Signature, hash: array[32, byte]): ?EthPublicKey =
func recover*(signature: Signature, state: State): ?EthAddress = func recover*(signature: Signature, state: State): ?EthAddress =
let hash = hashMessage(hashState(state)) let hash = hashMessage(hashState(state))
recover(signature, hash).?toAddress recover(signature, hash)?.toAddress
func verify*(signature: Signature, state: State, signer: EthAddress): bool = func verify*(signature: Signature, state: State, signer: EthAddress): bool =
recover(signature, state) == signer.some recover(signature, state) == signer.some
@ -43,7 +43,8 @@ func `$`*(signature: Signature): string =
bytes.toHex() bytes.toHex()
func parse*(_: type Signature, s: string): ?Signature = func parse*(_: type Signature, s: string): ?Signature =
without var bytes =? array[65, byte].fromHex(s).catch: let signature = catch:
return Signature.none var bytes = array[65, byte].fromHex(s)
bytes[64] = bytes[64] - 27 bytes[64] = bytes[64] - 27
SkRecoverableSignature.fromRaw(bytes).option SkRecoverableSignature.fromRaw(bytes).get()
signature.option

View File

@ -4,7 +4,7 @@ import ./channel
import ./outcome import ./outcome
import ./abi import ./abi
{.push raises: [].} push: {.upraises:[].}
export basics export basics
export channel export channel
@ -45,17 +45,21 @@ func variablePart*(state: State): VariablePart =
) )
func hashAppPart*(state: State): array[32, byte] = func hashAppPart*(state: State): array[32, byte] =
let encoding = AbiEncoder.encode: var encoder= AbiEncoder.init()
(state.challengeDuration, state.appDefinition, state.appData) encoder.startTuple()
keccak256.digest(encoding).data encoder.write(state.challengeDuration)
encoder.write(state.appDefinition)
encoder.write(state.appData)
encoder.finishTuple()
keccak256.digest(encoder.finish).data
func hashState*(state: State): array[32, byte] = func hashState*(state: State): array[32, byte] =
let encoding = AbiEncoder.encode: var encoder= AbiEncoder.init()
( encoder.startTuple()
state.turnNum, encoder.write(state.turnNum)
state.isFinal, encoder.write(state.isFinal)
getChannelId(state.channel), encoder.write(getChannelId(state.channel))
hashAppPart(state), encoder.write(hashAppPart(state))
hashOutcome(state.outcome) encoder.write(hashOutcome(state.outcome))
) encoder.finishTuple()
keccak256.digest(encoding).data keccak256.digest(encoder.finish).data

View File

@ -3,7 +3,7 @@ import std/sequtils
import ../basics import ../basics
import ../protocol import ../protocol
{.push raises: [].} push: {.upraises:[].}
export tables export tables
@ -31,7 +31,7 @@ func move*(balances: var Balances,
amount: UInt256): ?!void = amount: UInt256): ?!void =
try: try:
if balances[source] < amount: if balances[source] < amount:
return failure "insufficient funds" return void.failure "insufficient funds"
balances[source] -= amount balances[source] -= amount
if (balances.contains(destination)): if (balances.contains(destination)):
@ -39,6 +39,6 @@ func move*(balances: var Balances,
else: else:
balances[destination] = amount balances[destination] = amount
success() ok()
except KeyError: except KeyError:
failure "no funds" void.failure "no funds"

View File

@ -1,46 +0,0 @@
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`

View File

@ -1,34 +0,0 @@
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

View File

@ -1,7 +1,7 @@
import ../basics import ../basics
import ../protocol import ../protocol
{.push raises: [].} push: {.upraises:[].}
type type
SignedState* = object SignedState* = object

View File

@ -5,10 +5,8 @@ import ../protocol
import ./signedstate import ./signedstate
import ./ledger import ./ledger
import ./balances import ./balances
import ./nonces
import ./deref
{.push raises: [].} push: {.upraises:[].}
export basics export basics
export keys export keys
@ -19,26 +17,21 @@ type
Wallet* = object Wallet* = object
key: EthPrivateKey key: EthPrivateKey
channels: Table[ChannelId, SignedState] channels: Table[ChannelId, SignedState]
nonces: Nonces
WalletRef* = ref Wallet
ChannelId* = Destination ChannelId* = Destination
Payment* = tuple Payment* = tuple
destination: Destination destination: Destination
amount: UInt256 amount: UInt256
func init*(_: type Wallet, key: EthPrivateKey): Wallet = func init*(_: type Wallet, key: EthPrivateKey): Wallet =
Wallet(key: key) result.key = key
func new*(_: type WalletRef, key: EthPrivateKey): WalletRef = func publicKey*(wallet: Wallet): EthPublicKey =
WalletRef(key: key)
func publicKey*(wallet: Wallet): EthPublicKey {.deref.} =
wallet.key.toPublicKey wallet.key.toPublicKey
func address*(wallet: Wallet): EthAddress {.deref.} = func address*(wallet: Wallet): EthAddress =
wallet.publicKey.toAddress wallet.publicKey.toAddress
func destination*(wallet: Wallet): Destination {.deref.}= func destination*(wallet: Wallet): Destination =
wallet.address.toDestination wallet.address.toDestination
func sign(wallet: Wallet, state: SignedState): SignedState = func sign(wallet: Wallet, state: SignedState): SignedState =
@ -46,17 +39,12 @@ func sign(wallet: Wallet, state: SignedState): SignedState =
signed.signatures &= wallet.key.sign(state.state) signed.signatures &= wallet.key.sign(state.state)
signed 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 = func createChannel(wallet: var Wallet, state: SignedState): ?!ChannelId =
let id = getChannelId(state.state.channel) let id = getChannelId(state.state.channel)
if wallet.channels.contains(id): if wallet.channels.contains(id):
return failure "channel with id " & $id & " already exists" return ChannelId.failure("channel with id " & $id & " already exists")
wallet.channels[id] = wallet.sign(state) wallet.channels[id] = wallet.sign(state)
wallet.incNonce(state) ok id
success id
func updateChannel(wallet: var Wallet, state: SignedState) = func updateChannel(wallet: var Wallet, state: SignedState) =
let signed = wallet.sign(state) let signed = wallet.sign(state)
@ -68,43 +56,31 @@ func openLedgerChannel*(wallet: var Wallet,
chainId: UInt256, chainId: UInt256,
nonce: UInt48, nonce: UInt48,
asset: EthAddress, asset: EthAddress,
amount: UInt256): ?!ChannelId {.deref.} = amount: UInt256): ?!ChannelId =
let state = startLedger(wallet.address, hub, chainId, nonce, asset, amount) let state = startLedger(wallet.address, hub, chainId, nonce, asset, amount)
wallet.createChannel(state) wallet.createChannel(state)
func openLedgerChannel*(wallet: var Wallet, func acceptChannel*(wallet: var Wallet, signed: SignedState): ?!ChannelId =
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): if not signed.hasParticipant(wallet.address):
return failure "wallet owner is not a participant" return ChannelId.failure "wallet owner is not a participant"
if not verifySignatures(signed): if not verifySignatures(signed):
return failure "incorrect signatures" return ChannelId.failure "incorrect signatures"
wallet.createChannel(signed) wallet.createChannel(signed)
func latestSignedState*(wallet: Wallet, func latestSignedState*(wallet: Wallet, channel: ChannelId): ?SignedState =
channel: ChannelId): ?SignedState {.deref.} = wallet.channels?[channel]
wallet.channels.?[channel]
func state*(wallet: Wallet, func state*(wallet: Wallet, channel: ChannelId): ?State =
channel: ChannelId): ?State {.deref.} = wallet.latestSignedState(channel)?.state
wallet.latestSignedState(channel).?state
func signatures*(wallet: Wallet, func signatures*(wallet: Wallet, channel: ChannelId): ?seq[Signature] =
channel: ChannelId): ?seq[Signature] {.deref.} = wallet.latestSignedState(channel)?.signatures
wallet.latestSignedState(channel).?signatures
func signature*(wallet: Wallet, func signature*(wallet: Wallet,
channel: ChannelId, channel: ChannelId,
address: EthAddress): ?Signature {.deref.} = address: EthAddress): ?Signature =
if signed =? wallet.latestSignedState(channel): if signed =? wallet.latestSignedState(channel):
for signature in signed.signatures: for signature in signed.signatures:
if signer =? signature.recover(signed.state): if signer =? signature.recover(signed.state):
@ -115,27 +91,30 @@ func signature*(wallet: Wallet,
func balance(state: State, func balance(state: State,
asset: EthAddress, asset: EthAddress,
destination: Destination): UInt256 = destination: Destination): UInt256 =
without balance =? state.outcome.balances(asset).?[destination]: if balances =? state.outcome.balances(asset):
return 0.u256 if balance =? (balances?[destination]):
balance balance
else:
0.u256
else:
0.u256
func balance*(wallet: Wallet, func balance*(wallet: Wallet,
channel: ChannelId, channel: ChannelId,
asset: EthAddress, asset: EthAddress,
destination: Destination): UInt256 {.deref.} = destination: Destination): UInt256 =
without state =? wallet.state(channel): if state =? wallet.state(channel):
return 0.u256
state.balance(asset, destination) state.balance(asset, destination)
else:
0.u256
func balance*(wallet: Wallet, func balance*(wallet: Wallet,
channel: ChannelId, channel: ChannelId,
asset: EthAddress, asset: EthAddress,
address: EthAddress): UInt256 {.deref.} = address: EthAddress): UInt256 =
wallet.balance(channel, asset, address.toDestination) wallet.balance(channel, asset, address.toDestination)
func balance*(wallet: Wallet, func balance*(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 =
channel: ChannelId,
asset: EthAddress): UInt256 {.deref.} =
wallet.balance(channel, asset, wallet.address) wallet.balance(channel, asset, wallet.address)
func total(state: State, asset: EthAddress): UInt256 = func total(state: State, asset: EthAddress): UInt256 =
@ -146,64 +125,65 @@ func total(state: State, asset: EthAddress): UInt256 =
total total
func total(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 = func total(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 =
without state =? wallet.state(channel): if state =? wallet.state(channel):
return 0.u256
state.total(asset) state.total(asset)
else:
0.u256
func pay*(wallet: var Wallet, func pay*(wallet: var Wallet,
channel: ChannelId, channel: ChannelId,
asset: EthAddress, asset: EthAddress,
receiver: Destination, receiver: Destination,
amount: UInt256): ?!SignedState {.deref.} = amount: UInt256): ?!SignedState =
without var state =? wallet.state(channel): if var state =? wallet.state(channel):
return failure "channel not found" if var balances =? state.outcome.balances(asset):
without var balances =? state.outcome.balances(asset):
return failure "asset not found"
?balances.move(wallet.destination, receiver, amount) ?balances.move(wallet.destination, receiver, amount)
state.outcome.update(asset, balances) state.outcome.update(asset, balances)
wallet.updateChannel(SignedState(state: state)) wallet.updateChannel(SignedState(state: state))
success !wallet.latestSignedState(channel) ok(wallet.channels?[channel].get)
else:
SignedState.failure "asset not found"
else:
SignedState.failure "channel not found"
func pay*(wallet: var Wallet, func pay*(wallet: var Wallet,
channel: ChannelId, channel: ChannelId,
asset: EthAddress, asset: EthAddress,
receiver: EthAddress, receiver: EthAddress,
amount: UInt256): ?!SignedState {.deref.} = amount: UInt256): ?!SignedState =
wallet.pay(channel, asset, receiver.toDestination, amount) wallet.pay(channel, asset, receiver.toDestination, amount)
func acceptPayment*(wallet: var Wallet, func acceptPayment*(wallet: var Wallet,
channel: ChannelId, channel: ChannelId,
asset: EthAddress, asset: EthAddress,
sender: EthAddress, sender: EthAddress,
payment: SignedState): ?!void {.deref.} = payment: SignedState): ?!void =
if not wallet.channels.contains(channel): if not wallet.channels.contains(channel):
return failure "unknown channel" return void.failure "unknown channel"
if not (getChannelId(payment.state.channel) == channel): if not (getChannelId(payment.state.channel) == channel):
return failure "payment does not match channel" return void.failure "payment does not match channel"
let currentBalance = wallet.balance(channel, asset) let currentBalance = wallet.balance(channel, asset)
let futureBalance = payment.state.balance(asset, wallet.destination) let futureBalance = payment.state.balance(asset, wallet.destination)
if futureBalance <= currentBalance: if futureBalance <= currentBalance:
return failure "payment should not decrease balance" return void.failure "payment should not decrease balance"
let currentTotal = wallet.total(channel, asset) let currentTotal = wallet.total(channel, asset)
let futureTotal = payment.state.total(asset) let futureTotal = payment.state.total(asset)
if futureTotal != currentTotal: if futureTotal != currentTotal:
return failure "total supply of asset should not change" return void.failure "total supply of asset should not change"
if not payment.isSignedBy(sender): if not payment.isSignedBy(sender):
return failure "missing signature on payment" return void.failure "missing signature on payment"
without updatedBalances =? payment.state.outcome.balances(asset): if updatedBalances =? payment.state.outcome.balances(asset):
return failure "payment misses balances for asset" var expectedState: State = wallet.channels?[channel]?.state.get
var expectedState: State = !wallet.state(channel)
expectedState.outcome.update(asset, updatedBalances) expectedState.outcome.update(asset, updatedBalances)
if payment.state != expectedState: if payment.state != expectedState:
return failure "payment has unexpected changes in state" return void.failure "payment has unexpected changes in state"
else:
return void.failure "payment misses balances for asset"
wallet.channels[channel] = payment wallet.channels[channel] = payment
success() ok()

View File

@ -19,10 +19,10 @@ proc example*[T](_: type seq[T], len = 0..5): seq[T] =
newSeqWith(chosenlen, T.example) newSeqWith(chosenlen, T.example)
proc example*(_: type UInt256): UInt256 = proc example*(_: type UInt256): UInt256 =
UInt256.fromBytesBE(array[32, byte].example) UInt256.fromBytes(array[32, byte].example)
proc example*(_: type UInt128): UInt128 = proc example*(_: type UInt128): UInt128 =
UInt128.fromBytesBE(array[16, byte].example) UInt128.fromBytes(array[16, byte].example)
proc example*(_: type EthAddress): EthAddress = proc example*(_: type EthAddress): EthAddress =
EthAddress(array[20, byte].example) EthAddress(array[20, byte].example)

View File

@ -6,11 +6,154 @@ suite "ABI encoding":
proc zeroes(amount: int): seq[byte] = proc zeroes(amount: int): seq[byte] =
newSeq[byte](amount) 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": test "encodes ethereum addresses":
let address = EthAddress.example let address = EthAddress.example
check AbiEncoder.encode(address) == 12.zeroes & @(address.toArray) check AbiEncoder.encode(address) == 12.zeroes & @(address.toArray)
test "encodes nitro destinations": test "encodes tuples":
let destination = Destination.example let a = true
check: let b = @[1'u8, 2'u8, 3'u8]
AbiEncoder.encode(destination) == AbiEncoder.encode(destination.toArray) 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

View File

@ -7,8 +7,13 @@ suite "channel definition":
let channel = ChannelDefinition.example let channel = ChannelDefinition.example
test "calculates channel id": test "calculates channel id":
let encoded = AbiEncoder.encode: var encoder= AbiEncoder.init()
(channel.chainId, channel.participants, channel.nonce) 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 let hashed = keccak256.digest(encoded).data
check getChannelId(channel) == Destination(hashed) check getChannelId(channel) == Destination(hashed)
@ -17,10 +22,10 @@ suite "channel definition":
chainId: 9001.u256, chainId: 9001.u256,
nonce: 1, nonce: 1,
participants: @[ participants: @[
!EthAddress.init("24b905Dcc8A11C0FE57C2592f3D25f0447402C10") EthAddress.parse("24b905Dcc8A11C0FE57C2592f3D25f0447402C10").get()
] ]
) )
let expected = !Destination.parse( let expected = Destination.parse(
"4f8cce57e9fe88edaab05234972eaf0c2d183e4f6b175aff293375fbe4d5d7cc" "4f8cce57e9fe88edaab05234972eaf0c2d183e4f6b175aff293375fbe4d5d7cc"
) ).get()
check getChannelId(channel) == expected check getChannelId(channel) == expected

View File

@ -6,20 +6,31 @@ suite "outcome":
test "encodes guarantees": test "encodes guarantees":
let guarantee = Guarantee.example let guarantee = Guarantee.example
let expected = AbiEncoder.encode: var encoder= AbiEncoder.init()
((guarantee.targetChannelId, guarantee.destinations),) encoder.startTuple()
check AbiEncoder.encode(guarantee) == expected encoder.startTuple()
encoder.write(guarantee.targetChannelId)
encoder.write(guarantee.destinations)
encoder.finishTuple()
encoder.finishTuple()
check AbiEncoder.encode(guarantee) == encoder.finish()
test "encodes allocation items": test "encodes allocation items":
let item = AllocationItem.example let item = AllocationItem.example
let expected = AbiEncoder.encode: (item.destination, item.amount) var encoder= AbiEncoder.init()
check AbiEncoder.encode(item) == expected encoder.startTuple()
encoder.write(item.destination)
encoder.write(item.amount)
encoder.finishTuple()
check AbiEncoder.encode(item) == encoder.finish()
test "encodes allocation": test "encodes allocation":
let allocation = Allocation.example let allocation = Allocation.example
let expected = AbiEncoder.encode: var encoder= AbiEncoder.init()
(seq[AllocationItem](allocation),) encoder.startTuple()
check AbiEncoder.encode(allocation) == expected encoder.write(seq[AllocationItem](allocation))
encoder.finishTuple()
check AbiEncoder.encode(allocation) == encoder.finish()
test "encodes allocation outcome": test "encodes allocation outcome":
let assetOutcome = AssetOutcome( let assetOutcome = AssetOutcome(
@ -27,11 +38,19 @@ suite "outcome":
assetHolder: EthAddress.example, assetHolder: EthAddress.example,
allocation: Allocation.example allocation: Allocation.example
) )
let content = AbiEncoder.encode: var content= AbiEncoder.init()
((allocationType, AbiEncoder.encode(assetOutcome.allocation)),) content.startTuple()
let expected = AbiEncoder.encode: content.startTuple()
(assetOutcome.assetHolder, content) content.write(allocationType)
check AbiEncoder.encode(assetOutcome) == expected 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()
test "encodes guarantee outcome": test "encodes guarantee outcome":
let assetOutcome = AssetOutcome( let assetOutcome = AssetOutcome(
@ -39,17 +58,27 @@ suite "outcome":
assetHolder: EthAddress.example, assetHolder: EthAddress.example,
guarantee: Guarantee.example guarantee: Guarantee.example
) )
let content = AbiEncoder.encode: var content= AbiEncoder.init()
((guaranteeType, AbiEncoder.encode(assetOutcome.guarantee)),) content.startTuple()
let expected = AbiEncoder.encode: content.startTuple()
(assetOutcome.assetHolder, content) content.write(guaranteeType)
check AbiEncoder.encode(assetOutcome) == expected 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()
test "encodes outcomes": test "encodes outcomes":
let outcome = Outcome.example() let outcome = Outcome.example()
let expected = AbiEncoder.encode: var encoder= AbiEncoder.init()
(seq[AssetOutcome](outcome),) encoder.startTuple()
check AbiEncoder.encode(outcome) == expected encoder.write(seq[AssetOutcome](outcome))
encoder.finishTuple()
check AbiEncoder.encode(outcome) == encoder.finish()
test "hashes outcomes": test "hashes outcomes":
let outcome = Outcome.example let outcome = Outcome.example
@ -61,31 +90,31 @@ suite "outcome":
let outcome = Outcome(@[ let outcome = Outcome(@[
AssetOutcome( AssetOutcome(
kind: allocationType, kind: allocationType,
assetHolder: !EthAddress.init( assetHolder: EthAddress.parse(
"1E90B49563da16D2537CA1Ddd9b1285279103D93" "1E90B49563da16D2537CA1Ddd9b1285279103D93"
), ).get(),
allocation: Allocation(@[ allocation: Allocation(@[
( (
destination: !Destination.parse( destination: Destination.parse(
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" "f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
), ).get(),
amount: 0x05.u256 amount: 0x05.u256
) )
]) ])
), ),
AssetOutcome( AssetOutcome(
kind: guaranteeType, kind: guaranteeType,
assetHolder: !EthAddress.init( assetHolder: EthAddress.parse(
"1E90B49563da16D2537CA1Ddd9b1285279103D93" "1E90B49563da16D2537CA1Ddd9b1285279103D93"
), ).get(),
guarantee: Guarantee( guarantee: Guarantee(
targetChannelId: !Destination.parse( targetChannelId: Destination.parse(
"cac1bb71f0a97c8ac94ca9546b43178a9ad254c7b757ac07433aa6df35cd8089" "cac1bb71f0a97c8ac94ca9546b43178a9ad254c7b757ac07433aa6df35cd8089"
), ).get(),
destinations: @[ destinations: @[
!Destination.parse( Destination.parse(
"f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" "f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"
) ).get()
] ]
) )
) )

View File

@ -40,7 +40,7 @@ suite "signature":
chainId: 0x1.u256, chainId: 0x1.u256,
nonce: 1, nonce: 1,
participants: @[ participants: @[
!EthAddress.init("0x8a64E10FF40Bc9C90EA5750313dB5e036495c10E") EthAddress.parse("0x8a64E10FF40Bc9C90EA5750313dB5e036495c10E").get()
] ]
), ),
outcome: Outcome(@[]), outcome: Outcome(@[]),
@ -50,12 +50,12 @@ suite "signature":
appDefinition: EthAddress.default, appDefinition: EthAddress.default,
challengeDuration: 5 challengeDuration: 5
) )
let seckey = !EthPrivateKey.parse( let seckey = EthPrivateKey.parse(
"41b0f5f91967dded8af487277874f95116094cc6004ac2b2169b5b6a87608f3e" "41b0f5f91967dded8af487277874f95116094cc6004ac2b2169b5b6a87608f3e"
) ).get()
let expected = !Signature.parse( let expected = Signature.parse(
"9b966cf0065586d59c8b9eb475ac763c96ad8316b81061238f32968a631f9e21" & "9b966cf0065586d59c8b9eb475ac763c96ad8316b81061238f32968a631f9e21" &
"251363c193c78c89b3eb2fec23f0ea5c3c72acff7d1f27430cfb84b9da9831fb" & "251363c193c78c89b3eb2fec23f0ea5c3c72acff7d1f27430cfb84b9da9831fb" &
"1c" "1c"
) ).get()
check seckey.sign(state) == expected check seckey.sign(state) == expected

View File

@ -23,20 +23,26 @@ suite "state":
) )
test "hashes app part of state": test "hashes app part of state":
let encoded = AbiEncoder.encode: var encoder= AbiEncoder.init()
(state.challengeDuration, state.appDefinition, state.appData) 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 let hashed = keccak256.digest(encoded).data
check hashAppPart(state) == hashed check hashAppPart(state) == hashed
test "hashes state": test "hashes state":
let encoded = AbiEncoder.encode: var encoder= AbiEncoder.init()
( encoder.startTuple()
state.turnNum, encoder.write(state.turnNum)
state.isFinal, encoder.write(state.isFinal)
getChannelId(state.channel), encoder.write(getChannelId(state.channel))
hashAppPart(state), encoder.write(hashAppPart(state))
hashOutcome(state.outcome) encoder.write(hashOutcome(state.outcome))
) encoder.finishTuple()
let encoded = encoder.finish()
let hashed = keccak256.digest(encoded).data let hashed = keccak256.digest(encoded).data
check hashState(state) == hashed check hashState(state) == hashed
@ -46,7 +52,7 @@ suite "state":
chainId: 0x1.u256, chainId: 0x1.u256,
nonce: 1, nonce: 1,
participants: @[ participants: @[
!EthAddress.init("DBE821484648c73C1996Da25f2355342B9803eBD") EthAddress.parse("DBE821484648c73C1996Da25f2355342B9803eBD").get()
] ]
), ),
outcome: Outcome(@[]), outcome: Outcome(@[]),

View File

@ -23,35 +23,29 @@ suite "wallet: opening ledger channel":
setup: setup:
wallet = Wallet.init(key) wallet = Wallet.init(key)
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, amount) channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).get
test "sets correct channel definition": test "sets correct channel definition":
let definition = (!wallet.state(channel)).channel let definition = wallet.state(channel).get.channel
check definition.chainId == chainId check definition.chainId == chainId
check definition.nonce == nonce check definition.nonce == nonce
check definition.participants == @[wallet.address, hub] 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": test "provides correct outcome":
let outcome = (!wallet.state(channel)).outcome let outcome = wallet.state(channel).get.outcome
check outcome == Outcome.init(asset, {wallet.destination: amount}) check outcome == Outcome.init(asset, {wallet.destination: amount})
test "signs the state": test "signs the state":
let state = !wallet.state(channel) let state = wallet.state(channel).get
let signatures = !wallet.signatures(channel) let signatures = wallet.signatures(channel).get
check signatures == @[key.sign(state)] check signatures == @[key.sign(state)]
test "sets app definition and app data to zero": test "sets app definition and app data to zero":
check (!wallet.state(channel)).appDefinition == EthAddress.zero check wallet.state(channel).get.appDefinition == EthAddress.zero
check (!wallet.state(channel)).appData.len == 0 check wallet.state(channel).get.appData.len == 0
test "does not allow opening a channel that already exists": test "does not allow opening a channel that already exists":
check wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).isFailure check wallet.openLedgerChannel(hub, chainId, nonce, asset, amount).isErr
suite "wallet: accepting incoming channel": suite "wallet: accepting incoming channel":
@ -65,26 +59,26 @@ suite "wallet: accepting incoming channel":
signed.state.channel.participants &= @[wallet.address] signed.state.channel.participants &= @[wallet.address]
test "returns the new channel id": test "returns the new channel id":
let channel = !wallet.acceptChannel(signed) let channel = wallet.acceptChannel(signed).get
check !wallet.state(channel) == signed.state check wallet.state(channel).get == signed.state
test "signs the channel state": test "signs the channel state":
let channel = !wallet.acceptChannel(signed) let channel = wallet.acceptChannel(signed).get
let expectedSignatures = @[key.sign(signed.state)] let expectedSignatures = @[key.sign(signed.state)]
check !wallet.signatures(channel) == expectedSignatures check wallet.signatures(channel).get == expectedSignatures
test "fails when wallet address is not a participant": test "fails when wallet address is not a participant":
let wrongParticipants = seq[EthAddress].example let wrongParticipants = seq[EthAddress].example
signed.state.channel.participants = wrongParticipants signed.state.channel.participants = wrongParticipants
check wallet.acceptChannel(signed).isFailure check wallet.acceptChannel(signed).isErr
test "fails when signatures are incorrect": test "fails when signatures are incorrect":
signed.signatures = @[key.sign(State.example)] signed.signatures = @[key.sign(State.example)]
check wallet.acceptChannel(signed).isFailure check wallet.acceptChannel(signed).isErr
test "fails when channel with this id already exists": test "fails when channel with this id already exists":
check wallet.acceptChannel(signed).isSuccess check wallet.acceptChannel(signed).isOk
check wallet.acceptChannel(signed).isFailure check wallet.acceptChannel(signed).isErr
suite "wallet: making payments": suite "wallet: making payments":
@ -99,54 +93,54 @@ suite "wallet: making payments":
test "paying updates the channel state": test "paying updates the channel state":
wallet = Wallet.init(key) wallet = Wallet.init(key)
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256) channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256).get
check wallet.pay(channel, asset, hub, 1.u256).isSuccess check wallet.pay(channel, asset, hub, 1.u256).isOk
check wallet.balance(channel, asset) == 99.u256 check wallet.balance(channel, asset) == 99.u256
check wallet.balance(channel, asset, hub) == 1.u256 check wallet.balance(channel, asset, hub) == 1.u256
check wallet.pay(channel, asset, hub, 2.u256).isSuccess check wallet.pay(channel, asset, hub, 2.u256).isOk
check wallet.balance(channel, asset) == 97.u256 check wallet.balance(channel, asset) == 97.u256
check wallet.balance(channel, asset, hub) == 3.u256 check wallet.balance(channel, asset, hub) == 3.u256
test "paying updates signatures": test "paying updates signatures":
wallet = Wallet.init(key) wallet = Wallet.init(key)
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256) channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 100.u256).get
check wallet.pay(channel, asset, hub, 1.u256).isSuccess check wallet.pay(channel, asset, hub, 1.u256).isOk
let expectedSignature = key.sign(!wallet.state(channel)) let expectedSignature = key.sign(wallet.state(channel).get)
check wallet.signature(channel, wallet.address) == expectedSignature.some check wallet.signature(channel, wallet.address) == expectedSignature.some
test "pay returns the updated signed state": test "pay returns the updated signed state":
wallet = Wallet.init(key) wallet = Wallet.init(key)
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 42.u256) channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 42.u256).get
let updated = wallet.pay(channel, asset, hub, 1.u256).option let updated = wallet.pay(channel, asset, hub, 1.u256).option
check updated.?state == wallet.state(channel) check updated?.state == wallet.state(channel)
check updated.?signatures == wallet.signatures(channel) check updated?.signatures == wallet.signatures(channel)
test "payment fails when channel not found": test "payment fails when channel not found":
wallet = Wallet.init(key) wallet = Wallet.init(key)
check wallet.pay(channel, asset, hub, 1.u256).isFailure check wallet.pay(channel, asset, hub, 1.u256).isErr
test "payment fails when asset not found": test "payment fails when asset not found":
wallet = Wallet.init(key) wallet = Wallet.init(key)
var state = State.example var state = State.example
state.channel.participants &= wallet.address state.channel.participants &= wallet.address
channel = !wallet.acceptChannel(SignedState(state: state)) channel = wallet.acceptChannel(SignedState(state: state)).get
check wallet.pay(channel, asset, hub, 1.u256).isFailure check wallet.pay(channel, asset, hub, 1.u256).isErr
test "payment fails when payer has no allocation": test "payment fails when payer has no allocation":
wallet = Wallet.init(key) wallet = Wallet.init(key)
var state: State var state: State
state.channel = ChannelDefinition(participants: @[wallet.address]) state.channel = ChannelDefinition(participants: @[wallet.address])
state.outcome = Outcome.init(asset, @[]) state.outcome = Outcome.init(asset, @[])
channel = !wallet.acceptChannel(SignedState(state: state)) channel = wallet.acceptChannel(SignedState(state: state)).get
check wallet.pay(channel, asset, hub, 1.u256).isFailure check wallet.pay(channel, asset, hub, 1.u256).isErr
test "payment fails when payer has insufficient funds": test "payment fails when payer has insufficient funds":
wallet = Wallet.init(key) wallet = Wallet.init(key)
channel = !wallet.openLedgerChannel(hub, chainId, nonce, asset, 1.u256) channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 1.u256).get
check wallet.pay(channel, asset, hub, 1.u256).isSuccess check wallet.pay(channel, asset, hub, 1.u256).isOk
check wallet.pay(channel, asset, hub, 1.u256).isFailure check wallet.pay(channel, asset, hub, 1.u256).isErr
suite "wallet: accepting payments": suite "wallet: accepting payments":
@ -161,71 +155,55 @@ suite "wallet: accepting payments":
setup: setup:
payer = Wallet.init(payerKey) payer = Wallet.init(payerKey)
receiver = Wallet.init(receiverKey) receiver = Wallet.init(receiverKey)
channel = !payer.openLedgerChannel( channel = payer.openLedgerChannel(
receiver.address, chainId, nonce, asset, 100.u256) receiver.address, chainId, nonce, asset, 100.u256).get
let update = !payer.latestSignedState(channel) let update = payer.latestSignedState(channel).get
discard receiver.acceptChannel(update) discard receiver.acceptChannel(update)
test "updates channel state": test "updates channel state":
let payment = !payer.pay(channel, asset, receiver.address, 42.u256) let payment = payer.pay(channel, asset, receiver.address, 42.u256).get
check receiver.acceptPayment(channel, asset, payer.address, payment).isSuccess check receiver.acceptPayment(channel, asset, payer.address, payment).isOk
check receiver.balance(channel, asset) == 42.u256 check receiver.balance(channel, asset) == 42.u256
test "fails when receiver balance is decreased": test "fails when receiver balance is decreased":
let payment1 = !payer.pay(channel, asset, receiver.address, 10.u256) let payment1 = payer.pay(channel, asset, receiver.address, 10.u256).get
let payment2 = !payer.pay(channel, asset, receiver.address, 10.u256) let payment2 = payer.pay(channel, asset, receiver.address, 10.u256).get
check receiver.acceptPayment(channel, asset, payer.address, payment1).isSuccess check receiver.acceptPayment(channel, asset, payer.address, payment1).isOk
check receiver.acceptPayment(channel, asset, payer.address, payment2).isSuccess check receiver.acceptPayment(channel, asset, payer.address, payment2).isOk
check receiver.acceptPayment(channel, asset, payer.address, payment1).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment1).isErr
check receiver.balance(channel, asset) == 20 check receiver.balance(channel, asset) == 20
test "fails when the total supply of the asset changes": test "fails when the total supply of the asset changes":
var payment = !payer.pay(channel, asset, receiver.address, 10.u256) var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
var balances = !payment.state.outcome.balances(asset) var balances = payment.state.outcome.balances(asset).get
balances[payer.destination] += 10.u256 balances[payer.destination] += 10.u256
payment.state.outcome.update(asset, balances) payment.state.outcome.update(asset, balances)
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
test "fails without a signature": test "fails without a signature":
var payment = !payer.pay(channel, asset, receiver.address, 10.u256) var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
payment.signatures = @[] payment.signatures = @[]
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
test "fails with an incorrect signature": test "fails with an incorrect signature":
var payment = !payer.pay(channel, asset, receiver.address, 10.u256) var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
payment.signatures = @[Signature.example] payment.signatures = @[Signature.example]
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
test "fails when channel is unknown": test "fails when channel is unknown":
let newChannel = !payer.openLedgerChannel( let newChannel = payer.openLedgerChannel(
receiver.address, chainId, nonce + 1, asset, 100.u256) receiver.address, chainId, nonce + 1, asset, 100.u256).get
let payment = !payer.pay(newChannel, asset, receiver.address, 10.u256) let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
check receiver.acceptPayment(newChannel, asset, payer.address, payment).isFailure check receiver.acceptPayment(newChannel, asset, payer.address, payment).isErr
test "fails when payment does not match channel": test "fails when payment does not match channel":
let newChannel = !payer.openLedgerChannel( let newChannel = payer.openLedgerChannel(
receiver.address, chainId, nonce + 1, asset, 100.u256) receiver.address, chainId, nonce + 1, asset, 100.u256).get
let payment = !payer.pay(newChannel, asset, receiver.address, 10.u256) let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
test "fails when state is updated in unrelated areas": test "fails when state is updated in unrelated areas":
var payment = !payer.pay(channel, asset, receiver.address, 10.u256) var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
payment.state.appDefinition = EthAddress.example payment.state.appDefinition = EthAddress.example
payment.signatures = @[payerKey.sign(payment.state)] payment.signatures = @[payerKey.sign(payment.state)]
check receiver.acceptPayment(channel, asset, payer.address, payment).isFailure check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
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

View File

@ -1,37 +0,0 @@
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

View File

@ -3,7 +3,6 @@ import ./nitro/protocol/testChannel
import ./nitro/protocol/testOutcome import ./nitro/protocol/testOutcome
import ./nitro/protocol/testState import ./nitro/protocol/testState
import ./nitro/protocol/testSignature import ./nitro/protocol/testSignature
import ./nitro/wallet/testNonces
import ./nitro/testWallet import ./nitro/testWallet
import ./nitro/testJson import ./nitro/testJson