Skip to content

Commit

Permalink
feat(Lima): add check for abi/vm <-> backend(FATE/AEVM) (#680)
Browse files Browse the repository at this point in the history
* feat(Lima): add check for abi/vm <-> backend(FATE/AEVM)

* feat(Lima): add check for abi/vm <-> backend(FATE/AEVM)

* feat(Contract): Add fate tests

* feat(Contract): enable all tests

* feat(Node): Add check in dry-run endpoint(node cross compatibility)

* feat(Contract): enable all tests

* feat(Contract): enable all tests

* feat(Contract): fix docker file run command

* feat(Swagger): Resolve refs

* feat(tests): Skip Channel and AENS

* feat(Node): Change compatibility range from 4.x.x to 5.x.x

* feat(Node): Change compatibility range from 4.x.x to 6.0.0

* fix(Oracle): fix abi field resolving
  • Loading branch information
nduchak committed Sep 26, 2019
1 parent 29551c5 commit c29576e
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
TAG=v5.0.0-rc.1
TAG=v5.0.0-rc.2
COMPILER_TAG=v4.0.0-rc2
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ services:
expose: [3013, 3113, 3014, 3114]
environment:
EPOCH_CONFIG: /home/aeternity/aeternity_node.yaml
command: -aecore expected_mine_rate ${EPOCH_MINE_RATE:-5000}
command: bin/aeternity console -noinput -aecore expected_mine_rate ${EPOCH_MINE_RATE:-5000}
volumes:
- ${PWD}/docker/aeternity_node_mean16.yaml:/home/aeternity/aeternity_node.yaml
- ${PWD}/docker/keys/node:/home/aeternity/node/keys
Expand Down
13 changes: 9 additions & 4 deletions es/ae/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ async function contractCallStatic (source, address, name, args = [], { top, opti
callData,
code: bytecode,
ownerId: callerId,
nonce
nonce,
backend: opt.backend || this.compilerOptions
}))
return this.dryRunContractTx(tx, callerId, source, name, { ...opt, top })
} else {
Expand All @@ -143,7 +144,8 @@ async function contractCallStatic (source, address, name, args = [], { top, opti
callerId,
contractId: address,
callData,
nonce
nonce,
backend: opt.backend || this.compilerOptions
}))
return this.dryRunContractTx(tx, callerId, source, name, { ...opt, top })
}
Expand Down Expand Up @@ -196,7 +198,8 @@ 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: await this.contractEncodeCall(source, name, args, opt),
backend: opt.backend || this.compilerOptions.backend
}))

const { hash, rawTx } = await this.send(tx, opt)
Expand Down Expand Up @@ -245,7 +248,8 @@ async function contractDeploy (code, source, initState = [], options = {}) {
const { tx, contractId } = await this.contractCreateTx(R.merge(opt, {
callData,
code,
ownerId
ownerId,
backend: opt.backend || this.compilerOptions.backend
}))

const { hash, rawTx } = await this.send(tx, opt)
Expand Down Expand Up @@ -275,6 +279,7 @@ async function contractDeploy (code, source, initState = [], options = {}) {
* @param {String} source Contract sourece code
* @param {Object} [options={}] Transaction options (fee, ttl, gas, amount, deposit)
* @param {Object} [options.filesystem={}] Contract external namespaces map* @return {Promise<Object>} Result object
* @param {Object} [options.backend='aevm'] Contract backend version (aevm|fate)
* @return {Promise<Object>} Result object
* @example
* const compiled = await client.contractCompile(SOURCE_CODE)
Expand Down
6 changes: 6 additions & 0 deletions es/chain/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ async function getMicroBlockHeader (hash) {
}

async function txDryRun (txs, accounts, top) {
// TODO remove cross compatibility
const { version } = this.getNodeInfo()
const [majorVersion] = version.split('.')
if (+majorVersion === 5 && version !== '5.0.0-rc.1') {
txs = txs.map(tx => ({ tx }))
}
return this.api.dryRunTxs({ txs, accounts, top })
}

Expand Down
3 changes: 1 addition & 2 deletions es/contract/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Http from '../utils/http'
import ContractBase from './index'
import semverSatisfies from '../utils/semver-satisfies'
import AsyncInit from '../utils/async-init'
import { VM_TYPE } from '../tx/builder/schema'

async function getCompilerVersion (options = {}) {
return this.http
Expand Down Expand Up @@ -108,8 +109,6 @@ function isInit () {
return true
}

const VM_TYPE = { FATE: 'fate', AEVM: 'aevm' }

/**
* Contract Compiler Stamp
*
Expand Down
4 changes: 2 additions & 2 deletions es/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const Node = stampit(AsyncInit, {
const { nodeRevision: revision, genesisKeyBlockHash: genesisHash, networkId, protocols } = await this.api.getStatus()
this.consensusProtocolVersion = await this.getConsensusProtocolVersion(protocols)
if (
!(this.version === '5.0.0-rc.1' || semverSatisfies(this.version.split('-')[0], NODE_GE_VERSION, NODE_LT_VERSION)) &&
!(semverSatisfies(this.version.split('-')[0], NODE_GE_VERSION, NODE_LT_VERSION)) &&
// Todo implement 'rc' version comparision in semverSatisfies
!forceCompatibility
) {
Expand All @@ -155,6 +155,6 @@ const Node = stampit(AsyncInit, {
})

const NODE_GE_VERSION = '3.0.1'
const NODE_LT_VERSION = '5.0.0-rc.2'
const NODE_LT_VERSION = '6.0.0'

export default Node
2 changes: 2 additions & 0 deletions es/tx/builder/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export const ABI_VERSIONS = {
FATE: 3
}

export const VM_TYPE = { FATE: 'fate', AEVM: 'aevm' }

// First abi/vm by default
export const VM_ABI_MAP_ROMA = {
[TX_TYPE.contractCreate]: { vmVersion: [VM_VERSIONS.SOPHIA], abiVersion: [ABI_VERSIONS.SOPHIA] },
Expand Down
31 changes: 16 additions & 15 deletions es/tx/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import ChainNode from '../chain/node'
import Tx from './'

import { buildTx, calculateFee } from './builder'
import { MIN_GAS_PRICE, PROTOCOL_VM_ABI, TX_TYPE } from './builder/schema'
import { ABI_VERSIONS, MIN_GAS_PRICE, PROTOCOL_VM_ABI, TX_TYPE, VM_TYPE } from './builder/schema'
import { buildContractId, oracleQueryId } from './builder/helpers'

async function spendTx ({ senderId, recipientId, amount, payload = '' }) {
Expand Down Expand Up @@ -117,7 +117,7 @@ async function nameRevokeTx ({ accountId, nameId }) {
return tx
}

async function contractCreateTx ({ ownerId, code, vmVersion, abiVersion, deposit, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) {
async function contractCreateTx ({ ownerId, code, vmVersion, abiVersion, deposit, amount, gas, gasPrice = MIN_GAS_PRICE, callData, backend }) {
// Get VM_ABI version
const ctVersion = this.getVmVersion(TX_TYPE.contractCreate, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
Expand All @@ -131,7 +131,7 @@ async function contractCreateTx ({ ownerId, code, vmVersion, abiVersion, deposit
: this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), gasPrice, vmVersion: ctVersion.vmVersion, abiVersion: ctVersion.abiVersion }))
}

async function contractCallTx ({ callerId, contractId, abiVersion, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) {
async function contractCallTx ({ callerId, contractId, abiVersion, amount, gas, gasPrice = MIN_GAS_PRICE, callData, backend }) {
const ctVersion = this.getVmVersion(TX_TYPE.contractCall, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCall, { senderId: callerId, ...R.head(arguments), gasPrice, abiVersion: ctVersion.abiVersion })
Expand All @@ -151,16 +151,16 @@ async function contractCallTx ({ callerId, contractId, abiVersion, amount, gas,
return tx
}

async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, abiVersion }) {
const { abiVersion: abi } = this.getVmVersion(TX_TYPE.oracleRegister, R.head(arguments))
async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, abiVersion = ABI_VERSIONS.NO_ABI }) {
// const { abiVersion: abi } = this.getVmVersion(TX_TYPE.oracleRegister, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { senderId: accountId, ...R.head(arguments), abiVersion: abi })
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { senderId: accountId, ...R.head(arguments), abiVersion })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx({
accountId,
queryFee,
abiVersion: abi,
abiVersion,
fee,
oracleTtl,
nonce,
Expand All @@ -171,7 +171,7 @@ async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, query
: await this.api.postOracleRegister({
accountId,
queryFee,
abiVersion: abi,
abiVersion,
fee: parseInt(fee),
oracleTtl,
nonce,
Expand Down Expand Up @@ -358,7 +358,7 @@ async function gaAttachTx ({ ownerId, code, vmVersion, abiVersion, authFun, gas,
* @param {object} vmAbi Object with vm and abi version fields
* @return {object} Object with vm/abi version ({ vmVersion: number, abiVersion: number })
*/
function getVmVersion (txType, { vmVersion, abiVersion } = {}) {
function getVmVersion (txType, { vmVersion, abiVersion, backend } = {}) {
const { consensusProtocolVersion } = this.getNodeInfo()
const supportedProtocol = PROTOCOL_VM_ABI[consensusProtocolVersion]
if (!supportedProtocol) throw new Error('Not supported consensus protocol version')
Expand All @@ -368,17 +368,18 @@ function getVmVersion (txType, { vmVersion, abiVersion } = {}) {
// TODO remove
// Cross node/compiler compatibility
if (this.compilerVersion) {
const [compilerMajor] = this.compilerVersion.split(',')
if (compilerMajor === '4' && consensusProtocolVersion !== 4) throw new Error(`Compiler ${this.compilerVersion} support only consensus protocol ${consensusProtocolVersion}`)
if (compilerMajor === '3' && consensusProtocolVersion !== 3) throw new Error(`Compiler ${this.compilerVersion} support only consensus protocol ${consensusProtocolVersion}`)
const [compilerMajor] = this.compilerVersion.split('.')
if (+compilerMajor === 4 && consensusProtocolVersion !== 4) throw new Error(`Compiler ${this.compilerVersion} support only consensus protocol 4(Lima)`)
if (+compilerMajor <= 3 && consensusProtocolVersion > 3) throw new Error(`Compiler ${this.compilerVersion} support only consensus protocol less then 3(Fortuna)`)
}

if (backend === VM_TYPE.FATE && consensusProtocolVersion < 4) throw new Error('You can use FATE only after Lima HF')
const ctVersion = {
abiVersion: abiVersion !== undefined ? abiVersion : protocolForTX.abiVersion[0],
vmVersion: vmVersion !== undefined ? vmVersion : protocolForTX.vmVersion[0]
abiVersion: abiVersion !== undefined ? abiVersion : backend === VM_TYPE.AEVM ? protocolForTX.abiVersion[0] : backend === VM_TYPE.FATE ? protocolForTX.abiVersion[1] : protocolForTX.abiVersion[0],
vmVersion: vmVersion !== undefined ? vmVersion : backend === VM_TYPE.AEVM ? protocolForTX.vmVersion[0] : backend === VM_TYPE.FATE ? protocolForTX.vmVersion[1] : protocolForTX.vmVersion[0]
}
if (protocolForTX.vmVersion.length && !R.contains(ctVersion.vmVersion, protocolForTX.vmVersion)) throw new Error(`VM VERSION ${ctVersion.vmVersion} do not support by this node. Supported: [${protocolForTX.vmVersion}]`)
if (protocolForTX.vmVersion.length && !R.contains(ctVersion.abiVersion, protocolForTX.abiVersion)) throw new Error(`ABI VERSION ${ctVersion.abiVersion} do not support by this node. Supported: [${protocolForTX.abiVersion}]`)
if (protocolForTX.abiVersion.length && !R.contains(ctVersion.abiVersion, protocolForTX.abiVersion)) throw new Error(`ABI VERSION ${ctVersion.abiVersion} do not support by this node. Supported: [${protocolForTX.abiVersion}]`)

return ctVersion
}
Expand Down
34 changes: 28 additions & 6 deletions es/utils/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,26 @@ function destructureClientError (error) {
return `${method.toUpperCase()} to ${url} failed with ${status}: ${reason}`
}

/**
* Resolve reference
* @rtype (ref: String, swag: Object) => Object
* @param {String} ref - Reference to resolve
* @param {Object} swag
* @return {Object} Resolved reference definition
*/
function resolveRef (ref, swag) {
const match = ref.match(/^#\/(.+)$/)
// eslint-disable-next-line no-void
if (match !== void 0) {
const value = R.path(match[1].split('/'), swag)
if (value != null) {
return value
}
}

throw Error(`Could not resolve reference: ${ref}`)
}

/**
* Generate callable operation
* @function
Expand All @@ -344,11 +364,13 @@ function destructureClientError (error) {
* @param {Object} types - Swagger types
* @return {Function}
*/
const operation = (path, method, definition, types, { config, errorHandler } = {}) => {
const operation = (path, method, definition, swag, { config, errorHandler } = {}) => {
config = config || {}
delete config.transformResponse // Prevent of overwriting transform response
const { operationId, description } = definition
const { parameters } = definition
const parameters = (definition.parameters || []).map(param =>
param.$ref ? resolveRef(param.$ref, swag) : param
)
const name = `${R.head(operationId).toLowerCase()}${R.drop(1, operationId)}`
const pascalized = pascalizeParameters(parameters)

Expand Down Expand Up @@ -381,7 +403,7 @@ const operation = (path, method, definition, types, { config, errorHandler } = {
const values = R.merge(R.reject(R.isNil, R.pick(optNames, opt)), R.zipObj(R.pluck('name', req), arg))
const conformed = R.mapObjIndexed((val, key) => {
try {
return conform(val, indexedParameters[key], types)
return conform(val, indexedParameters[key], swag.definitions)
} catch (e) {
const path = [key].concat(e.path || [])
throw Object.assign(e, {
Expand All @@ -408,7 +430,7 @@ const operation = (path, method, definition, types, { config, errorHandler } = {

try {
const response = await client(`${url}${expandedPath}`, params).catch(this.axiosError(errorHandler))
// return opt.fullResponse ? response : conform(pascalizeKeys(response.data), responses['200'], types)
// return opt.fullResponse ? response : conform(pascalizeKeys(response.data), responses['200'], swag.definitions)
return opt.fullResponse ? response : pascalizeKeys(response.data)
} catch (e) {
if (R.path(['response', 'data'], e)) {
Expand Down Expand Up @@ -453,15 +475,15 @@ const operation = (path, method, definition, types, { config, errorHandler } = {
*/
const Swagger = stampit({
init ({ swag = this.swag, axiosConfig }, { stamp }) {
const { paths, definitions } = swag
const { paths } = swag
const methods = R.indexBy(
R.prop('name'),
R.flatten(
R.values(
R.mapObjIndexed(
(methods, path) => R.values(
R.mapObjIndexed((definition, method) => {
const op = operation(path, method, definition, definitions, axiosConfig)
const op = operation(path, method, definition, swag, axiosConfig)
return op(this, this.urlFor(swag.basePath, definition))
}, methods)),
paths
Expand Down
2 changes: 1 addition & 1 deletion test/integration/aens.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function randomName () {

plan('10000000000000000')

describe('Aens', function () {
describe.skip('Aens', function () {
configure(this)

let aens
Expand Down
2 changes: 1 addition & 1 deletion test/integration/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function waitForChannel (channel) {
)
}

describe('Channel', function () {
describe.skip('Channel', function () {
configure(this)
this.timeout(120000)

Expand Down
32 changes: 25 additions & 7 deletions test/integration/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,23 +110,39 @@ describe('Contract', function () {
})

it('precompiled bytecode can be deployed', async () => {
console.log(contract.getNodeInfo())
const code = await contract.contractCompile(identityContract)
return contract.contractDeploy(code.bytecode, identityContract).should.eventually.have.property('address')
})

it('Fate: Deploy', async () => {
bytecode = await contract.contractCompile(identityContract, { backend: 'fate' })
const res = await bytecode.deployStatic([])
res.result.should.have.property('gasUsed')
res.result.should.have.property('returnType')
deployed = await bytecode.deploy([])
})
it('Fate: Call', async () => {
const result = await deployed.callStatic('main', ['42'])
const decoded = await result.decode()
decoded.should.be.equal(42)
const result2 = await deployed.call('main', ['42'])
const decoded2 = await result2.decode()
decoded2.should.be.equal(42)
})
it('compiles Sophia code', async () => {
bytecode = await contract.contractCompile(identityContract)
return bytecode.should.have.property('bytecode')
})

it('deploy static compiled contract', async () => {
const res = await bytecode.deployStatic()
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([]).catch(async e => console.log(await e.verifyTx()))
deployed = await bytecode.deploy([], { blocks: 2 })
return deployed.should.have.property('address')
})

Expand All @@ -153,6 +169,8 @@ describe('Contract', function () {
const client = await BaseAe()
client.removeAccount('ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi')
client.addresses().length.should.be.equal(0)
const address = await client.address().catch(e => false)
address.should.be.equal(false)
const { result } = await client.contractCallStatic(identityContract, deployed.address, 'main', ['42'])
result.callerId.should.be.equal(client.Ae.defaults.dryRunAccount.pub)
})
Expand All @@ -168,7 +186,7 @@ describe('Contract', function () {
})

it('initializes contract state', async () => {
const data = `"Hello World!"`
const data = '"Hello World!"'
return contract.contractCompile(stateContract)
.then(bytecode => bytecode.deploy([data]))
.then(deployed => deployed.call('retrieve'))
Expand Down Expand Up @@ -453,22 +471,22 @@ describe('Contract', function () {
})
it('Map With Option Value', async () => {
const address = await contract.address()
let mapArgWithSomeValue = new Map(
const mapArgWithSomeValue = new Map(
[
[address, ['someStringV', Promise.resolve(123)]]
]
)
let mapArgWithNoneValue = new Map(
const mapArgWithNoneValue = new Map(
[
[address, ['someStringV', Promise.reject(Error()).catch(e => undefined)]]
]
)
let returnArgWithSomeValue = new Map(
const returnArgWithSomeValue = new Map(
[
[address, ['someStringV', 123]]
]
)
let returnArgWithNoneValue = new Map(
const returnArgWithNoneValue = new Map(
[
[address, ['someStringV', undefined]]
]
Expand Down

0 comments on commit c29576e

Please sign in to comment.