Monitor E2E rewrite (#187)
* Started monitor-e2e rewrite * axios * Implemented tests * Monitor start * First deploy * Wait for monitor * Removed redundant files * Tests. * TODO * Links to minitor in constants * Typo. * [PR-into-PR] Monitor E2E rewrite - balance (#191) * Test run for more monitor-e2e tests * macos/docker * timeout * Little refactor * Trying to test balances in other types of bridges. * Utils. * test * check all * erc to erc try * Final tests * typo * All jobs * Lint * Roll back docker in docker * WaitUntil * Axios version * New validator checks (#192)
This commit is contained in:
parent
c39e81f97d
commit
dd9add50a0
@ -3,6 +3,7 @@ FROM node:10
|
|||||||
WORKDIR /mono
|
WORKDIR /mono
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY oracle-e2e/package.json ./oracle-e2e/
|
COPY oracle-e2e/package.json ./oracle-e2e/
|
||||||
|
COPY monitor-e2e/package.json ./monitor-e2e/
|
||||||
COPY contracts/package.json ./contracts/
|
COPY contracts/package.json ./contracts/
|
||||||
|
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
|
@ -19,20 +19,23 @@
|
|||||||
"home": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
|
"home": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
|
||||||
"foreign": "0x2B6871b9B02F73fa24F4864322CdC78604207769",
|
"foreign": "0x2B6871b9B02F73fa24F4864322CdC78604207769",
|
||||||
"foreignToken": "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B",
|
"foreignToken": "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B",
|
||||||
"ui": "http://localhost:3000"
|
"ui": "http://localhost:3000",
|
||||||
|
"monitor": "http://monitor:3010"
|
||||||
},
|
},
|
||||||
"ercToErcBridge": {
|
"ercToErcBridge": {
|
||||||
"home": "0x1feB40aD9420b186F019A717c37f5546165d411E",
|
"home": "0x1feB40aD9420b186F019A717c37f5546165d411E",
|
||||||
"foreign": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127",
|
"foreign": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127",
|
||||||
"homeToken": "0x792455a6bCb62Ed4C4362D323E0590654CA4765c",
|
"homeToken": "0x792455a6bCb62Ed4C4362D323E0590654CA4765c",
|
||||||
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
|
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
|
||||||
"ui": "http://localhost:3001"
|
"ui": "http://localhost:3001",
|
||||||
|
"monitor": "http://monitor-erc20:3011"
|
||||||
},
|
},
|
||||||
"ercToNativeBridge": {
|
"ercToNativeBridge": {
|
||||||
"home": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
"home": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
||||||
"foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
"foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
||||||
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
|
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
|
||||||
"ui": "http://localhost:3002"
|
"ui": "http://localhost:3002",
|
||||||
|
"monitor": "http://monitor-erc20-native:3012"
|
||||||
},
|
},
|
||||||
"homeRPC": {
|
"homeRPC": {
|
||||||
"URL": "http://parity1:8545",
|
"URL": "http://parity1:8545",
|
||||||
|
@ -239,7 +239,7 @@ services:
|
|||||||
- FOREIGN_GAS_PRICE_FACTOR=1
|
- FOREIGN_GAS_PRICE_FACTOR=1
|
||||||
- LEFT_TX_THRESHOLD=100
|
- LEFT_TX_THRESHOLD=100
|
||||||
- PORT=3010
|
- PORT=3010
|
||||||
entrypoint: yarn start
|
entrypoint: yarn check-and-start
|
||||||
ports:
|
ports:
|
||||||
- "3010:3010"
|
- "3010:3010"
|
||||||
networks:
|
networks:
|
||||||
@ -267,7 +267,7 @@ services:
|
|||||||
- FOREIGN_GAS_PRICE_FACTOR=1
|
- FOREIGN_GAS_PRICE_FACTOR=1
|
||||||
- LEFT_TX_THRESHOLD=100
|
- LEFT_TX_THRESHOLD=100
|
||||||
- PORT=3011
|
- PORT=3011
|
||||||
entrypoint: yarn start
|
entrypoint: yarn check-and-start
|
||||||
ports:
|
ports:
|
||||||
- "3011:3011"
|
- "3011:3011"
|
||||||
networks:
|
networks:
|
||||||
@ -295,7 +295,7 @@ services:
|
|||||||
- FOREIGN_GAS_PRICE_FACTOR=1
|
- FOREIGN_GAS_PRICE_FACTOR=1
|
||||||
- LEFT_TX_THRESHOLD=100
|
- LEFT_TX_THRESHOLD=100
|
||||||
- PORT=3012
|
- PORT=3012
|
||||||
entrypoint: yarn start
|
entrypoint: yarn check-and-start
|
||||||
ports:
|
ports:
|
||||||
- "3012:3012"
|
- "3012:3012"
|
||||||
networks:
|
networks:
|
||||||
|
16
monitor-e2e/.eslintrc
Normal file
16
monitor-e2e/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:node/recommended",
|
||||||
|
"airbnb-base",
|
||||||
|
"../.eslintrc"
|
||||||
|
],
|
||||||
|
"plugins": ["node"],
|
||||||
|
"rules": {
|
||||||
|
"node/no-unpublished-require": "off",
|
||||||
|
"node/no-extraneous-require": "off",
|
||||||
|
"import/no-extraneous-dependencies": "off"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"mocha": true
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
set -e # exit when any command fails
|
|
||||||
|
|
||||||
echo "MONITOR E2E - ERC TO ERC"
|
|
||||||
export URL=localhost:3011
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return balances"
|
|
||||||
OUTPUT=$(curl -s http://$URL/)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return validators"
|
|
||||||
OUTPUT=$(curl -s http://$URL/validators)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return eventsStats"
|
|
||||||
OUTPUT=$(curl -s http://$URL/eventsStats)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return alerts"
|
|
||||||
OUTPUT=$(curl -s http://$URL/alerts)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
@ -1,26 +0,0 @@
|
|||||||
set -e # exit when any command fails
|
|
||||||
|
|
||||||
echo "MONITOR E2E - ERC TO NATIVE"
|
|
||||||
export URL=localhost:3012
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return balances"
|
|
||||||
OUTPUT=$(curl -s http://$URL/)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return validators"
|
|
||||||
OUTPUT=$(curl -s http://$URL/validators)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return eventsStats"
|
|
||||||
OUTPUT=$(curl -s http://$URL/eventsStats)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return alerts"
|
|
||||||
OUTPUT=$(curl -s http://$URL/alerts)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
@ -1,26 +0,0 @@
|
|||||||
set -e # exit when any command fails
|
|
||||||
|
|
||||||
echo "MONITOR E2E - NATIVE TO ERC"
|
|
||||||
export URL="localhost:3010"
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return balances"
|
|
||||||
OUTPUT=$(curl -s http://$URL/)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return validators"
|
|
||||||
OUTPUT=$(curl -s http://$URL/validators)
|
|
||||||
grep -q home <<< "$OUTPUT"
|
|
||||||
grep -q foreign <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return eventsStats"
|
|
||||||
OUTPUT=$(curl -s http://$URL/eventsStats)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
||||||
|
|
||||||
echo "Test case - Web Interface should return alerts"
|
|
||||||
OUTPUT=$(curl -s http://$URL/alerts)
|
|
||||||
grep -q lastChecked <<< "$OUTPUT"
|
|
||||||
(! grep -q error <<< "$OUTPUT")
|
|
19
monitor-e2e/package.json
Normal file
19
monitor-e2e/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "monitor-e2e",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "mocha --timeout 30000",
|
||||||
|
"lint": "eslint . --ignore-path ../.eslintignore"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"axios": "0.19.0"
|
||||||
|
}
|
||||||
|
}
|
6
monitor-e2e/periodically-check-all.sh
Executable file
6
monitor-e2e/periodically-check-all.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
while true; do
|
||||||
|
sleep 3
|
||||||
|
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor yarn check-all
|
||||||
|
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 yarn check-all
|
||||||
|
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native yarn check-all
|
||||||
|
done
|
@ -1,51 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
set -e # exit when any command fails
|
|
||||||
|
|
||||||
|
../e2e-commons/up.sh deploy monitor
|
||||||
|
|
||||||
##### Helper Functions #####
|
./wait-for-monitor.sh
|
||||||
|
nohup ./periodically-check-all.sh < /dev/null > /dev/null 2>&1 &
|
||||||
|
checkPID=$!
|
||||||
|
|
||||||
|
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace monitor-e2e run start
|
||||||
|
rc=$?
|
||||||
|
|
||||||
function cleanup {
|
|
||||||
../e2e-commons/down.sh
|
../e2e-commons/down.sh
|
||||||
}
|
kill $checkPID
|
||||||
trap cleanup EXIT
|
exit $rc
|
||||||
|
|
||||||
FILES=(getBalances.json validators.json eventsStats.json alerts.json)
|
|
||||||
|
|
||||||
check_files_exist() {
|
|
||||||
rc=0
|
|
||||||
for f in "${FILES[@]}"; do
|
|
||||||
command="test -f responses/$f"
|
|
||||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "$command") || rc=1
|
|
||||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "$command") || rc=1
|
|
||||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "$command") || rc=1
|
|
||||||
done
|
|
||||||
return $rc
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
##### Initialization #####
|
|
||||||
|
|
||||||
../e2e-commons/up.sh deploy oracle monitor
|
|
||||||
|
|
||||||
|
|
||||||
##### Initial checks #####
|
|
||||||
|
|
||||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "yarn check-all"
|
|
||||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "yarn check-all"
|
|
||||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "yarn check-all"
|
|
||||||
check_files_exist
|
|
||||||
|
|
||||||
|
|
||||||
##### Test cases #####
|
|
||||||
|
|
||||||
./native-to-erc.sh
|
|
||||||
|
|
||||||
./erc-to-erc.sh
|
|
||||||
|
|
||||||
./erc-to-native.sh
|
|
||||||
|
|
||||||
|
|
||||||
##### Cleanup #####
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
99
monitor-e2e/test/common.js
Normal file
99
monitor-e2e/test/common.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const axios = require('axios')
|
||||||
|
const { nativeToErcBridge, ercToErcBridge, ercToNativeBridge, validator } = require('../../e2e-commons/constants.json')
|
||||||
|
|
||||||
|
const types = [
|
||||||
|
{ description: 'NATIVE TO ERC', baseUrl: nativeToErcBridge.monitor },
|
||||||
|
{ description: 'ERC TO ERC', baseUrl: ercToErcBridge.monitor },
|
||||||
|
{ description: 'ERC TO NATIVE', baseUrl: ercToNativeBridge.monitor }
|
||||||
|
]
|
||||||
|
|
||||||
|
types.forEach(type => {
|
||||||
|
describe(type.description, () => {
|
||||||
|
describe('balances', async () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${type.baseUrl}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('home', async () => {
|
||||||
|
it('should contain deposits:', () => assert(data.home.deposits === 0))
|
||||||
|
it('should contain withdrawals', () => assert(data.home.withdrawals === 0))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('foreign', async () => {
|
||||||
|
it('should contain deposits:', () => assert(data.foreign.deposits === 0))
|
||||||
|
it('should contain withdrawals', () => assert(data.foreign.withdrawals === 0))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('general', async () => {
|
||||||
|
it('should contain balanceDiff', () => assert(data.balanceDiff === 0))
|
||||||
|
it('should contain depositsDiff', () => assert(data.depositsDiff === 0))
|
||||||
|
it('should contain withdrawalDiff', () => assert(data.withdrawalDiff === 0))
|
||||||
|
it('should contain timeDiff', () => assert(data.timeDiff >= 0))
|
||||||
|
it('should contain lastChecked', () => assert(data.lastChecked >= 0))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('validators', async () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${type.baseUrl}/validators`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('home', () => {
|
||||||
|
assert(typeof data.home.validators === 'object')
|
||||||
|
assert(data.home.validators[validator.address].balance > 0)
|
||||||
|
assert(data.home.validators[validator.address].leftTx > 0)
|
||||||
|
assert(data.home.validators[validator.address].gasPrice > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('foreign', () => {
|
||||||
|
assert(typeof data.foreign.validators === 'object')
|
||||||
|
assert(data.foreign.validators[validator.address].balance > 0)
|
||||||
|
assert(data.foreign.validators[validator.address].leftTx > 0)
|
||||||
|
assert(data.foreign.validators[validator.address].gasPrice > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('requiredSignaturesMatch', () => assert(data.requiredSignaturesMatch, 1))
|
||||||
|
it('validatorsMatch', () => assert(data.validatorsMatch))
|
||||||
|
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||||
|
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||||
|
it('homeOk', () => assert(data.homeOk))
|
||||||
|
it('foreignOk', () => assert(data.foreignOk))
|
||||||
|
it('ok', () => assert(data.ok))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('eventsStats', async () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${type.baseUrl}/eventsStats`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ok', () => assert(data.ok))
|
||||||
|
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||||
|
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||||
|
it('onlyInHomeDeposits', () => assert(typeof data.onlyInHomeDeposits === 'object'))
|
||||||
|
it('onlyInForeignDeposits', () => assert(typeof data.onlyInForeignDeposits === 'object'))
|
||||||
|
it('onlyInHomeWithdrawals', () => assert(typeof data.onlyInHomeWithdrawals === 'object'))
|
||||||
|
it('onlyInForeignWithdrawals', () => assert(typeof data.onlyInForeignWithdrawals === 'object'))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('alerts', async () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${type.baseUrl}/alerts`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ok', () => assert(data.ok))
|
||||||
|
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||||
|
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||||
|
it('executeSignatures', () => assert(typeof data.executeSignatures === 'object'))
|
||||||
|
it('executeAffirmations', () => assert(typeof data.executeAffirmations === 'object'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
43
monitor-e2e/test/ercToErc.js
Normal file
43
monitor-e2e/test/ercToErc.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const axios = require('axios')
|
||||||
|
const { ercToErcBridge, user, foreignRPC, validator } = require('../../e2e-commons/constants.json')
|
||||||
|
const { waitUntil, sendTokens, addValidator } = require('../utils')
|
||||||
|
|
||||||
|
const baseUrl = ercToErcBridge.monitor
|
||||||
|
|
||||||
|
describe('ERC TO ERC', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance', () => assert(parseInt(data.foreign.erc20Balance, 10) >= 0))
|
||||||
|
it('should contain totalSupply', () => assert(data.home.totalSupply === '0'))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ERC TO ERC with changing state of contracts', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
assert((await axios.get(`${baseUrl}`)).data.balanceDiff === 0)
|
||||||
|
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change balanceDiff', async () => {
|
||||||
|
await sendTokens(foreignRPC.URL, user, ercToErcBridge.foreignToken, ercToErcBridge.foreign)
|
||||||
|
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
return data.balanceDiff !== 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change validatorsMatch', async () => {
|
||||||
|
await addValidator(foreignRPC.URL, validator, ercToErcBridge.foreign)
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}/validators`))
|
||||||
|
return data.validatorsMatch === false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
43
monitor-e2e/test/ercToNative.js
Normal file
43
monitor-e2e/test/ercToNative.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const axios = require('axios')
|
||||||
|
const { ercToNativeBridge, user, foreignRPC, validator } = require('../../e2e-commons/constants.json')
|
||||||
|
const { waitUntil, sendTokens, addValidator } = require('../utils')
|
||||||
|
|
||||||
|
const baseUrl = ercToNativeBridge.monitor
|
||||||
|
|
||||||
|
describe('ERC TO NATIVE', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance', () => assert(parseInt(data.foreign.erc20Balance, 10) >= 0))
|
||||||
|
it('should contain totalSupply', () => assert(data.home.totalSupply === '0'))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ERC TO NATIVE with changing state of contracts', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
assert((await axios.get(`${baseUrl}`)).data.balanceDiff === 0)
|
||||||
|
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change balanceDiff', async () => {
|
||||||
|
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.foreignToken, ercToNativeBridge.foreign)
|
||||||
|
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
return data.balanceDiff !== 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change validatorsMatch', async () => {
|
||||||
|
await addValidator(foreignRPC.URL, validator, ercToNativeBridge.foreign)
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}/validators`))
|
||||||
|
return data.validatorsMatch === false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
43
monitor-e2e/test/nativeToErc.js
Normal file
43
monitor-e2e/test/nativeToErc.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const axios = require('axios')
|
||||||
|
const { nativeToErcBridge, user, homeRPC, foreignRPC, validator } = require('../../e2e-commons/constants.json')
|
||||||
|
const { waitUntil, sendEther, addValidator } = require('../utils')
|
||||||
|
|
||||||
|
const baseUrl = nativeToErcBridge.monitor
|
||||||
|
|
||||||
|
describe('NATIVE TO ERC', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance', () => assert(parseInt(data.home.balance, 10) >= 0))
|
||||||
|
it('should contain totalSupply', () => assert(data.foreign.totalSupply === '0'))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('NATIVE TO ERC with changing state of contracts', () => {
|
||||||
|
let data
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
assert((await axios.get(`${baseUrl}`)).data.balanceDiff === 0)
|
||||||
|
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change balanceDiff', async () => {
|
||||||
|
await sendEther(homeRPC.URL, user, nativeToErcBridge.home)
|
||||||
|
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}`))
|
||||||
|
return data.balanceDiff !== 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change validatorsMatch', async () => {
|
||||||
|
await addValidator(foreignRPC.URL, validator, nativeToErcBridge.foreign)
|
||||||
|
await waitUntil(async () => {
|
||||||
|
;({ data } = await axios.get(`${baseUrl}/validators`))
|
||||||
|
return data.validatorsMatch === false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
57
monitor-e2e/utils.js
Normal file
57
monitor-e2e/utils.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const Web3 = require('web3')
|
||||||
|
const { ERC677_BRIDGE_TOKEN_ABI, BRIDGE_VALIDATORS_ABI, FOREIGN_NATIVE_TO_ERC_ABI } = require('../commons')
|
||||||
|
|
||||||
|
const waitUntil = async (predicate, step = 100, timeout = 10000) => {
|
||||||
|
const stopTime = Date.now() + timeout
|
||||||
|
while (Date.now() <= stopTime) {
|
||||||
|
const result = await predicate()
|
||||||
|
if (result) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, step)) // sleep
|
||||||
|
}
|
||||||
|
throw new Error(`waitUntil timed out after ${timeout} ms`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendEther = async (rpcUrl, account, to) => {
|
||||||
|
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
|
||||||
|
web3.eth.accounts.wallet.add(account.privateKey)
|
||||||
|
|
||||||
|
await web3.eth.sendTransaction({
|
||||||
|
from: account.address,
|
||||||
|
to,
|
||||||
|
gasPrice: '1',
|
||||||
|
gas: '50000',
|
||||||
|
value: '1000000000000000000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendTokens = async (rpcUrl, account, tokenAddress, recipientAddress) => {
|
||||||
|
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
|
||||||
|
web3.eth.accounts.wallet.add(account.privateKey)
|
||||||
|
const erc20Token = new web3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, tokenAddress)
|
||||||
|
|
||||||
|
await erc20Token.methods.transfer(recipientAddress, web3.utils.toWei('0.01')).send({
|
||||||
|
from: account.address,
|
||||||
|
gas: '1000000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addValidator = async (rpcUrl, account, bridgeAddress) => {
|
||||||
|
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
|
||||||
|
web3.eth.accounts.wallet.add(account.privateKey)
|
||||||
|
const bridgeContract = new web3.eth.Contract(FOREIGN_NATIVE_TO_ERC_ABI, bridgeAddress)
|
||||||
|
const foreignValidatorsAddress = await bridgeContract.methods.validatorContract().call()
|
||||||
|
const foreignBridgeValidators = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress)
|
||||||
|
await foreignBridgeValidators.methods.addValidator('0xE71FBa5db00172bb0C93d649362B006300000935').send({
|
||||||
|
from: account.address,
|
||||||
|
gas: '5000000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
waitUntil,
|
||||||
|
sendEther,
|
||||||
|
sendTokens,
|
||||||
|
addValidator
|
||||||
|
}
|
27
monitor-e2e/wait-for-monitor.sh
Executable file
27
monitor-e2e/wait-for-monitor.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
FILES=(getBalances.json validators.json eventsStats.json alerts.json)
|
||||||
|
|
||||||
|
check_files_exist() {
|
||||||
|
rc=0
|
||||||
|
for f in "${FILES[@]}"; do
|
||||||
|
command="test -f responses/$f"
|
||||||
|
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "$command") || rc=1
|
||||||
|
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "$command") || rc=1
|
||||||
|
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "$command") || rc=1
|
||||||
|
done
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
i=0
|
||||||
|
until check_files_exist
|
||||||
|
do
|
||||||
|
((i++))
|
||||||
|
if [ "$i" -gt 30 ]
|
||||||
|
then
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Waiting for monitor to start..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Monitor started"
|
@ -29,13 +29,14 @@
|
|||||||
"ui",
|
"ui",
|
||||||
"ui-e2e",
|
"ui-e2e",
|
||||||
"monitor",
|
"monitor",
|
||||||
|
"monitor-e2e",
|
||||||
"contracts"
|
"contracts"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"initialize": "yarn clean && git submodule update --init && yarn install --unsafe-perm --frozen-lockfile && yarn install:deploy && yarn compile:contracts",
|
"initialize": "yarn clean && git submodule update --init && yarn install --unsafe-perm --frozen-lockfile && yarn install:deploy && yarn compile:contracts",
|
||||||
"build": "yarn workspace ui run build",
|
"build": "yarn workspace ui run build",
|
||||||
"lint": "yarn wsrun --exclude token-bridge-contracts lint",
|
"lint": "yarn wsrun --exclude token-bridge-contracts lint",
|
||||||
"test": "yarn wsrun --exclude monitor --exclude oracle-e2e --exclude ui-e2e test",
|
"test": "yarn wsrun --exclude monitor --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",
|
||||||
"oracle-e2e": "./oracle-e2e/run-tests.sh",
|
"oracle-e2e": "./oracle-e2e/run-tests.sh",
|
||||||
"ui-e2e": "./ui-e2e/run-tests.sh",
|
"ui-e2e": "./ui-e2e/run-tests.sh",
|
||||||
"clean": "rm -rf ./node_modules ./**/node_modules ./**/**/node_modules ./**/build",
|
"clean": "rm -rf ./node_modules ./**/node_modules ./**/**/node_modules ./**/build",
|
||||||
|
55
yarn.lock
55
yarn.lock
@ -2783,6 +2783,14 @@ aws4@^1.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||||
|
|
||||||
|
axios@^0.19.0:
|
||||||
|
version "0.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
|
||||||
|
integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "1.5.10"
|
||||||
|
is-buffer "^2.0.2"
|
||||||
|
|
||||||
axobject-query@^2.0.2:
|
axobject-query@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
|
||||||
@ -5441,7 +5449,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0, debug@=3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
@ -5469,7 +5477,7 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
debuglog@*, debuglog@^1.0.1:
|
debuglog@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||||
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
|
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
|
||||||
@ -7648,6 +7656,13 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
readable-stream "^2.3.6"
|
||||||
|
|
||||||
|
follow-redirects@1.5.10:
|
||||||
|
version "1.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||||
|
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||||
|
dependencies:
|
||||||
|
debug "=3.1.0"
|
||||||
|
|
||||||
follow-redirects@^1.0.0:
|
follow-redirects@^1.0.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
|
||||||
@ -8989,7 +9004,7 @@ import-local@^2.0.0:
|
|||||||
pkg-dir "^3.0.0"
|
pkg-dir "^3.0.0"
|
||||||
resolve-cwd "^2.0.0"
|
resolve-cwd "^2.0.0"
|
||||||
|
|
||||||
imurmurhash@*, imurmurhash@^0.1.4:
|
imurmurhash@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||||
@ -9241,7 +9256,7 @@ is-buffer@^1.0.2, is-buffer@^1.1.5:
|
|||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
is-buffer@^2.0.0:
|
is-buffer@^2.0.0, is-buffer@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
||||||
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
|
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
|
||||||
@ -11064,11 +11079,6 @@ lockfile@^1.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
lodash._baseindexof@*:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
|
|
||||||
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
|
|
||||||
|
|
||||||
lodash._baseuniq@~4.6.0:
|
lodash._baseuniq@~4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
|
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
|
||||||
@ -11077,33 +11087,11 @@ lodash._baseuniq@~4.6.0:
|
|||||||
lodash._createset "~4.0.0"
|
lodash._createset "~4.0.0"
|
||||||
lodash._root "~3.0.0"
|
lodash._root "~3.0.0"
|
||||||
|
|
||||||
lodash._bindcallback@*:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
|
|
||||||
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
|
|
||||||
|
|
||||||
lodash._cacheindexof@*:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
|
|
||||||
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
|
|
||||||
|
|
||||||
lodash._createcache@*:
|
|
||||||
version "3.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
|
|
||||||
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
|
|
||||||
dependencies:
|
|
||||||
lodash._getnative "^3.0.0"
|
|
||||||
|
|
||||||
lodash._createset@~4.0.0:
|
lodash._createset@~4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
|
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
|
||||||
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
|
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
|
||||||
|
|
||||||
lodash._getnative@*, lodash._getnative@^3.0.0:
|
|
||||||
version "3.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
|
||||||
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
|
|
||||||
|
|
||||||
lodash._reinterpolate@~3.0.0:
|
lodash._reinterpolate@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
@ -11194,11 +11182,6 @@ lodash.pick@^4.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||||
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
|
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
|
||||||
|
|
||||||
lodash.restparam@*:
|
|
||||||
version "3.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
|
|
||||||
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
|
|
||||||
|
|
||||||
lodash.sample@^4.2.1:
|
lodash.sample@^4.2.1:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d"
|
resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d"
|
||||||
|
Loading…
Reference in New Issue
Block a user