diff --git a/README.md b/README.md index a842966..112bc8a 100644 --- a/README.md +++ b/README.md @@ -46,34 +46,46 @@ Then, in a different terminal, run `./docker-run.sh` ##### Local installation Install dependencies: -``` +```bash $ npm i ``` -### Using Batchpayments -Run the test suite to verify everything is going smoothly: -``` -npm run test -``` -To run a development client use `ganache-cli`: +Run the test suite to check everything is going smoothly: ``` $ npx ganache-cli +$ npm run test ``` -**Simple example** -Run the following command to complete a couple of basic operations on BP +### Using Batch Payments library +```javascript +const { Batpay } = require('../lib')(web3, artifacts) - $ npx truffle exec src/run.js +// Initialize Baypay +batpay = new Batpay.BP( + batpayContractInstance, + yourTokenContractInstance +) -**Getting Benchmark Data** -The following command will exeute BatchPayments and measure gas usage, producing a detailed report +batpay.init() - $ npx truffle exec src/benchmarks.js +// Start registering Batch Payments +batpay.registerPayment(...) +``` -**Complete Walkthrough** -Take a look at *src/demo.js* for a detailed exploration of most BP features. +Take a look at [this example](src/demo.js) for a walkthrough most of BatPay features. $ npx truffle exec src/demo.js -**Remote Deployment** +#### Getting Benchmark Data +The following command will execute BatchPayments and measure gas usage, producing a detailed report + + $ npx truffle exec src/benchmarks.js + +### Using Batch Payments contracts +#### Simple example +Run the following command to complete a couple of basic operations on BP. + + $ npx truffle exec src/run.js + +### Remote Deployment In order to deploy any remote environment, the `deploy.json` file must be set up. You can start by copying the `deploy.example.json` file, renaming it and editing it as suitable. @@ -84,7 +96,7 @@ $ vi deploy.json If a remote network is used, a private key is needed to sign the deployment transactions. -### Configuration +#### Configuration * `infuraToken`: The API key supplied by [Infura](https://infura.io/) to be used on remote environments. * `environments`: Allowed keys are `development`, `coverage`, `remoteDevelopment`, `staging` and `production`. * Environment options: diff --git a/lib/bat.js b/lib/Batpay.js similarity index 54% rename from lib/bat.js rename to lib/Batpay.js index bf467c1..a8b3e04 100644 --- a/lib/bat.js +++ b/lib/Batpay.js @@ -1,11 +1,9 @@ -var BigNumber = global.web3.BigNumber -var merkle = require('../lib/merkle') -var abi = require('ethereumjs-abi') +const BigNumber = global.web3.BigNumber +const merkle = require('../lib/merkle') +const abi = require('ethereumjs-abi') const bytesPerId = 4 - -var NEW_ACCOUNT_FLAG = new BigNumber(2).pow(256).minus(1) - +const NEW_ACCOUNT_FLAG = new BigNumber(2).pow(256).minus(1) const prefs = { default: { maxBulk: 5000, @@ -15,17 +13,17 @@ const prefs = { collectStake: 500, challengeStake: 100, unlockBlocks: 5, - maxCollectAmount : 1000000 + maxCollectAmount: 1000000 }, testing: { maxBulk: 5000, maxTransfer: 5000, - challengeBlocks: 5, + challengeBlocks: 15, challengeStepBlocks: 5, collectStake: 500, challengeStake: 100, - unlockBlocks: 5, - maxCollectAmount : 1000000 + unlockBlocks: 15, + maxCollectAmount: 1000000 }, recommended: { maxBulk: 5000, @@ -35,96 +33,20 @@ const prefs = { collectStake: 1000000, challengeStake: 100000, unlockBlocks: 60, - maxCollectAmount : 1000000 - } -} - -function findEvent (array, eventName) { - let x = array.find(ev => ev.event == eventName) - if (x) return x.args - throw new Error(eventName + ' not found') -} - -function hex (x) { - return ('00' + x.toString(16)).substr(-2) -} - -function hexStr (n, len) { - let s = n.toString(16) - while (s.length < len * 2) s = '0' + s - return s -} - -function toLeftPaddedBytes32 (value) { - const h = value.toString(16); - const b = ('0000000000000000000000000000000000000000000000000000000000000000' + h).substr(-64); - return `0x${b}`; -} - -function item (amount, index) { - return hexStr(amount, 8) + hexStr(index, 4) -} - -function getChallengeData (amounts, indexes) { - let data = '0x' - for (let i = 0; i < amounts.length && i < indexes.length; i++) { data = data + item(amounts[i], indexes[i]) } - - return data -} - -function getPayData (list) { - list.sort((a, b) => a - b) - - var last = 0 - var data = '' - - for (let i = 0; i < list.length; i++) { - let delta = list[i] - last - - let number = '' - for (let j = 0; j < bytesPerId; j++) { - number = hex(delta % 256) + number - delta = Math.trunc(delta / 256) - } - - data = data + number - last = list[i] + maxCollectAmount: 1000000 } - - return new web3.BigNumber('0xff' + hex(bytesPerId) + data) -} - -function hashLock (unlocker, key) { - let hash = abi.soliditySHA3(['uint32', 'bytes'], [unlocker, Buffer.from(key, 'utf8')]).toString('hex') - return '0x' + hash -} - -function hashCollect (instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) { - let hash = abi.soliditySHA3( - ['address', 'uint32', 'uint32', 'uint32', 'uint32', 'uint64', 'uint64', 'address'], - [instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr]).toString('hex') - - return '0x' + hash -} - -async function signCollect (account, instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) { - let hash = hashCollect(instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) - - // asyncSign is necessary because web3-provider-engine does not support - // synchronous requests - const asyncSign = (account, hash) => new Promise((resolve, reject) => { - web3.eth.sign(account, hash, (err, result) => { - if (err) { - return reject(err) - } - resolve(result) - }) - }) - - return asyncSign(account, hash) } +/** + * BP is a wrapper providing a simplified interface to BatPay smart contracts. + */ class BP { + /** + * Constructor takes two instances of a already deployed TruffleContract, one + * for BatPay and the other for the token to be handled by BatPay. + * @param {TruffleContract} bp - BatPay TruffleContract instance. + * @param {TruffleContract} token Token TruffleContract instance. + */ constructor (bp, token) { this.bp = bp this.st = token @@ -136,6 +58,9 @@ class BP { this.ids = {} } + /** + * Initialize BP to BatPay's contract config. + */ async init () { let params = await this.bp.params.call() this.maxBulk = params[0].toNumber() @@ -146,60 +71,90 @@ class BP { this.challengeStake = params[5].toNumber() this.unlockBlocks = params[6].toNumber() this.maxCollectAmount = params[13].toNumber() - this.INSTANT_SLOT = (await this.bp.INSTANT_SLOT.call()).toNumber() } - async register (addr) { - let t = await this.bp.register({ from: addr }) + /** + * Register a new account in the name of `address`. + * @param {string} address Ethereum adderess owner of the newly created account. + * @return {[id, tx_receipt]} + */ + async registerAccount (address) { + let t = await this.bp.register({ from: address }) let recp = await t.receipt let log = findEvent(t.logs, 'AccountRegistered') let id = log.accountId.toNumber() - this.ids[id] = addr + this.ids[id] = address return [ id, recp ] } - async bulkRegister (list) { - let nbulk = list.length - let tree = merkle.merkle(list) - let tx = await this.bp.bulkRegister(nbulk, tree.roothash) + /** + * Reserve a list of accounts and generate the proof that will be later be + * used to claim the accounts. + * @param {string[]} addresses List of addresses to be registered. + */ + async bulkRegister (addresses) { + let bulkSize = addresses.length + // Use the addresses list to build a Merkle tree that will later be used as proof. + let tree = merkle.merkle(addresses) + + // Reserve accounts and provide the hash of the root of the Merkle tree. + // This hash will later be used to verify the identity of the claimer of + // the accounts reserved. + let tx = await this.bp.bulkRegister(bulkSize, tree.roothash) let recp = await tx.receipt - let z = findEvent(tx.logs, 'BulkRegister') + let bulkRegisterLog = findEvent(tx.logs, 'BulkRegister') + let smallestAccountId = bulkRegisterLog.smallestAccountId.toNumber() + let bulkId = bulkRegisterLog.bulkId.toNumber() - let smallestAccountId = z.smallestAccountId.toNumber() - let bulkId = z.bulkId.toNumber() - - for (let i = 0; i < list.length; i++) { - this.ids[smallestAccountId + i] = list[i] + // Save accountIds and their corresponding addresses. + for (let i = 0; i < addresses.length; i++) { + this.ids[smallestAccountId + i] = addresses[i] } return { tree, smallestAccountId, bulkId, recp } } - async claimBulkRegistrationId (bulk, addr, id) { - let i = id - bulk.smallestAccountId + /** + * Finish registration of an account and claim it, providing the Merkle tree + * generated by `bulkRegister`. + * @param {object} bulk object containing the merkle tree, the + * smallestAccountId and the bulkId generated by `bulkRegister`. + * @param {string} address account owner + * @param {number} accountId Id of the account to be registered + */ + async claimBulkRegistrationId (bulk, address, accountId) { + let i = accountId - bulk.smallestAccountId let proof = merkle.getProof(bulk.tree, i) proof = proof.map(x => x.v) - let tx = await this.bp.claimBulkRegistrationId(addr, proof, id, bulk.bulkId) + let tx = await this.bp.claimBulkRegistrationId(address, proof, accountId, bulk.bulkId) let recp = await tx.receipt let log = findEvent(tx.logs, 'AccountRegistered') - return [log.accountId, log.accountAddress, recp ] + return [log.accountId, log.accountAddress, recp] } - async deposit (amount, id, from) { - let addr = from || this.ids[id] - let nid = id + /** + * Fund BatPay contract. + * Approve an ERC20 token transfer and transfer those tokens to the BatPay + * contract. + * @param {number} amount amount of tokens + * @param {number} accountId tokens owner + * @param {string} from address issuing the transaction + */ + async deposit (amount, accountId, from) { + let addr = from || this.ids[accountId] + let nid = accountId let t1 = await this.st.approve(this.bp.address, amount, { from: addr }) await t1.receipt - let t2 = await this.bp.deposit(amount, id, { from: addr }) + let t2 = await this.bp.deposit(amount, accountId, { from: addr }) await t2.receipt - if (id == -1) { + if (accountId == -1) { let log = findEvent(t2.logs, 'AccountRegistered') nid = log.accountId.toNumber() this.ids[nid] = from @@ -207,20 +162,29 @@ class BP { return [nid, t1.receipt, t2.receipt] } - async registerPayment (from, amount, fee, list, lockingKeyHash) { - let data = getPayData(list) - - let tx = await this.bp.registerPayment(from, amount, fee, data, 0, 0, lockingKeyHash, 0) - await tx.receipt - - let payIndex = (await this.bp.getPaymentsLength.call()) - 1 + /** + * Register a new payment to multiple account IDs. + * @param {number} fromAccountId Account to debit funds from. + * @param {number} amount Tokens to pay to each destination. + * @param {number} fee Tokens to be payed to the party providing the unlocking service. + * @param {number[]} payeesAccountsIds List of account ids to pay `amount` to. + * @param {string} lockingKeyHash hash of (unlockerAccountId, unlocker's secret key). + * See `utils.hashLock`. + */ + async registerPayment ({ fromAccountId, amount, unlockerFee, payeesAccountsIds, lockingKeyHash } = {}) { + // Build a gas-efficient representation of the destination accounts list. + const payData = getPayData(payeesAccountsIds) + + const tx = await this.bp.registerPayment(fromAccountId, amount, unlockerFee, payData, 0, 0, lockingKeyHash, 0) + + const payIndex = (await this.bp.getPaymentsLength.call()) - 1 this.payments[payIndex] = amount - this.payList[payIndex] = list - list.forEach(x => { - if (this.accountList[x] == undefined) { - this.accountList[x] = [] + this.payList[payIndex] = payeesAccountsIds + payeesAccountsIds.forEach(payeeAccountId => { + if (this.accountList[payeeAccountId] === undefined) { + this.accountList[payeeAccountId] = [] } - this.accountList[x].push(payIndex) + this.accountList[payeeAccountId].push(payIndex) }) return [payIndex, tx.receipt] @@ -240,12 +204,12 @@ class BP { let smallestAccountId = z.smallestAccountId.toNumber() let bulkId = z.bulkId.toNumber() let payIndex = (await this.bp.getPaymentsLength.call()) - 1 - + for (let i = 0; i < bulkList.length; i++) { let j = smallestAccountId + i - + this.ids[j] = bulkList[i] - + if (this.accountList[j] == undefined) { this.accountList[j] = [] } @@ -255,7 +219,7 @@ class BP { this.payments[payIndex] = amount this.payList[payIndex] = list this.bulkPayList[payIndex] = bulkList - + list.forEach(x => { if (this.accountList[x] == undefined) { this.accountList[x] = [] @@ -263,14 +227,22 @@ class BP { this.accountList[x].push(payIndex) }) - - return [ payIndex, { tree, smallestAccountId, bulkId }, tx.receipt] } - - async unlock (payIndex, unlocker, key) { - let t = await this.bp.unlock(payIndex, unlocker, key) + /** + * Unlock a previously registered payment, while revealing the key used to + * lock the payment. + * Unlocking the payment and revealing the key atomically allows for the + * trustless exchange of digital goods (if the same key was previously used + * to encrypt such digital goods). + * @param {number} payIndex Payment index returned by `registerPayment` + * @param {number} unlockerAccountId ID of the party providing the unlocking service. + * @param {string} key Key used to generate the lockingKeyHash when the payment + * was registered. + */ + async unlock (payIndex, unlockerAccountId, key) { + let t = await this.bp.unlock(payIndex, unlockerAccountId, key) return await t.receipt } @@ -279,10 +251,23 @@ class BP { return await t.receipt } - async collect (delegate, slot, to, fromId, toId, amount, fee, addr) { - let signature = await signCollect(this.ids[to], this.bp.address, delegate, to, fromId, toId, amount, fee, addr) - - let tx = await this.bp.collect(delegate, slot, to, toId, amount, fee, addr, signature) + /** + * Collect a batch of registered payments between two payment IDs. + * @param {nulmber} delegate ID of the account performing the collect operation on + * behalf of the user. + * @param {number} slot ID of the slot that will be used for the challenge game. + * @param {number} toAccountId Payee account ID. + * @param {number} fromPaymentId lowest payment ID to collect. + * @param {number} toPaymentId highest payment ID to collect. + * @param {number} amount sum of tokens corresponding to the payments between + * `fromPaymentId` and `toPaymentId`. + * @param {number} fee Fee to be payed to the delegate. + * @param {string} address Address to transfer the funds to. + */ + async collect ({ delegate, slot, toAccountId, fromPaymentId, toPaymentId, amount, fee, address } = {}) { + let signature = await signCollect(this.ids[toAccountId], this.bp.address, delegate, toAccountId, fromPaymentId, toPaymentId, amount, fee, address) + + let tx = await this.bp.collect(delegate, slot, toAccountId, toPaymentId, amount, fee, address, signature) return tx } @@ -342,9 +327,9 @@ class BP { return await this.bp.accounts.call(id) } - async getCollectedIndex (id) { - let x = await this.getAccount(id) - return x[2].toNumber() + async getCollectedIndex (accountId) { + let account = await this.getAccount(accountId) + return account[2].toNumber() } async getCollectAmount (id, fromIndex, toIndex) { @@ -377,17 +362,109 @@ class BP { } async showBalance () { + console.log(' '.repeat(66)) + let accounts = [] for (let i = 0; i < 10; i++) { - let [addr, balance, lastCollectedPaymentId] = await this.getAccount(i) - let tb = await this.tokenBalance(this.ids[i]) - tb = tb.toString(10).padStart(9, ' ') - balance = balance.toString(10).padStart(6, ' ') - console.log(i + ':' + tb + ' ' + balance + ' ' + lastCollectedPaymentId + '\t' + addr) + const [addr, balance, lastCollectedPaymentId] = await this.getAccount(i) + const tokenBalance = await this.tokenBalance(this.ids[i]) + accounts.push({ + 'Account ID': i, + 'Token balance': tokenBalance.toNumber(), + 'Token balance (deposited in BatPay)': balance.toNumber(), + 'Last collected payment ID': lastCollectedPaymentId.toNumber(), + 'Address': addr + }) } - console.log('----------------') + console.table(accounts) + console.log() } } +function findEvent (array, eventName) { + let x = array.find(ev => ev.event == eventName) + if (x) return x.args + throw new Error(eventName + ' not found') +} + +function hex (x) { + return ('00' + x.toString(16)).substr(-2) +} + +function hexStr (n, len) { + let s = n.toString(16) + while (s.length < len * 2) s = '0' + s + return s +} + +function toLeftPaddedBytes32 (value) { + const h = value.toString(16) + const b = ('0000000000000000000000000000000000000000000000000000000000000000' + h).substr(-64) + return `0x${b}` +} + +function item (amount, index) { + return hexStr(amount, 8) + hexStr(index, 4) +} + +function getChallengeData (amounts, indexes) { + let data = '0x' + for (let i = 0; i < amounts.length && i < indexes.length; i++) { data = data + item(amounts[i], indexes[i]) } + + return data +} + +function getPayData (list) { + list.sort((a, b) => a - b) + + var last = 0 + var data = '' + + for (let i = 0; i < list.length; i++) { + let delta = list[i] - last + + let number = '' + for (let j = 0; j < bytesPerId; j++) { + number = hex(delta % 256) + number + delta = Math.trunc(delta / 256) + } + + data = data + number + last = list[i] + } + + return new web3.BigNumber('0xff' + hex(bytesPerId) + data) +} + +function hashLock (unlocker, key) { + let hash = abi.soliditySHA3(['uint32', 'bytes'], [unlocker, Buffer.from(key, 'utf8')]).toString('hex') + return '0x' + hash +} + +function hashCollect (instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) { + let hash = abi.soliditySHA3( + ['address', 'uint32', 'uint32', 'uint32', 'uint32', 'uint64', 'uint64', 'address'], + [instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr]).toString('hex') + + return '0x' + hash +} + +async function signCollect (account, instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) { + let hash = hashCollect(instance, delegate, toId, fromPayIndex, toPayIndex, amount, fee, addr) + + // asyncSign is necessary because web3-provider-engine does not support + // synchronous requests + const asyncSign = (account, hash) => new Promise((resolve, reject) => { + web3.eth.sign(account, hash, (err, result) => { + if (err) { + return reject(err) + } + resolve(result) + }) + }) + + return asyncSign(account, hash) +} + module.exports = { NEW_ACCOUNT_FLAG, BP, diff --git a/lib/eth-client.js b/lib/eth-client.js new file mode 100644 index 0000000..d69bcc2 --- /dev/null +++ b/lib/eth-client.js @@ -0,0 +1,51 @@ +const assert = require('assert') +const semver = require('semver') + +const EthClient = { + async nickname () { + return (await this.checkVersion())[0] + }, + + async version () { + return (await this.checkVersion())[1] + }, + + async checkVersion () { + let matches = {} + const allowedClients = { + 'geth': /Geth\/v([0-9.]+)/, + 'ganache': /TestRPC\/v([0-9.]+)\/ethereum-js/ + } + const fullVersion = await this._fullVersion() + + Object.keys(allowedClients).map(async (key, _) => { + matches[key] = allowedClients[key].exec(fullVersion) + }) + + const filteredMatches = Object.entries(matches).filter(a => a[1]) + assert(filteredMatches.length === 1, 'One and only one client should match') + let [client, match] = filteredMatches.pop() + + assert( + (client === 'ganache' && semver.satisfies(match[1], '2.5.x')) || + (client === 'geth' && semver.satisfies(match[1], '1.8.x')), + `I don't know how handle the current client ${fullVersion}` + ) + return [client, match[1]] + }, + + _fullVersion () { + return new Promise((resolve, reject) => { + web3.version.getNode(EthClient._web3Callback(resolve, reject)) + }) + }, + + _web3Callback (resolve, reject) { + return (error, value) => { + if (error) reject(error) + else resolve(value) + } + } +} + +module.exports = EthClient diff --git a/lib/index.js b/lib/index.js index 24af5f3..9bb93a9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,17 +1,15 @@ - module.exports = function (web3, artifacts) { + // truffle seems to require injecting these globals global.web3 = web3 global.artifacts = artifacts - // truffle seems to require injecting these globals - var utils = require('../lib/utils') var { getInstances, newInstances } = utils - var bat = require('../lib/bat') + var Batpay = require('../lib/Batpay') var merkle = require('../lib/merkle') return { - bat, + Batpay, merkle, utils, getInstances, diff --git a/lib/utils.js b/lib/utils.js index 5df59e1..3474f0a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,22 +1,19 @@ -// use +// usage // const utils = require('../lib/utils.js')(artifacts, web3); // var abi = require('ethereumjs-abi') var StandardToken = artifacts.require('StandardToken') var BatPay = artifacts.require('BatPay') var Merkle = artifacts.require('Merkle') -var TestHelper = artifacts.require('TestHelper') -var Accounts = artifacts.require('Accounts') -var Payments = artifacts.require('Payments') var Challenge = artifacts.require('Challenge') var MassExitLib = artifacts.require('MassExitLib') -var bat = require('../lib/bat.js') var seedRandom = require('seedrandom') +var bat = require('../lib/Batpay.js') +const EthClient = require('./eth-client.js') + const bytesPerId = 4 -var testhelper -bat async function getInstances () { let bp = await BatPay.deployed() const tAddress = await bp.token.call() @@ -40,7 +37,6 @@ async function newInstances (params = {}, tokenAddress) { stdToken = await StandardToken.new('Token', 'TOK', 2, 100000000); batPayTokenAddress = stdToken.address; } - const merkle = await Merkle.new(); const challenge = await Challenge.new(); const massExitLib = await MassExitLib.new(); @@ -59,18 +55,17 @@ async function newInstances (params = {}, tokenAddress) { batPayParams.unlockBlocks, batPayParams.maxCollectAmount ); - let token = stdToken return { bp, token, merkle, challenge, massExitLib }; } -async function skipBlocks (n) { - let v = [...Array(n)].map(advanceBlock); +async function skipGanacheBlocks (n) { + let v = [...Array(n)].map(advanceGanacheBlock); await Promise.all(v) } -function advanceBlock () { +function advanceGanacheBlock () { return new Promise((resolve, reject) => { web3.currentProvider.sendAsync({ jsonrpc: '2.0', @@ -154,6 +149,34 @@ function signCollect (account, instance, delegate, toId, fromPayIndex, toPayInde return sign } +function sleep (seconds) { + return new Promise(resolve => setTimeout(resolve, seconds * 1000)) +} + +function range (start, end) { + return Array(end - start + 1).fill().map((_, idx) => start + idx) +} + +async function waitBlocks (blocksToWait) { + const initialBlockNumber = web3.eth.blockNumber + while (true) { + if (web3.eth.blockNumber >= initialBlockNumber + blocksToWait) break + await sleep(5) + } +} + +async function skipBlocks (numberOfBlocks) { + const clientName = await EthClient.nickname() + + if (clientName === 'ganache') { + await skipGanacheBlocks(numberOfBlocks) + } else if (clientName === 'geth') { + await waitBlocks(numberOfBlocks) + } else { + throw new Error('Not implemented') + } +} + module.exports = { getInstances, newInstances, @@ -164,5 +187,7 @@ module.exports = { signCollect, hashCollect, hashLock, - skipBlocks + range, + skipBlocks, + sleep } diff --git a/src/benchmarks.js b/src/benchmarks.js index e8460a1..1d02281 100644 --- a/src/benchmarks.js +++ b/src/benchmarks.js @@ -1,5 +1,5 @@ var lib = require('../lib')(web3, artifacts) -var bat = lib.bat +var bat = lib.Batpay var utils = lib.utils var bp, st var b @@ -40,9 +40,9 @@ async function init () { } async function register () { - let [i0] = await b.register(acc[0]) + let [i0] = await b.registerAccount(acc[0]) - let [i1, t2] = await b.register(acc[0]) + let [i1, t2] = await b.registerAccount(acc[0]) addStat('register', t2.gasUsed) id = i0 @@ -74,14 +74,20 @@ async function bulkReg (count) { let bulk = await b.bulkRegister(list) addStat('bulkRegister-' + count, bulk.recp.gasUsed) - let [,,t] = await b.claimBulkRegistrationId(bulk, list[0], bulk.smallestAccountId) + let [,, t] = await b.claimBulkRegistrationId(bulk, list[0], bulk.smallestAccountId) addStat('claimBulkRegistrationId', t.gasUsed) } async function registerPayment (amount, fee, count, lockingKeyHash, name) { let list = utils.randomIds(count, max, 'batpay tests seed') - let [, t] = await b.registerPayment(0, 10, fee, list, lockingKeyHash) + let [, t] = await b.registerPayment({ + fromAccountId: 0, + amount: 10, + unlockerFee: fee, + payeesAccountsIds: list, + lockingKeyHash: lockingKeyHash + }) addStat(name, t.gasUsed) } @@ -89,8 +95,13 @@ async function unlock () { let list = [] for (let i = 0; i < 100; i++) list.push(i) let lockingKeyHash = utils.hashLock(id2, passcode) - let [pid] = await b.registerPayment(0, 10, 1, list, lockingKeyHash) - + let [pid] = await b.registerPayment({ + fromAccountId: 0, + amount: 10, + unlockerFee: 1, + payeesAccountsIds: list, + lockingKeyHash: lockingKeyHash + }) let t2 = await b.unlock(pid, id2, passcode) addStat('unlock', t2.gasUsed) } @@ -99,7 +110,13 @@ async function refundLockedPayment () { let list = [] for (let i = 0; i < 100; i++) list.push(i) let lockingKeyHash = utils.hashLock(id2, passcode) - let [pid] = await b.registerPayment(0, 10, 1, list, lockingKeyHash) + let [pid] = await b.registerPayment({ + fromAccountId: 0, + amount: 10, + unlockerFee: 1, + payeesAccountsIds: list, + lockingKeyHash: lockingKeyHash + }) await utils.skipBlocks(b.unlockBlocks) @@ -113,13 +130,49 @@ async function withdraw (amount) { } async function collect () { - await b.collect(id, 0, id2, 0, 1, 100, 2, 0) - let t2 = await b.collect(id, 1, id2, 1, 2, 100, 2, 0) + await b.collect({ + delegate: id, + slot: 0, + toAccountId: id2, + fromPaymentId: 0, + toPaymentId: 1, + amount: 100, + fee: 2, + address: 0 + }) + let t2 = await b.collect({ + delegate: id, + slot: 1, + toAccountId: id2, + fromPaymentId: 1, + toPaymentId: 2, + amount: 100, + fee: 2, + address: 0 + }) addStat('collect-empty-nowd', t2.receipt.gasUsed) - let t3 = await b.collect(id, 2, id2, 2, 3, 100, 2, acc[0]) + let t3 = await b.collect({ + delegate: id, + slot: 2, + toAccountId: id2, + fromPaymentId: 2, + toPaymentId: 3, + amount: 100, + fee: 2, + address: acc[0] + }) addStat('collect-empty-withdraw', t3.receipt.gasUsed) - let t7 = await b.collect(id, b.INSTANT_SLOT, id2, 3, 4, 100, 2, acc[0]) + let t7 = await b.collect({ + delegate: id, + slot: b.INSTANT_SLOT, + toAccountId: id2, + fromPaymentId: 3, + toPaymentId: 4, + amount: 100, + fee: 2, + address: acc[0] + }) addStat('collect-empty-instant-withdraw', t7.receipt.gasUsed) await utils.skipBlocks(b.challengeBlocks) @@ -129,7 +182,16 @@ async function collect () { let t5 = await b.freeSlot(id, 2) addStat('freeSlot-withdraw', t5.gasUsed) - let t6 = await b.collect(id, 1, id2, 4, 5, 100, 2, acc[0]) + let t6 = await b.collect({ + delegate: id, + slot: 1, + toAccountId: id2, + fromPaymentId: 4, + toPaymentId: 5, + amount: 100, + fee: 2, + address: acc[0] + }) addStat('collect-reuse-nowd', t6.receipt.gasUsed) } @@ -146,19 +208,19 @@ async function doStuff () { await depositE(depositAmount) await bulkReg(100) await bulkReg(1000) - + await withdraw(10) - + let lockingKeyHash = utils.hashLock(0, passcode) let run = [10, 50, 100, 250, 500, 1000, 2000, 3000] - + for (let i = 0; i < run.length; i++) { let count = run[i] await registerPayment(1, 0, count, 0, 'registerPayment-' + count + '-nolock') await registerPayment(1, 2, count, lockingKeyHash, 'registerPayment-' + count + '-lock') console.log(count) } - + await unlock() await refundLockedPayment() await collect() @@ -170,6 +232,7 @@ async function doStuff () { } } -module.exports = function () { - doStuff() +module.exports = async function (callback) { + await doStuff() + callback() } diff --git a/src/demo.js b/src/demo.js index 26006f6..66bdb35 100644 --- a/src/demo.js +++ b/src/demo.js @@ -1,181 +1,225 @@ -var lib = require('../lib')(web3, artifacts) -const BatPay = artifacts.require('./BatPay') -const StandardToken = artifacts.require('./StandardToken') -var utils = lib.utils -var merkle = lib.merkle -var BP = lib.bat.BP -var bat = lib.bat - -var b - -async function skipBlocks (n) { - console.log('skipping ' + n + ' blocks') - await utils.skipBlocks(n) +const { utils, Batpay, newInstances } = require('../lib')(web3, artifacts) + +// Globals +var accounts +var batpay + +async function main () { + // + // Deploy & initialize BatPay + // + let instances = await newInstances(Batpay.prefs.testing) + batpay = new Batpay.BP(instances.bp, instances.token) + await batpay.init() + + // + // Accounts registration + // + + // Simple registration + const numberOfAccounts = 10 + console.log(`Registering ${numberOfAccounts} accounts`) + for (let i = 0; i < numberOfAccounts; i++) { + log(`Registering account ${i + 1} of ${numberOfAccounts}\r`) + await batpay.registerAccount(accounts[i]) + } + await batpay.showBalance() + + // Bulk registration + let bulkSize = 100 + console.log(`Bulk registering ${bulkSize} accounts`) + let addressesBulk = utils.range(1, bulkSize).map((seed) => `0x${web3.sha3(seed.toString()).slice(-40)}`) + let bulk = await batpay.bulkRegister(addressesBulk) + + // Claiming of the bulk registered accounts + console.log(`Claiming bulk registered accounts`) + let claimedAccountsPromises = utils.range(0, bulkSize - 1).map( + (id) => batpay.claimBulkRegistrationId(bulk, addressesBulk[id], id + bulk.smallestAccountId) + ) + await Promise.all(claimedAccountsPromises) + + // + // Fund accounts with a good ol' ERC20.transfer + // + console.log('Funding accounts') + for (let i = 1; i < accounts.length; i++) { + log(`Transfering to account ${i + 1} of ${accounts.length}\r`) + await instances.token.transfer(accounts[i], 1000, { from: accounts[0] }) + } + await batpay.showBalance() + + // + // Fund BatPay proxy contract to let it manage batch payments. + // + console.log('Funding BatPay') + await batpay.deposit(10000, 0) + await batpay.deposit(500, 8) + await batpay.showBalance() + + // + // Register payments. + // + const numberOfPayments = 20 + const unlockerAccountId = 9 + const secretKey = 'hello world' + const paymentsIndexes = [] + + console.log(`Registering ${numberOfPayments} payments and unlocking them.`) + for (let i = 0; i < numberOfPayments; i++) { + log(`Payment ${i + 1} of ${numberOfPayments}\r`) + const [payIndex] = await batpay.registerPayment({ + fromAccountId: 0, + amount: 10, + unlockerFee: 1, + payeesAccountsIds: [1, 2, 3, 4, 5], + lockingKeyHash: utils.hashLock(unlockerAccountId, secretKey) + }) + paymentsIndexes.push(payIndex) + + // Unlocker provides their secret to unlock the payment and collect the fee. + await batpay.unlock(payIndex, unlockerAccountId, secretKey) + } + await batpay.showBalance() + + // Registered payments can timeout if no one unlocks them. This way, payers + // can recover the funds they vouched. Collection of payments is blocked + // until the lock timeout is over. + // Wait for the lock timeout to end. + await utils.skipBlocks(batpay.unlockBlocks) + + // + // Collect a batch of registered payments. + // + + // We must provide the index of the payments we wish to collect. BatPay will + // transfer the tokens corresponding to all payments between those indexes. + // Our first payment to collect will be the one right after the last previously + // collected payment. + const collectFromPaymentId = await batpay.getCollectedIndex(3) + let collectUntilPaymentId = paymentsIndexes[(paymentsIndexes.length / 2) - 1] + 1 + const collectors = [1, 2, 3, 4, 5] + + console.log(`Collecting payments ${collectFromPaymentId} to ${collectUntilPaymentId}\ + for accounts ${collectors[0]} to ${collectors.slice(-1)[0]}.`) + collectors.forEach(async (collector) => { + let [collectorAddress, , lastCollectedPaymentId] = await batpay.getAccount(collector) + lastCollectedPaymentId = lastCollectedPaymentId.toNumber() + collectorAddress = 0 + // Account #5 withdraws to #6 + if (collector == 5) collectorAddress = batpay.ids[6] + + // Get the sum of tokens corresponding to the payments we chose to collect. + const amount = await batpay.getCollectAmount(collector, lastCollectedPaymentId, collectUntilPaymentId) + + await batpay.collect({ + delegate: 0, + slot: collector, + toAccountId: collector, + fromPaymentId: lastCollectedPaymentId, + toPaymentId: collectUntilPaymentId, + amount: amount, + fee: 2, + address: collectorAddress + }) + }) + await batpay.showBalance() + + // + // Challenges + // + + // Scalability is also achieved through skipping some costly verifications and + // introducing a challenge game that allows the payer to repudiate payments + // if the `collect` amount requested is incorrect. + // + console.log('Challenging account #3.') + let data = batpay.getCollectData(3, collectFromPaymentId, collectUntilPaymentId) + await challenge(0, 3, 8, data) + await utils.skipBlocks(batpay.challengeBlocks) + + // + // Free slot, pay the delegate and the destination account. + // + console.log('Freeing collect slots.') + for (let i = 1; i <= 5; i++) { + await batpay.freeSlot(0, i) + } + await batpay.showBalance() + + // + // Collect remaining payments. + // + collectUntilPaymentId = paymentsIndexes[paymentsIndexes.length - 1] + 1 + console.log(`Collecting remaining payments for accounts\ + ${collectors[0]} to ${collectors.slice(-1)[0]}.`) + console.log('Collecting with instant slot.') + collectors.forEach(async (accountId) => { + let [, , lastCollectedPaymentId] = await batpay.getAccount(accountId) + let address = 0 + let amount = await batpay.getCollectAmount(accountId, lastCollectedPaymentId, collectUntilPaymentId) + + // Account #5 withdraws to #6 + if (accountId === 5) address = batpay.ids[6] + if (accountId === 3) amount = amount + 100 + + await batpay.collect({ + delegate: 0, + slot: accountId + batpay.INSTANT_SLOT, + toAccountId: accountId, + fromPaymentId: lastCollectedPaymentId.toNumber(), + toPaymentId: collectUntilPaymentId, + amount: amount, + fee: 1, + address: address + }) + }) + await batpay.showBalance() + + // In order to free the slots and forward the funds we need to wait for the + // challenge timeout to be over. + await utils.skipBlocks(batpay.challengeBlocks + 1) + console.log('Freeing collect slots.') + for (let i = 1; i <= 5; i++) { + await batpay.freeSlot(0, i + batpay.INSTANT_SLOT) + } + await batpay.showBalance() } +const log = (obj) => process.stdout.write(obj) + async function showSlot (delegate, slot) { - let x = await b.bp.collects.call(delegate, slot) + let x = await batpay.bp.collects.call(delegate, slot) x = x.map(x => x.toNumber ? x.toNumber() : x) console.log('state=' + x[6]) } async function challenge (delegate, slot, challenger, list) { - await showSlot(delegate, slot) - let c1 = await b.challenge_1(delegate, slot, challenger) - await c1 - console.log('challenge_1 ' + c1.transactionHash) + await batpay.challenge_1(delegate, slot, challenger) - let amounts = list.map(x => b.payments[x]) + let amounts = list.map(x => batpay.payments[x]) + let data = Batpay.getChallengeData(amounts, list) - let data = lib.bat.getChallengeData(amounts, list) + await batpay.challenge_2(delegate, slot, data) - await showSlot(delegate, slot) - console.log(data) - - let c2 = await b.challenge_2(delegate, slot, data) - await c2 - console.log('challenge_2 ' + c2.transactionHash) - await showSlot(delegate, slot) - - let c3 = await b.challenge_3(delegate, slot, data, 1, challenger) - await c3 - console.log('challenge_3 ' + c3.transactionHash) + await batpay.challenge_3(delegate, slot, data, 1, challenger) let payData = utils.getPayData([1, 2, 3, 4, 5]) - await showSlot(delegate, slot) - - let c4 = await b.challenge_4(delegate, slot, payData) - await c4 - console.log('challenge_4 ' + c4.transactionHash) - showSlot(delegate, slot) - - console.log('delegate=' + await b.balanceOf(delegate)) - let c5 = await b.challenge_failed(delegate, slot) - await c5 - console.log('challenge_failed ' + c5.transactionHash) - showSlot(delegate, slot) - console.log('delegate=' + await b.balanceOf(delegate)) -} - -async function doStuff () { - try { - console.log('Instantiate contracts') - let x = await lib.newInstances(bat.prefs.testing) - - b = new BP(x.bp, x.token) - await b.init() - - let acc = web3.eth.accounts - - console.log('registering') - for (let i = 0; i < 10; i++) await b.register(acc[i]) - await b.showBalance() - - console.log('bulkRegistering accounts') - let list = [] - let nbulk = 100 - for (let i = 0; i < nbulk; i++) list.push(acc[i % 10]) - - let bulk = await b.bulkRegister(list) - console.log('claiming ' + nbulk + ' accounts') - let w = [] - for (let i = 0; i < nbulk; i++) { - w.push(b.claimBulkRegistrationId(bulk, list[i], i + bulk.smallestAccountId)) - } - await Promise.all(w) - - console.log('transfering some tokens') - for (let i = 1; i < acc.length; i++) { await b.tokenTransfer(acc[0], acc[i], 1000) } - - await b.showBalance() - - console.log('deposit') - await b.deposit(10000, 0) - await b.deposit(500, 8) - await b.showBalance() - - let key = 'hello world' - let p = [] - let m = 20 - - let unlocker = 9 - - console.log('doing ' + m + ' transfers & unlocks') - for (let i = 0; i < m; i++) { - let [payIndex] = await b.registerPayment(0, 10, 1, [1, 2, 3, 4, 5], utils.hashLock(unlocker, key)) - p.push(payIndex) - await b.unlock(payIndex, unlocker, key) - } - - await b.showBalance() - console.log('payments:') - console.log(b.payments) - console.log('payList') - console.log(b.payList) - - await skipBlocks(b.unlockBlocks) - - let max = p[(p.length / 2) - 1] + 1 - - console.log('collect without instant slot. payIndex=' + max) - let minIndex = await b.getCollectedIndex(3) - - for (let i = 1; i <= 5; i++) { - let [addr, bb, c] = await b.getAccount(i) - - c = c.toNumber() - addr = 0 - if (i == 5) addr = b.ids[6] // #5 withdraw to #6 - - let amount = await b.getCollectAmount(i, c, max) - await b.collect(0, i, i, c, max, amount, 2, addr) - } - - await b.showBalance() - console.log('challenging #3') - - let data = b.getCollectData(3, minIndex, max) - await challenge(0, 3, 8, data) - await skipBlocks(b.challengeBlocks) - console.log('Freeing collect slots') - for (let i = 1; i <= 5; i++) { - await b.freeSlot(0, i) - } - await b.showBalance() - - max = p[p.length - 1] + 1 - console.log('collect with instant slot. payIndex=' + max) - - for (let i = 1; i <= 5; i++) { - let [addr, bb, c] = await b.getAccount(i) + await batpay.challenge_4(delegate, slot, payData) - c = c.toNumber() - addr = 0 - if (i == 5) addr = b.ids[6] // #5 withdraw to #6 - - let amount = await b.getCollectAmount(i, c, max) - if (i == 3) amount = amount + 100 - - await b.collect(0, i + b.INSTANT_SLOT, i, c, max, amount, 1, addr) - } - - await b.showBalance() + await batpay.challenge_failed(delegate, slot) +} - await skipBlocks(b.challengeBlocks) - console.log('Freeing collect slots') - for (let i = 1; i <= 5; i++) { - await b.freeSlot(0, i + b.INSTANT_SLOT) +function demo (callback) { + web3.eth.getAccounts(async (error, acc) => { + accounts = acc + if (error) throw new Error('Could not get accounts') + try { + await main() + callback() + } catch (e) { + callback(e) } - await b.showBalance() - } catch (e) { - console.log(e) - } + }) } -module.exports = function () { - try { - doStuff() - } catch (e) { - console.log(e) - } -} +module.exports = demo diff --git a/src/run.js b/src/run.js index 9ef7413..5ae1f0b 100644 --- a/src/run.js +++ b/src/run.js @@ -105,9 +105,10 @@ async function doStuff () { } } -module.exports = function () { +module.exports = async function (callback) { try { - doStuff() + await doStuff() + callback() } catch (e) { console.log(e) } diff --git a/test/test_accounts.js b/test/test_accounts.js index b6cf2a6..a2c558f 100644 --- a/test/test_accounts.js +++ b/test/test_accounts.js @@ -7,7 +7,7 @@ const assertPasses = truffleAssertions.passes const eventEmitted = truffleAssertions.eventEmitted var BigNumber = web3.BigNumber var lib = require('../lib')(web3, artifacts) -var { utils, bat } = lib +var { utils } = lib const TestHelper = artifacts.require('./TestHelper') const merkle = lib.merkle diff --git a/test/test_batpay.js b/test/test_batpay.js index 8fa1644..a545fad 100644 --- a/test/test_batpay.js +++ b/test/test_batpay.js @@ -7,7 +7,7 @@ const assertPasses = truffleAssertions.passes const eventEmitted = truffleAssertions.eventEmitted var BigNumber = web3.BigNumber var lib = require('../lib')(web3, artifacts) -var { utils, bat } = lib +var { utils } = lib const TestHelper = artifacts.require('./TestHelper') const merkle = lib.merkle diff --git a/test/test_challenge.js b/test/test_challenge.js index 81325bd..f5d8d7b 100644 --- a/test/test_challenge.js +++ b/test/test_challenge.js @@ -7,7 +7,7 @@ const assertPasses = truffleAssertions.passes const eventEmitted = truffleAssertions.eventEmitted var BigNumber = web3.BigNumber var lib = require('../lib')(web3, artifacts) -var { utils, bat } = lib +var { utils, Batpay } = lib const merkle = lib.merkle var globalDebug = false @@ -39,7 +39,7 @@ async function challenge (delegate, slot, challenger, list, index, payList, stop let amounts = list.map(x => b.payments[x]) - let data = lib.bat.getChallengeData(amounts, list) + let data = lib.Batpay.getChallengeData(amounts, list) if (debug) { await showSlot(delegate, slot) @@ -99,7 +99,7 @@ contract('challenge', (accounts) => { await utils.skipBlocks(1) let ins = await utils.newInstances() - b = new bat.BP(ins.bp, ins.token) + b = new Batpay.BP(ins.bp, ins.token) await b.init() let [mainId, receipt] = await b.deposit(100000, -1, accounts[0]) @@ -111,7 +111,7 @@ contract('challenge', (accounts) => { challenger = ch for (let i = 0; i < nUsers; i++) { - let [ id, t ] = await b.register(accounts[0]) + let [ id, t ] = await b.registerAccount(accounts[0]) userid.push(id) } }) @@ -123,11 +123,23 @@ contract('challenge', (accounts) => { for (let i = 0; i < nPays; i++) { let lockingKeyHash = 0 if (i == 2) lockingKeyHash = 1 - let [ pid, t ] = await b.registerPayment(id, 10, 0, userid, lockingKeyHash) + let [ pid, t ] = await b.registerPayment({ + fromAccountId: id, + amount: 10, + unlockerFee: 0, + payeesAccountsIds: userid, + lockingKeyHash: lockingKeyHash + }) payid.push(pid) if (pid > maxPayIndex) maxPayIndex = pid } - let [ pid, t ] = await b.registerPayment(id, 10, 0, [id], 0) + let [ pid, t ] = await b.registerPayment({ + fromAccountId: id, + amount: 10, + unlockerFee: 0, + payeesAccountsIds: [id], + lockingKeyHash: 0 + }) if (pid > maxPayIndex) maxPayIndex = pid otherIndex = pid await utils.skipBlocks(b.unlockBlocks) @@ -136,7 +148,16 @@ contract('challenge', (accounts) => { amount = await b.getCollectAmount(mid, minIndex, maxPayIndex + 1) data = b.getCollectData(mid, minIndex, maxPayIndex + 1) - await b.collect(id, slot, mid, minIndex, maxPayIndex + 1, amount, 0, 0) + await b.collect({ + delegate: id, + slot: slot, + toAccountId: mid, + fromPaymentId: minIndex, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: 0, + address: 0 + }) }) it('should complete a full challenge game #0', async () => { @@ -232,8 +253,16 @@ contract('challenge', (accounts) => { let amount = await b.getCollectAmount(toId, 0, pid+1) let data = b.getCollectData(toId, 0, pid + 1) - await b.collect(id, 0, toId, 0, pid + 1, amount, 0, 0) - + await b.collect({ + delegate: id, + slot: 0, + toAccountId: toId, + fromPaymentId: 0, + toPaymentId: pid + 1, + amount: amount, + fee: 0, + address: 0 + }) let index = 0 let payIndex = data[index] diff --git a/test/test_challenge_pure_fns.js b/test/test_challenge_pure_fns.js index 1ff74d9..0b19122 100644 --- a/test/test_challenge_pure_fns.js +++ b/test/test_challenge_pure_fns.js @@ -1,6 +1,6 @@ const { reverts, ErrorType } = require('truffle-assertions') const Challenge = artifacts.require('Challenge') -const { getChallengeData } = require('../lib/bat') +const { getChallengeData } = require('../lib/Batpay') const { getPayData } = require('../lib/utils') const assertRevert = (promise, message) => reverts(promise, ErrorType.REVERT, message); diff --git a/test/test_merkle.js b/test/test_merkle.js index 4f6f612..9e50e5a 100644 --- a/test/test_merkle.js +++ b/test/test_merkle.js @@ -1,4 +1,4 @@ -const { merkle, bat: { toLeftPaddedBytes32 } } = require('../lib')(web3, artifacts) +const { merkle, Batpay: { toLeftPaddedBytes32 } } = require('../lib')(web3, artifacts) const { sha3_1 } = merkle; var BigNumber = web3.BigNumber diff --git a/test/test_payments.js b/test/test_payments.js index b919098..b16baa3 100644 --- a/test/test_payments.js +++ b/test/test_payments.js @@ -8,7 +8,7 @@ const assertPasses = truffleAssertions.passes const eventEmitted = truffleAssertions.eventEmitted var BigNumber = web3.BigNumber var lib = require('../lib')(web3, artifacts) -var { utils, bat } = lib +var { utils, Batpay } = lib const TestHelper = artifacts.require('./TestHelper') const merkle = lib.merkle @@ -256,19 +256,25 @@ contract('Payments', (accounts) => { let maxPayIndex = 0 before(async () => { - b = new bat.BP(bp, st) + b = new Batpay.BP(bp, st) await b.init() let [mainId, receipt] = await b.deposit(100000, -1, accounts[0]) id = mainId for (let i = 0; i < nUsers; i++) { - let [ id, t ] = await b.register(accounts[0]) + let [ id, t ] = await b.registerAccount(accounts[0]) userid.push(id) } for (let i = 0; i < nPays; i++) { - let [ pid, t ] = await b.registerPayment(id, 10, 0, userid, 0) + let [ pid, t ] = await b.registerPayment({ + fromAccountId: id, + amount: 10, + unlockerFee: 0, + payeesAccountsIds: userid, + lockingKeyHash: 0 + }) payid.push(pid) if (pid > maxPayIndex) maxPayIndex = pid } @@ -280,7 +286,16 @@ contract('Payments', (accounts) => { let mid = userid[0] let amount = await b.getCollectAmount(mid, 0, maxPayIndex + 1) let b0 = (await b.balanceOf(mid)).toNumber() - const txr = await b.collect(id, 0, mid, 0, maxPayIndex + 1, amount, 0, 0) + const txr = await b.collect({ + delegate: id, + slot: 0, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: 0, + address: 0 + }) await utils.skipBlocks(b.challengeBlocks) await b.freeSlot(id, 0) let b1 = (await b.balanceOf(mid)).toNumber() @@ -292,7 +307,16 @@ contract('Payments', (accounts) => { let mid = userid[1] let amount = await b.getCollectAmount(mid, 0, maxPayIndex + 1) let b0 = (await b.balanceOf(mid)).toNumber() - await b.collect(id, b.INSTANT_SLOT, mid, 0, maxPayIndex + 1, amount, 0, 0) + await b.collect({ + delegate: id, + slot: b.INSTANT_SLOT, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: 0, + address: 0 + }) let b1 = (await b.balanceOf(mid)).toNumber() assert.equal(b0 + amount, b1) @@ -302,11 +326,29 @@ contract('Payments', (accounts) => { let mid = userid[2] let amount = await b.getCollectAmount(mid, 0, maxPayIndex / 2) let b0 = (await b.balanceOf(mid)).toNumber() - await b.collect(id, 1, mid, 0, maxPayIndex / 2, amount, 0, 0) + await b.collect({ + delegate: id, + slot: 1, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex / 2, + amount: amount, + fee: 0, + address: 0 + }) await utils.skipBlocks(b.challengeBlocks) let amount2 = await b.getCollectAmount(mid, maxPayIndex / 2, maxPayIndex + 1) // await b.freeSlot(id, 1); - await b.collect(id, 1, mid, maxPayIndex / 2, maxPayIndex + 1, amount2, 0, 0) + await b.collect({ + delegate: id, + slot: 1, + toAccountId: mid, + fromPaymentId: maxPayIndex / 2, + toPaymentId: maxPayIndex + 1, + amount: amount2, + fee: 0, + address: 0 + }) let b1 = (await b.balanceOf(mid)).toNumber() await utils.skipBlocks(b.challengeBlocks) await b.freeSlot(id, 1) @@ -322,7 +364,16 @@ contract('Payments', (accounts) => { let fee = Math.floor(amount / 3) let b0 = (await b.balanceOf(mid)).toNumber() let c0 = (await b.balanceOf(id)).toNumber() - await b.collect(id, slot, mid, 0, maxPayIndex + 1, amount, amount / 3, 0) + await b.collect({ + delegate: id, + slot: slot, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: amount / 3, + address: 0 + }) await utils.skipBlocks(b.challengeBlocks) await b.freeSlot(id, slot) @@ -340,7 +391,16 @@ contract('Payments', (accounts) => { let fee = Math.floor(amount / 3) let b0 = (await b.balanceOf(mid)).toNumber() let c0 = (await b.balanceOf(id)).toNumber() - await b.collect(id, slot, mid, 0, maxPayIndex + 1, amount, amount / 3, 0) + await b.collect({ + delegate: id, + slot: slot, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: amount / 3, + address: 0 + }) await utils.skipBlocks(b.challengeBlocks) await b.freeSlot(id, slot) @@ -360,7 +420,16 @@ contract('Payments', (accounts) => { let c0 = (await b.balanceOf(id)).toNumber() let d0 = (await b.tokenBalance(accounts[1])).toNumber() - await b.collect(id, slot, mid, 0, maxPayIndex + 1, amount, amount / 3, accounts[1]) + await b.collect({ + delegate: id, + slot: slot, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: amount / 3, + address: accounts[1] + }) await utils.skipBlocks(b.challengeBlocks) await b.freeSlot(id, slot) @@ -382,7 +451,16 @@ contract('Payments', (accounts) => { let c0 = (await b.balanceOf(id)).toNumber() let d0 = (await b.tokenBalance(accounts[1])).toNumber() - await b.collect(id, slot, mid, 0, maxPayIndex + 1, amount, amount / 3, accounts[1]) + await b.collect({ + delegate: id, + slot: slot, + toAccountId: mid, + fromPaymentId: 0, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: amount / 3, + address: accounts[1] + }) let b1 = (await b.balanceOf(mid)).toNumber() let d1 = (await b.tokenBalance(accounts[1])).toNumber() @@ -402,7 +480,16 @@ contract('Payments', (accounts) => { let amount = await b.getCollectAmount(collectorAccountId, 0, maxPayIndex + 1) let [, , lastCollectedPaymentId] = await b.getAccount(collectorAccountId) - await assertRequire(b.collect(id, slot, collectorAccountId, lastCollectedPaymentId.toNumber() - 1, maxPayIndex + 1, amount, amount / 3, accounts[1])) + await assertRequire(b.collect({ + delegate: id, + slot: slot, + toAccountId: collectorAccountId, + fromPaymentId: lastCollectedPaymentId.toNumber() - 1, + toPaymentId: maxPayIndex + 1, + amount: amount, + fee: amount / 3, + address: accounts[1] + })) }) it('should reject if payIndex is invalid', async () => { @@ -413,15 +500,16 @@ contract('Payments', (accounts) => { const tooHighPayIndex = (await b.getPaymentsLength()) + 1 await assertRequire( - b.collect( - id, - slot, - collectorId, - lastCollectedPaymentId.toNumber(), - tooHighPayIndex, - amount, - amount / 3, - accounts[1]), + b.collect({ + delegate: id, + slot: slot, + toAccountId: collectorId, + fromPaymentId: lastCollectedPaymentId.toNumber(), + toPaymentId: tooHighPayIndex, + amount: amount, + fee: amount / 3, + address: accounts[1] + }), 'invalid payIndex, payments is not that long yet' ) }) @@ -435,7 +523,16 @@ contract('Payments', (accounts) => { b.ids[invalidCollectorId] = accounts[0] await assertRequire( - b.collect(id, slot, invalidCollectorId, 0, 1, amount, amount / 3, accounts[1]), + b.collect({ + delegate: id, + slot: slot, + toAccountId: invalidCollectorId, + fromPaymentId: 0, + toPaymentId: 1, + amount: amount, + fee: amount / 3, + address: accounts[1] + }), 'toAccountId must be a valid account id' ) }) @@ -447,16 +544,16 @@ contract('Payments', (accounts) => { const invalidFromPayIndex = 123454321 await assertRequire( - b.collect( - id, - slot, - collectorId, - invalidFromPayIndex, - maxPayIndex, - amount, - amount / 3, - 0 - ), + b.collect({ + delegate: id, + slot: slot, + toAccountId: collectorId, + fromPaymentId: invalidFromPayIndex, + toPaymentId: maxPayIndex, + amount: amount, + fee: amount / 3, + address: 0 + }), 'Bad user signature' ) }) @@ -464,11 +561,26 @@ contract('Payments', (accounts) => { it('Should not allow race condition on collect', async () => { let stake = b.collectStake let [id, r0] = await b.deposit(2*stake+1, -1, a0) - let [pid, r1] = await b.registerPayment(id, 1, 0, [id], 0) + let [pid, r1] = await b.registerPayment({ + fromAccountId: id, + amount: 1, + unlockerFee: 0, + payeesAccountsIds: [id], + lockingKeyHash: 0 + }) utils.skipBlocks(b.unlockBlocks) let b0 = (await b.balanceOf(id)).toNumber() - await b.collect(id, b.INSTANT_SLOT, id, 0, pid+1, stake, 0, 0) + await b.collect({ + delegate: id, + slot: b.INSTANT_SLOT, + toAccountId: id, + fromPaymentId: 0, + toPaymentId: pid+1, + amount: stake, + fee: 0, + address: 0 + }) let b1 = (await b.balanceOf(id)).toNumber() assert.isBelow(b1, b0) @@ -478,22 +590,52 @@ contract('Payments', (accounts) => { it('Should reject collects over maxCollectAmount', async () => { let stake = b.collectStake let [id, r0] = await b.deposit(2*stake+1, -1, a0) - let [pid, r1] = await b.registerPayment(id, 1, 0, [id], 0) + let [pid, r1] = await b.registerPayment({ + fromAccountId: id, + amount: 1, + unlockerFee: 0, + payeesAccountsIds: [id], + lockingKeyHash: 0 + }) utils.skipBlocks(b.unlockBlocks) let b0 = (await b.balanceOf(id)).toNumber() - await assertRequire( b.collect(id, 0, id, 0, pid+1, b.maxCollectAmount+1, 0, 0), + await assertRequire(b.collect({ + delegate: id, + slot: 0, + toAccountId: id, + fromPaymentId: 0, + toPaymentId: pid+1, + amount: b.maxCollectAmount+1, + fee: 0, + address: 0 + }), "declaredAmount is too big") }) it('Should accept collects with just maxCollectAmount', async () => { let stake = b.collectStake let [id, r0] = await b.deposit(2*stake+1, -1, a0) - let [pid, r1] = await b.registerPayment(id, 1, 0, [id], 0) + let [pid, r1] = await b.registerPayment({ + fromAccountId: id, + amount: 1, + unlockerFee: 0, + payeesAccountsIds: [id], + lockingKeyHash: 0 + }) utils.skipBlocks(b.unlockBlocks) let b0 = (await b.balanceOf(id)).toNumber() - await b.collect(id, 0, id, 0, pid+1, b.maxCollectAmount, 0, 0) + await b.collect({ + delegate: id, + slot: 0, + toAccountId: id, + fromPaymentId: 0, + toPaymentId: pid+1, + amount: b.maxCollectAmount, + fee: 0, + address: 0 + }) }) diff --git a/truffle.js b/truffle.js index ad14671..7d2f99d 100644 --- a/truffle.js +++ b/truffle.js @@ -26,6 +26,11 @@ module.exports = { network_id: 1, gasPrice: 10 * 1000000000, }, + localChain: { + host: '127.0.0.1', + port: 2545, + network_id: 987 + } }, solc: { optimizer: {