Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Contract): Add ability to pass args array or callData string for contract API #768

Merged
merged 2 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 32 additions & 27 deletions es/ae/contract.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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