diff --git a/es/ae/contract.js b/es/ae/contract.js index 8707bd2202..594c1a4ec1 100644 --- a/es/ae/contract.js +++ b/es/ae/contract.js @@ -98,6 +98,7 @@ async function contractDecodeData (source, fn, callValue, callResult, options) { * @param {Array} args Argument's for call function * @param {Object} options [options={}] Options * @param {String} top [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) * @return {Promise} Result object * @example @@ -107,25 +108,45 @@ async function contractDecodeData (source, fn, callValue, callResult, options) { * decode: (type) => Decode call result * } */ -async function contractCallStatic (source, address, name, args = [], { top, options = {} } = {}) { +async function contractCallStatic (source, address, name, args = [], { top, options = {}, bytecode } = {}) { const opt = R.merge(this.Ae.defaults, options) const callerId = opt.onAccount ? await this.address(opt) : await this.address().catch(e => opt.dryRunAccount.pub) + // Prepare call-data + const callData = await this.contractEncodeCall(source, name, args) + // Get block hash by height if (top && !isNaN(top)) { top = (await this.getKeyBlock(top)).hash } + // Prepare nonce + const nonce = top ? (await this.getAccount(callerId, { hash: top })).nonce + 1 : undefined - // Prepare `call` transaction - const tx = await this.contractCallTx(R.merge(opt, { - callerId, - contractId: address, - callData: await this.contractEncodeCall(source, name, args), - nonce: top ? (await this.getAccount(callerId, { hash: top })).nonce + 1 : undefined - })) + if (name === 'init') { + // Prepare deploy transaction + const { tx } = await this.contractCreateTx(R.merge(opt, { + callData, + code: bytecode, + ownerId: callerId, + nonce + })) + return this.dryRunContractTx(tx, callerId, source, name, { ...opt, top }) + } else { + // Prepare `call` transaction + const tx = await this.contractCallTx(R.merge(opt, { + callerId, + contractId: address, + callData, + nonce + })) + return this.dryRunContractTx(tx, callerId, source, name, { ...opt, top }) + } +} +async function dryRunContractTx (tx, callerId, source, name, opt = {}) { + const { top } = opt // Dry-run const dryRunAmount = BigNumber(opt.dryRunAccount.amount).gt(BigNumber(opt.amount || 0)) ? opt.dryRunAccount.amount : opt.amount const dryRunAccount = { @@ -142,7 +163,7 @@ async function contractCallStatic (source, address, name, args = [], { top, opti } return { result: callObj, - decode: () => this.contractDecodeData(source, name, returnValue, returnType, options) + decode: () => this.contractDecodeData(source, name, returnValue, returnType, opt) } } @@ -261,7 +282,8 @@ async function contractCompile (source, options = {}) { const bytecode = await this.compileContractAPI(source, options) return Object.freeze(Object.assign({ encodeCall: async (name, args) => this.contractEncodeCall(source, name, args), - deploy: async (init, options = {}) => this.contractDeploy(bytecode, source, init, options) + deploy: async (init, options = {}) => this.contractDeploy(bytecode, source, init, options), + deployStatic: async (init, options = {}) => this.contractCallStatic(source, null, 'init', init, { bytecode, top: options.top, options }) }, { bytecode })) } @@ -302,6 +324,7 @@ export const ContractAPI = Ae.compose(ContractBase, ContractACI, { contractCall, contractEncodeCall, contractDecodeData, + dryRunContractTx, handleCallError }, deepProps: { diff --git a/es/contract/aci/helpers.js b/es/contract/aci/helpers.js index c657b68d4f..794b2b63c1 100644 --- a/es/contract/aci/helpers.js +++ b/es/contract/aci/helpers.js @@ -25,13 +25,6 @@ export function getFunctionACI (aci, name) { * @return {Object} Contract instance methods */ export const buildContractMethods = (instance) => () => ({ - ...instance.aci ? { - init () { - const { arguments: aciArgs } = getFunctionACI(instance.aci, 'init') - const { opt, args } = parseArguments(aciArgs)(arguments) - return instance.deploy(args, opt) - } - } : {}, ...instance.aci ? instance .aci @@ -60,7 +53,28 @@ export const buildContractMethods = (instance) => () => ({ }), {} ) - : {} + : {}, + ...instance.aci ? { + init: Object.assign( + function () { + const { arguments: aciArgs } = getFunctionACI(instance.aci, 'init') + const { opt, args } = parseArguments(aciArgs)(arguments) + return instance.deploy(args, opt) + }, + { + get () { + const { arguments: aciArgs } = getFunctionACI(instance.aci, 'init') + const { opt, args } = parseArguments(aciArgs)(arguments) + return instance.deploy(args, { ...opt, callStatic: true }) + }, + send () { + const { arguments: aciArgs } = getFunctionACI(instance.aci, 'init') + const { opt, args } = parseArguments(aciArgs)(arguments) + return instance.deploy(args, { ...opt, callStatic: false }) + } + } + ) + } : {} }) const parseArguments = (aciArgs = []) => (args) => ({ diff --git a/es/contract/aci/index.js b/es/contract/aci/index.js index 59b52b9940..793c70da84 100644 --- a/es/contract/aci/index.js +++ b/es/contract/aci/index.js @@ -167,13 +167,22 @@ const call = ({ client, instance }) => async (fn, params = [], options = {}) => const deploy = ({ client, instance }) => async (init = [], options = {}) => { const opt = R.merge(instance.options, options) const fnACI = getFunctionACI(instance.aci, 'init') + const source = opt.source || instance.source if (!instance.compiled) await instance.compile() init = !opt.skipArgsConvert ? await prepareArgsForEncode(fnACI, init) : init - const { owner, transaction, address, createdAt, result, rawTx } = await client.contractDeploy(instance.compiled, opt.source || instance.source, init, opt) - instance.deployInfo = { owner, transaction, address, createdAt, result, rawTx } - return instance.deployInfo + if (opt.callStatic) { + return client.contractCallStatic(source, null, 'init', init, { + top: opt.top, + options: opt, + bytecode: instance.compiled + }) + } else { + const { owner, transaction, address, createdAt, result, rawTx } = await client.contractDeploy(instance.compiled, opt.source || instance.source, init, opt) + instance.deployInfo = { owner, transaction, address, createdAt, result, rawTx } + return instance.deployInfo + } } const compile = ({ client, instance }) => async () => { diff --git a/es/node-pool/index.js b/es/node-pool/index.js index 5fd2fdb15e..84f4d12573 100644 --- a/es/node-pool/index.js +++ b/es/node-pool/index.js @@ -19,7 +19,7 @@ import AsyncInit from '../utils/async-init' * @return {Object} NodePool instance */ export const NodePool = AsyncInit.compose({ - async init ({ nodes = [], url = this.url, internalUrl = this.internalUrl } = {}) { + async init ({ nodes = [], url = this.url, internalUrl = this.internalUrl, forceCompatibility = false } = {}) { this.pool = new Map() this.validateNodes(nodes) @@ -32,7 +32,7 @@ export const NodePool = AsyncInit.compose({ // DEPRECATED. TODO Remove deprecated param // Prevent BREAKING CHANGES. Support for init params `url`, `internalUrl` if (url) { - this.addNode('default', await Node({ url, internalUrl }), true) + this.addNode('default', await Node({ url, internalUrl, forceCompatibility }), true) } }, propertyDescriptors: { diff --git a/test/integration/contract.js b/test/integration/contract.js index 6c3bf8fe9a..2d5dd67faa 100644 --- a/test/integration/contract.js +++ b/test/integration/contract.js @@ -23,7 +23,6 @@ import * as R from 'ramda' const identityContract = ` contract Identity = - type state = () entrypoint main(x : int) = x ` const stateContract = ` @@ -110,8 +109,14 @@ describe('Contract', function () { return bytecode.should.have.property('bytecode') }) + it('deploy static compiled contract', async () => { + const res = await bytecode.deployStatic() + res.result.should.have.property('gasUsed') + res.result.should.have.property('returnType') + }) + it('deploys compiled contracts', async () => { - deployed = await bytecode.deploy() + deployed = await bytecode.deploy([]) return deployed.should.have.property('address') }) @@ -127,6 +132,13 @@ describe('Contract', function () { callStaticRes.result.callerId.should.be.equal(onAccount) }) + it('Call-Static deploy transaction', async () => { + const compiled = bytecode.bytecode + const res = await contract.contractCallStatic(identityContract, null, 'init', [], { bytecode: compiled }) + res.result.should.have.property('gasUsed') + res.result.should.have.property('returnType') + }) + it('Dry-run without accounts', async () => { const client = await BaseAe() client.removeAccount('ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi') @@ -213,6 +225,19 @@ describe('Contract', function () { const isCompiled = contractObject.compiled.length && contractObject.compiled.slice(0, 3) === 'cb_' isCompiled.should.be.equal(true) }) + it('Dry-run deploy fn', async () => { + const res = await contractObject.methods.init.get('123', 1, Promise.resolve('hahahaha')) + res.result.should.have.property('gasUsed') + res.result.should.have.property('returnType') + }) + it('Dry-run deploy fn on specific account', async () => { + const current = await contract.address() + const onAccount = contract.addresses().find(acc => acc !== current) + const { result } = await contractObject.methods.init.get('123', 1, Promise.resolve('hahahaha'), { onAccount }) + result.should.have.property('gasUsed') + result.should.have.property('returnType') + result.callerId.should.be.equal(onAccount) + }) it('Deploy contract before compile', async () => { contractObject.compiled = null