Skip to content

Commit

Permalink
feat(ACI/Contract): Implement static-call for deploy transaction for …
Browse files Browse the repository at this point in the history
…ACI methods/Contract low lvl API (#630)

* fix(NodePool): add options to Node ins in deprecated way of init sdk client

* feat(ACI/Contract): Implement static-call for deploy transaction for ACI methods/Contract low lvl API. Add tests

* chore(linter): Fix linter error

* fix(Test): fix Contract tests
  • Loading branch information
nduchak authored Aug 27, 2019
1 parent c866e8c commit 5b7eeb4
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 25 deletions.
43 changes: 33 additions & 10 deletions es/ae/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object>} Result object
* @example
Expand All @@ -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 = {
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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 }))
}

Expand Down Expand Up @@ -302,6 +324,7 @@ export const ContractAPI = Ae.compose(ContractBase, ContractACI, {
contractCall,
contractEncodeCall,
contractDecodeData,
dryRunContractTx,
handleCallError
},
deepProps: {
Expand Down
30 changes: 22 additions & 8 deletions es/contract/aci/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) => ({
Expand Down
15 changes: 12 additions & 3 deletions es/contract/aci/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
4 changes: 2 additions & 2 deletions es/node-pool/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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: {
Expand Down
29 changes: 27 additions & 2 deletions test/integration/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import * as R from 'ramda'

const identityContract = `
contract Identity =
type state = ()
entrypoint main(x : int) = x
`
const stateContract = `
Expand Down Expand Up @@ -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')
})

Expand All @@ -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')
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5b7eeb4

Please sign in to comment.