Skip to content

Commit

Permalink
feat(Contract): Add ability to pass arguments or callData for contrac…
Browse files Browse the repository at this point in the history
…t `deploy/call/callStatic` API (#768)
  • Loading branch information
nduchak committed Nov 7, 2019
1 parent 62b54b1 commit a828076
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 28 deletions.
59 changes: 32 additions & 27 deletions es/ae/contract.js
Expand Up @@ -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
Expand Down Expand Up @@ -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<Object>} Result object
* @example
Expand All @@ -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)) {
Expand Down Expand Up @@ -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') {
Expand All @@ -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<Object>} Result object
* @example
Expand All @@ -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)
}
})
)
}

/**
Expand All @@ -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<Object>} Result object
* @return {Promise<Object>} Result object
Expand All @@ -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, {
Expand All @@ -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,
Expand All @@ -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)
}
)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion es/ae/oracle.js
Expand Up @@ -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 } = {}) {
Expand Down
28 changes: 28 additions & 0 deletions test/integration/contract.js
Expand Up @@ -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)
Expand Down

0 comments on commit a828076

Please sign in to comment.