diff --git a/.circleci/config.yml b/.circleci/config.yml index e2d2e53..5f30c5f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -94,6 +94,11 @@ jobs: name: "Stop dev environment" command: docker kill ganache_side ganache_home - bridge/save_ganache_data + - run: + name: "Prefund bnc addresses" + command: | + echo "FOREIGN_PRIVATE_KEY=$FOREIGN_PRIVATE_KEY" > ./src/test-services/.keys.$TARGET_NETWORK + cat ./tests/config.json | jq .users[].bncAddress | xargs -I {} ./src/test-services/binanceSend/run.sh {} 100 0.1 run_tests: executor: bridge/node-dev steps: @@ -107,17 +112,15 @@ jobs: name: "Init tests environment" command: | ./demo/start-environment.sh - echo "FOREIGN_PRIVATE_KEY=$FOREIGN_PRIVATE_KEY" > ./src/test-services/.keys.$TARGET_NETWORK - cat ./tests/config.json | jq .users[].bncAddress | xargs -I {} ./src/test-services/binanceSend/run.sh {} 100 0.1 N=1 ./demo/validator-demo.sh -d N=2 ./demo/validator-demo.sh -d N=3 ./demo/validator-demo.sh -d - run: name: "Wait until validator nodes are ready" command: | - until curl -X GET http://localhost:5001 > /dev/null 2>&1; do sleep 1; done - until curl -X GET http://localhost:5002 > /dev/null 2>&1; do sleep 1; done - until curl -X GET http://localhost:5003 > /dev/null 2>&1; do sleep 1; done + docker run --network validator1_test_network --entrypoint ash appropriate/curl:latest -c "until curl -X GET http://proxy:8002/info > /dev/null 2>&1; do sleep 1; done" + docker run --network validator2_test_network --entrypoint ash appropriate/curl:latest -c "until curl -X GET http://proxy:8002/info > /dev/null 2>&1; do sleep 1; done" + docker run --network validator3_test_network --entrypoint ash appropriate/curl:latest -c "until curl -X GET http://proxy:8002/info > /dev/null 2>&1; do sleep 1; done" - run: name: "Build and prepare tests container" command: | @@ -125,11 +128,16 @@ jobs: docker create --rm -e HOME_RPC_URL --name tests tests $@ docker network connect blockchain_side tests docker network connect blockchain_home tests + docker network connect validator1_test_network tests + docker network connect validator2_test_network tests + docker network connect validator3_test_network tests environment: HOME_RPC_URL: 'http://ganache_home:8545' - run: name: "Run tests" - command: docker start tests + command: | + docker start -a tests + docker cp "tests:/tests/results.xml" "./tests/results.xml" workflows: version: 2 main: diff --git a/demo/start-environment.sh b/demo/start-environment.sh index 788c5a1..111446d 100755 --- a/demo/start-environment.sh +++ b/demo/start-environment.sh @@ -135,7 +135,7 @@ deploy_all() { if [[ "$TARGET_NETWORK" == "development" ]]; then - if [[ "$(docker volume ls | grep ganache_side_db)" ]] || [[ "$(docker volume ls | grep ganache_home_db)" ]]; then + if [[ "$(docker volume ls | grep ganache_side_data)" ]] || [[ "$(docker volume ls | grep ganache_home_data)" ]]; then echo "Restarting dev blockchain networks" else echo "Starting dev blockchain networks and deploying contracts" diff --git a/tests/.mocharc.yml b/tests/.mocharc.yml new file mode 100644 index 0000000..82c1ba6 --- /dev/null +++ b/tests/.mocharc.yml @@ -0,0 +1,3 @@ +reporter: spec +reporter-option: + - mochaFile=./results.xml diff --git a/tests/Dockerfile b/tests/Dockerfile index 8acf8e4..2400a1b 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -2,13 +2,15 @@ FROM node:10.16.0-alpine WORKDIR /tests -RUN npm install -g mocha +RUN npm install -g mocha mocha-junit-reporter + +RUN apk update && apk add libssl1.1 eudev-dev libressl-dev curl build-base python linux-headers libusb-dev COPY ./package.json . RUN npm install -COPY config.json ./ +COPY config.json .mocharc.yml ./ COPY test ./test ENTRYPOINT ["mocha"] diff --git a/tests/init.sh b/tests/init.sh old mode 100644 new mode 100755 index 35b7742..e9831b3 --- a/tests/init.sh +++ b/tests/init.sh @@ -1,11 +1,11 @@ #!/bin/bash set -e +set -v ./demo/start-environment.sh -echo "FOREIGN_PRIVATE_KEY=$FOREIGN_PRIVATE_KEY" > ./src/test-services/.keys.$TARGET_NETWORK - +cat ./tests/config.json | jq .users[].ethAddress | xargs -I {} ./src/test-services/ethereumSend/run.sh {} 100 cat ./tests/config.json | jq .users[].bncAddress | xargs -I {} ./src/test-services/binanceSend/run.sh {} 100 0.1 N=1 ./demo/validator-demo.sh -d diff --git a/tests/package.json b/tests/package.json index 3941246..de759d4 100644 --- a/tests/package.json +++ b/tests/package.json @@ -2,6 +2,9 @@ "name": "tests", "version": "0.0.1", "dependencies": { - "web3": "1.0.0-beta.55" + "ethers": "4.0.38", + "axios": "0.19.0", + "@binance-chain/javascript-sdk": "2.16.1", + "bignumber.js": "9.0.0" } } diff --git a/tests/run.sh b/tests/run.sh index 59fdd2f..b78d747 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,8 +1,29 @@ #!/bin/bash set -e -set -v docker build -t tests ./tests -docker run --network blockchain_home --rm -e HOME_RPC_URL tests $@ +set -a +source ./demo/validator1/.env.development +set +a + +docker rm tests || true +docker create --name tests \ + -e HOME_RPC_URL \ + -e FOREIGN_URL \ + -e HOME_BRIDGE_ADDRESS \ + -e HOME_TOKEN_ADDRESS \ + -e FOREIGN_PRIVATE_KEY \ + -e FOREIGN_ASSET \ + tests $@ + +docker network connect blockchain_home tests +docker network connect validator1_test_network tests +docker network connect validator2_test_network tests +docker network connect validator3_test_network tests + +docker start -a tests || true + +docker cp "tests:/tests/results.xml" "./tests/results.xml" || true +docker rm tests || true diff --git a/tests/test/ethToBnc.js b/tests/test/ethToBnc.js new file mode 100644 index 0000000..09bc2c5 --- /dev/null +++ b/tests/test/ethToBnc.js @@ -0,0 +1,51 @@ +const assert = require('assert') +const createUser = require('./utils/user') +const { getSequence } = require('./utils/bncController') +const { waitPromise, delay } = require('./utils/wait') + +const usersConfig = require('../config').users + +const { HOME_BRIDGE_ADDRESS } = process.env + +module.exports = (foreignBridgeAddress) => { + describe('exchanges tokens in eth => bnc direction', function () { + let ethBalances + let bncBalances + let bncBridgeSequence + let users + + before(async function () { + this.timeout(60000) + users = await usersConfig.seqMap(user => createUser(user.privateKey)) + ethBalances = await Promise.all(users.map(user => user.getEthBalance())) + bncBalances = await users.seqMap(user => user.getBncBalance()) + + bncBridgeSequence = await getSequence(foreignBridgeAddress()) + await Promise.all(users.map((user, i) => user.approveEth(HOME_BRIDGE_ADDRESS, 5 + i))) + }) + + it('should accept exchange requests', async function () { + this.timeout(60000) + await Promise.all(users.map((user, i) => user.exchangeEth(5 + i))) + const newEthBalances = await Promise.all(users.map(user => user.getEthBalance())) + for (let i = 0; i < 3; i++) { + assert(newEthBalances[i] === ethBalances[i] - 5 - i, `Balance of ${usersConfig[i].ethAddress} did not updated as expected`) + } + ethBalances = newEthBalances + }) + + it('should make exchange transaction on bnc side', async function () { + this.timeout(300000) + await waitPromise(() => getSequence(foreignBridgeAddress()), sequence => sequence === bncBridgeSequence + 1) + }) + + it('should make correct exchange transaction', async function () { + this.timeout(60000) + const newBncBalances = await Promise.all(users.map(user => user.getBncBalance())) + for (let i = 0; i < 3; i++) { + assert(newBncBalances[i] === bncBalances[i] + 5 + i, `Balance of ${usersConfig[i].bncAddress} did not updated as expected`) + } + bncBalances = newBncBalances + }) + }) +} diff --git a/tests/test/index.js b/tests/test/index.js index b6f7c8a..6b8e518 100644 --- a/tests/test/index.js +++ b/tests/test/index.js @@ -1,12 +1,32 @@ -const Web3 = require('web3') -const { users } = require('../config') +const createController = require('./utils/proxyController') +const createUser = require('./utils/user') +const { waitPromise } = require('./utils/wait') -const web3 = new Web3(process.env.HOME_RPC_URL) +const testEthToBnc = require('./ethToBnc') -describe('check balance', function () { - it('should have correct balance', async function () { - const balance = await web3.eth.getBalance(users[0].ethAddress) - console.log(balance.toNumber()) - return 0 +const { FOREIGN_PRIVATE_KEY } = process.env + +let user + +let { getInfo } = createController(1) + +let info + +describe('generates initial epoch keys', function () { + before(async function () { + this.timeout(60000) + user = await createUser(FOREIGN_PRIVATE_KEY) + }) + + it('should generate keys in 2 min', async function () { + this.timeout(120000) + info = await waitPromise(getInfo, info => info.epoch === 1) + }) + + after(async function () { + this.timeout(60000) + await user.transferBnc(info.foreignBridgeAddress, 50, 0.1) }) }) + +testEthToBnc(() => info.foreignBridgeAddress) diff --git a/tests/test/utils/bncClient.js b/tests/test/utils/bncClient.js new file mode 100644 index 0000000..4396b53 --- /dev/null +++ b/tests/test/utils/bncClient.js @@ -0,0 +1,42 @@ +const Bnc = require('@binance-chain/javascript-sdk') + +const { delay } = require('./wait') + +const { FOREIGN_URL, FOREIGN_ASSET } = process.env + +module.exports = async function main (privateKey) { + const client = new Bnc(FOREIGN_URL) + client.chooseNetwork('testnet') + + await client.setPrivateKey(privateKey) + + await client.initChain() + const from = client.getClientKeyAddress() + + await delay(1000) + + return { + transfer: async function (to, tokens, bnbs) { + const outputs = [{ + to, + coins: [] + }] + if (tokens) { + outputs[0].coins.push({ + denom: FOREIGN_ASSET, + amount: tokens + }) + } + if (bnbs) { + outputs[0].coins.push({ + denom: 'BNB', + amount: bnbs + }) + } + await client.multiSend(from, outputs, 'funding') + }, + exchange: async function (to, value) { + await client.transfer(from, to, value.toString(), FOREIGN_ASSET, 'exchange') + } + } +} diff --git a/tests/test/utils/bncController.js b/tests/test/utils/bncController.js new file mode 100644 index 0000000..589b4a4 --- /dev/null +++ b/tests/test/utils/bncController.js @@ -0,0 +1,29 @@ +const axios = require('axios') + +const { FOREIGN_URL, FOREIGN_ASSET } = process.env + +const bnc = axios.create({ + baseURL: FOREIGN_URL, + timeout: 5000 +}) + +module.exports = { + getBalance: async function (address) { + try { + const response = await bnc.get(`/api/v1/account/${address}`) + + return parseFloat(response.data.balances.find(x => x.symbol === FOREIGN_ASSET).free) + } catch (e) { + return 0 + } + }, + getSequence: async function(address) { + try { + const response = await bnc.get(`/api/v1/account/${address}/sequence`) + + return response.data.sequence + } catch (e) { + return 0 + } + } +} diff --git a/tests/test/utils/homeContracts.js b/tests/test/utils/homeContracts.js new file mode 100644 index 0000000..6912273 --- /dev/null +++ b/tests/test/utils/homeContracts.js @@ -0,0 +1,24 @@ +const ethers = require('ethers') + +const { HOME_RPC_URL, HOME_TOKEN_ADDRESS, HOME_BRIDGE_ADDRESS } = process.env + +const abiToken = [ + 'function balanceOf(address account) view returns (uint)', + 'function transfer(address to, uint value)', + 'function approve(address to, uint value)', + 'function allowance(address owner, address spender) view returns (uint)' +] +const abiBridge = [ + 'function exchange(uint value)' +] + +const provider = new ethers.providers.JsonRpcProvider(HOME_RPC_URL) + +const tokenContract = new ethers.Contract(HOME_TOKEN_ADDRESS, abiToken, provider) +const bridgeContract = new ethers.Contract(HOME_BRIDGE_ADDRESS, abiBridge, provider) + +module.exports = { + tokenContract, + bridgeContract, + provider +} diff --git a/tests/test/utils/proxyController.js b/tests/test/utils/proxyController.js new file mode 100644 index 0000000..1000a5a --- /dev/null +++ b/tests/test/utils/proxyController.js @@ -0,0 +1,31 @@ +const axios = require('axios') + +module.exports = function (validatorId) { + const url = `http://validator${validatorId}_proxy_1:8002/` + + const proxy = axios.create({ + baseURL: url, + timeout: 5000 + }) + + return { + getInfo: async function () { + return (await proxy.get('/info')).data + }, + voteStartVoting: async function () { + return (await proxy.get('/vote/startVoting')).data + }, + voteStartKeygen: async function () { + return (await proxy.get('/vote/startKeygen')).data + }, + voteAddValidator: async function (validatorAddress) { + return (await proxy.get(`/vote/addValidator/${validatorAddress}`)).data + }, + voteRemoveValidator: async function (validatorAddress) { + return (await proxy.get(`/vote/removeValidator/${validatorAddress}`)).data + }, + voteChangeThreshold: async function (threshold) { + return (await proxy.get(`/vote/changeThreshold/${threshold}`)).data + } + } +} diff --git a/tests/test/utils/user.js b/tests/test/utils/user.js new file mode 100644 index 0000000..da7dde2 --- /dev/null +++ b/tests/test/utils/user.js @@ -0,0 +1,59 @@ +const ethers = require('ethers') +const BN = require('bignumber.js') +const { getAddressFromPrivateKey } = require('@binance-chain/javascript-sdk/lib/crypto') + +const createBncClient = require('./bncClient') +const { getBalance } = require('./bncController') +const { tokenContract, bridgeContract, provider } = require('./homeContracts') +const { delay } = require('./wait') + +const txOptions = { + gasLimit: 200000 +} + +module.exports = async function (privateKey) { + const wallet = new ethers.Wallet(privateKey, provider) + const ethAddress = wallet.address + const bncAddress = getAddressFromPrivateKey(privateKey) + const token = tokenContract.connect(wallet) + const bridge = bridgeContract.connect(wallet) + + const bncClient = await createBncClient(privateKey) + + return { + getEthBalance: async function () { + const balance = await token.balanceOf(ethAddress) + return parseFloat(new BN(balance).dividedBy(10 ** 18).toFixed(8, 3)) + }, + transferEth: async function (to, value) { + const tx = await token.transfer(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + await tx.wait() + }, + approveEth: async function (to, value) { + console.log('approving', to, value) + const tx = await token.approve(to, '0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + console.log('sent', tx) + await tx.wait() + console.log('done') + console.log(await token.allowance(ethAddress, to)) + }, + exchangeEth: async function (value) { + console.log(value) + const tx = await bridge.exchange('0x' + (new BN(value).multipliedBy(10 ** 18).toString(16)), txOptions) + console.log(tx) + await tx.wait() + console.log('done') + }, + getBncBalance: async function () { + const balance = await getBalance(bncAddress) + await delay(1000) + return balance + }, + transferBnc: async function (bridgeAddress, tokens, bnbs) { + return await bncClient.transfer(bridgeAddress, tokens, bnbs) + }, + exchangeBnc: async function (bridgeAddress, value) { + return await bncClient.exchange(bridgeAddress, value) + } + } +} diff --git a/tests/test/utils/wait.js b/tests/test/utils/wait.js new file mode 100644 index 0000000..733d9f4 --- /dev/null +++ b/tests/test/utils/wait.js @@ -0,0 +1,25 @@ +async function delay(ms) { + await new Promise(res => setTimeout(res, ms)) +} + +async function waitPromise (getPromise, checker) { + do { + const result = await getPromise() + if (checker(result)) + return result + await delay(1000) + } while (true) +} + +Array.prototype.seqMap = async function (transition) { + const results = [] + for (let i = 0; i < this.length; i++) { + results[i] = await transition(this[i]) + } + return results +} + +module.exports = { + waitPromise, + delay +}