nim-nitro/nitro/wallet/wallet.nim

210 lines
6.6 KiB
Nim
Raw Normal View History

2021-03-17 13:38:17 +01:00
import std/tables
import ../basics
import ../keys
import ../protocol
import ./signedstate
import ./ledger
2021-03-18 14:15:58 +01:00
import ./balances
2021-04-14 12:57:14 +02:00
import ./nonces
import ./deref
2021-03-17 13:38:17 +01:00
{.push raises: [].}
2021-03-17 13:38:17 +01:00
export basics
export keys
export signedstate
2021-03-18 14:15:58 +01:00
export balances
2021-03-17 13:38:17 +01:00
type
Wallet* = object
key: EthPrivateKey
2021-03-17 13:38:17 +01:00
channels: Table[ChannelId, SignedState]
2021-04-14 12:57:14 +02:00
nonces: Nonces
WalletRef* = ref Wallet
2021-03-17 13:38:17 +01:00
ChannelId* = Destination
2021-03-18 14:15:58 +01:00
Payment* = tuple
destination: Destination
amount: UInt256
2021-03-17 13:38:17 +01:00
func init*(_: type Wallet, key: EthPrivateKey): Wallet =
Wallet(key: key)
2021-03-17 13:38:17 +01:00
func new*(_: type WalletRef, key: EthPrivateKey): WalletRef =
WalletRef(key: key)
func publicKey*(wallet: Wallet): EthPublicKey {.deref.} =
2021-04-06 13:49:06 +02:00
wallet.key.toPublicKey
func address*(wallet: Wallet): EthAddress {.deref.} =
2021-04-06 13:49:06 +02:00
wallet.publicKey.toAddress
2021-03-17 13:38:17 +01:00
func destination*(wallet: Wallet): Destination {.deref.}=
2021-03-18 14:15:58 +01:00
wallet.address.toDestination
2021-03-17 13:38:17 +01:00
func sign(wallet: Wallet, state: SignedState): SignedState =
var signed = state
signed.signatures &= wallet.key.sign(state.state)
2021-03-17 13:38:17 +01:00
signed
2021-04-14 12:57:14 +02:00
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 failure "channel with id " & $id & " already exists"
wallet.channels[id] = wallet.sign(state)
2021-04-14 12:57:14 +02:00
wallet.incNonce(state)
success id
2021-03-17 13:38:17 +01:00
2021-03-18 14:15:58 +01:00
func updateChannel(wallet: var Wallet, state: SignedState) =
let signed = wallet.sign(state)
let id = getChannelId(signed.state.channel)
wallet.channels[id] = signed
2021-03-17 13:38:17 +01:00
func openLedgerChannel*(wallet: var Wallet,
hub: EthAddress,
chainId: UInt256,
nonce: UInt48,
asset: EthAddress,
amount: UInt256): ?!ChannelId {.deref.} =
2021-03-17 13:38:17 +01:00
let state = startLedger(wallet.address, hub, chainId, nonce, asset, amount)
wallet.createChannel(state)
2021-04-14 12:57:14 +02:00
func openLedgerChannel*(wallet: var Wallet,
hub: EthAddress,
chainId: UInt256,
asset: EthAddress,
amount: UInt256): ?!ChannelId {.deref.} =
2021-04-14 12:57:14 +02:00
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.} =
2021-03-17 13:38:17 +01:00
if not signed.hasParticipant(wallet.address):
return failure "wallet owner is not a participant"
2021-03-17 13:38:17 +01:00
if not verifySignatures(signed):
return failure "incorrect signatures"
2021-03-17 13:38:17 +01:00
wallet.createChannel(signed)
2021-03-18 14:15:58 +01:00
func latestSignedState*(wallet: Wallet,
channel: ChannelId): ?SignedState {.deref.} =
2021-04-12 16:29:44 +02:00
wallet.channels.?[channel]
2021-03-22 14:04:28 +01:00
func state*(wallet: Wallet,
channel: ChannelId): ?State {.deref.} =
2021-04-12 16:29:44 +02:00
wallet.latestSignedState(channel).?state
2021-03-18 14:15:58 +01:00
func signatures*(wallet: Wallet,
channel: ChannelId): ?seq[Signature] {.deref.} =
2021-04-12 16:29:44 +02:00
wallet.latestSignedState(channel).?signatures
2021-03-18 14:15:58 +01:00
func signature*(wallet: Wallet,
channel: ChannelId,
address: EthAddress): ?Signature {.deref.} =
if signed =? wallet.latestSignedState(channel):
for signature in signed.signatures:
if signer =? signature.recover(signed.state):
if signer == address:
return signature.some
2021-03-18 14:15:58 +01:00
Signature.none
2021-03-22 14:04:28 +01:00
func balance(state: State,
asset: EthAddress,
destination: Destination): UInt256 =
without balance =? state.outcome.balances(asset).?[destination]:
return 0.u256
balance
2021-03-22 14:04:28 +01:00
2021-03-18 14:15:58 +01:00
func balance*(wallet: Wallet,
channel: ChannelId,
asset: EthAddress,
destination: Destination): UInt256 {.deref.} =
without state =? wallet.state(channel):
return 0.u256
state.balance(asset, destination)
2021-03-18 14:15:58 +01:00
func balance*(wallet: Wallet,
channel: ChannelId,
asset: EthAddress,
address: EthAddress): UInt256 {.deref.} =
2021-03-18 14:15:58 +01:00
wallet.balance(channel, asset, address.toDestination)
func balance*(wallet: Wallet,
channel: ChannelId,
asset: EthAddress): UInt256 {.deref.} =
2021-03-22 15:50:57 +01:00
wallet.balance(channel, asset, wallet.address)
2021-03-22 14:04:28 +01:00
func total(state: State, asset: EthAddress): UInt256 =
var total: UInt256
if balances =? state.outcome.balances(asset):
for amount in balances.values:
total += amount # TODO: overflow?
total
func total(wallet: Wallet, channel: ChannelId, asset: EthAddress): UInt256 =
without state =? wallet.state(channel):
return 0.u256
state.total(asset)
2021-03-22 14:04:28 +01:00
2021-03-18 14:15:58 +01:00
func pay*(wallet: var Wallet,
channel: ChannelId,
asset: EthAddress,
receiver: Destination,
amount: UInt256): ?!SignedState {.deref.} =
2021-04-16 12:38:05 +02:00
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))
2021-04-19 16:11:30 +02:00
success !wallet.latestSignedState(channel)
2021-03-18 14:15:58 +01:00
func pay*(wallet: var Wallet,
channel: ChannelId,
asset: EthAddress,
receiver: EthAddress,
amount: UInt256): ?!SignedState {.deref.} =
2021-03-18 14:15:58 +01:00
wallet.pay(channel, asset, receiver.toDestination, amount)
2021-03-22 14:04:28 +01:00
func acceptPayment*(wallet: var Wallet,
channel: ChannelId,
asset: EthAddress,
sender: EthAddress,
payment: SignedState): ?!void {.deref.} =
2021-03-22 14:04:28 +01:00
if not wallet.channels.contains(channel):
return failure "unknown channel"
2021-03-22 14:04:28 +01:00
if not (getChannelId(payment.state.channel) == channel):
return failure "payment does not match channel"
2021-03-22 14:04:28 +01:00
let currentBalance = wallet.balance(channel, asset)
2021-03-22 14:04:28 +01:00
let futureBalance = payment.state.balance(asset, wallet.destination)
if futureBalance <= currentBalance:
return failure "payment should not decrease balance"
2021-03-22 14:04:28 +01:00
let currentTotal = wallet.total(channel, asset)
let futureTotal = payment.state.total(asset)
if futureTotal != currentTotal:
return failure "total supply of asset should not change"
2021-03-22 14:04:28 +01:00
if not payment.isSignedBy(sender):
return failure "missing signature on payment"
2021-03-22 14:04:28 +01:00
2021-04-16 12:38:05 +02:00
without updatedBalances =? payment.state.outcome.balances(asset):
return failure "payment misses balances for asset"
2021-04-19 16:11:30 +02:00
var expectedState: State = !wallet.state(channel)
2021-04-16 12:38:05 +02:00
expectedState.outcome.update(asset, updatedBalances)
if payment.state != expectedState:
return failure "payment has unexpected changes in state"
2021-03-22 14:04:28 +01:00
wallet.channels[channel] = payment
success()