Skip to content

Commit

Permalink
feat(Contract): Contract high level commands (#116)
Browse files Browse the repository at this point in the history
* fix(AENS): Change tld from `aet` to `chain`

* refactor(AENS): Fix full-claim test

* feat(Contract): Add --json to compile command

* chore(package): Update sdk version to 6.0.0

* feat(Contract): Add command for deploying contract

* chore(node): bump node version to 5.5.4. Resolve conflict

* chore(build): regenerate lock

* fix(Test): Fix contract test's

* fix(Test): regenerate lock
  • Loading branch information
nduchak committed Jun 9, 2020
1 parent fcb767b commit 8848a7b
Show file tree
Hide file tree
Showing 7 changed files with 1,012 additions and 1,413 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
NODE_TAG=v5.5.0
NODE_TAG=v5.5.4
COMPILER_TAG=v4.2.0
4 changes: 2 additions & 2 deletions bin/aecli-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ program
//
// Example: `aecli contract deploy ./myWalletFile --password tstpass ./contractSourceCodeFile --gas 2222222`
program
.command('deploy <wallet_path> <contract_path> [init...]')
.command('deploy <wallet_path> <contract_path> <callData>')
.option('--networkId [networkId]', 'Network id (default: ae_mainnet)')
.option('-W, --no-waitMined', 'Force waiting until transaction will be mined')
.option('-P, --password [password]', 'Wallet Password')
Expand All @@ -140,7 +140,7 @@ program
.option('-T, --ttl [ttl]', 'Validity of the spend transaction in number of blocks (default forever)', utils.constant.TX_TTL)
.option('-N, --nonce [nonce]', 'Override the nonce that the transaction is going to be sent with')
.description('Deploy a contract on the chain')
.action(async (walletPath, path, init, ...arguments) => await Contract.deploy(walletPath, path, init, utils.cli.getCmdFromArguments(arguments)))
.action(async (walletPath, path, callData, ...arguments) => await Contract.deploy(walletPath, path, callData, utils.cli.getCmdFromArguments(arguments)))


// Handle unknown command's
Expand Down
106 changes: 65 additions & 41 deletions bin/commands/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import * as R from 'ramda'
import path from 'path'

import { prepareCallParams, readFile, writeFile } from '../utils/helpers'
import { initClientByWalletFile, initCompiler } from '../utils/cli'
import { exit, initClientByWalletFile, initCompiler } from '../utils/cli'
import { handleApiError } from '../utils/errors'
import { printError, print, logContractDescriptor, printTransaction, printUnderscored } from '../utils/print'
import { COMPILER_BACKEND } from '../utils/constant'

// ## Function which compile your `source` code
export async function compile (file, options) {
const { backend } = options
const { backend, json } = options
try {
const code = readFile(path.resolve(process.cwd(), file), 'utf-8')
if (!code) throw new Error('Contract file not found')
Expand All @@ -39,8 +40,11 @@ export async function compile (file, options) {
await handleApiError(async () => {
// Call `node` API which return `compiled code`
const contract = await client.compileContractAPI(code, { backend })
print(`Contract bytecode:
${contract}`)
if (json) {
print({ bytecode: contract })
} else {
print(`Contract bytecode: ${contract}`)
}
})
} catch (e) {
printError(e.message)
Expand Down Expand Up @@ -126,55 +130,69 @@ export async function decodeCallData (data, options) {
}

// ## Function which `deploy ` contract
async function deploy (walletPath, contractPath, init = [], options) {
const { json, gas, gasPrince, backend, ttl, nonce, fee } = options
async function deploy (walletPath, contractPath, callData = "", options) {
const { json, gas, gasPrince, backend = COMPILER_BACKEND, ttl, nonce, fee } = options
// Deploy a contract to the chain and create a deploy descriptor
// with the contract informations that can be use to invoke the contract
// later on.
// The generated descriptor will be created in the same folde of the contract
// source file. Multiple deploy of the same contract file will generate different
// deploy descriptor
if (callData.split('_')[0] !== 'cb') throw new Error('"callData" should be a string with "cb" prefix')
try {
// Get `keyPair` by `walletPath`, decrypt using password and initialize `Ae` client with this `keyPair`
const client = await initClientByWalletFile(walletPath, options)
const contractFile = readFile(path.resolve(process.cwd(), contractPath), 'utf-8')

await handleApiError(
async () => {
// `contractCompile` takes a raw Sophia contract in string form and sends it
// off to the node for bytecode compilation. This might in the future be done
// without talking to the node, but requires a bytecode compiler
// implementation directly in the SDK.
const contract = await client.getContractInstance(contractFile)
// Invoking `deploy` on the bytecode object will result in the contract
// being written to the chain, once the block has been mined.
// Sophia contracts always have an `init` method which needs to be invoked,
// even when the contract's `state` is `unit` (`()`). The arguments to
// `init` have to be provided at deployment time and will be written to the
// block as well, together with the contract's bytecode.
const deployDescriptor = await contract.deploy([...init], { fee, ttl, nonce, gas, gasPrince, backend })
// Write contractDescriptor to file
const descPath = `${R.last(contractPath.split('/'))}.deploy.${deployDescriptor.owner.slice(3)}.json`
const contractDescriptor = R.merge({
descPath,
source: contractFile,
bytecode: contract.compiled,
}, contract.deployInfo)

writeFile(
descPath,
JSON.stringify(contractDescriptor)
)

// Log contract descriptor
json
? print(contractDescriptor)
: logContractDescriptor(contractDescriptor, 'Contract was successfully deployed')
const ownerId = await client.address()
const { bytecode: code } = await client.contractCompile(contractFile, { backend })
const opt = R.merge(client.Ae.defaults, { gas, gasPrince, backend, ttl, nonce, fee })

// Prepare contract create transaction
const { tx, contractId } = await client.contractCreateTx(R.merge(opt, {
callData,
code,
ownerId
}))
// Broadcast transaction
const { hash } = await client.send(tx, opt)
const result = await client.getTxInfo(hash)

if (result.returnType === 'ok') {
const deployDescriptor = Object.freeze({
result,
owner: ownerId,
transaction: hash,
address: contractId,
createdAt: new Date()
})
// Prepare contract descriptor
const descPath = `${R.last(contractPath.split('/'))}.deploy.${ownerId.slice(3)}.json`
const contractDescriptor = R.merge({
descPath,
source: contractFile,
bytecode: code
}, deployDescriptor)
// Write to file
writeFile(
descPath,
JSON.stringify(contractDescriptor)
)
// Log contract descriptor
json
? print({ descPath, ...deployDescriptor })
: logContractDescriptor(contractDescriptor, 'Contract was successfully deployed', json)
exit()
} else {
await this.handleCallError(result)
}
}
)
} catch (e) {
printError(e.message)
process.exit(1)
exit(1)
}
}

Expand All @@ -183,7 +201,7 @@ async function call (walletPath, fn, args, options) {
const { callStatic, json, top } = options
if (!fn) {
program.outputHelp()
process.exit(1)
exit(1)
}
try {
// If callStatic init `Chain` stamp else get `keyPair` by `walletPath`, decrypt using password and initialize `Ae` client with this `keyPair`
Expand All @@ -195,16 +213,22 @@ async function call (walletPath, fn, args, options) {
// Call static or call
const contract = await client.getContractInstance(params.source, { contractAddress: params.address })
const callResult = await contract.call(fn, args, { ...params.options, callStatic, top })
if (json) {
print(callResult)
} else {
// The execution result, if successful, will be an AEVM-encoded result
// value. Once type decoding will be implemented in the SDK, this value will
// not be a hexadecimal string, anymore.
json && print(callResult)
if (!json) {
if (callResult && callResult.hash) printTransaction(await client.tx(callResult.hash), json)
print('----------------------Transaction info-----------------------')
printUnderscored('Contract address', params.address)
printUnderscored('Gas price', R.path(['result', 'gasPrice'])(callResult))
printUnderscored('Gas used', R.path(['result', 'gasUsed'])(callResult))
printUnderscored('Return value (encoded)', R.path(['result', 'returnValue'])(callResult))
printUnderscored('Return value (decoded)', callResult.decodedRes)
// Decode result
const decoded = await callResult.decode()
printUnderscored('Return value (decoded)', decoded)
}

}
)
} catch (e) {
Expand Down

0 comments on commit 8848a7b

Please sign in to comment.