@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
847 lines (804 loc) • 28.7 kB
JavaScript
/*
* ISC License (ISC)
* Copyright (c) 2018 aeternity developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
import { describe, it, before } from 'mocha'
import * as sinon from 'sinon'
import { BigNumber } from 'bignumber.js'
import { configure, ready, plan, BaseAe, networkId } from './'
import { generateKeyPair } from '../../es/utils/crypto'
import { unpackTx, buildTx } from '../../es/tx/builder'
import { decode } from '../../es/tx/builder/helpers'
import Channel from '../../es/channel'
const wsUrl = process.env.TEST_WS_URL || 'ws://localhost:3014/channel'
plan('10000000000000000')
const identityContract = `
contract Identity =
type state = ()
entrypoint main(x : int) = x
`
function waitForChannel (channel) {
return new Promise(resolve =>
channel.on('statusChanged', (status) => {
if (status === 'open') {
resolve()
}
})
)
}
describe('Channel', function () {
configure(this)
let initiator
let responder
let initiatorCh
let responderCh
let responderShouldRejectUpdate
let existingChannelId
let offchainTx
let contractAddress
let contractEncodeCall
let callerNonce
const initiatorSign = sinon.spy((tag, tx) => initiator.signTransaction(tx))
const responderSign = sinon.spy((tag, tx) => {
if (responderShouldRejectUpdate) {
return null
}
return responder.signTransaction(tx)
})
const sharedParams = {
url: wsUrl,
pushAmount: 3,
initiatorAmount: 1000000000000000,
responderAmount: 1000000000000000,
channelReserve: 20000000000,
ttl: 10000,
host: 'localhost',
port: 3001,
lockPeriod: 1
}
before(async function () {
initiator = await ready(this)
responder = await BaseAe({ nativeMode: true, networkId })
responder.setKeypair(generateKeyPair())
sharedParams.initiatorId = await initiator.address()
sharedParams.responderId = await responder.address()
await initiator.spend('6000000000000000', await responder.address())
})
after(() => {
initiatorCh.disconnect()
responderCh.disconnect()
})
beforeEach(() => {
responderShouldRejectUpdate = false
})
afterEach(() => {
initiatorSign.resetHistory()
responderSign.resetHistory()
})
it('can open a channel', async () => {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
sinon.assert.calledOnce(initiatorSign)
sinon.assert.calledWithExactly(initiatorSign, sinon.match('initiator_sign'), sinon.match.string)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(responderSign, sinon.match('responder_sign'), sinon.match.string)
const expectedTxParams = {
initiator: await initiator.address(),
responder: await responder.address(),
initiatorAmount: sharedParams.initiatorAmount.toString(),
responderAmount: sharedParams.responderAmount.toString(),
channelReserve: sharedParams.channelReserve.toString(),
// TODO: investigate why ttl is "0"
// ttl: sharedParams.ttl.toString(),
lockPeriod: sharedParams.lockPeriod.toString()
}
const { txType: initiatorTxType, tx: initiatorTx } = unpackTx(initiatorSign.firstCall.args[1])
const { txType: responderTxType, tx: responderTx } = unpackTx(responderSign.firstCall.args[1])
initiatorTxType.should.equal('channelCreate')
initiatorTx.should.eql({ ...initiatorTx, ...expectedTxParams })
responderTxType.should.equal('channelCreate')
responderTx.should.eql({ ...responderTx, ...expectedTxParams })
})
it('can post update and accept', async () => {
responderShouldRejectUpdate = false
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 1
const result = await initiatorCh.update(
await initiator.address(),
await responder.address(),
amount,
sign
)
result.accepted.should.equal(true)
result.signedTx.should.be.a('string')
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('update_ack'),
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount: sinon.match(amount),
from: sinon.match(await initiator.address()),
to: sinon.match(await responder.address()),
op: sinon.match('OffChainTransfer')
}])
})
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(
sign,
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount: sinon.match(amount),
from: sinon.match(await initiator.address()),
to: sinon.match(await responder.address()),
op: sinon.match('OffChainTransfer')
}])
})
)
const { txType } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelOffChain')
sign.firstCall.args[1].should.eql({
updates: [
{
amount,
from: await initiator.address(),
to: await responder.address(),
op: 'OffChainTransfer'
}
]
})
})
it('can post update and reject', async () => {
responderShouldRejectUpdate = true
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 1
const result = await initiatorCh.update(
await responder.address(),
await initiator.address(),
amount,
sign
)
result.accepted.should.equal(false)
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('update_ack'),
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount: sinon.match(amount),
from: sinon.match(await responder.address()),
to: sinon.match(await initiator.address()),
op: sinon.match('OffChainTransfer')
}])
})
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(
sign,
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount: sinon.match(amount),
from: sinon.match(await responder.address()),
to: sinon.match(await initiator.address()),
op: sinon.match('OffChainTransfer')
}])
})
)
const { txType } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelOffChain')
sign.firstCall.args[1].should.eql({
updates: [
{
amount,
from: await responder.address(),
to: await initiator.address(),
op: 'OffChainTransfer'
}
]
})
})
it('can get proof of inclusion', async () => {
const initiatorAddr = await initiator.address()
const responderAddr = await responder.address()
const params = { accounts: [initiatorAddr, responderAddr] }
const initiatorPoi = await initiatorCh.poi(params)
const responderPoi = await responderCh.poi(params)
initiatorPoi.should.be.a('string')
responderPoi.should.be.a('string')
const unpackedInitiatorPoi = unpackTx(decode(initiatorPoi, 'pi'), true)
const unpackedResponderPoi = unpackTx(decode(responderPoi, 'pi'), true)
buildTx(unpackedInitiatorPoi.tx, unpackedInitiatorPoi.txType, { prefix: 'pi' }).tx.should.equal(initiatorPoi)
buildTx(unpackedResponderPoi.tx, unpackedResponderPoi.txType, { prefix: 'pi' }).tx.should.equal(responderPoi)
})
it('can get balances', async () => {
const initiatorAddr = await initiator.address()
const responderAddr = await responder.address()
const addresses = [initiatorAddr, responderAddr]
const initiatorBalances = await initiatorCh.balances(addresses)
const responderBalances = await responderCh.balances(addresses)
initiatorBalances.should.be.an('object')
responderBalances.should.be.an('object')
initiatorBalances[initiatorAddr].should.be.a('number')
initiatorBalances[responderAddr].should.be.a('number')
responderBalances[initiatorAddr].should.be.a('number')
responderBalances[responderAddr].should.be.a('number')
})
it('can send a message', async () => {
const sender = await initiator.address()
const recipient = await responder.address()
const info = 'hello world'
initiatorCh.sendMessage(info, recipient)
const message = await new Promise(resolve => responderCh.on('message', resolve))
message.should.eql({
// TODO: don't ignore `channel_id` equality check
channel_id: message.channel_id,
from: sender,
to: recipient,
info
})
})
it('can request a withdraw and accept', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 2
const onOnChainTx = sinon.spy()
const onOwnWithdrawLocked = sinon.spy()
const onWithdrawLocked = sinon.spy()
responderShouldRejectUpdate = false
const result = await initiatorCh.withdraw(
amount,
sign,
{ onOnChainTx, onOwnWithdrawLocked, onWithdrawLocked }
)
result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
sinon.assert.called(onOnChainTx)
sinon.assert.calledOnce(onOwnWithdrawLocked)
sinon.assert.calledOnce(onWithdrawLocked)
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('withdraw_ack'),
sinon.match.string,
sinon.match({
updates: [{
amount,
op: 'OffChainWithdrawal',
to: await initiator.address()
}]
})
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(
sign,
sinon.match.string,
sinon.match({
updates: [{
amount,
op: 'OffChainWithdrawal',
to: await initiator.address()
}]
})
)
const { txType, tx } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelWithdraw')
tx.should.eql({
...tx,
toId: await initiator.address(),
amount: amount.toString()
})
})
it('can request a withdraw and reject', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 2
const onOnChainTx = sinon.spy()
const onOwnWithdrawLocked = sinon.spy()
const onWithdrawLocked = sinon.spy()
responderShouldRejectUpdate = true
const result = await initiatorCh.withdraw(
amount,
sign,
{ onOnChainTx, onOwnWithdrawLocked, onWithdrawLocked }
)
result.should.eql({ accepted: false })
sinon.assert.notCalled(onOnChainTx)
sinon.assert.notCalled(onOwnWithdrawLocked)
sinon.assert.notCalled(onWithdrawLocked)
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('withdraw_ack'),
sinon.match.string,
sinon.match({
updates: [{
amount,
op: 'OffChainWithdrawal',
to: await initiator.address()
}]
})
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(
sign,
sinon.match.string,
sinon.match({
updates: [{
amount,
op: 'OffChainWithdrawal',
to: await initiator.address()
}]
})
)
const { txType, tx } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelWithdraw')
tx.should.eql({
...tx,
toId: await initiator.address(),
amount: amount.toString()
})
})
it('can request a deposit and accept', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 2
const onOnChainTx = sinon.spy()
const onOwnDepositLocked = sinon.spy()
const onDepositLocked = sinon.spy()
responderShouldRejectUpdate = false
const result = await initiatorCh.deposit(
amount,
sign,
{ onOnChainTx, onOwnDepositLocked, onDepositLocked }
)
result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
sinon.assert.called(onOnChainTx)
sinon.assert.calledOnce(onOwnDepositLocked)
sinon.assert.calledOnce(onDepositLocked)
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('deposit_ack'),
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount,
op: 'OffChainDeposit',
from: await initiator.address()
}])
})
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(
sign,
sinon.match.string,
sinon.match({
updates: sinon.match([{
amount,
op: 'OffChainDeposit',
from: await initiator.address()
}])
})
)
const { txType, tx } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelDeposit')
tx.should.eql({
...tx,
fromId: await initiator.address(),
amount: amount.toString()
})
})
it('can request a deposit and reject', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = 2
const onOnChainTx = sinon.spy()
const onOwnDepositLocked = sinon.spy()
const onDepositLocked = sinon.spy()
responderShouldRejectUpdate = true
const result = await initiatorCh.deposit(
amount,
sign,
{ onOnChainTx, onOwnDepositLocked, onDepositLocked }
)
result.should.eql({ accepted: false })
sinon.assert.notCalled(onOnChainTx)
sinon.assert.notCalled(onOwnDepositLocked)
sinon.assert.notCalled(onDepositLocked)
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('deposit_ack'),
sinon.match.string,
sinon.match({
updates: [{
amount,
op: 'OffChainDeposit',
from: await initiator.address()
}]
})
)
const { txType, tx } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelDeposit')
tx.should.eql({
...tx,
fromId: await initiator.address(),
amount: amount.toString()
})
})
it('can close a channel', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const result = await initiatorCh.shutdown(sign)
result.should.be.a('string')
sinon.assert.notCalled(initiatorSign)
sinon.assert.calledOnce(responderSign)
sinon.assert.calledWithExactly(
responderSign,
sinon.match('shutdown_sign_ack'),
sinon.match.string,
sinon.match.any
)
sinon.assert.calledOnce(sign)
sinon.assert.calledWithExactly(sign, sinon.match.string)
const { txType, tx } = unpackTx(sign.firstCall.args[0])
txType.should.equal('channelCloseMutual')
tx.should.eql({
...tx,
fromId: await initiator.address(),
// TODO: check `initiatorAmountFinal` and `responderAmountFinal`
})
})
it('can leave a channel', async () => {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
const result = await initiatorCh.leave()
result.channelId.should.be.a('string')
result.signedTx.should.be.a('string')
existingChannelId = result.channelId
offchainTx = result.signedTx
})
it('can reestablish a channel', async () => {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
port: 3002,
existingChannelId,
offchainTx,
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
port: 3002,
existingChannelId,
offchainTx,
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
sinon.assert.notCalled(initiatorSign)
sinon.assert.notCalled(responderSign)
initiatorCh.disconnect()
responderCh.disconnect()
})
it('can solo close a channel', async () => {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
port: 3003,
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
port: 3003,
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
const initiatorAddr = await initiator.address()
const responderAddr = await responder.address()
const { signedTx } = await initiatorCh.update(
await initiator.address(),
await responder.address(),
100,
tx => initiator.signTransaction(tx)
)
const poi = await initiatorCh.poi({
accounts: [initiatorAddr, responderAddr],
})
const balances = await initiatorCh.balances([initiatorAddr, responderAddr])
const initiatorBalanceBeforeClose = await initiator.balance(initiatorAddr)
const responderBalanceBeforeClose = await responder.balance(responderAddr)
const closeSoloTx = await initiator.channelCloseSoloTx({
channelId: await initiatorCh.id(),
fromId: initiatorAddr,
poi,
payload: signedTx
})
const closeSoloTxFee = unpackTx(closeSoloTx).tx.fee
await initiator.sendTransaction(await initiator.signTransaction(closeSoloTx), { waitMined: true })
const settleTx = await initiator.channelSettleTx({
channelId: await initiatorCh.id(),
fromId: initiatorAddr,
initiatorAmountFinal: balances[initiatorAddr],
responderAmountFinal: balances[responderAddr]
})
const settleTxFee = unpackTx(settleTx).tx.fee
await initiator.sendTransaction(await initiator.signTransaction(settleTx), { waitMined: true })
const initiatorBalanceAfterClose = await initiator.balance(initiatorAddr)
const responderBalanceAfterClose = await responder.balance(responderAddr)
new BigNumber(initiatorBalanceAfterClose).minus(initiatorBalanceBeforeClose).plus(closeSoloTxFee).plus(settleTxFee).isEqualTo(
new BigNumber(balances[initiatorAddr])
).should.be.equal(true)
new BigNumber(responderBalanceAfterClose).minus(responderBalanceBeforeClose).isEqualTo(
new BigNumber(balances[responderAddr])
).should.be.equal(true)
})
it('can dispute via slash tx', async () => {
const initiatorAddr = await initiator.address()
const responderAddr = await responder.address()
initiatorCh = await Channel({
...sharedParams,
lockPeriod: 5,
role: 'initiator',
sign: initiatorSign,
port: 3004
})
responderCh = await Channel({
...sharedParams,
lockPeriod: 5,
role: 'responder',
sign: responderSign,
port: 3004
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
const initiatorBalanceBeforeClose = await initiator.balance(initiatorAddr)
const responderBalanceBeforeClose = await responder.balance(responderAddr)
const oldUpdate = await initiatorCh.update(initiatorAddr, responderAddr, 100, (tx) => initiator.signTransaction(tx))
const oldPoi = await initiatorCh.poi({
accounts: [initiatorAddr, responderAddr]
})
const recentUpdate = await initiatorCh.update(initiatorAddr, responderAddr, 100, (tx) => initiator.signTransaction(tx))
const recentPoi = await responderCh.poi({
accounts: [initiatorAddr, responderAddr]
})
const recentBalances = await responderCh.balances([initiatorAddr, responderAddr])
const closeSoloTx = await initiator.channelCloseSoloTx({
channelId: initiatorCh.id(),
fromId: initiatorAddr,
poi: oldPoi,
payload: oldUpdate.signedTx
})
const closeSoloTxFee = unpackTx(closeSoloTx).tx.fee
await initiator.sendTransaction(await initiator.signTransaction(closeSoloTx), { waitMined: true })
const slashTx = await responder.channelSlashTx({
channelId: responderCh.id(),
fromId: responderAddr,
poi: recentPoi,
payload: recentUpdate.signedTx
})
const slashTxFee = unpackTx(slashTx).tx.fee
await responder.sendTransaction(await responder.signTransaction(slashTx), { waitMined: true })
const settleTx = await responder.channelSettleTx({
channelId: responderCh.id(),
fromId: responderAddr,
initiatorAmountFinal: recentBalances[initiatorAddr],
responderAmountFinal: recentBalances[responderAddr]
})
const settleTxFee = unpackTx(settleTx).tx.fee
await responder.sendTransaction(await responder.signTransaction(settleTx), { waitMined: true })
const initiatorBalanceAfterClose = await initiator.balance(initiatorAddr)
const responderBalanceAfterClose = await responder.balance(responderAddr)
new BigNumber(initiatorBalanceAfterClose).minus(initiatorBalanceBeforeClose).plus(closeSoloTxFee).isEqualTo(
new BigNumber(recentBalances[initiatorAddr])
).should.be.equal(true)
new BigNumber(responderBalanceAfterClose).minus(responderBalanceBeforeClose).plus(slashTxFee).plus(settleTxFee).isEqualTo(
new BigNumber(recentBalances[responderAddr])
).should.be.equal(true)
})
it('can create a contract and accept', async () => {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
port: 3005,
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
port: 3005,
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
const code = await initiator.compileContractAPI(identityContract)
const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [])
const result = await initiatorCh.createContract({
code,
callData,
deposit: 1000,
vmVersion: 4,
abiVersion: 1
}, async (tx) => await initiator.signTransaction(tx))
result.should.eql({ accepted: true, address: result.address, signedTx: (await initiatorCh.state()).signedTx })
contractAddress = result.address
contractEncodeCall = (method, args) => initiator.contractEncodeCallDataAPI(identityContract, method, args)
})
it('can create a contract and reject', async () => {
responderShouldRejectUpdate = true
const code = await initiator.compileContractAPI(identityContract)
const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [])
const result = await initiatorCh.createContract({
code,
callData,
deposit: 1000,
vmVersion: 4,
abiVersion: 1
}, async (tx) => await initiator.signTransaction(tx))
result.should.eql({ accepted: false })
})
it('can call a contract and accept', async () => {
const result = await initiatorCh.callContract({
amount: 0,
callData: await contractEncodeCall('main', ['42']),
contract: contractAddress,
abiVersion: 1
}, async (tx) => await initiator.signTransaction(tx))
result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
callerNonce = Number(unpackTx((await initiatorCh.state()).signedTx).tx.encodedTx.tx.round)
})
it('can call a contract and reject', async () => {
responderShouldRejectUpdate = true
const result = await initiatorCh.callContract({
amount: 0,
callData: await contractEncodeCall('main', ['42']),
contract: contractAddress,
abiVersion: 1
}, async (tx) => await initiator.signTransaction(tx))
result.should.eql({ accepted: false })
})
it('can get contract call', async () => {
const result = await initiatorCh.getContractCall({
caller: await initiator.address(),
contract: contractAddress,
round: callerNonce
})
result.should.eql({
callerId: await initiator.address(),
callerNonce,
contractId: contractAddress,
gasPrice: result.gasPrice,
gasUsed: result.gasUsed,
height: result.height,
log: result.log,
returnType: 'ok',
returnValue: result.returnValue
})
const value = await initiator.contractDecodeDataAPI('int', result.returnValue)
value.should.eql({ type: 'word', value: 42 })
})
it('can call a contract using dry-run', async () => {
const result = await initiatorCh.callContractStatic({
amount: 0,
callData: await contractEncodeCall('main', ['42']),
contract: contractAddress,
abiVersion: 1
})
result.should.eql({
callerId: await initiator.address(),
callerNonce: result.callerNonce,
contractId: contractAddress,
gasPrice: result.gasPrice,
gasUsed: result.gasUsed,
height: result.height,
log: result.log,
returnType: 'ok',
returnValue: result.returnValue
})
const value = await initiator.contractDecodeDataAPI('int', result.returnValue)
value.should.eql({ type: 'word', value: 42 })
})
it('can clean contract calls', async () => {
await initiatorCh.cleanContractCalls()
initiatorCh.getContractCall({
caller: await initiator.address(),
contract: contractAddress,
round: callerNonce
}).should.eventually.be.rejectedWith('Rejected: Call not found')
})
it('can get contract state', async () => {
const result = await initiatorCh.getContractState(contractAddress)
result.should.eql({
contract: {
abiVersion: 1,
active: true,
deposit: 1000,
id: contractAddress,
ownerId: await initiator.address(),
referrerIds: [],
vmVersion: 4,
},
contractState: result.contractState
})
// TODO: contractState deserialization
})
it('can post snapshot solo transaction', async () => {
const snapshotSoloTx = await initiator.channelSnapshotSoloTx({
channelId: initiatorCh.id(),
fromId: await initiator.address(),
payload: (await initiatorCh.state()).signedTx
})
await initiator.sendTransaction(await initiator.signTransaction(snapshotSoloTx), { waitMined: true })
})
describe('throws errors', function () {
before(async function () {
initiatorCh = await Channel({
...sharedParams,
role: 'initiator',
port: 3006,
sign: initiatorSign
})
responderCh = await Channel({
...sharedParams,
role: 'responder',
port: 3006,
sign: responderSign
})
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
})
async function update ({ from, to, amount, sign }) {
return initiatorCh.update(
from || await initiator.address(),
to || await responder.address(),
amount || 1,
sign || initiator.signTransaction
)
}
it('when posting an update with negative amount', async () => {
return update({ amount: -10 }).should.eventually.be.rejectedWith('Amount cannot be negative')
})
it('when posting an update with insufficient balance', async () => {
return update({ amount: 2000000000000000 }).should.eventually.be.rejectedWith('Insufficient balance')
})
it('when posting an update with incorrect address', async () => {
return update({ from: 'ak_123' }).should.eventually.be.rejectedWith('Rejected')
})
it('when posting an update with incorrect amount', async () => {
return update({ amount: '1' }).should.eventually.be.rejectedWith('Rejected')
})
})
})