From e04163e6c8e5e3e3bd4757ad5089788a102f306b Mon Sep 17 00:00:00 2001 From: Mauro Leggieri Date: Thu, 26 Apr 2018 15:24:59 -0300 Subject: [PATCH] Completed ClientFund before changes for settlement (#25) * Whole code of ClientFund SC * Some unit tests for ClientFund SC * Added Unit test Helper SC * Added more tests for ClientFunds * Lots of unit tests added. * More tests and added methods of issue #23 * Completed code before issue #24 major change. --- contracts/ClientFund.sol | 358 +++++++++++++++++++- contracts/UnitTestHelpers.sol | 62 ++++ test/helpers.js | 39 +++ test/main.js | 294 +++++++++-------- test/scenarios/ClientFund.js | 591 +++++++++++++++++++++++++++++++++- 5 files changed, 1192 insertions(+), 152 deletions(-) create mode 100644 contracts/UnitTestHelpers.sol create mode 100644 test/helpers.js diff --git a/contracts/ClientFund.sol b/contracts/ClientFund.sol index f6a653b9..0a1f6e23 100644 --- a/contracts/ClientFund.sol +++ b/contracts/ClientFund.sol @@ -7,28 +7,77 @@ */ pragma solidity ^0.4.21; +import "./SafeMath.sol"; +import "./ERC20.sol"; + /** @title Client fund @notice Where clients’ crypto is deposited into, staged and withdrawn from. @dev Factored out from previous Trade smart contract. */ contract ClientFund { + using SafeMath for uint256; + + // + // Structures + // ----------------------------------------------------------------------------------------------------------------- + struct DepositInfo { + uint256 amount; + uint256 timestamp; + address token; //0 for ethers + } + + struct WithdrawalInfo { + uint256 amount; + uint256 timestamp; + address token; //0 for ethers + } + + struct WalletInfo { + uint256 tradeNonce; + + DepositInfo[] deposits; + WithdrawalInfo[] withdrawals; + + // Active balance of ethers and tokens. + uint256 activeEtherBalance; + mapping (address => uint256) activeTokenBalance; + + // Staged balance of ethers and tokens. + uint256 stagedEtherBalance; + mapping (address => uint256) stagedTokenBalance; + } // // Variables // ----------------------------------------------------------------------------------------------------------------- address private owner; + mapping (address => WalletInfo) private walletInfoMap; + + uint256 serviceActivationTimeout; + mapping (address => uint256) registeredServicesMap; + mapping (uint256 => bool) disabledServicesMap; // // Events // ----------------------------------------------------------------------------------------------------------------- event OwnerChangedEvent(address oldOwner, address newOwner); + event DepositEvent(address from, uint256 amount, address token); //token==0 for ethers + event TransferFromActiveToStagedBalanceEvent(address from, address to, uint256 amount, address token); //token==0 for ethers + event WithdrawFromActiveBalanceEvent(address from, address to, uint256 amount, address token); //token==0 for ethers + event DepositToStagedBalance(address to, uint256 amount, address token); //token==0 for ethers + event UnstageEvent(address from, uint256 amount, address token); //token==0 for ethers + event WithdrawEvent(address to, uint256 amount, address token); //token==0 for ethers + event RegisterServiceEvent(address service); + event EnableRegisteredServiceEvent(address wallet, address service); + event DisableRegisteredServiceEvent(address wallet, address service); // // Constructor // ----------------------------------------------------------------------------------------------------------------- function ClientFund(address _owner) public notNullAddress(_owner) { owner = _owner; + serviceActivationTimeout = 30 * 3600; //30 minutes } // @@ -47,6 +96,303 @@ contract ClientFund { } } + function setServiceActivationTimeout(uint256 timeout) public onlyOwner { + serviceActivationTimeout = timeout; + } + + // + // Deposit functions + // ----------------------------------------------------------------------------------------------------------------- + function () public notOwner payable { + require(msg.value > 0); + + //add to per-wallet active balance + walletInfoMap[msg.sender].activeEtherBalance = walletInfoMap[msg.sender].activeEtherBalance.add(msg.value); + walletInfoMap[msg.sender].deposits.push(DepositInfo(msg.value, block.timestamp, address(0))); + + //emit event + emit DepositEvent(msg.sender, msg.value, address(0)); + } + + //NOTE: msg.sender must call ERC20.approve first + function depositTokens(address token, uint256 amount) notOwner public { + ERC20 erc20_token; + + require(token != address(0)); + require(amount > 0); + + //try to execute token transfer + erc20_token = ERC20(token); + require(erc20_token.transferFrom(msg.sender, this, amount)); + + //add to per-wallet active balance + walletInfoMap[msg.sender].activeTokenBalance[token] = walletInfoMap[msg.sender].activeTokenBalance[token].add(amount); + walletInfoMap[msg.sender].deposits.push(DepositInfo(amount, block.timestamp, token)); + + //emit event + emit DepositEvent(msg.sender, amount, token); + } + + function deposit(address wallet, uint index) public view onlyOwner returns (uint256 amount, uint256 timestamp, address token) { + require(index < walletInfoMap[wallet].deposits.length); + + amount = walletInfoMap[wallet].deposits[index].amount; + timestamp = walletInfoMap[wallet].deposits[index].timestamp; + token = walletInfoMap[wallet].deposits[index].token; + } + + function depositCount(address wallet) public view onlyOwner returns (uint256) { + return walletInfoMap[wallet].deposits.length; + } + + // + // Balance functions + // ----------------------------------------------------------------------------------------------------------------- + function activeBalance(address wallet, address token) public view returns (uint256) { + require(wallet != address(0)); + + return token == address(0) ? walletInfoMap[wallet].activeEtherBalance : walletInfoMap[wallet].activeTokenBalance[token]; + } + + function stagedBalance(address wallet, address token) public view returns (uint256) { + require(wallet != address(0)); + + return token == address(0) ? walletInfoMap[wallet].stagedEtherBalance : walletInfoMap[wallet].stagedTokenBalance[token]; + } + + function transferFromActiveToStagedBalance(address sourceWallet, address destWallet, uint256 amount, address token) public notOwner { + require(isAcceptedServiceForWallet(msg.sender, sourceWallet)); + require(isAcceptedServiceForWallet(msg.sender, destWallet)); + require(sourceWallet != address(0)); + require(destWallet != address(0)); + require(sourceWallet != destWallet); + require(amount > 0); + + if (token == address(0)) { + //check for sufficient balance + require(amount <= walletInfoMap[sourceWallet].activeEtherBalance); + + //move from active balance to staged + walletInfoMap[sourceWallet].activeEtherBalance = walletInfoMap[sourceWallet].activeEtherBalance.sub(amount); + walletInfoMap[destWallet].stagedEtherBalance = walletInfoMap[destWallet].stagedEtherBalance.add(amount); + } else { + //check for sufficient balance + require(amount <= walletInfoMap[sourceWallet].activeTokenBalance[token]); + + //move from active balance to staged + walletInfoMap[sourceWallet].activeTokenBalance[token] = walletInfoMap[sourceWallet].activeTokenBalance[token].sub(amount); + walletInfoMap[destWallet].stagedTokenBalance[token] = walletInfoMap[destWallet].stagedTokenBalance[token].add(amount); + } + + //emit event + emit TransferFromActiveToStagedBalanceEvent(sourceWallet, destWallet, amount, token); + } + + function withdrawFromActiveBalance(address sourceWallet, address destWallet, uint256 amount, address token) public notOwner { + ERC20 erc20_token; + + require(isAcceptedServiceForWallet(msg.sender, sourceWallet)); + require(isAcceptedServiceForWallet(msg.sender, destWallet)); + require(sourceWallet != address(0)); + require(destWallet != address(0)); + require(sourceWallet != destWallet); + require(amount > 0); + + if (token == address(0)) { + //check for sufficient balance + require(amount <= walletInfoMap[sourceWallet].activeEtherBalance); + walletInfoMap[sourceWallet].activeEtherBalance = walletInfoMap[sourceWallet].activeEtherBalance.sub(amount); + + //execute transfer + destWallet.transfer(amount); + } else { + //check for sufficient balance + require(amount <= walletInfoMap[sourceWallet].activeTokenBalance[token]); + walletInfoMap[sourceWallet].activeTokenBalance[token] = walletInfoMap[sourceWallet].activeTokenBalance[token].sub(amount); + + //execute transfer + erc20_token = ERC20(token); + erc20_token.transfer(destWallet, amount); + } + + //emit event + emit WithdrawFromActiveBalanceEvent(sourceWallet, destWallet, amount, token); + } + + function depositEthersToStagedBalance(address destWallet) public payable notOwner { + require(isAcceptedServiceForWallet(msg.sender, destWallet)); + require(destWallet != address(0)); + require(msg.value > 0); + + //add to per-wallet staged balance + walletInfoMap[destWallet].stagedEtherBalance = walletInfoMap[destWallet].stagedEtherBalance.add(msg.value); + + //emit event + emit DepositToStagedBalance(destWallet, msg.value, address(0)); + } + + //NOTE: msg.sender must call ERC20.approve first + function depositTokensToStagedBalance(address destWallet, address token, uint256 amount) public notOwner { + ERC20 erc20_token; + + require(isAcceptedServiceForWallet(msg.sender, destWallet)); + require(destWallet != address(0)); + require(amount > 0); + + //add to per-wallet staged balance + walletInfoMap[destWallet].stagedTokenBalance[token] = walletInfoMap[destWallet].stagedTokenBalance[token].add(amount); + + //try to execute token transfer + erc20_token = ERC20(token); + require(erc20_token.transferFrom(msg.sender, destWallet, amount)); + + //emit event + emit DepositToStagedBalance(destWallet, amount, token); + } + + function unstage(uint256 amount, address token) public notOwner { + if (token == address(0)) { + //clamp amount to move + if (amount > walletInfoMap[msg.sender].stagedEtherBalance) + amount = walletInfoMap[msg.sender].stagedEtherBalance; + if (amount == 0) + return; + + //move from staged balance to active + walletInfoMap[msg.sender].stagedEtherBalance = walletInfoMap[msg.sender].stagedEtherBalance.sub(amount); + walletInfoMap[msg.sender].activeEtherBalance = walletInfoMap[msg.sender].activeEtherBalance.add(amount); + } else { + //clamp amount to move + if (amount > walletInfoMap[msg.sender].stagedTokenBalance[token]) + amount = walletInfoMap[msg.sender].stagedTokenBalance[token]; + if (amount == 0) + return; + + //move between balances + walletInfoMap[msg.sender].stagedTokenBalance[token] = walletInfoMap[msg.sender].stagedTokenBalance[token].sub(amount); + walletInfoMap[msg.sender].activeTokenBalance[token] = walletInfoMap[msg.sender].activeTokenBalance[token].add(amount); + } + + //emit event + emit UnstageEvent(msg.sender, amount, token); + } + + // + // Withdrawal functions + // ----------------------------------------------------------------------------------------------------------------- + function withdrawEthers(uint256 amount) public notOwner { + require(amount > 0); + + //check for sufficient balance + require(amount <= walletInfoMap[msg.sender].stagedEtherBalance); + + //subtract to per-wallet staged balance + walletInfoMap[msg.sender].stagedEtherBalance = walletInfoMap[msg.sender].stagedEtherBalance.sub(amount); + walletInfoMap[msg.sender].withdrawals.push(WithdrawalInfo(amount, block.timestamp, address(0))); + + //execute transfer + msg.sender.transfer(amount); + + //emit event + emit WithdrawEvent(msg.sender, amount, address(0)); + } + + function withdrawTokens(uint256 amount, address token) public notOwner { + ERC20 erc20_token; + + require(token != address(0)); + require(amount > 0); + + //check for sufficient balance + require(amount <= walletInfoMap[msg.sender].stagedTokenBalance[token]); + + //subtract to per-wallet staged balance + walletInfoMap[msg.sender].stagedTokenBalance[token] = walletInfoMap[msg.sender].stagedTokenBalance[token].sub(amount); + walletInfoMap[msg.sender].withdrawals.push(WithdrawalInfo(amount, block.timestamp, token)); + + //execute transfer + erc20_token = ERC20(token); + erc20_token.transfer(msg.sender, amount); + + //emit event + emit WithdrawEvent(msg.sender, amount, token); + } + + function withdrawal(address wallet, uint index) public view onlyOwner returns (uint256 amount, uint256 timestamp, address token) { + require(index < walletInfoMap[wallet].withdrawals.length); + + amount = walletInfoMap[wallet].withdrawals[index].amount; + timestamp = walletInfoMap[wallet].withdrawals[index].timestamp; + token = walletInfoMap[wallet].withdrawals[index].token; + } + + function withdrawalCount(address wallet) public view onlyOwner returns (uint256) { + return walletInfoMap[wallet].withdrawals.length; + } + + // + // Service functions + // ----------------------------------------------------------------------------------------------------------------- + function registerService(address service) public onlyOwner notNullAddress(service) notMySelfAddress(service) { + require(service != owner); + + //ensure service is not already registered + require(registeredServicesMap[service] == 0); + + //register and set activation time + registeredServicesMap[service] = block.timestamp + serviceActivationTimeout; + + //emit event + emit RegisterServiceEvent(service); + } + + function enableRegisteredService(address service) public notOwner notNullAddress(service) { + require(msg.sender != service); + + //ensure service is registered + require(registeredServicesMap[service] != 0); + + //enable service for given wallet + disabledServicesMap[indexFromWalletService(msg.sender, service)] = false; + + //emit event + emit EnableRegisteredServiceEvent(msg.sender, service); + } + + function disableRegisteredService(address service) public notOwner notNullAddress(service) { + require(msg.sender != service); + + //ensure service is registered + require(registeredServicesMap[service] != 0); + + //disable service for given wallet + disabledServicesMap[indexFromWalletService(msg.sender, service)] = true; + + //emit event + emit DisableRegisteredServiceEvent(msg.sender, service); + } + + // + // Private methods + // ----------------------------------------------------------------------------------------------------------------- + function indexFromWalletService(address wallet, address service) private pure returns (uint256) { + return uint256(keccak256(wallet, service)); + } + + function isWalletServiceDisabled(address wallet, address service) private view returns (bool) { + return disabledServicesMap[indexFromWalletService(wallet, service)]; + } + + function isAcceptedServiceForWallet(address service, address wallet) private view returns (bool) { + if (service == wallet) + return false; + if (registeredServicesMap[service] == 0) + return false; + if (block.timestamp < registeredServicesMap[service]) + return false; + return !disabledServicesMap[indexFromWalletService(wallet, service)]; + } + // // Modifiers // ----------------------------------------------------------------------------------------------------------------- @@ -59,4 +405,14 @@ contract ClientFund { require(msg.sender == owner); _; } -} \ No newline at end of file + + modifier notOwner() { + require(msg.sender != owner); + _; + } + + modifier notMySelfAddress(address _address) { + require(_address != address(this)); + _; + } +} diff --git a/contracts/UnitTestHelpers.sol b/contracts/UnitTestHelpers.sol new file mode 100644 index 00000000..165b73fa --- /dev/null +++ b/contracts/UnitTestHelpers.sol @@ -0,0 +1,62 @@ +/*! + * Hubii - Omphalos + * + * Compliant with the Omphalos specification v0.12. + * + * Copyright (C) 2017-2018 Hubii AS + */ +pragma solidity ^0.4.21; + +import "./SafeMath.sol"; +import "./ERC20.sol"; +import "./ClientFund.sol"; + +/** +@title UnitTestHelpers +@notice A dummy SC where several functions are added to assist in unit testing. +*/ +contract UnitTestHelpers { + using SafeMath for uint256; + + // + // Constructor + // ----------------------------------------------------------------------------------------------------------------- + function UnitTestHelpers() public { + } + + function () public payable { + } + + // + // Functions + // ----------------------------------------------------------------------------------------------------------------- + function callToTransferFromActiveToStagedBalance(address clientFunds, address sourceWallet, address destWallet, uint256 amount, address token) public { + require(clientFunds != address(0)); + ClientFund sc = ClientFund(clientFunds); + sc.transferFromActiveToStagedBalance(sourceWallet, destWallet, amount, token); + } + + function callToWithdrawFromActiveBalance(address clientFunds, address sourceWallet, address destWallet, uint256 amount, address token) public { + require(clientFunds != address(0)); + ClientFund sc = ClientFund(clientFunds); + sc.withdrawFromActiveBalance(sourceWallet, destWallet, amount, token); + } + + function callToDepositEthersToStagedBalance(address clientFunds, address destWallet) public payable { + require(clientFunds != address(0)); + ClientFund sc = ClientFund(clientFunds); + sc.depositEthersToStagedBalance.value(msg.value)(destWallet); + } + + function callToDepositTokensToStagedBalance(address clientFunds, address destWallet, address token, uint256 amount) public { + require(clientFunds != address(0)); + ClientFund sc = ClientFund(clientFunds); + sc.depositTokensToStagedBalance(destWallet, token, amount); + } + + function erc20_approve(address token, address spender, uint256 value) public { + require(token != address(0)); + ERC20 tok = ERC20(token); + tok.approve(spender, value); + } +} diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 00000000..7e74ce39 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,39 @@ +module.exports.augmentWeb3 = function (web3) +{ + web3.eth.sendTransactionPromise = function(transactionObject) { + return new Promise((resolve, reject) => { + web3.eth.sendTransaction(transactionObject, function (err) { + if (!err) + resolve(); + else + reject(err); + }); + }); + } + + web3.eth.getBalancePromise = function(addressHexString) { + return new Promise((resolve, reject) => { + web3.eth.getBalance(addressHexString, function (err, balance) { + if (!err) + resolve(balance); + else + reject(err); + }); + }); + } +} + +module.exports.TestCounter = function () +{ + function TestCounter() + { + var testCounter = 0; + + this.next = function() + { + testCounter++; + return "T" + ("000" + testCounter.toString()).slice(-3); + } + } + return new TestCounter(); +} \ No newline at end of file diff --git a/test/main.js b/test/main.js index edb68889..6079a0a7 100644 --- a/test/main.js +++ b/test/main.js @@ -7,6 +7,7 @@ var async = require('async'); var ethers = require('ethers'); var keccak256 = require("augmented-keccak256"); +var Helpers = require('./helpers'); var w3prov = new ethers.providers.Web3Provider(web3.currentProvider); var ClientFund = artifacts.require("ClientFund"); @@ -18,6 +19,12 @@ var RevenueFund = artifacts.require("RevenueFund"); var SecurityBond = artifacts.require("SecurityBond"); var TokenHolderRevenueFund = artifacts.require("TokenHolderRevenueFund"); var ERC20Token = artifacts.require("StandardTokenEx"); +var UnitTestHelpers = artifacts.require("UnitTestHelpers"); + +//augmented sendTransaction using promises +Helpers.augmentWeb3(web3); + + contract('Smart contract checks', function () { var glob = { @@ -33,21 +40,17 @@ contract('Smart contract checks', function () { signer_d: w3prov.getSigner(this.user_d), gasLimit: 1800000 - - //var web3ClientFund = null; - //var ethersIoClientFund = null; // Uses Ethers.io for convenience using structs,etc. - //var web3Erc20 = null; }; const minRequiredEthersPerUser = 10; const initialTokensSupply = 1000; - const initialTokensForUserA = 10; + const initialTokensForAll = 100; //------------------------------------------------------------------------- // Preflight stage //------------------------------------------------------------------------- - before("Preflight: Check available account addresses and balances", function(done) { + before("Preflight: Check available account addresses and balances", async() => { assert.notEqual(glob.user_a, null); assert.notEqual(glob.user_b, null); assert.notEqual(glob.user_c, null); @@ -57,165 +60,174 @@ contract('Smart contract checks', function () { assert.ok(web3.fromWei(web3.eth.getBalance(glob.user_b), "ether") >= minRequiredEthersPerUser); assert.ok(web3.fromWei(web3.eth.getBalance(glob.user_c), "ether") >= minRequiredEthersPerUser); assert.ok(web3.fromWei(web3.eth.getBalance(glob.user_d), "ether") >= minRequiredEthersPerUser); - - done(); }); - before("Preflight: Instantiate test token", function (done) { - ERC20Token.new().then( - function (_t) { - assert.notEqual(_t, null); - glob.web3Erc20 = _t; - - glob.web3Erc20.totalSupply = initialTokensSupply; - - done(); - }, - function () { - done(new Error('Failed to instantiate ERC20Token instance')); - } - ); + before("Preflight: Instantiate test token", async() => { + try { + let _t = await ERC20Token.new(); + assert.notEqual(_t, null); + glob.web3Erc20 = _t; + + glob.web3Erc20.totalSupply = initialTokensSupply; + } + catch (err) { + assert(false, 'Failed to instantiate ERC20Token instance. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate ClientFund contract", function (done) { - ClientFund.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3ClientFund = _d; - glob.ethersIoClientFund = new ethers.Contract(_d.address, ClientFund.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate ClientFund contract address')); - } - ); + before("Preflight: Deploy unit test helper contract for validation tests", async() => { + try { + let _t = await UnitTestHelpers.new(); + assert.notEqual(_t, null); + + glob.web3UnitTestHelpers_SUCCESS_TESTS = _t; + glob.ethersUnitTestHelpers_SUCCESS_TESTS = new ethers.Contract(_t.address, UnitTestHelpers.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to create a new instance of UnitTestHelpers. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate CommunityVote contract", function (done) { - CommunityVote.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3CommunityVote = _d; - glob.ethersIoCommunityVote = new ethers.Contract(_d.address, CommunityVote.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate CommunityVote contract address')); - } - ); + before("Preflight: Deploy a second instance of unit test helper contract for other tests", async() => { + try { + let _t = await UnitTestHelpers.new(); + assert.notEqual(_t, null); + + glob.web3UnitTestHelpers_FAIL_TESTS = _t; + glob.ethersUnitTestHelpers_FAIL_TESTS = new ethers.Contract(_t.address, UnitTestHelpers.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to create a second instance of UnitTestHelpers. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate Configuration contract", function (done) { - Configuration.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3Configuration = _d; - glob.ethersIoConfiguration = new ethers.Contract(_d.address, Configuration.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate Configuration contract address')); - } - ); + before("Preflight: Instantiate ClientFund contract", async() => { + try { + let _d = await ClientFund.deployed(); + assert.notEqual(_d, null); + + glob.web3ClientFund = _d; + glob.ethersIoClientFund = new ethers.Contract(_d.address, ClientFund.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate ClientFund contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate Exchange contract", function (done) { - Exchange.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3Exchange = _d; - glob.ethersIoExchange = new ethers.Contract(_d.address, Exchange.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate Exchange contract address')); - } - ); + before("Preflight: Instantiate CommunityVote contract", async() => { + try { + let _d = await CommunityVote.deployed(); + assert.notEqual(_d, null); + + glob.web3CommunityVote = _d; + glob.ethersIoCommunityVote = new ethers.Contract(_d.address, CommunityVote.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate CommunityVote contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate ReserveFund contract", function (done) { - ReserveFund.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3ReserveFund = _d; - glob.ethersIoReserveFund = new ethers.Contract(_d.address, ReserveFund.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate ReserveFund contract address')); - } - ); + before("Preflight: Instantiate Configuration contract", async() => { + try { + let _d = await Configuration.deployed(); + assert.notEqual(_d, null); + + glob.web3Configuration = _d; + glob.ethersIoConfiguration = new ethers.Contract(_d.address, Configuration.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate Configuration contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate RevenueFund contract", function (done) { - RevenueFund.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3RevenueFund = _d; - glob.ethersIoRevenueFund = new ethers.Contract(_d.address, RevenueFund.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate RevenueFund contract address')); - } - ); + before("Preflight: Instantiate Exchange contract", async() => { + try { + let _d = await Exchange.deployed(); + assert.notEqual(_d, null); + + glob.web3Exchange = _d; + glob.ethersIoExchange = new ethers.Contract(_d.address, Exchange.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate Exchange contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate SecurityBond contract", function (done) { - SecurityBond.deployed().then( - function (_d) { - assert.notEqual(_d, null); - - glob.web3SecurityBond = _d; - glob.ethersIoSecurityBond = new ethers.Contract(_d.address, SecurityBond.abi, w3prov); - - done(); - }, - function () { - done(new Error('Failed to instantiate SecurityBond contract address')); - } - ); + before("Preflight: Instantiate ReserveFund contract", async() => { + try { + let _d = await ReserveFund.deployed(); + assert.notEqual(_d, null); + + glob.web3ReserveFund = _d; + glob.ethersIoReserveFund = new ethers.Contract(_d.address, ReserveFund.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate ReserveFund contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: Instantiate TokenHolderRevenueFund contract", function (done) { - TokenHolderRevenueFund.deployed().then( - function (_d) { - assert.notEqual(_d, null); + before("Preflight: Instantiate RevenueFund contract", async() => { + try { + let _d = await RevenueFund.deployed(); + assert.notEqual(_d, null); + + glob.web3RevenueFund = _d; + glob.ethersIoRevenueFund = new ethers.Contract(_d.address, RevenueFund.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate RevenueFund contract address. [Error: ' + err.toString() + ']'); + } + }); - glob.web3TokenHolderRevenueFund = _d; - glob.ethersIoTokenHolderRevenueFund = new ethers.Contract(_d.address, TokenHolderRevenueFund.abi, w3prov); + before("Preflight: Instantiate SecurityBond contract", async() => { + try { + let _d = await SecurityBond.deployed(); + assert.notEqual(_d, null); + + glob.web3SecurityBond = _d; + glob.ethersIoSecurityBond = new ethers.Contract(_d.address, SecurityBond.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate SecurityBond contract address. [Error: ' + err.toString() + ']'); + } + }); - done(); - }, - function () { - done(new Error('Failed to instantiate TokenHolderRevenueFund contract address')); - } - ); + before("Preflight: Instantiate TokenHolderRevenueFund contract", async() => { + try { + let _d = await TokenHolderRevenueFund.deployed(); + assert.notEqual(_d, null); + + glob.web3TokenHolderRevenueFund = _d; + glob.ethersIoTokenHolderRevenueFund = new ethers.Contract(_d.address, TokenHolderRevenueFund.abi, w3prov); + } + catch (err) { + assert(false, 'Failed to instantiate TokenHolderRevenueFund contract address. [Error: ' + err.toString() + ']'); + } }); - before("Preflight: distribute test tokens", function (done) { - glob.web3Erc20.testMint(glob.user_a, initialTokensForUserA).then( - function () { - done(); - }, - function (err) { - done(new Error('Cannot assign tokens for user A: ' + err.toString())); - } - ); + before("Preflight: Distribute test ethers", async() => { + try { + await web3.eth.sendTransactionPromise({ from: glob.owner, to: glob.web3UnitTestHelpers_SUCCESS_TESTS.address, value: web3.toWei('10', "ether") }); + await web3.eth.sendTransactionPromise({ from: glob.owner, to: glob.web3UnitTestHelpers_FAIL_TESTS.address, value: web3.toWei('10', "ether") }); + } + catch (err) { + assert(false, 'Cannot distribute money to smart contracts. [Error: ' + err.toString() + ']'); + } }); + before("Preflight: Distribute test tokens", async() => { + try { + await glob.web3Erc20.testMint(glob.user_a, initialTokensForAll); + await glob.web3Erc20.testMint(glob.user_b, initialTokensForAll); + await glob.web3Erc20.testMint(glob.user_c, initialTokensForAll); + await glob.web3Erc20.testMint(glob.user_d, initialTokensForAll); + await glob.web3Erc20.testMint(glob.web3UnitTestHelpers_SUCCESS_TESTS.address, initialTokensForAll); + await glob.web3Erc20.testMint(glob.web3UnitTestHelpers_FAIL_TESTS.address, initialTokensForAll); + } + catch (err) { + assert(false, 'Cannot assign tokens for users and smart contracts. [Error: ' + err.toString() + ']'); + } + }); //------------------------------------------------------------------------- // Tests start here diff --git a/test/scenarios/ClientFund.js b/test/scenarios/ClientFund.js index 31d786d3..8d4408a5 100644 --- a/test/scenarios/ClientFund.js +++ b/test/scenarios/ClientFund.js @@ -1,15 +1,586 @@ +var Helpers = require('../helpers'); + module.exports = function (glob) { + var testCounter = Helpers.TestCounter(); + describe("ClientFund", function () { - it("T001: MUST FAIL [payable]: cannot be called from owner", function (done) { - web3.eth.sendTransaction({ - from: glob.owner, - to: glob.web3ClientFund.address, - value: web3.toWei(10, 'ether'), - gas: glob.gasLimit - }, - function (err) { - done(err == null ? new Error('This test must fail') : null); - }); + it(testCounter.next() + ": MUST FAIL [payable]: cannot be called from owner", async() => { + try { + await web3.eth.sendTransactionPromise({ + from: glob.owner, + to: glob.web3ClientFund.address, + value: web3.toWei(10, 'ether'), + gas: glob.gasLimit + }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [payable]: cannot be called with 0 ethers", async() => { + try { + await web3.eth.sendTransactionPromise({ + from: glob.user_a, + to: glob.web3ClientFund.address, + value: web3.toWei(0, 'ether'), + gas: glob.gasLimit + }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [payable]: add 2.5 Ethers to user A active balance", async() => { + try { + await web3.eth.sendTransactionPromise({ + from: glob.user_a, + to: glob.web3ClientFund.address, + value: web3.toWei(2.5, 'ether'), + gas: glob.gasLimit + }); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [payable]: add 6.5 Ethers to user B active balance", async() => { + try { + await web3.eth.sendTransactionPromise({ + from: glob.user_b, + to: glob.web3ClientFund.address, + value: web3.toWei(6.5, 'ether'), + gas: glob.gasLimit + }); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [depositTokens]: 10 tokens added to A active balance", async() => { + try { + await glob.web3Erc20.approve(glob.web3ClientFund.address, 10, { from: glob.user_a }); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + try { + await glob.web3ClientFund.depositTokens(glob.web3Erc20.address, 10, { from: glob.user_a }); + } + catch (err) { + assert(false, 'This test must succeed. Error: ERC20 failed to approve token transfer. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositTokens]: Cannot be called from owner address", async() => { + try { + await glob.web3ClientFund.depositTokens(glob.web3Erc20.address, 5, { from: glob.owner }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositTokens]: Cannot be called with zero address", async() => { + try { + await glob.web3ClientFund.depositTokens(0, 5, { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositTokens]: Cannot be called with zero amount", async() => { + try { + await glob.web3ClientFund.depositTokens(glob.web3Erc20.address, 0, { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositTokens]: User does not have enough tokens to deposit.", async() => { + try { + await glob.web3Erc20.approve(glob.web3ClientFund.address, 9999, { from: glob.user_a }); + } + catch (err) { + assert(false, 'Error: ERC20 failed to approve token transfer.'); + } + try { + await glob.web3ClientFund.depositTokens(glob.web3Erc20.address, 9999, { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [depositCount]: User A should have 2 deposits", async() => { + try { + let count = await glob.web3ClientFund.depositCount(glob.user_a); + assert.equal(count, 2, 'This test must succeed. Error: Deposit count: ' + count.toString()); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [depositCount]: User B should have 1 deposit", async() => { + try { + let count = await glob.web3ClientFund.depositCount(glob.user_b); + assert.equal(count, 1, 'This test must succeed. Error: Deposit count: ' + count.toString()); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositCount]: Cannot be called from non-owner address", async() => { + try { + await glob.web3ClientFund.depositCount(glob.user_a, { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [deposit]: User B should have 6.5 ETH at index 0", async() => { + try { + let args = await glob.web3ClientFund.deposit(glob.user_b, 0); + const _amount = args[0]; + const _timestamp = args[1]; + const _token = args[2]; + + assert.equal(_token, 0, "Unexpected token deposit."); + assert.equal(_amount, web3.toWei(6.5, 'ether'), "Unexpected ether deposit amount."); + assert.notEqual(_timestamp, 0, "Timestamp cannot be null."); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [deposit]: Invalid index deposit 1 for user B.", async() => { + try { + await glob.web3ClientFund.deposit(glob.user_b, 1); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [deposit]: User A should have 10 tokens at index 1", async() => { + try { + let args = await glob.web3ClientFund.deposit(glob.user_a, 1); + const _amount = args[0]; + const _timestamp = args[1]; + const _token = args[2]; + + assert.equal(_token, glob.web3Erc20.address, "Unexpected ether or other token deposit."); + assert.equal(_amount, 10, "Unexpeced token deposit amount."); + assert.notEqual(_timestamp, 0, "Timestamp cannot be null."); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [activeBalance]: 2.5 ETH for User A", async() => { + try { + let balance = await glob.web3ClientFund.activeBalance(glob.user_a, 0); + assert.equal(balance, web3.toWei(2.5, 'ether'), 'Wrong balance [' + web3.fromWei(balance, 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [activeBalance]: 10 tokens for User A", async() => { + try { + let balance = await glob.web3ClientFund.activeBalance(glob.user_a, glob.web3Erc20.address); + assert.equal(balance, 10, 'Wrong balance [' + balance.toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [activeBalance]: 0 tokens for User B", async() => { + try { + let balance = await glob.web3ClientFund.activeBalance(glob.user_b, glob.web3Erc20.address); + assert.equal(balance, 0, 'Wrong balance [' + balance.toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [stagedBalance]: 0 ETH for User A", async() => { + try { + let balance = await glob.web3ClientFund.stagedBalance(glob.user_a, 0); + assert.equal(balance, web3.toWei(0, 'ether'), 'Wrong balance [' + web3.fromWei(balance, 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [stagedBalance]: 0 tokens for User A", async() => { + try { + let balance = await glob.web3ClientFund.stagedBalance(glob.user_a, glob.web3Erc20.address); + assert.equal(balance, 0, 'Wrong balance [' + balance.toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [stagedBalance]: 0 tokens for User B", async() => { + try { + let balance = await glob.web3ClientFund.stagedBalance(glob.user_b, glob.web3Erc20.address); + assert.equal(balance, 0, 'Wrong balance [' + balance.toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [setServiceActivationTimeout]: Set the service activation timeout to 0", async() => { + try { + await glob.web3ClientFund.setServiceActivationTimeout(0); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [registerService]: Register ReserveFunds SC as a service", async() => { + try { + await glob.web3ClientFund.registerService(glob.web3ReserveFund.address); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [registerService]: Register ClientFund SC as a service", async() => { + try { + await glob.web3ClientFund.registerService(glob.web3ClientFund.address); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [registerService]: Register UnitTestHelpers_FAIL SC as a service from non-owner", async() => { + try { + await glob.web3ClientFund.registerService(glob.web3UnitTestHelpers_FAIL_TESTS.address, { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [registerService]: Register UnitTestHelpers_SUCCESS SC as a service", async() => { + try { + await glob.web3ClientFund.registerService(glob.web3UnitTestHelpers_SUCCESS_TESTS.address); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [registerService]: Register UnitTestHelpers_FAIL SC as a service", async() => { + try { + await glob.web3ClientFund.registerService(glob.web3UnitTestHelpers_FAIL_TESTS.address); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [disableRegisteredService]: Disable UnitTestHelpers_FAIL as a service for User A", async() => { + try { + await glob.web3ClientFund.disableRegisteredService(glob.web3UnitTestHelpers_FAIL_TESTS.address, { from: glob.user_a }); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [transferFromActiveToStagedBalance]: User A uses UnitTestHelpers_SUCCESS as a service to send 0.2 ETH to User D", async() => { + try { + let oldActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, 0); + let oldStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, 0); + + await glob.web3UnitTestHelpers_SUCCESS_TESTS.callToTransferFromActiveToStagedBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, web3.toWei(0.2, 'ether'), 0); + + let newActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, 0); + let newStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, 0); + + assert.equal(newStagedBalance.sub(oldStagedBalance), web3.toWei(0.2, 'ether'), 'Wrong staged balance [Diff ' + web3.fromWei(newStagedBalance.sub(oldStagedBalance), 'ether') + ' ethers].'); + assert.equal(newActiveBalance.sub(oldActiveBalance), web3.toWei(-0.2, 'ether'), 'Wrong active balance [Diff ' + web3.fromWei(newActiveBalance.sub(oldActiveBalance), 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [transferFromActiveToStagedBalance]: User A disabled UnitTestHelpers_FAIL as a service to send 0.2 ETH to User D", async() => { + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.callToTransferFromActiveToStagedBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, web3.toWei(0.2, 'ether'), 0); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [transferFromActiveToStagedBalance]: User A uses UnitTestHelpers_SUCCESS as a service to send 6 tokens to User D", async() => { + try { + let oldActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, glob.web3Erc20.address); + let oldStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, glob.web3Erc20.address); + + await glob.web3UnitTestHelpers_SUCCESS_TESTS.callToTransferFromActiveToStagedBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, 6, glob.web3Erc20.address); + + let newActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, glob.web3Erc20.address); + let newStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, glob.web3Erc20.address); + + assert.equal(newStagedBalance.sub(oldStagedBalance), 6, 'Wrong staged balance [Diff ' + newStagedBalance.sub(oldStagedBalance).toString() + ' tokens].'); + assert.equal(newActiveBalance.sub(oldActiveBalance), -6, 'Wrong active balance [Diff ' + newActiveBalance.sub(oldActiveBalance).toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [transferFromActiveToStagedBalance]: User A disabled UnitTestHelpers_FAIL as a service to send 6 tokens to User D", async() => { + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.callToTransferFromActiveToStagedBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, 6, glob.web3Erc20.address); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [withdrawFromActiveBalance]: User A uses UnitTestHelpers_SUCCESS as a service to withdraw 0.3 ETH to User D", async() => { + try { + let oldActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, 0); + let oldEthersBalance = await web3.eth.getBalancePromise(glob.user_d); + + await glob.web3UnitTestHelpers_SUCCESS_TESTS.callToWithdrawFromActiveBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, web3.toWei(0.3, 'ether'), 0); + + let newActiveBalance = await glob.web3ClientFund.activeBalance(glob.user_a, 0); + let newEthersBalance = await web3.eth.getBalancePromise(glob.user_d); + + assert.equal(newEthersBalance.sub(oldEthersBalance), web3.toWei(0.3, 'ether'), 'Wrong balance [Diff ' + web3.fromWei(newEthersBalance.sub(oldEthersBalance), 'ether') + ' ethers].'); + assert.equal(newActiveBalance.sub(oldActiveBalance), web3.toWei(-0.3, 'ether'), 'Wrong balance [Diff ' + web3.fromWei(newActiveBalance.sub(oldActiveBalance), 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [withdrawFromActiveBalance]: User A disabled unit test helper SC as a service to withdraw 0.3 ETH to User D", async() => { + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.callToWithdrawFromActiveBalance(glob.web3ClientFund.address, glob.user_a, glob.user_d, web3.toWei(0.3, 'ether'), 0); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [depositEthersToStagedBalance]: UnitTestHelpers_SUCCESS deposits 0.4 ETH to User C", async() => { + try { + let oldStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_c, 0); + let oldEthersBalance = await web3.eth.getBalancePromise(glob.web3ClientFund.address); + + await glob.web3UnitTestHelpers_SUCCESS_TESTS.callToDepositEthersToStagedBalance(glob.web3ClientFund.address, glob.user_c, { value: web3.toWei(0.4, 'ether') }); + + let newStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_c, 0); + let newEthersBalance = await web3.eth.getBalancePromise(glob.web3ClientFund.address); + + assert.equal(newEthersBalance.sub(oldEthersBalance), web3.toWei(0.4, 'ether'), 'Wrong SC balance [Diff ' + web3.fromWei(newEthersBalance.sub(oldEthersBalance), 'ether') + ' ethers].'); + assert.equal(newStagedBalance.sub(oldStagedBalance), web3.toWei(0.4, 'ether'), 'Wrong balance [Diff ' + web3.fromWei(newStagedBalance.sub(oldStagedBalance), 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositEthersToStagedBalance]: UnitTestHelpers_FAIL deposits 0.4 ETH to User A", async() => { + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.callToDepositEthersToStagedBalance(glob.web3ClientFund.address, glob.user_a, { value: web3.toWei(0.4, 'ether') }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [depositTokensToStagedBalance]: UnitTestHelpers_SUCCESS deposits 4 tokens to User D", async() => { + try { + await glob.web3UnitTestHelpers_SUCCESS_TESTS.erc20_approve(glob.web3Erc20.address, glob.web3ClientFund.address, 4); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + try { + let oldStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, glob.web3Erc20.address); + let oldTokensBalance = await glob.web3Erc20.balanceOf(glob.web3UnitTestHelpers_SUCCESS_TESTS.address); + + await glob.web3UnitTestHelpers_SUCCESS_TESTS.callToDepositTokensToStagedBalance(glob.web3ClientFund.address, glob.user_d, glob.web3Erc20.address, 4); + + let newStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, glob.web3Erc20.address); + let newTokensBalance = await glob.web3Erc20.balanceOf(glob.web3UnitTestHelpers_SUCCESS_TESTS.address); + + assert.equal(newTokensBalance.sub(oldTokensBalance), -4, 'Wrong SC balance [Diff ' + newTokensBalance.sub(oldTokensBalance).toString() + ' tokens].'); + assert.equal(newStagedBalance.sub(oldStagedBalance), 4, 'Wrong balance [Diff ' + newStagedBalance.sub(oldStagedBalance).toString() + ' tokens].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST FAIL [depositTokensToStagedBalance]: UnitTestHelpers_FAIL deposits 4 tokens to User A", async() => { + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.erc20_approve(glob.web3Erc20.address, glob.web3ClientFund.address, 4); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + try { + await glob.web3UnitTestHelpers_FAIL_TESTS.callToDepositTokensToStagedBalance(glob.web3ClientFund.address, glob.user_a, glob.web3Erc20.address, 4); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } + }); + + //------------------------------------------------------------------------ + + it(testCounter.next() + ": MUST SUCCEED [withdrawEthers]: User D wants to withdraw 0.1 ETH", async() => { + try { + let oldStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, 0); + let oldEthersBalance = await web3.eth.getBalancePromise(glob.user_d); + + let result = await glob.web3ClientFund.withdrawEthers(web3.toWei(0.1, 'ether'), { from: glob.user_d }); + + let tx = web3.eth.getTransaction(result.tx); + let totalGasPrice = new web3.BigNumber(result.receipt.gasUsed); + totalGasPrice = totalGasPrice.mul(new web3.BigNumber(tx.gasPrice)); + + let newStagedBalance = await glob.web3ClientFund.stagedBalance(glob.user_d, 0); + let newEthersBalance = await web3.eth.getBalancePromise(glob.user_d); + + assert.equal(newEthersBalance.add(totalGasPrice).sub(oldEthersBalance), web3.toWei(0.1, 'ether'), 'Wrong user D balance [Diff ' + web3.fromWei(newEthersBalance.add(totalGasPrice).sub(oldEthersBalance), 'ether') + ' ethers].'); + assert.equal(oldStagedBalance.sub(newStagedBalance), web3.toWei(0.1, 'ether'), 'Wrong staged balance [Diff ' + web3.fromWei(oldStagedBalance.sub(newStagedBalance), 'ether') + ' ethers].'); + } + catch (err) { + assert(false, 'This test must succeed. [Error: ' + err.toString() + ']'); + } + }); + + //------------------------------------------------------------------------- + + it(testCounter.next() + ": MUST SUCCEED [withdrawEthers]: User A wants to withdraw 0.1 ETH", async() => { + try { + await glob.web3ClientFund.withdrawEthers(web3.toWei(0.1, 'ether'), { from: glob.user_a }); + assert(false, 'This test must fail.'); + } + catch (err) { + assert(err.toString().includes('revert'), err.toString()); + } }); }); }; +