You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
18 KiB
455 lines
18 KiB
7 years ago
|
'use strict'
|
||
5 years ago
|
const ethers = require('ethers')
|
||
|
const helper = require('./txHelper')
|
||
|
const asyncJS = require('async')
|
||
|
const solcLinker = require('solc/linker')
|
||
|
const ethJSUtil = require('ethereumjs-util')
|
||
7 years ago
|
|
||
|
module.exports = {
|
||
|
|
||
|
/**
|
||
|
* build the transaction data
|
||
|
*
|
||
|
* @param {Object} function abi
|
||
|
* @param {Object} values to encode
|
||
|
* @param {String} contractbyteCode
|
||
|
*/
|
||
|
encodeData: function (funABI, values, contractbyteCode) {
|
||
5 years ago
|
let encoded
|
||
|
let encodedHex
|
||
7 years ago
|
try {
|
||
|
encoded = helper.encodeParams(funABI, values)
|
||
|
encodedHex = encoded.toString('hex')
|
||
|
} catch (e) {
|
||
|
return { error: 'cannot encode arguments' }
|
||
|
}
|
||
|
if (contractbyteCode) {
|
||
7 years ago
|
return { data: '0x' + contractbyteCode + encodedHex.replace('0x', '') }
|
||
7 years ago
|
} else {
|
||
7 years ago
|
return { data: helper.encodeFunctionId(funABI) + encodedHex.replace('0x', '') }
|
||
7 years ago
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
7 years ago
|
* encode function / constructor parameters
|
||
|
*
|
||
|
* @param {Object} params - input paramater of the function to call
|
||
|
* @param {Object} funAbi - abi definition of the function to call. null if building data for the ctor.
|
||
|
* @param {Function} callback - callback
|
||
|
*/
|
||
|
encodeParams: function (params, funAbi, callback) {
|
||
5 years ago
|
let data = ''
|
||
|
let dataHex = ''
|
||
|
let funArgs
|
||
7 years ago
|
if (params.indexOf('raw:0x') === 0) {
|
||
|
// in that case we consider that the input is already encoded and *does not* contain the method signature
|
||
|
dataHex = params.replace('raw:0x', '')
|
||
|
data = Buffer.from(dataHex, 'hex')
|
||
|
} else {
|
||
|
try {
|
||
|
params = params.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
|
||
|
params = params.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
|
||
|
funArgs = JSON.parse('[' + params + ']')
|
||
|
} catch (e) {
|
||
5 years ago
|
return callback('Error encoding arguments: ' + e)
|
||
7 years ago
|
}
|
||
|
if (funArgs.length > 0) {
|
||
|
try {
|
||
|
data = helper.encodeParams(funAbi, funArgs)
|
||
|
dataHex = data.toString('hex')
|
||
|
} catch (e) {
|
||
5 years ago
|
return callback('Error encoding arguments: ' + e)
|
||
7 years ago
|
}
|
||
|
}
|
||
|
if (data.slice(0, 9) === 'undefined') {
|
||
|
dataHex = data.slice(9)
|
||
|
}
|
||
|
if (data.slice(0, 2) === '0x') {
|
||
|
dataHex = data.slice(2)
|
||
|
}
|
||
|
}
|
||
|
callback(null, { data: data, dataHex: dataHex, funArgs: funArgs })
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* encode function call (function id + encoded parameters)
|
||
|
*
|
||
|
* @param {Object} params - input paramater of the function to call
|
||
|
* @param {Object} funAbi - abi definition of the function to call. null if building data for the ctor.
|
||
|
* @param {Function} callback - callback
|
||
|
*/
|
||
|
encodeFunctionCall: function (params, funAbi, callback) {
|
||
|
this.encodeParams(params, funAbi, (error, encodedParam) => {
|
||
|
if (error) return callback(error)
|
||
|
callback(null, { dataHex: helper.encodeFunctionId(funAbi) + encodedParam.dataHex, funAbi, funArgs: encodedParam.funArgs })
|
||
|
})
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* encode constructor creation and link with provided libraries if needed
|
||
|
*
|
||
|
* @param {Object} contract - input paramater of the function to call
|
||
|
* @param {Object} params - input paramater of the function to call
|
||
|
* @param {Object} funAbi - abi definition of the function to call. null if building data for the ctor.
|
||
|
* @param {Object} linkLibraries - contains {linkReferences} object which list all the addresses to be linked
|
||
|
* @param {Object} linkReferences - given by the compiler, contains the proper linkReferences
|
||
|
* @param {Function} callback - callback
|
||
|
*/
|
||
|
encodeConstructorCallAndLinkLibraries: function (contract, params, funAbi, linkLibraries, linkReferences, callback) {
|
||
|
this.encodeParams(params, funAbi, (error, encodedParam) => {
|
||
|
if (error) return callback(error)
|
||
5 years ago
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
||
7 years ago
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
||
|
if (linkLibraries && linkReferences) {
|
||
5 years ago
|
for (let libFile in linkLibraries) {
|
||
|
for (let lib in linkLibraries[libFile]) {
|
||
|
const address = linkLibraries[libFile][lib]
|
||
7 years ago
|
if (!ethJSUtil.isValidAddress(address)) return callback(address + ' is not a valid address. Please check the provided address is valid.')
|
||
|
bytecodeToDeploy = this.linkLibraryStandardFromlinkReferences(lib, address.replace('0x', ''), bytecodeToDeploy, linkReferences)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
||
|
return callback('Failed to link some libraries')
|
||
|
}
|
||
|
return callback(null, { dataHex: bytecodeToDeploy + encodedParam.dataHex, funAbi, funArgs: encodedParam.funArgs, contractBytecode: contract.evm.bytecode.object })
|
||
|
})
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* encode constructor creation and deploy librairies if needed
|
||
|
*
|
||
|
* @param {String} contractName - current contract name
|
||
|
* @param {Object} contract - input paramater of the function to call
|
||
|
* @param {Object} contracts - map of all compiled contracts.
|
||
|
* @param {Object} params - input paramater of the function to call
|
||
|
* @param {Object} funAbi - abi definition of the function to call. null if building data for the ctor.
|
||
|
* @param {Function} callback - callback
|
||
|
* @param {Function} callbackStep - callbackStep
|
||
|
* @param {Function} callbackDeployLibrary - callbackDeployLibrary
|
||
|
* @param {Function} callback - callback
|
||
|
*/
|
||
|
encodeConstructorCallAndDeployLibraries: function (contractName, contract, contracts, params, funAbi, callback, callbackStep, callbackDeployLibrary) {
|
||
|
this.encodeParams(params, funAbi, (error, encodedParam) => {
|
||
|
if (error) return callback(error)
|
||
5 years ago
|
let dataHex = ''
|
||
|
const contractBytecode = contract.evm.bytecode.object
|
||
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
||
7 years ago
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
||
|
this.linkBytecode(contract, contracts, (err, bytecode) => {
|
||
|
if (err) {
|
||
|
callback('Error deploying required libraries: ' + err)
|
||
|
} else {
|
||
|
bytecodeToDeploy = bytecode + dataHex
|
||
|
return callback(null, {dataHex: bytecodeToDeploy, funAbi, funArgs: encodedParam.funArgs, contractBytecode, contractName: contractName})
|
||
|
}
|
||
|
}, callbackStep, callbackDeployLibrary)
|
||
|
return
|
||
|
} else {
|
||
|
dataHex = bytecodeToDeploy + encodedParam.dataHex
|
||
|
}
|
||
|
callback(null, {dataHex: bytecodeToDeploy, funAbi, funArgs: encodedParam.funArgs, contractBytecode, contractName: contractName})
|
||
|
})
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* (DEPRECATED) build the transaction data
|
||
|
*
|
||
|
* @param {String} contractName
|
||
|
* @param {Object} contract - abi definition of the current contract.
|
||
|
* @param {Object} contracts - map of all compiled contracts.
|
||
|
* @param {Bool} isConstructor - isConstructor.
|
||
|
* @param {Object} funAbi - abi definition of the function to call. null if building data for the ctor.
|
||
|
* @param {Object} params - input paramater of the function to call
|
||
|
* @param {Function} callback - callback
|
||
|
* @param {Function} callbackStep - callbackStep
|
||
|
* @param {Function} callbackDeployLibrary - callbackDeployLibrary
|
||
|
*/
|
||
7 years ago
|
buildData: function (contractName, contract, contracts, isConstructor, funAbi, params, callback, callbackStep, callbackDeployLibrary) {
|
||
5 years ago
|
let funArgs = []
|
||
|
let data = ''
|
||
|
let dataHex = ''
|
||
7 years ago
|
|
||
7 years ago
|
if (params.indexOf('raw:0x') === 0) {
|
||
|
// in that case we consider that the input is already encoded and *does not* contain the method signature
|
||
|
dataHex = params.replace('raw:0x', '')
|
||
7 years ago
|
data = Buffer.from(dataHex, 'hex')
|
||
|
} else {
|
||
|
try {
|
||
6 years ago
|
if (params.length > 0) {
|
||
6 years ago
|
funArgs = this.parseFunctionParams(params)
|
||
6 years ago
|
}
|
||
7 years ago
|
} catch (e) {
|
||
5 years ago
|
return callback('Error encoding arguments: ' + e)
|
||
7 years ago
|
}
|
||
7 years ago
|
try {
|
||
|
data = helper.encodeParams(funAbi, funArgs)
|
||
|
dataHex = data.toString('hex')
|
||
|
} catch (e) {
|
||
5 years ago
|
return callback('Error encoding arguments: ' + e)
|
||
7 years ago
|
}
|
||
|
if (data.slice(0, 9) === 'undefined') {
|
||
|
dataHex = data.slice(9)
|
||
|
}
|
||
|
if (data.slice(0, 2) === '0x') {
|
||
|
dataHex = data.slice(2)
|
||
|
}
|
||
|
}
|
||
5 years ago
|
let contractBytecode
|
||
7 years ago
|
if (isConstructor) {
|
||
|
contractBytecode = contract.evm.bytecode.object
|
||
5 years ago
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
||
7 years ago
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
||
7 years ago
|
this.linkBytecode(contract, contracts, (err, bytecode) => {
|
||
7 years ago
|
if (err) {
|
||
|
callback('Error deploying required libraries: ' + err)
|
||
|
} else {
|
||
|
bytecodeToDeploy = bytecode + dataHex
|
||
|
return callback(null, {dataHex: bytecodeToDeploy, funAbi, funArgs, contractBytecode, contractName: contractName})
|
||
|
}
|
||
7 years ago
|
}, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
return
|
||
|
} else {
|
||
|
dataHex = bytecodeToDeploy + dataHex
|
||
|
}
|
||
|
} else {
|
||
7 years ago
|
dataHex = helper.encodeFunctionId(funAbi) + dataHex
|
||
7 years ago
|
}
|
||
|
callback(null, { dataHex, funAbi, funArgs, contractBytecode, contractName: contractName })
|
||
|
},
|
||
|
|
||
|
atAddress: function () {},
|
||
|
|
||
7 years ago
|
linkBytecodeStandard: function (contract, contracts, callback, callbackStep, callbackDeployLibrary) {
|
||
5 years ago
|
let contractBytecode = contract.evm.bytecode.object
|
||
7 years ago
|
asyncJS.eachOfSeries(contract.evm.bytecode.linkReferences, (libs, file, cbFile) => {
|
||
|
asyncJS.eachOfSeries(contract.evm.bytecode.linkReferences[file], (libRef, libName, cbLibDeployed) => {
|
||
5 years ago
|
const library = contracts[file][libName]
|
||
7 years ago
|
if (library) {
|
||
7 years ago
|
this.deployLibrary(file + ':' + libName, libName, library, contracts, (error, address) => {
|
||
7 years ago
|
if (error) {
|
||
|
return cbLibDeployed(error)
|
||
|
}
|
||
5 years ago
|
let hexAddress = address.toString('hex')
|
||
7 years ago
|
if (hexAddress.slice(0, 2) === '0x') {
|
||
|
hexAddress = hexAddress.slice(2)
|
||
|
}
|
||
7 years ago
|
contractBytecode = this.linkLibraryStandard(libName, hexAddress, contractBytecode, contract)
|
||
7 years ago
|
cbLibDeployed()
|
||
7 years ago
|
}, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
} else {
|
||
|
cbLibDeployed('Cannot find compilation data of library ' + libName)
|
||
|
}
|
||
|
}, (error) => {
|
||
|
cbFile(error)
|
||
|
})
|
||
|
}, (error) => {
|
||
|
if (error) {
|
||
|
callbackStep(error)
|
||
|
}
|
||
7 years ago
|
callback(error, contractBytecode)
|
||
7 years ago
|
})
|
||
|
},
|
||
|
|
||
7 years ago
|
linkBytecodeLegacy: function (contract, contracts, callback, callbackStep, callbackDeployLibrary) {
|
||
5 years ago
|
const libraryRefMatch = contract.evm.bytecode.object.match(/__([^_]{1,36})__/)
|
||
7 years ago
|
if (!libraryRefMatch) {
|
||
|
return callback('Invalid bytecode format.')
|
||
|
}
|
||
5 years ago
|
const libraryName = libraryRefMatch[1]
|
||
7 years ago
|
// file_name:library_name
|
||
5 years ago
|
const libRef = libraryName.match(/(.*):(.*)/)
|
||
7 years ago
|
if (!libRef) {
|
||
|
return callback('Cannot extract library reference ' + libraryName)
|
||
|
}
|
||
|
if (!contracts[libRef[1]] || !contracts[libRef[1]][libRef[2]]) {
|
||
|
return callback('Cannot find library reference ' + libraryName)
|
||
|
}
|
||
5 years ago
|
const libraryShortName = libRef[2]
|
||
|
const library = contracts[libRef[1]][libraryShortName]
|
||
7 years ago
|
if (!library) {
|
||
|
return callback('Library ' + libraryName + ' not found.')
|
||
|
}
|
||
7 years ago
|
this.deployLibrary(libraryName, libraryShortName, library, contracts, (err, address) => {
|
||
7 years ago
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
5 years ago
|
let hexAddress = address.toString('hex')
|
||
7 years ago
|
if (hexAddress.slice(0, 2) === '0x') {
|
||
|
hexAddress = hexAddress.slice(2)
|
||
|
}
|
||
|
contract.evm.bytecode.object = this.linkLibrary(libraryName, hexAddress, contract.evm.bytecode.object)
|
||
7 years ago
|
this.linkBytecode(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
||
|
}, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
},
|
||
|
|
||
7 years ago
|
linkBytecode: function (contract, contracts, callback, callbackStep, callbackDeployLibrary) {
|
||
7 years ago
|
if (contract.evm.bytecode.object.indexOf('_') < 0) {
|
||
|
return callback(null, contract.evm.bytecode.object)
|
||
|
}
|
||
|
if (contract.evm.bytecode.linkReferences && Object.keys(contract.evm.bytecode.linkReferences).length) {
|
||
7 years ago
|
this.linkBytecodeStandard(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
} else {
|
||
7 years ago
|
this.linkBytecodeLegacy(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
}
|
||
|
},
|
||
|
|
||
7 years ago
|
deployLibrary: function (libraryName, libraryShortName, library, contracts, callback, callbackStep, callbackDeployLibrary) {
|
||
5 years ago
|
const address = library.address
|
||
7 years ago
|
if (address) {
|
||
|
return callback(null, address)
|
||
|
}
|
||
5 years ago
|
const bytecode = library.evm.bytecode.object
|
||
7 years ago
|
if (bytecode.indexOf('_') >= 0) {
|
||
7 years ago
|
this.linkBytecode(library, contracts, (err, bytecode) => {
|
||
7 years ago
|
if (err) callback(err)
|
||
5 years ago
|
else {
|
||
|
library.evm.bytecode.object = bytecode
|
||
|
this.deployLibrary(libraryName, libraryShortName, library, contracts, callback, callbackStep, callbackDeployLibrary)
|
||
|
}
|
||
7 years ago
|
}, callbackStep, callbackDeployLibrary)
|
||
7 years ago
|
} else {
|
||
|
callbackStep(`creation of library ${libraryName} pending...`)
|
||
5 years ago
|
const data = {dataHex: bytecode, funAbi: {type: 'constructor'}, funArgs: [], contractBytecode: bytecode, contractName: libraryShortName}
|
||
7 years ago
|
callbackDeployLibrary({ data: data, useCall: false }, (err, txResult) => {
|
||
7 years ago
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
5 years ago
|
const address = txResult.result.createdAddress || txResult.result.contractAddress
|
||
7 years ago
|
library.address = address
|
||
|
callback(err, address)
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
linkLibraryStandardFromlinkReferences: function (libraryName, address, bytecode, linkReferences) {
|
||
5 years ago
|
for (let file in linkReferences) {
|
||
|
for (let libName in linkReferences[file]) {
|
||
7 years ago
|
if (libraryName === libName) {
|
||
|
bytecode = this.setLibraryAddress(address, bytecode, linkReferences[file][libName])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bytecode
|
||
|
},
|
||
|
|
||
7 years ago
|
linkLibraryStandard: function (libraryName, address, bytecode, contract) {
|
||
|
return this.linkLibraryStandardFromlinkReferences(libraryName, address, bytecode, contract.evm.bytecode.linkReferences)
|
||
7 years ago
|
},
|
||
|
|
||
|
setLibraryAddress: function (address, bytecodeToLink, positions) {
|
||
|
if (positions) {
|
||
5 years ago
|
for (let pos of positions) {
|
||
|
const regpos = bytecodeToLink.match(new RegExp(`(.{${2 * pos.start}})(.{${2 * pos.length}})(.*)`))
|
||
7 years ago
|
if (regpos) {
|
||
|
bytecodeToLink = regpos[1] + address + regpos[3]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bytecodeToLink
|
||
|
},
|
||
|
|
||
|
linkLibrary: function (libraryName, address, bytecodeToLink) {
|
||
7 years ago
|
return solcLinker.linkBytecode(bytecodeToLink, { [libraryName]: ethJSUtil.addHexPrefix(address) })
|
||
7 years ago
|
},
|
||
|
|
||
|
decodeResponse: function (response, fnabi) {
|
||
|
// Only decode if there supposed to be fields
|
||
|
if (fnabi.outputs && fnabi.outputs.length > 0) {
|
||
|
try {
|
||
5 years ago
|
let i
|
||
7 years ago
|
|
||
5 years ago
|
const outputTypes = []
|
||
7 years ago
|
for (i = 0; i < fnabi.outputs.length; i++) {
|
||
5 years ago
|
const type = fnabi.outputs[i].type
|
||
6 years ago
|
outputTypes.push(type.indexOf('tuple') === 0 ? helper.makeFullTypeDefinition(fnabi.outputs[i]) : type)
|
||
7 years ago
|
}
|
||
|
|
||
7 years ago
|
if (!response.length) response = new Uint8Array(32 * fnabi.outputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
|
||
7 years ago
|
// decode data
|
||
5 years ago
|
const abiCoder = new ethers.utils.AbiCoder()
|
||
|
const decodedObj = abiCoder.decode(outputTypes, response)
|
||
7 years ago
|
|
||
5 years ago
|
const json = {}
|
||
7 years ago
|
for (i = 0; i < outputTypes.length; i++) {
|
||
5 years ago
|
const name = fnabi.outputs[i].name
|
||
7 years ago
|
json[i] = outputTypes[i] + ': ' + (name ? name + ' ' + decodedObj[i] : decodedObj[i])
|
||
|
}
|
||
|
|
||
|
return json
|
||
|
} catch (e) {
|
||
|
return { error: 'Failed to decode output: ' + e }
|
||
|
}
|
||
|
}
|
||
|
return {}
|
||
6 years ago
|
},
|
||
|
|
||
|
parseFunctionParams: function (params) {
|
||
|
let args = []
|
||
5 years ago
|
// Check if parameter string starts with array or string
|
||
|
let startIndex = this.isArrayOrStringStart(params, 0) ? -1 : 0
|
||
6 years ago
|
for (let i = 0; i < params.length; i++) {
|
||
5 years ago
|
// If a quote is received
|
||
|
if (params.charAt(i) === '"') {
|
||
|
startIndex = -1
|
||
|
let endQuoteIndex = false
|
||
|
// look for closing quote. On success, push the complete string in arguments list
|
||
|
for (let j = i + 1; !endQuoteIndex; j++) {
|
||
|
if (params.charAt(j) === '"') {
|
||
|
args.push(params.substring(i + 1, j))
|
||
|
endQuoteIndex = true
|
||
|
i = j
|
||
6 years ago
|
}
|
||
5 years ago
|
// Throw error if end of params string is arrived but couldn't get end quote
|
||
|
if (!endQuoteIndex && j === params.length - 1) {
|
||
|
throw new Error('invalid params')
|
||
|
}
|
||
6 years ago
|
}
|
||
5 years ago
|
} else if (params.charAt(i) === '[') { // If an array/struct opening bracket is received
|
||
5 years ago
|
startIndex = -1
|
||
|
let bracketCount = 1
|
||
|
let j
|
||
|
for (j = i + 1; bracketCount !== 0; j++) {
|
||
|
// Increase count if another array opening bracket is received (To handle nested array)
|
||
|
if (params.charAt(j) === '[') {
|
||
|
bracketCount++
|
||
|
} else if (params.charAt(j) === ']') { // // Decrease count if an array closing bracket is received (To handle nested array)
|
||
|
bracketCount--
|
||
5 years ago
|
}
|
||
5 years ago
|
// Throw error if end of params string is arrived but couldn't get end of tuple
|
||
|
if (bracketCount !== 0 && j === params.length - 1) {
|
||
|
throw new Error('invalid tuple params')
|
||
|
}
|
||
5 years ago
|
}
|
||
5 years ago
|
// If bracketCount = 0, it means complete array/nested array parsed, push it to the arguments list
|
||
|
args.push(JSON.parse(params.substring(i, j)))
|
||
|
i = j - 1
|
||
|
} else if (params.charAt(i) === ',') {
|
||
|
// if startIndex >= 0, it means a parameter was being parsed, it can be first or other parameter
|
||
|
if (startIndex >= 0) {
|
||
|
args.push(params.substring(startIndex, i))
|
||
|
}
|
||
|
// Register start index of a parameter to parse
|
||
|
startIndex = this.isArrayOrStringStart(params, i + 1) ? -1 : i + 1
|
||
|
} else if (startIndex >= 0 && i === params.length - 1) {
|
||
|
// If start index is registered and string is completed (To handle last parameter)
|
||
|
args.push(params.substring(startIndex, params.length))
|
||
6 years ago
|
}
|
||
|
}
|
||
5 years ago
|
args = args.map(e => {
|
||
|
if (!Array.isArray(e)) {
|
||
|
return e.trim()
|
||
|
} else {
|
||
|
return e
|
||
|
}
|
||
|
})
|
||
6 years ago
|
return args
|
||
5 years ago
|
},
|
||
|
|
||
|
isArrayOrStringStart: function (str, index) {
|
||
|
return str.charAt(index) === '"' || str.charAt(index) === '['
|
||
7 years ago
|
}
|
||
|
}
|
||
|
|