UNPKG

@aeternity/aepp-sdk

Version:
318 lines (294 loc) 10.7 kB
/* * ISC License (ISC) * Copyright (c) 2018 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /** * Contract module - routines to interact with the æternity contract * * High level documentation of the contracts are available at * https://github.com/aeternity/protocol/tree/master/contracts and * * @module @aeternity/aepp-sdk/es/ae/contract * @export Contract * @example import Contract from '@aeternity/aepp-sdk/es/ae/contract' (Using tree-shaking) * @example import { Contract } from '@aeternity/aepp-sdk' (Using bundle) */ import Ae from './' import * as R from 'ramda' import { isBase64 } from '../utils/crypto' import ContractCompilerAPI from '../contract/compiler' import ContractBase from '../contract' import ContractACI from '../contract/aci' /** * Handle contract call error * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {Object} result call result object * @throws Error Decoded error * @return {Promise<void>} */ async function handleCallError (result) { const error = Buffer.from(result.returnValue).toString() if (isBase64(error.slice(3))) { const decodedError = Buffer.from(error.slice(3), 'base64').toString() throw Object.assign(Error(`Invocation failed: ${error}. Decoded: ${decodedError}`), R.merge(result, { error, decodedError })) } const decodedError = await this.contractDecodeDataAPI('string', error) throw Object.assign(Error(`Invocation failed: ${error}. Decoded: ${decodedError}`), R.merge(result, { error, decodedError })) } /** * Encode call data for contract call * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} source Contract source code * @param {String} name Name of function to call * @param {Array} args Argument's for call * @return {Promise<String>} */ async function contractEncodeCall (source, name, args) { return this.contractEncodeCallDataAPI(source, name, args) } /** * Decode contract call result data * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} source - source code * @param {String } fn - function name * @param {String} callValue - result call data * @param {String} callResult - result status * @return {Promise<String>} Result object * @example * const decodedData = await client.contractDecodeData(SourceCode ,'functionName', 'cb_asdasdasd...', 'ok|revert')lt * @param options */ async function contractDecodeData (source, fn, callValue, callResult, options) { return this.contractDecodeCallResultAPI(source, fn, callValue, callResult, options) } /** * Static contract call(using dry-run) * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} source Contract source code * @param {String} address Contract address * @param {String} name Name of function to call * @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 {String} options [options.options] Transaction options (fee, ttl, gas, amount, deposit) * @return {Promise<Object>} Result object * @example * const callResult = await client.contractCallStatic(source, address, fnName, args = [], { top, options = {} }) * { * result: TX_DATA, * decode: (type) => Decode call result * } */ async function contractCallStatic (source, address, name, args = [], { top, options = {} } = {}) { const opt = R.merge(this.Ae.defaults, options) const callerId = await this.address() // Get block hash by height if (top && !isNaN(top)) { top = (await this.getKeyBlock(top)).hash } // 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 })) // Dry-run const [{ result: status, callObj, reason }] = (await this.txDryRun([tx], [{ amount: opt.amount, pubKey: callerId }], top)).results // check response if (status !== 'ok') throw new Error('Dry run error, ' + reason) const { returnType, returnValue } = callObj if (returnType !== 'ok') { await this.handleCallError(callObj) } return { result: callObj, decode: () => this.contractDecodeData(source, name, returnValue, returnType, options) } } /** * Call contract function * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} source Contract source code * @param {String} address Contract address * @param {String} name Name of function to call * @param {Array} args Argument's for call function * @param {Object} options Transaction options (fee, ttl, gas, amount, deposit) * @return {Promise<Object>} Result object * @example * const callResult = await client.contractCall(source, address, fnName, args = [], options) * { * hash: TX_HASH, * result: TX_DATA, * decode: (type) => Decode call result * } */ async function contractCall (source, address, name, args = [], options = {}) { const opt = R.merge(this.Ae.defaults, options) const tx = await this.contractCallTx(R.merge(opt, { callerId: await this.address(), contractId: address, callData: await this.contractEncodeCall(source, name, args) })) const { hash, rawTx } = await this.send(tx, opt) const result = await this.getTxInfo(hash) if (result.returnType === 'ok') { return { hash, rawTx, result, decode: () => this.contractDecodeData(source, name, result.returnValue, result.returnType) } } else { await this.handleCallError(result) } } /** * Deploy contract to the node * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} code Compiled contract * @param {String} source Contract source code * @param {Array} initState Arguments of contract constructor(init) function * @param {Object} options Transaction options (fee, ttl, gas, amount, deposit) * @return {Promise<Object>} Result object * @example * const deployed = await client.contractDeploy(bytecode, source, init = [], options) * { * owner: OWNER_PUB_KEY, * transaction: TX_HASH, * address: CONTRACT_ADDRESS, * createdAt: Date, * result: DEPLOY_TX_DATA, * call: (fnName, args = [], options) => Call contract function, * callStatic: (fnName, args = [], options) => Static all contract function * } */ async function contractDeploy (code, source, initState = [], options = {}) { const opt = R.merge(this.Ae.defaults, options) const callData = await this.contractEncodeCall(source, 'init', initState) const ownerId = await this.address() const { tx, contractId } = await this.contractCreateTx(R.merge(opt, { callData, code, ownerId })) const { hash, rawTx } = await this.send(tx, opt) const result = await this.getTxInfo(hash) if (result.returnType === 'ok') { return Object.freeze({ result, owner: ownerId, transaction: hash, rawTx, address: contractId, call: async (name, args = [], options) => this.contractCall(source, contractId, name, args, options), callStatic: async (name, args = [], options) => this.contractCallStatic(source, contractId, name, args, options), createdAt: new Date() }) } else { await this.handleCallError(result) } } /** * Compile contract source code * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @category async * @param {String} source Contract sourece code * @param {Object} options Transaction options (fee, ttl, gas, amount, deposit) * @return {Promise<Object>} Result object * @example * const compiled = await client.contractCompile(SOURCE_CODE) * { * bytecode: CONTRACT_BYTE_CODE, * deploy: (init = [], options = {}) => Deploy Contract, * encodeCall: (fnName, args = []) => Prepare callData * } */ 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) }, { bytecode })) } /** * Contract Stamp * * Provide contract implementation * {@link module:@aeternity/aepp-sdk/es/ae--Ae} clients. * @function * @alias module:@aeternity/aepp-sdk/es/ae/contract * @rtype Stamp * @param {Object} [options={}] - Initializer object * @return {Object} Contract instance * @example * import Transaction from '@aeternity/aepp-sdk/es/tx/tx * import MemoryAccount from '@aeternity/aepp-sdk/es/account/memory * import ChainNode from '@aeternity/aepp-sdk/es/chain/node * import ContractCompilerAPI from '@aeternity/aepp-sdk/es/contract/compiler * // or using bundle * import { * Transaction, * MemoryAccount, * ChainNode, * ContractCompilerAPI * } from '@aeternity/aepp-sdk * * const ContractWithAE = await Contract * .compose(Transaction, MemoryAccount, ChainNode) // AE implementation * .compose(ContractCompilerAPI) // ContractBase implementation * const client = await ContractWithAe({ url, internalUrl, compilerUrl, keypair, ... }) * */ export const Contract = Ae.compose(ContractBase, ContractACI, { methods: { contractCompile, contractCallStatic, contractDeploy, contractCall, contractEncodeCall, contractDecodeData, handleCallError }, deepProps: { Ae: { defaults: { deposit: 0, gasPrice: 1000000000, // min gasPrice 1e9 amount: 0, gas: 1600000 - 21000, options: '' } } } }) export const ContractWithCompiler = Contract.compose(ContractCompilerAPI) export default ContractWithCompiler