mirror of
https://github.com/logos-storage/nim-nitro.git
synced 2026-01-07 16:13:09 +00:00
Accepting payments
This commit is contained in:
parent
40b4782f9d
commit
b797a53e32
@ -5,6 +5,8 @@ import ../protocol
|
||||
|
||||
include questionable/errorban
|
||||
|
||||
export tables
|
||||
|
||||
type
|
||||
Balances* = OrderedTable[Destination, UInt256]
|
||||
|
||||
|
||||
@ -12,6 +12,12 @@ type
|
||||
func hasParticipant*(signed: SignedState, participant: EthAddress): bool =
|
||||
signed.state.channel.participants.contains(participant)
|
||||
|
||||
func isSignedBy*(signed: SignedState, account: EthAddress): bool =
|
||||
for (signer, signature) in signed.signatures:
|
||||
if signer == account and signature.verify(signed.state, signer):
|
||||
return true
|
||||
false
|
||||
|
||||
func verifySignatures*(signed: SignedState): bool =
|
||||
for (participant, signature) in signed.signatures:
|
||||
if not signed.hasParticipant(participant):
|
||||
|
||||
@ -65,17 +65,17 @@ func acceptChannel*(wallet: var Wallet, signed: SignedState): ?!ChannelId =
|
||||
|
||||
wallet.createChannel(signed).success
|
||||
|
||||
func state*(wallet: Wallet, channel: ChannelId): ?State =
|
||||
func latestSignedState*(wallet: Wallet, channel: ChannelId): ?SignedState =
|
||||
try:
|
||||
wallet.channels[channel].state.some
|
||||
wallet.channels[channel].some
|
||||
except KeyError:
|
||||
State.none
|
||||
SignedState.none
|
||||
|
||||
func state*(wallet: Wallet, channel: ChannelId): ?State =
|
||||
wallet.latestSignedState(channel)?.state
|
||||
|
||||
func signatures*(wallet: Wallet, channel: ChannelId): ?Signatures =
|
||||
try:
|
||||
wallet.channels[channel].signatures.some
|
||||
except KeyError:
|
||||
Signatures.none
|
||||
wallet.latestSignedState(channel)?.signatures
|
||||
|
||||
func signature*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
@ -86,17 +86,25 @@ func signature*(wallet: Wallet,
|
||||
return signature.some
|
||||
Signature.none
|
||||
|
||||
func balance(state: State,
|
||||
asset: EthAddress,
|
||||
destination: Destination): UInt256 =
|
||||
if balances =? state.outcome.balances(asset):
|
||||
try:
|
||||
balances[destination]
|
||||
except KeyError:
|
||||
0.u256
|
||||
else:
|
||||
0.u256
|
||||
|
||||
func balance*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
destination: Destination): UInt256 =
|
||||
if state =? wallet.state(channel):
|
||||
if balances =? state.outcome.balances(asset):
|
||||
try:
|
||||
return balances[destination]
|
||||
except KeyError:
|
||||
return 0.u256
|
||||
0.u256
|
||||
state.balance(asset, destination)
|
||||
else:
|
||||
0.u256
|
||||
|
||||
func balance*(wallet: Wallet,
|
||||
channel: ChannelId,
|
||||
@ -104,6 +112,19 @@ func balance*(wallet: Wallet,
|
||||
address: EthAddress): UInt256 =
|
||||
wallet.balance(channel, asset, address.toDestination)
|
||||
|
||||
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 =
|
||||
if state =? wallet.state(channel):
|
||||
state.total(asset)
|
||||
else:
|
||||
0.u256
|
||||
|
||||
func pay*(wallet: var Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
@ -129,3 +150,30 @@ func pay*(wallet: var Wallet,
|
||||
receiver: EthAddress,
|
||||
amount: UInt256): ?!SignedState =
|
||||
wallet.pay(channel, asset, receiver.toDestination, amount)
|
||||
|
||||
func acceptPayment*(wallet: var Wallet,
|
||||
channel: ChannelId,
|
||||
asset: EthAddress,
|
||||
sender: EthAddress,
|
||||
payment: SignedState): ?!void =
|
||||
if not wallet.channels.contains(channel):
|
||||
return void.failure "unknown channel"
|
||||
|
||||
if not (getChannelId(payment.state.channel) == channel):
|
||||
return void.failure "payment does not match channel"
|
||||
|
||||
let currentBalance = wallet.balance(channel, asset, wallet.address)
|
||||
let futureBalance = payment.state.balance(asset, wallet.destination)
|
||||
if futureBalance <= currentBalance:
|
||||
return void.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"
|
||||
|
||||
if not payment.isSignedBy(sender):
|
||||
return void.failure "missing signature on payment"
|
||||
|
||||
wallet.channels[channel] = payment
|
||||
ok()
|
||||
|
||||
@ -78,3 +78,8 @@ proc example*(_: type State): State =
|
||||
appDefinition: EthAddress.example,
|
||||
appData: seq[byte].example
|
||||
)
|
||||
|
||||
proc example*(_: type Signature): Signature =
|
||||
let key = PrivateKey.random
|
||||
let state = State.example
|
||||
key.sign(state)
|
||||
|
||||
@ -137,3 +137,63 @@ suite "wallet: making payments":
|
||||
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, 1.u256)
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isOk
|
||||
check wallet.pay(channel, asset, hub, 1.u256).isErr
|
||||
|
||||
suite "wallet: accepting payments":
|
||||
|
||||
let payerKey, receiverKey = PrivateKey.random()
|
||||
let asset = EthAddress.example
|
||||
let chainId = UInt256.example
|
||||
let nonce = UInt48.example
|
||||
|
||||
var payer, receiver: Wallet
|
||||
var channel: ChannelId
|
||||
|
||||
setup:
|
||||
payer = Wallet.init(payerKey)
|
||||
receiver = Wallet.init(receiverKey)
|
||||
channel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce, asset, 100.u256)
|
||||
let update = payer.latestSignedState(channel).get
|
||||
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
|
||||
check receiver.balance(channel, asset, receiver.address) == 42.u256
|
||||
|
||||
test "does not accept a decrease in receiver balance":
|
||||
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
|
||||
check receiver.balance(channel, asset, receiver.address) == 20
|
||||
|
||||
test "does not accept a payment where 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
|
||||
balances[payer.destination] += 10.u256
|
||||
payment.state.outcome.update(asset, balances)
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
|
||||
test "does not accept a payment without a signature":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
payment.signatures = @[]
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
|
||||
test "does not accept a payment with an incorrect signature":
|
||||
var payment = payer.pay(channel, asset, receiver.address, 10.u256).get
|
||||
payment.signatures = @{payer.address: Signature.example}
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
|
||||
test "does not accept a payment for an unknown channel":
|
||||
let newChannel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256)
|
||||
let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
|
||||
check receiver.acceptPayment(newChannel, asset, payer.address, payment).isErr
|
||||
|
||||
test "does not accept a payment for a different channel":
|
||||
let newChannel = payer.openLedgerChannel(
|
||||
receiver.address, chainId, nonce + 1, asset, 100.u256)
|
||||
let payment = payer.pay(newChannel, asset, receiver.address, 10.u256).get
|
||||
check receiver.acceptPayment(channel, asset, payer.address, payment).isErr
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user