Skip to content

Commit

Permalink
Merge pull request #1008 from aeternity/release/7.4.0
Browse files Browse the repository at this point in the history
Release 7.4.0
  • Loading branch information
nduchak authored May 29, 2020
2 parents b697be4 + 10406b9 commit ea11bb9
Show file tree
Hide file tree
Showing 13 changed files with 2,356 additions and 1,633 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [7.4.0](https://github.com/aeternity/aepp-sdk-js/compare/7.3.1...7.4.0) (2020-05-29)


### Bug Fixes

* **AEX-2:** Fix `getBrowserAPI` helper for cross-browser compatibility ([#1007](https://github.com/aeternity/aepp-sdk-js/issues/1007)) ([98b0e29](https://github.com/aeternity/aepp-sdk-js/commit/98b0e29))


### Features

* **ACI:** Event decoding ([#1006](https://github.com/aeternity/aepp-sdk-js/issues/1006)) ([6b8e6fe](https://github.com/aeternity/aepp-sdk-js/commit/6b8e6fe))



## [7.3.1](https://github.com/aeternity/aepp-sdk-js/compare/7.2.1...7.3.1) (2020-05-25)

### Improvements
Expand Down
8 changes: 7 additions & 1 deletion docs/guides/contract-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ contract EventExample =
```
- Decode using ACI
```js
// Auto decode of events on contract call
const callRes = await contractIns.methods.emitEvents()
console.log(callRes.decodedEvents)
// decode of events using contract instance
const decodedUsingInstance = contractIns.decodeEvents('emitEvents', callRes.result.log)
// decode of events using contract instance ACI methods
const decodedUsingInstanceMethods = contractIns.methods.emitEvents.decodeEvents(callRes.result.log)
// callRes.decodedEvents === decodedUsingInstance === decodedUsingInstanceMethods
console.log(callRes.decodedEvents || decodedUsingInstance || decodedUsingInstanceMethods)
/*
[
{ address: 'ct_N9s65ZMz9SUUKx2HDLCtxVNpEYrzzmYEuESdJwmbEsAo5TzxM',
Expand Down
49 changes: 49 additions & 0 deletions es/contract/aci/helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as R from 'ramda'
import { unpackTx } from '../../tx/builder'
import { decodeEvents as unpackEvents, transform, transformDecodedData, validateArguments } from './transformation'

/**
* Get function schema from contract ACI object
Expand All @@ -8,6 +9,7 @@ import { unpackTx } from '../../tx/builder'
* @return {Object} function ACI
*/
export function getFunctionACI (aci, name) {
if (!aci) throw new Error('ACI required')
const fn = aci.functions.find(f => f.name === name)
if (!fn && name !== 'init') throw new Error(`Function ${name} doesn't exist in contract`)

Expand Down Expand Up @@ -49,6 +51,9 @@ export const buildContractMethods = (instance) => () => ({
const { opt, args } = parseArguments(aciArgs)(arguments)
if (name === 'init') return instance.deploy(args, opt)
return instance.call(name, args, { ...opt, callStatic: false })
},
decodeEvents (events) {
return instance.decodeEvents(name, events)
}
}
)
Expand Down Expand Up @@ -85,3 +90,47 @@ export const parseArguments = (aciArgs = []) => (args) => ({
})

export const unpackByteCode = (bytecode) => unpackTx(bytecode, false, 'cb').tx

/**
* Validated contract call arguments using contract ACI
* @function validateCallParams
* @rtype (aci: Object, params: Array) => Object
* @param {Object} aci Contract ACI
* @param {Array} params Contract call arguments
* @return Promise{Array} Object with validation errors
*/
export const prepareArgsForEncode = async (aci, params) => {
if (!aci || !aci.arguments) return params
// Validation
if (aci.arguments.length > params.length) {
throw new Error(`Function "${aci.name}" require ${aci.arguments.length} arguments of types [${aci.arguments.map(a => JSON.stringify(a.type))}] but get [${params.map(JSON.stringify)}]`)
}

validateArguments(aci, params)
const bindings = aci.bindings
// Cast argument from JS to Sophia type
return Promise.all(aci.arguments.map(async ({ type }, i) => transform(type, params[i], {
bindings
})))
}

export const decodeEvents = (events, fnACI) => {
if (!fnACI || !fnACI.event || !fnACI.event.length) return []

const eventsSchema = fnACI.event.map(e => {
const name = Object.keys(e)[0]
return { name, types: e[name] }
})
return unpackEvents(events, { schema: eventsSchema })
}

export const decodeCallResult = async (result, fnACI, opt) => {
return {
decodedResult: await transformDecodedData(
fnACI.returns,
await result.decode(),
{ ...opt, bindings: fnACI.bindings }
),
decodedEvents: decodeEvents(result.result.log, fnACI)
}
}
77 changes: 28 additions & 49 deletions es/contract/aci/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,43 +24,22 @@
*/

import * as R from 'ramda'
import { BigNumber } from 'bignumber.js'

import AsyncInit from '../../utils/async-init'
import semverSatisfies from '../../utils/semver-satisfies'
import {
validateArguments,
transform,
transformDecodedData,
decodeEvents
} from './transformation'
import { buildContractMethods, getFunctionACI } from './helpers'
buildContractMethods,
decodeCallResult,
decodeEvents,
getFunctionACI,
prepareArgsForEncode as prepareArgs
} from './helpers'
import { isAddressValid } from '../../utils/crypto'
import AsyncInit from '../../utils/async-init'
import { BigNumber } from 'bignumber.js'
import { COMPILER_LT_VERSION } from '../compiler'
import semverSatisfies from '../../utils/semver-satisfies'
import { AMOUNT, DEPOSIT, GAS, MIN_GAS_PRICE } from '../../tx/builder/schema'

/**
* Validated contract call arguments using contract ACI
* @function validateCallParams
* @rtype (aci: Object, params: Array) => Object
* @param {Object} aci Contract ACI
* @param {Array} params Contract call arguments
* @return Promise{Array} Object with validation errors
*/
export async function prepareArgsForEncode (aci, params) {
if (!aci || !aci.arguments) return params
// Validation
if (aci.arguments.length > params.length) {
throw new Error(`Function "${aci.name}" require ${aci.arguments.length} arguments of types [${aci.arguments.map(a => JSON.stringify(a.type))}] but get [${params.map(JSON.stringify)}]`)
}

validateArguments(aci, params)
const bindings = aci.bindings
// Cast argument from JS to Sophia type
return Promise.all(aci.arguments.map(async ({ type }, i) => transform(type, params[i], {
bindings
})))
}
// TODO remove when Breaking Changes release is coming
export const prepareArgsForEncode = prepareArgs

/**
* Generate contract ACI object with predefined js methods for contract usage - can be used for creating a reference to already deployed contracts
Expand Down Expand Up @@ -152,6 +131,15 @@ async function getContractInstance (source, { aci, contractAddress, filesystem =
* @return {Object} CallResult
*/
instance.call = call({ client: this, instance })
/**
* Decode Events
* @alias module:@aeternity/aepp-sdk/es/contract/aci
* @rtype (fn: String, events: Array) => DecodedEvents: Array
* @param {String} fn Function name
* @param {Array} events Array of encoded events(callRes.result.log)
* @return {Object} DecodedEvents
*/
instance.decodeEvents = eventDecode({ instance })

/**
* Generate proto function based on contract function using Contract ACI schema
Expand All @@ -165,21 +153,10 @@ async function getContractInstance (source, { aci, contractAddress, filesystem =
return instance
}

const decodeCallResult = async (result, fnACI, opt) => {
const eventsSchema = fnACI.event.map(e => {
const name = Object.keys(e)[0]
return { name, types: e[name] }
})

return {
decodedResult: await transformDecodedData(
fnACI.returns,
await result.decode(),
{ ...opt, bindings: fnACI.bindings }
),
decodedEvents: decodeEvents(result.result.log, { ...opt, schema: eventsSchema })
}
const eventDecode = ({ instance }) => (fn, events) => {
return decodeEvents(events, getFunctionACI(instance.aci, fn))
}

const call = ({ client, instance }) => async (fn, params = [], options = {}) => {
const opt = R.merge(instance.options, options)
const fnACI = getFunctionACI(instance.aci, fn)
Expand All @@ -191,7 +168,7 @@ const call = ({ client, instance }) => async (fn, params = [], options = {}) =>
BigNumber(opt.amount).gt(0) &&
(Object.prototype.hasOwnProperty.call(fnACI, 'payable') && !fnACI.payable)
) throw new Error(`You try to pay "${opt.amount}" to function "${fn}" which is not payable. Only payable function can accept tokens`)
params = !opt.skipArgsConvert ? await prepareArgsForEncode(fnACI, params) : params
params = !opt.skipArgsConvert ? await prepareArgs(fnACI, params) : params
const result = opt.callStatic
? await client.contractCallStatic(source, instance.deployInfo.address, fn, params, {
top: opt.top,
Expand All @@ -210,7 +187,7 @@ const deploy = ({ client, instance }) => async (init = [], options = {}) => {
const source = opt.source || instance.source

if (!instance.compiled) await instance.compile(opt)
init = !opt.skipArgsConvert ? await prepareArgsForEncode(fnACI, init) : init
init = !opt.skipArgsConvert ? await prepareArgs(fnACI, init) : init

if (opt.callStatic) {
return client.contractCallStatic(source, null, 'init', init, {
Expand Down Expand Up @@ -240,8 +217,10 @@ const compile = ({ client, instance }) => async (options = {}) => {
* @return {Object} Contract compiler instance
* @example ContractACI()
*/
export default AsyncInit.compose({

export const ContractACI = AsyncInit.compose({
methods: {
getContractInstance
}
})
export default ContractACI
8 changes: 4 additions & 4 deletions es/utils/aepp-wallet-communication/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const isWeb = () => location && location.protocol && location.protocol.startsWit

export const getBrowserAPI = (force = false) => {
// Chrome, Opera support
if (chrome === Object(chrome)) return chrome
if (typeof chrome !== 'undefined' && chrome === Object(chrome)) return chrome
// Firefox support
if (browser === Object(browser)) return browser
if (typeof browser !== 'undefined' && browser === Object(browser)) return browser
if (!force) throw new Error('Browser is not detected')
return {}
}
Expand All @@ -24,8 +24,8 @@ export const isContentScript = () => isExtensionContext() && isWeb()

export const isInIframe = () => window !== window.parent

export const getWindow = () => {
if (!window) throw new Error('Browser is not detected')
export const getWindow = (force = false) => {
if (!window && !force) throw new Error('Browser is not detected')
return window
}

Expand Down
Loading

0 comments on commit ea11bb9

Please sign in to comment.