diff --git a/es/ae/contract.js b/es/ae/contract.js index 9911f56d19..f8dc45b51d 100644 --- a/es/ae/contract.js +++ b/es/ae/contract.js @@ -36,6 +36,18 @@ import ContractACI from '../contract/aci' import BigNumber from 'bignumber.js' import NodePool from '../node-pool' +function sendAndProcess (tx, options) { + return async function (onSuccess, onError) { + // Send transaction and get transaction info + const { hash, rawTx } = await this.send(tx, options) + const result = await this.getTxInfo(hash) + + return result.returnType === 'ok' + ? onSuccess({ hash, rawTx, result }) + : typeof onError === 'function' ? onError(result) : this.handleCallError(result) + } +} + /** * Handle contract call error * @function @@ -99,11 +111,11 @@ async function contractDecodeData (source, fn, callValue, callResult, options) { * @param {String} source Contract source code * @param {String} address Contract address * @param {String} name Name of function to call - * @param {Array} args Argument's for call function + * @param {Array|String} args Argument's or callData for call/deploy transaction * @param {Object} [options={}] Options * @param {String} [options.top] Block hash on which you want to call contract - * @param bytecode - * @param {String} options [options.options] Transaction options (fee, ttl, gas, amount, deposit) + * @param {String} [options.bytecode] Block hash on which you want to call contract + * @param {Object} options [options.options] Transaction options (fee, ttl, gas, amount, deposit) * @param {Object} filesystem [options.options.filesystem] Contract external namespaces map * @return {Promise} Result object * @example @@ -120,7 +132,7 @@ async function contractCallStatic (source, address, name, args = [], { top, opti : await this.address().catch(e => opt.dryRunAccount.pub) // Prepare call-data - const callData = await this.contractEncodeCall(source, name, args, opt) + const callData = Array.isArray(args) ? await this.contractEncodeCall(source, name, args, opt) : args // Get block hash by height if (top && !isNaN(top)) { @@ -151,15 +163,16 @@ async function contractCallStatic (source, address, name, args = [], { top, opti async function dryRunContractTx (tx, callerId, source, name, opt = {}) { const { top } = opt - // Dry-run + // Resolve Account for Dry-run const dryRunAmount = BigNumber(opt.dryRunAccount.amount).gt(BigNumber(opt.amount || 0)) ? opt.dryRunAccount.amount : opt.amount const dryRunAccount = { amount: dryRunAmount, pubKey: callerId } + // Dry-run const [{ result: status, callObj, reason }] = (await this.txDryRun([tx], [dryRunAccount], top)).results - // check response + // Process response if (status !== 'ok') throw new Error('Dry run error, ' + reason) const { returnType, returnValue } = callObj if (returnType !== 'ok') { @@ -179,7 +192,7 @@ async function dryRunContractTx (tx, callerId, source, name, opt = {}) { * @param {String} source Contract source code * @param {String} address Contract address * @param {String} name Name of function to call - * @param {Array} args Argument's for call function + * @param {Array|String} args Argument's or callData for call function * @param {Object} [options={}] Transaction options (fee, ttl, gas, amount, deposit) * @param {Object} [options.filesystem={}] Contract external namespaces map* @return {Promise} Result object * @example @@ -196,22 +209,18 @@ async function contractCall (source, address, name, args = [], options = {}) { const tx = await this.contractCallTx(R.merge(opt, { callerId: await this.address(opt), contractId: address, - callData: await this.contractEncodeCall(source, name, args, opt) + callData: Array.isArray(args) ? await this.contractEncodeCall(source, name, args, opt) : args })) - const { hash, rawTx } = await this.send(tx, opt) - const result = await this.getTxInfo(hash) - - if (result.returnType === 'ok') { - return { + return sendAndProcess(tx, opt).call( + this, + ({ hash, rawTx, result }) => ({ hash, rawTx, result, decode: () => this.contractDecodeData(source, name, result.returnValue, result.returnType, opt) - } - } else { - await this.handleCallError(result) - } + }) + ) } /** @@ -221,7 +230,7 @@ async function contractCall (source, address, name, args = [], options = {}) { * @category async * @param {String} code Compiled contract * @param {String} source Contract source code - * @param {Array} initState Arguments of contract constructor(init) function + * @param {Array|String} initState Arguments of contract constructor(init) function. Can be array of arguments or callData string * @param {Object} [options={}] Transaction options (fee, ttl, gas, amount, deposit) * @param {Object} [options.filesystem={}] Contract external namespaces map* @return {Promise} Result object * @return {Promise} Result object @@ -239,7 +248,7 @@ async function contractCall (source, address, name, args = [], options = {}) { */ async function contractDeploy (code, source, initState = [], options = {}) { const opt = R.merge(this.Ae.defaults, options) - const callData = await this.contractEncodeCall(source, 'init', initState, opt) + const callData = Array.isArray(initState) ? await this.contractEncodeCall(source, 'init', initState, opt) : initState const ownerId = await this.address(opt) const { tx, contractId } = await this.contractCreateTx(R.merge(opt, { @@ -248,11 +257,9 @@ async function contractDeploy (code, source, initState = [], options = {}) { ownerId })) - const { hash, rawTx } = await this.send(tx, opt) - const result = await this.getTxInfo(hash) - - if (result.returnType === 'ok') { - return Object.freeze({ + return sendAndProcess(tx, opt).call( + this, + ({ hash, rawTx, result }) => Object.freeze({ result, owner: ownerId, transaction: hash, @@ -262,9 +269,7 @@ async function contractDeploy (code, source, initState = [], options = {}) { callStatic: async (name, args = [], options = {}) => this.contractCallStatic(source, contractId, name, args, { ...options, options: { onAccount: opt.onAccount, ...R.merge(opt, options.options) } }), createdAt: new Date() }) - } else { - await this.handleCallError(result) - } + ) } /** diff --git a/es/ae/oracle.js b/es/ae/oracle.js index 4a090bb92f..e843c30569 100644 --- a/es/ae/oracle.js +++ b/es/ae/oracle.js @@ -64,7 +64,7 @@ async function getOracleObject (oracleId) { * @param {String} oracleId Oracle public key * @param {Function} onQuery OnQuery callback * @param {Object} [options] Options object - * @param {Object} [options.interval] Poll interval(default: 5000) + * @param {Number} [options.interval] Poll interval(default: 5000) * @return {Function} stopPolling - Stop polling function */ async function pollForQueries (oracleId, onQuery, { interval = 5000 } = {}) { diff --git a/test/integration/contract.js b/test/integration/contract.js index 554ad957ca..95068353aa 100644 --- a/test/integration/contract.js +++ b/test/integration/contract.js @@ -134,6 +134,34 @@ describe('Contract', function () { return deployed.should.have.property('address') }) + it('Deploy/Call/Dry-run contract using callData', async () => { + const callArg = 1 + const { bytecode } = await contract.contractCompile(identityContract) + const callDataDeploy = await contract.contractEncodeCall(identityContract, 'init', []) + const callDataCall = await contract.contractEncodeCall(identityContract, 'main', [callArg.toString()]) + + const deployStatic = await contract.contractCallStatic(identityContract, null, 'init', callDataDeploy, { bytecode }) + deployStatic.result.should.have.property('gasUsed') + deployStatic.result.should.have.property('returnType') + + const deployed = await contract.contractDeploy(bytecode, identityContract, callDataDeploy) + deployed.result.should.have.property('gasUsed') + deployed.result.should.have.property('returnType') + deployed.should.have.property('address') + + const callStaticRes = await contract.contractCallStatic(identityContract, deployed.address, 'main', callDataCall) + callStaticRes.result.should.have.property('gasUsed') + callStaticRes.result.should.have.property('returnType') + const decodedCallStaticResult = await callStaticRes.decode() + decodedCallStaticResult.should.be.equal(callArg) + + const callRes = await contract.contractCall(identityContract, deployed.address, 'main', callDataCall) + callRes.result.should.have.property('gasUsed') + callRes.result.should.have.property('returnType') + const decodedCallResult = await callRes.decode() + decodedCallResult.should.be.equal(callArg) + }) + it('Deploy and call contract on specific account', async () => { const current = await contract.address() const onAccount = contract.addresses().find(acc => acc !== current)