UNPKG

caver-js

Version:

caver-js is a JavaScript API library that allows developers to interact with a Klaytn node

602 lines (515 loc) 21.5 kB
/* Copyright 2018 The caver-js Authors This file is part of the caver-js library. The caver-js library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The caver-js library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the caver-js. If not, see <http://www.gnu.org/licenses/>. */ // Instruction // $ git submodule init && git submodule update // $ cp test/klaytn-integration-tests/env.template.json test/klaytn-integration-tests/env.json // // $ mocha test/intTest.js // or you can use script in the package.json like below // $ npm run intTest // // To execute a specific test, // $ mocha --grep INT-LEGACY/012 test/intTest.js // In `caver-js/package.json`, the test scripts are defined. (ex: `npm run intTxTest`, ...) const _ = require('lodash') const RLP = require('eth-lib/lib/rlp') const Bytes = require('eth-lib/lib/bytes') const elliptic = require('elliptic') const secp256k1 = new elliptic.ec('secp256k1') const path = require('path') const fs = require('fs') const util = require('util') const exec = util.promisify(require('child_process').exec) const Caver = require('../index') const testEnv = require('./klaytn-integration-tests/env.json') const conf = require('./klaytn-integration-tests/conf.json') const { expect, assert } = require('./extendedChai') const { overwriteSignature, getSenderTxHash } = require('../packages/caver-klay/caver-klay-accounts/src/makeRawTransaction') const deployedContractAddr = {} let caver function makePublicKey(x, y) { const keyPair = secp256k1.keyPair({ pub: { x: x.replace('0x', ''), y: y.replace('0x', ''), }, pubEnc: 'hex', }) return `0x${keyPair.getPublic(false, 'hex').slice(2)}` } function isJsonable(v) { try { return JSON.stringify(v) === JSON.stringify(JSON.parse(JSON.stringify(v))) } catch (e) { /* console.error("not a dict",e); */ return false } } function isDict(v) { return !!v && typeof v === 'object' && v !== null && !(v instanceof Array) && !(v instanceof Date) && isJsonable(v) } function replaceWithEnv(data) { function replaceElemWithEnv(elem) { if (deployedContractAddr[elem] !== undefined) return deployedContractAddr[elem].address.toLowerCase() if (elem === undefined) return elem if (elem === 'env.sender') return testEnv.sender.address if (typeof elem !== 'string') return elem if (elem === 'blockHash') { if (testEnv.blockHash === '' && testEnv.receipt !== {}) { return testEnv.receipt.blockHash } return testEnv.blockHash } if (elem === 'filterId') return testEnv.filterId if (elem === 'senderTxHash') return testEnv.receipt.senderTxHash if (elem === 'transactionIndex') return caver.utils.toHex(testEnv.receipt.transactionIndex) if (elem === 'blockNumber') return caver.utils.toHex(testEnv.receipt.blockNumber) if (elem === 'transactionHash') return testEnv.receipt.transactionHash if (elem === 'rawTransaction') return testEnv.rawTransaction const getRandomAccount = id => { if (testEnv.random === undefined) testEnv.random = {} if (testEnv.random[id] === undefined) { const keyring = caver.wallet.keyring.generate() caver.wallet.add(keyring) testEnv.random[id] = keyring } return testEnv.random[id] } if (elem.endsWith('.privateKey')) { return getRandomAccount(elem.replace('.privateKey', '')).key.privateKey } if (elem.startsWith('random')) { return getRandomAccount(elem).address.toLowerCase() } if (elem.startsWith('contract')) { return testEnv.contracts[elem] } if (elem.startsWith('env.accounts')) { const k = elem.replace('env.accounts.', '') return testEnv.accounts[k].address.toLowerCase() } return elem } if (isDict(data)) { Object.keys(data).forEach(k => { data[k] = replaceWithEnv(data[k]) }) return data } if (Array.isArray(data)) { data.forEach(function(p, idx) { data[idx] = replaceElemWithEnv(replaceWithEnv(p)) }) return data } return replaceElemWithEnv(data) } function resetRandomAccounts() { testEnv.random = {} testEnv.contracts = {} testEnv.blockHash = '' testEnv.receipt = {} testEnv.rawTransaction = '' testEnv.filterId = '' } function makeMultisigKey(multisig) { const ret = { threshold: multisig.threshold, keys: [], } if (Array.isArray(multisig.keys)) { multisig.keys.forEach(function(k) { ret.keys.push({ weight: k.weight, publicKey: makePublicKey(k.key.x, k.key.y), }) }) } return ret } function makeKey(key) { let ret = {} switch (key.keyType) { case 0: // Nil key. DO NOTHING. break case 1: ret.legacyKey = true break case 2: ret.publicKey = makePublicKey(key.key.x, key.key.y) break case 3: ret.failKey = true break case 4: ret = { multisig: makeMultisigKey(key.key) } break case 5: if (key.key.length > 0) { ret = Object.assign(ret, { roleTransactionKey: makeKey(key.key[0]) }) } if (key.key.length > 1) { ret = Object.assign(ret, { roleAccountUpdateKey: makeKey(key.key[1]) }) } if (key.key.length > 2) { ret = Object.assign(ret, { roleFeePayerKey: makeKey(key.key[2]) }) } break default: throw new Error(`not implemented keytype ${key.keyType}`) } return ret } function processAccountKey(tx) { if (tx.accountKey === undefined) return tx const k = makeKey(tx.accountKey) tx = Object.assign(tx, k) return tx } async function processCall(t) { const contractAddr = deployedContractAddr[t.call.to].address const abi = deployedContractAddr[t.call.to].abi const contract = new caver.contract(abi, contractAddr) const from = replaceWithEnv(t.call.from) const params = replaceWithEnv(t.call.params) let value = await contract.methods[t.call.method](...params).call({ from }) if (_.isString(value)) value = value.toLowerCase() expect(value).to.equal(replaceWithEnv(t.expected.returns)) } async function processSend(t) { const contractAddr = deployedContractAddr[t.send.to].address const abi = deployedContractAddr[t.send.to].abi const contract = new caver.contract(abi, contractAddr) const from = replaceWithEnv(t.send.from) let params = replaceWithEnv(t.send.params) let actual = true let receipt try { receipt = await contract.methods[t.send.method](...params).send({ from, gas: t.send.gas }) } catch (err) { // console.log(err) const receiptRaw = String(err).slice(String(err).indexOf('\n')) try { receipt = JSON.parse(receiptRaw) } catch (error) { actual = false receipt = undefined } } expect(actual).to.equal(t.expected.status) // console.log(receipt) if (t.expected.receipt) { expect(receipt).to.not.undefined if (t.expected.receipt.status) expect(receipt.status).to.equal(t.expected.receipt.status ? '0x1' : '0x0') if (t.expected.receipt.txError) expect(receipt.txError).to.equal(t.expected.receipt.txError) } if (t.expected.rawEvents) { Object.keys(t.expected.rawEvents).forEach(idx => { expect(receipt.events[idx].raw.topics).to.deep.equal(t.expected.rawEvents[idx].topics) if (t.expected.rawEvents[idx].data.encodeParameters) { params = replaceWithEnv(t.expected.rawEvents[idx].data.encodeParameters) const data = caver.abi.encodeParameters(...params) expect(receipt.events[idx].raw.data).to.equal(data) } }) } if (t.expected.events) { Object.keys(t.expected.events).forEach(e => { expect(receipt.events[e]).to.not.undefined if (t.expected.events[e]) { Object.keys(t.expected.events[e]).forEach(k => { let expected = t.expected.events[e][k] expected = replaceWithEnv(expected) if (typeof receipt.events[e].returnValues[k] === 'string') { expect(String(receipt.events[e].returnValues[k]).toLowerCase()).to.equal(expected.toLowerCase()) } else { expect(receipt.events[e].returnValues[k]).to.equal(expected) } }) } }) } } const getSignedRawTransaction = async t => { if (t.rawTx !== undefined) { return t.rawTx } let feePayer = t.tx.feePayer delete t.tx.feePayer t.tx.from = replaceWithEnv(t.tx.from) if (t.tx.from === undefined) { t.tx.from = testEnv.sender.address } t.tx.to = replaceWithEnv(t.tx.to) t.tx.privateKey = replaceWithEnv(t.tx.privateKey) let privateKey = testEnv.sender.privateKey if (t.tx.privateKey !== undefined) { privateKey = t.tx.privateKey } t.tx = processAccountKey(t.tx) const signedRawTx = await caver.klay.accounts.signTransaction(t.tx, privateKey) let rawTransaction = signedRawTx.rawTransaction if (t.tx.v !== undefined || t.tx.r !== undefined || t.tx.s !== undefined) { const txObj = caver.klay.decodeTransaction(rawTransaction, t.tx.type) if (t.tx.v !== undefined) { txObj.v = t.tx.v } if (t.tx.r !== undefined) { txObj.r = t.tx.r } if (t.tx.s !== undefined) { txObj.s = t.tx.s } rawTransaction = overwriteSignature(rawTransaction, txObj, [txObj.v, txObj.r, txObj.s]) } // if transaction is fee delegated, sign with feePayer if (t.tx.type.includes('FEE_DELEGATED_')) { feePayer = replaceWithEnv(feePayer) if (feePayer === undefined) { feePayer = testEnv.feePayer.address } t.tx.feePayerPrivateKey = replaceWithEnv(t.tx.feePayerPrivateKey) let feePayerPrivateKey = testEnv.feePayer.privateKey if (t.tx.feePayerPrivateKey !== undefined) { feePayerPrivateKey = t.tx.feePayerPrivateKey } const feePayerTx = { feePayer, senderRawTransaction: rawTransaction, } const signedFeePayerRawTx = await caver.klay.accounts.signTransaction(feePayerTx, feePayerPrivateKey) rawTransaction = signedFeePayerRawTx.rawTransaction // If v or r or s value is set in test case, overwrite with that. if (t.tx.payerV !== undefined || t.tx.payerR !== undefined || t.tx.payerS !== undefined) { const txObj = caver.klay.decodeTransaction(rawTransaction, t.tx.type) if (t.tx.payerV !== undefined) { txObj.payerV = t.tx.payerV } if (t.tx.payerR !== undefined) { txObj.payerR = t.tx.payerR } if (t.tx.payerS !== undefined) { txObj.payerS = t.tx.payerS } rawTransaction = overwriteSignature(rawTransaction, txObj, undefined, [txObj.payerV, txObj.payerR, txObj.payerS]) } } return rawTransaction } async function processTransaction(t) { const rawTransaction = await getSignedRawTransaction(t) let actual let receipt await caver.rpc.klay.sendRawTransaction(rawTransaction).then( r => { receipt = r // console.log(r) if (t.expected.receipt) { if (t.expected.receipt.checkContractAddress) { const addrHash = caver.utils.keccak256(RLP.encode([t.tx.from.toLowerCase(), Bytes.fromNat(t.tx.nonce)])) const address = caver.utils.toChecksumAddress(`0x${addrHash.slice(-40)}`) expect(r.contractAddress.toLowerCase()).to.equal(address.toLowerCase()) } if (t.expected.receipt.codeFormat) expect(r.codeFormat).to.equal(t.expected.receipt.codeFormat) if (t.expected.receipt.checkSenderTxHash) { expect(r.senderTxHash).to.equal(getSenderTxHash(rawTransaction)) } } actual = true }, err => { // console.log(err) const receiptRaw = String(err).slice(String(err).indexOf('\n')) actual = true try { receipt = JSON.parse(receiptRaw) } catch (error) { receipt = undefined actual = false } if (t.expected.errorString !== undefined) { expect(String(err)).to.include(t.expected.errorString) } } ) if (receipt !== undefined) { if (t.deployedAddress && receipt.contractAddress) { testEnv.contracts[t.deployedAddress] = receipt.contractAddress } if (t.expected.receipt && t.expected.receipt.status) { expect(receipt.status).to.equal(t.expected.receipt.status ? '0x1' : '0x0') } if (t.expected.receipt && t.expected.receipt.txError) { expect(receipt.txError).to.equal(t.expected.receipt.txError) } } expect(actual).to.equal(t.expected.status) } async function processApi(t) { if (t.api.pre !== undefined) { const rawTransaction = await getSignedRawTransaction(t.api.pre) testEnv.receipt = await caver.rpc.klay.sendRawTransaction(rawTransaction) } if (t.api.preSigned !== undefined) { testEnv.rawTransaction = await getSignedRawTransaction(t.api.preSigned) } await new Promise((resolve, reject) => { caver.rpc.klay._requestManager.send( { method: t.api.method, params: replaceWithEnv(t.api.params), }, (err, result) => { if (err) reject(err) else resolve(result) } ) }).then( result => { expect(result).not.to.null if ( t.api.method === 'klay_newFilter' || t.api.method === 'klay_newBlockFilter' || t.api.method === 'klay_newPendingTransactionFilter' ) { testEnv.filterId = result } if (t.api.method === 'klay_getBlockByNumber') testEnv.blockHash = result.hash if (t.expected) { if (t.expected.accType === 2) { expect(result.accType).to.be.deep.equal(t.expected.accType) } else { expect(result).to.be.deep.equal(t.expected) } } }, err => { if (t.errorString) { expect(String(err)).to.include(t.errorString) } else { expect(err).to.be.null } } ) } before(() => { caver = new Caver(testEnv.URL) // caver.klay.accounts.wallet.add(testEnv.sender.privateKey) const keyring = caver.wallet.keyring.createFromPrivateKey(testEnv.sender.privateKey) caver.wallet.add(keyring) if (testEnv.accounts) { Object.keys(testEnv.accounts).forEach(acc => { // caver.klay.accounts.wallet.add(testEnv.accounts[acc].privateKey) const testKeyring = caver.wallet.keyring.createFromPrivateKey(testEnv.accounts[acc].privateKey) caver.wallet.add(testKeyring) }) } }) async function checkSolidityVersion() { const { stdout } = await exec('solc --version') const regex = /Version: ([0-9]+.[0-9]+.[0-9]+)/ const found = stdout.match(regex) expect(found).to.not.null const version = found[1] expect(version).to.equal(conf.solidity) } describe('Integration tests', () => { const directoryPath = path.join(__dirname, 'klaytn-integration-tests') const intFiles = fs.readdirSync(directoryPath, { withFileTypes: true }) intFiles.forEach(function(intf) { if (intf.isDirectory() === false) return describe(`testing ${intf.name}`, () => { before(function(done) { // Check version of solidity compiler before INT-SOL integration test if (intf.name === 'INT-SOL') { checkSolidityVersion().then(done) } else { done() } }) const dir = path.join(__dirname, 'klaytn-integration-tests/', intf.name) const files = fs.readdirSync(dir) files.forEach(function(file) { if (file.endsWith('.json') === false) return const filename = path.join(dir, file) describe(`fileName: ${filename}`, () => { const tc = JSON.parse(fs.readFileSync(filename)) if (tc.skipJs) { console.log(`Skip ${tc.tcID} / ${tc.tcName}`) return } it('Resetting random accounts', () => { resetRandomAccounts() }) it('make abi and bin', async () => { for (const k in tc.deploy) { const { stdout } = await exec(`solc --abi --bin --allow-paths . ${path.join(dir, tc.deploy[k].file)}`) const regex = `\n======= .*${tc.deploy[k].file}:${k} =======\nBinary:\n(.*)\nContract JSON ABI\n(.*)` const found = stdout.match(regex) expect(found).to.not.null const bin = found[1] const abi = JSON.parse(found[2]) const fromAddress = testEnv.sender.address const gas = 100000000 const params = replaceWithEnv(tc.deploy[k].constructorParams) const contractInstance = new caver.contract(abi) const newContractInstance = await contractInstance .deploy({ data: `0x${bin}`, arguments: params, }) .send({ from: fromAddress, gas, value: 0, }) expect(newContractInstance).to.not.null deployedContractAddr[k] = { address: newContractInstance.options.address, abi } } }).timeout(10000) tc.test.forEach(function(t, idx) { it(`testName:${tc.tcName}[${idx}]`, async () => { try { if (t.api !== undefined) { await processApi(t) } else if (t.tx !== undefined || t.rawTx !== undefined) { await processTransaction(t) } else if (t.call !== undefined) { await processCall(t) } else if (t.send !== undefined) { await processSend(t) } } catch (err) { if (String(err).includes('AssertionError: expected')) throw err if (t.expected === undefined || t.expected.status === undefined || t.expected.status !== false) { console.log(err) } expect(t.expected.status).to.be.false if (t.expected) { if (t.expected.errorStringJs) expect(String(err)).to.include(t.expected.errorStringJs) else if (t.expected.errorString) expect(String(err)).to.include(t.expected.errorString) } else { console.log('catched err', err) assert(false) } } }).timeout(50000) }) }) }) }) }) })