Skip to content

Feature/add compiler commands #51

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

Merged
merged 8 commits into from
Apr 21, 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
39 changes: 38 additions & 1 deletion bin/aecli-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const { Contract } = require('./commands')
program
.option('-u --url [hostname]', 'Node to connect to', utils.constant.EPOCH_URL)
.option('--internalUrl [internal]', 'Node to connect to(internal)', utils.constant.EPOCH_INTERNAL_URL)
.option('--compilerUrl [compilerUrl]', 'Compiler URL', '')
.option('--compilerUrl [compilerUrl]', 'Compiler URL', utils.constant.COMPILER_URL)
.option('--networkId [networkId]', 'Network id (default: ae_mainnet)')
.option('--native', 'Build transaction natively')
.option('-T, --ttl [ttl]', 'Validity of the transaction in number of blocks (default forever)', utils.constant.TX_TTL)
Expand All @@ -48,6 +48,43 @@ program
.description('Compile a contract')
.action(async (file, ...arguments) => await Contract.compile(file, utils.cli.getCmdFromArguments(arguments)))


// ## Initialize `encode callData` command
//
// You can use this command to prepare `callData`
//
// Example: `aecli contract encodeData ./mycontract.contract testFn 1 2`
program
.command('encodeData <source> <fn> [args...]')
.description('Encode contract call data')
.action(async (source, fn, args, ...arguments) => await Contract.encodeData(source, fn, args, utils.cli.getCmdFromArguments(arguments)))


// ## Initialize `decode data` command
//
// You can use this command to decode contract return data
//
// Example: `aecli contract decodeData cb_asdasdasdasdasdas int`
program
.command('decodeData <data> <returnType>')
.description('Decode contract data')
.action(async (data, returnType, ...arguments) => await Contract.decodeData(data, returnType, utils.cli.getCmdFromArguments(arguments)))


// ## Initialize `decode call data` command
//
// You can use this command to decode contract call data using source or bytecode
//
// Example: `aecli contract decodeCallData
program
.command('decodeCallData <data>')
.option('--sourcePath [sourcePath]', 'Path to contract source')
.option('--code [code]', 'Compiler contract code')
.option('--fn [fn]', 'Function name')
.description('Decode contract call data')
.action(async (data, ...arguments) => await Contract.decodeCallData(data, utils.cli.getCmdFromArguments(arguments)))


// ## Initialize `call` command
//
// You can use this command to execute a function's of contract
Expand Down
11 changes: 6 additions & 5 deletions bin/aecli-tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,26 +116,27 @@ program
//
// Example: `aecli tx contract-deploy ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi test.contract`
program
.command('contract-deploy <ownerId> <contractPath>')
.command('contract-deploy <ownerId> <contractBytecode> <initCallData> <nonce>')
.option('-T, --ttl [ttl]', 'Validity of the transaction in number of blocks (default forever)', utils.constant.TX_TTL)
.option('-F, --fee [fee]', 'Transaction fee.')
.option('-I, --init [state]', 'Deploying contract arguments for constructor function')
.option('--amount [amount]', 'Amount', 0)
.option('--deposit [deposit]', 'Deposit', 0)
.option('-G --gas [gas]', 'Amount of gas to deploy the contract', utils.constant.GAS)
.description('Build contract create transaction.')
.action(async (ownerId, contractPath, ...arguments) => await Transaction.contractDeploy(ownerId, contractPath, utils.cli.getCmdFromArguments(arguments)))
.action(async (ownerId, contractBytecode, initCallData, nonce, ...arguments) => await Transaction.contractDeploy(ownerId, contractBytecode, initCallData, nonce, utils.cli.getCmdFromArguments(arguments)))

// ## Initialize `contract-call` command
//
// You can use this command to build `contract call` transaction
//
// Example: `aecli tx contract-call ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi ct_2134235423dsfsdfsdf sum int 1 2`
program
.command('contract-call <callerId> <contractId> <fn> <return_type> [args...]')
.command('contract-call <callerId> <contractId> <callData> <nonce>')
.option('-T, --ttl [ttl]', 'Validity of the transaction in number of blocks (default forever)', utils.constant.TX_TTL)
.option('-F, --fee [fee]', 'Transaction fee.')
.option('-G --gas [gas]', 'Amount of gas to deploy the contract', utils.constant.GAS)
.description('Build contract create transaction.')
.action(async (callerId, contractId, fn, returnType, args, ...arguments) => await Transaction.contractCall(callerId, contractId, fn, returnType, args, utils.cli.getCmdFromArguments(arguments)))
.action(async (callerId, contractId, callData, nonce, ...arguments) => await Transaction.contractCall(callerId, contractId, callData, nonce, utils.cli.getCmdFromArguments(arguments)))


// ## Initialize `oracle-register` command
Expand Down
122 changes: 81 additions & 41 deletions bin/commands/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,83 @@ export async function compile (file, options) {
}
}

// ## Function which compile your `source` code
export async function encodeData (source, fn, args = [], options) {
try {
const sourceCode = readFile(path.resolve(process.cwd(), source), 'utf-8')
if (!sourceCode) throw new Error('Contract file not found')

const client = await initCompiler(options)

await handleApiError(async () => {
// Call `node` API which return `compiled code`
const callData = await client.contractEncodeCallDataAPI(sourceCode, fn, args, options)
if (options.json) {
print(JSON.stringify({ callData }))
} else {
print(`Contract encoded call data: ${callData}`)
}
})
} catch (e) {
printError(e.message)
}
}

// ## Function which compile your `source` code
export async function decodeData (data, type, options) {
try {
const client = await initCompiler(options)

await handleApiError(async () => {
// Call `node` API which return `compiled code`
const decodedData = await client.contractDecodeDataAPI(type, data)
if (options.json) {
print(JSON.stringify({ decodedData }))
} else {
print(`Contract bytecode:`)
print(decodedData)
}
})
} catch (e) {
printError(e.message)
}
}

// ## Function which compile your `source` code
export async function decodeCallData (data, options) {
const { sourcePath, code, fn } = options
let sourceCode

if (!sourcePath && !code) throw new Error('Contract source(--sourcePath) or contract code(--code) required!')
if (sourcePath) {
if (!fn) throw new Error('Function name required in decoding by source!')
sourceCode = readFile(path.resolve(process.cwd(), sourcePath), 'utf-8')
if (!sourceCode) throw new Error('Contract file not found')
} else {
if (code.slice(0, 2) !== 'cb') throw new Error('Code must be like "cb_23dasdafgasffg...." ')
}

try {
const client = await initCompiler(options)

await handleApiError(async () => {
// Call `node` API which return `compiled code`
const decoded = code
? await client.contractDecodeCallDataByCodeAPI(code, data)
: await client.contractDecodeCallDataBySourceAPI(sourceCode, fn, data)

if (options.json) {
print(JSON.stringify({ decoded }))
} else {
print(`Decoded Call Data:`)
print(decoded)
}
})
} catch (e) {
printError(e.message)
}
}

// ## Function which `deploy ` contract
async function deploy (walletPath, contractPath, init = [], options) {
const { json, gas } = options
Expand Down Expand Up @@ -157,6 +234,7 @@ async function call (walletPath, fn, returnType, args, options) {
printUnderscored('Gas used', R.path(['result', 'gasUsed'])(callResult))
printUnderscored('Return value (encoded)', R.path(['result', 'returnValue'])(callResult))
// Decode result
console.log(callResult)
const decoded = await callResult.decode()
printUnderscored('Return value (decoded)', decoded)
}
Expand All @@ -167,49 +245,11 @@ async function call (walletPath, fn, returnType, args, options) {
}
}

// ## Function which `call` contract
async function callTypeChecked (walletPath, fn, returnType, callContract, options) {
const { callStatic, json, top } = options
if (!fn || !returnType) {
program.outputHelp()
process.exit(1)
}
try {
// If callStatic init `Chain` stamp else get `keyPair` by `walletPath`, decrypt using password and initialize `Ae` client with this `keyPair`
const client = await initClientByWalletFile(walletPath, options)
const params = await prepareCallParams(fn, R.merge(options, { callContract }))
const call = readFile(path.resolve(process.cwd(), callContract), 'utf-8')

await handleApiError(
async () => {
// Call static or call
const callResult = callStatic
? await client.contractCallStatic(params.address, 'sophia-address', params.name, { top, ...params.options, call })
: await client.contractCall(params.code, params.abi, params.address, params.name, { ...params.options, call })
// 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.
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))
// Decode result
const { type, value } = await callResult.decode(returnType)
printUnderscored('Return value (decoded)', value)
printUnderscored('Return remote type', type)
}
)
} catch (e) {
printError(e.message)
process.exit(1)
}
}

export const Contract = {
compile,
deploy,
call,
callTypeChecked
encodeData,
decodeData,
decodeCallData
}
33 changes: 10 additions & 23 deletions bin/commands/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,37 +251,31 @@ async function nameRevoke (accountId, nameId, nonce, options) {
}

// ## Build `contractDeploy` transaction
async function contractDeploy (ownerId, contractPath, options) {
let { ttl, json, nonce, fee, init = '()', gas } = options
async function contractDeploy (ownerId, contractByteCode, initCallData, nonce, options) {
let { ttl, json, fee, gas, deposit = 0, amount = 0 } = options
ttl = parseInt(ttl)
nonce = parseInt(nonce)
try {
// Get contract file
const contractFile = readFile(path.resolve(process.cwd(), contractPath), 'utf-8')

// Initialize `Ae`
const txBuilder = await initTxBuilder(options)
const chain = await initChain(options)
// Build `deploy` transaction's
await handleApiError(async () => {
// Compile contract using `debug API`
const { bytecode: code } = await chain.compileNodeContract(contractFile, { gas })
// Prepare `callData`
const callData = await chain.contractNodeEncodeCallData(code, 'sophia', 'init', init)
// Create `contract-deploy` transaction
const { tx, contractId } = await txBuilder.contractCreateTx({
...DEFAULT_CONTRACT_PARAMS,
code,
code: contractByteCode,
nonce,
fee,
ttl,
gas,
ownerId,
callData
callData: initCallData,
amount,
deposit
})

if (json) {
print({ tx, contractId })
print(JSON.stringify({ tx, contractId }))
} else {
printUnderscored('Unsigned Contract Deploy TX', tx)
printUnderscored('Contract ID', contractId)
Expand All @@ -294,21 +288,14 @@ async function contractDeploy (ownerId, contractPath, options) {
}

// ## Build `contractCall` transaction
async function contractCall (callerId, contractId, fn, returnType, args, options) {
let { ttl, json, nonce, fee, gas } = options
async function contractCall (callerId, contractId, callData, nonce, options) {
let { ttl, json, fee, gas } = options
nonce = parseInt(nonce)
try {
// Prepare args
args = args.filter(arg => arg !== '[object Object]')
args = args.length ? `(${args.join(',')})` : '()'

// Build `call` transaction's
await handleApiError(async () => {
// Initialize `Ae`
const txBuilder = await initTxBuilder(options)
const chain = await initChain(options)
// Prepare `callData`
const callData = await chain.contractNodeEncodeCallData(contractId, 'sophia-address', fn, args)
// Create `contract-call` transaction
const tx = await txBuilder.contractCallTx({
...DEFAULT_CONTRACT_PARAMS,
Expand All @@ -322,7 +309,7 @@ async function contractCall (callerId, contractId, fn, returnType, args, options
})

if (json)
print({ tx })
print(JSON.stringify({ tx }))
else
printUnderscored('Unsigned Contract Call TX', tx)
})
Expand Down
1 change: 1 addition & 0 deletions bin/utils/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const HASH_TYPES = {
export const EPOCH_URL = 'https://sdk-mainnet.aepps.com'
export const EPOCH_INTERNAL_URL = 'https://sdk-mainnet.aepps.com'
export const EPOCH_WEBSOCKET_URL = 'https://sdk-testnet.aepps.com'
export const COMPILER_URL = 'http://localhost:3080'

// ## CHAIN
export const PLAY_LIMIT = 10
Expand Down
11 changes: 5 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"aecli": "./bin/aecli.js"
},
"dependencies": {
"@aeternity/aepp-sdk": "2.4.1",
"@aeternity/aepp-sdk": "git://github.com/aeternity/aepp-sdk-js.git#feature/decode-call-data",
"child_process": "^1.0.2",
"commander": "^2.19.0",
"esm": "^3.0.84",
Expand Down
Loading