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.
462 lines
18 KiB
462 lines
18 KiB
'use strict'
|
|
import { ethers } from 'ethers'
|
|
import { encodeParams as encodeParamsHelper, encodeFunctionId, makeFullTypeDefinition } from './txHelper'
|
|
import { eachOfSeries } from 'async'
|
|
import { linkBytecode as linkBytecodeSolc } from 'solc/linker'
|
|
import { isValidAddress, addHexPrefix } from 'ethereumjs-util'
|
|
|
|
/**
|
|
* build the transaction data
|
|
*
|
|
* @param {Object} function abi
|
|
* @param {Object} values to encode
|
|
* @param {String} contractbyteCode
|
|
*/
|
|
export function encodeData (funABI, values, contractbyteCode) {
|
|
let encoded
|
|
let encodedHex
|
|
try {
|
|
encoded = encodeParamsHelper(funABI, values)
|
|
encodedHex = encoded.toString('hex')
|
|
} catch (e) {
|
|
return { error: 'cannot encode arguments' }
|
|
}
|
|
if (contractbyteCode) {
|
|
return { data: '0x' + contractbyteCode + encodedHex.replace('0x', '') }
|
|
} else {
|
|
return { data: encodeFunctionId(funABI) + encodedHex.replace('0x', '') }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
export function encodeParams (params, funAbi, callback) {
|
|
let data: Buffer | string = ''
|
|
let dataHex = ''
|
|
let funArgs
|
|
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) {
|
|
return callback('Error encoding arguments: ' + e)
|
|
}
|
|
if (funArgs.length > 0) {
|
|
try {
|
|
data = encodeParamsHelper(funAbi, funArgs)
|
|
dataHex = data.toString()
|
|
} catch (e) {
|
|
return callback('Error encoding arguments: ' + e)
|
|
}
|
|
}
|
|
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
|
|
*/
|
|
export function encodeFunctionCall (params, funAbi, callback) {
|
|
encodeParams(params, funAbi, (error, encodedParam) => {
|
|
if (error) return callback(error)
|
|
callback(null, { dataHex: 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
|
|
*/
|
|
export function encodeConstructorCallAndLinkLibraries (contract, params, funAbi, linkLibrariesAddresses, linkReferences, callback) {
|
|
encodeParams(params, funAbi, (error, encodedParam) => {
|
|
if (error) return callback(error)
|
|
linkLibraries(contract, linkLibrariesAddresses, linkReferences, (error, bytecodeToDeploy) => {
|
|
callback(error, { dataHex: bytecodeToDeploy + encodedParam.dataHex, funAbi, funArgs: encodedParam.funArgs, contractBytecode: contract.evm.bytecode.object })
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* link with provided libraries if needed
|
|
*
|
|
* @param {Object} contract - input paramater of the function to call
|
|
* @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
|
|
*/
|
|
export function linkLibraries (contract, linkLibraries, linkReferences, callback) {
|
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
|
if (linkLibraries && linkReferences) {
|
|
for (const libFile in linkLibraries) {
|
|
for (const lib in linkLibraries[libFile]) {
|
|
const address = linkLibraries[libFile][lib]
|
|
if (!isValidAddress(address)) return callback(address + ' is not a valid address. Please check the provided address is valid.')
|
|
bytecodeToDeploy = linkLibraryStandardFromlinkReferences(lib, address.replace('0x', ''), bytecodeToDeploy, linkReferences)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
|
return callback('Failed to link some libraries')
|
|
}
|
|
return callback(null, bytecodeToDeploy)
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
export function encodeConstructorCallAndDeployLibraries (contractName, contract, contracts, params, funAbi, callback, callbackStep, callbackDeployLibrary) {
|
|
encodeParams(params, funAbi, (error, encodedParam) => {
|
|
if (error) return callback(error)
|
|
let dataHex = ''
|
|
const contractBytecode = contract.evm.bytecode.object
|
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
|
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
|
|
*/
|
|
export function buildData (contractName, contract, contracts, isConstructor, funAbi, params, callback, callbackStep, callbackDeployLibrary) {
|
|
let funArgs = []
|
|
let data: Buffer | string = ''
|
|
let dataHex = ''
|
|
|
|
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 {
|
|
if (params.length > 0) {
|
|
funArgs = parseFunctionParams(params)
|
|
}
|
|
} catch (e) {
|
|
return callback('Error encoding arguments: ' + e)
|
|
}
|
|
try {
|
|
data = encodeParamsHelper(funAbi, funArgs)
|
|
dataHex = data.toString()
|
|
} catch (e) {
|
|
return callback('Error encoding arguments: ' + e)
|
|
}
|
|
if (data.slice(0, 9) === 'undefined') {
|
|
dataHex = data.slice(9)
|
|
}
|
|
if (data.slice(0, 2) === '0x') {
|
|
dataHex = data.slice(2)
|
|
}
|
|
}
|
|
let contractBytecode
|
|
if (isConstructor) {
|
|
contractBytecode = contract.evm.bytecode.object
|
|
let bytecodeToDeploy = contract.evm.bytecode.object
|
|
if (bytecodeToDeploy.indexOf('_') >= 0) {
|
|
linkBytecode(contract, contracts, (err, bytecode) => {
|
|
if (err) {
|
|
callback('Error deploying required libraries: ' + err)
|
|
} else {
|
|
bytecodeToDeploy = bytecode + dataHex
|
|
return callback(null, { dataHex: bytecodeToDeploy, funAbi, funArgs, contractBytecode, contractName: contractName })
|
|
}
|
|
}, callbackStep, callbackDeployLibrary)
|
|
return
|
|
} else {
|
|
dataHex = bytecodeToDeploy + dataHex
|
|
}
|
|
} else {
|
|
dataHex = encodeFunctionId(funAbi) + dataHex
|
|
}
|
|
callback(null, { dataHex, funAbi, funArgs, contractBytecode, contractName: contractName })
|
|
}
|
|
|
|
export function atAddress () {}
|
|
|
|
export function linkBytecodeStandard (contract, contracts, callback, callbackStep, callbackDeployLibrary) {
|
|
let contractBytecode = contract.evm.bytecode.object
|
|
eachOfSeries(contract.evm.bytecode.linkReferences, (libs, file, cbFile) => {
|
|
eachOfSeries(contract.evm.bytecode.linkReferences[file], (libRef, libName, cbLibDeployed) => {
|
|
const library = contracts[file][libName]
|
|
if (library) {
|
|
deployLibrary(file + ':' + libName, libName, library, contracts, (error, address) => {
|
|
if (error) {
|
|
return cbLibDeployed(error)
|
|
}
|
|
let hexAddress = address.toString('hex')
|
|
if (hexAddress.slice(0, 2) === '0x') {
|
|
hexAddress = hexAddress.slice(2)
|
|
}
|
|
contractBytecode = linkLibraryStandard(libName, hexAddress, contractBytecode, contract)
|
|
cbLibDeployed()
|
|
}, callbackStep, callbackDeployLibrary)
|
|
} else {
|
|
cbLibDeployed('Cannot find compilation data of library ' + libName)
|
|
}
|
|
}, (error) => {
|
|
cbFile(error)
|
|
})
|
|
}, (error) => {
|
|
if (error) {
|
|
callbackStep(error)
|
|
}
|
|
callback(error, contractBytecode)
|
|
})
|
|
}
|
|
|
|
export function linkBytecodeLegacy (contract, contracts, callback, callbackStep, callbackDeployLibrary) {
|
|
const libraryRefMatch = contract.evm.bytecode.object.match(/__([^_]{1,36})__/)
|
|
if (!libraryRefMatch) {
|
|
return callback('Invalid bytecode format.')
|
|
}
|
|
const libraryName = libraryRefMatch[1]
|
|
// file_name:library_name
|
|
const libRef = libraryName.match(/(.*):(.*)/)
|
|
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)
|
|
}
|
|
const libraryShortName = libRef[2]
|
|
const library = contracts[libRef[1]][libraryShortName]
|
|
if (!library) {
|
|
return callback('Library ' + libraryName + ' not found.')
|
|
}
|
|
deployLibrary(libraryName, libraryShortName, library, contracts, (err, address) => {
|
|
if (err) {
|
|
return callback(err)
|
|
}
|
|
let hexAddress = address.toString('hex')
|
|
if (hexAddress.slice(0, 2) === '0x') {
|
|
hexAddress = hexAddress.slice(2)
|
|
}
|
|
contract.evm.bytecode.object = linkLibrary(libraryName, hexAddress, contract.evm.bytecode.object)
|
|
linkBytecode(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
|
}, callbackStep, callbackDeployLibrary)
|
|
}
|
|
|
|
export function linkBytecode (contract, contracts, callback?, callbackStep?, callbackDeployLibrary?) {
|
|
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) {
|
|
linkBytecodeStandard(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
|
} else {
|
|
linkBytecodeLegacy(contract, contracts, callback, callbackStep, callbackDeployLibrary)
|
|
}
|
|
}
|
|
|
|
export function deployLibrary (libraryName, libraryShortName, library, contracts, callback, callbackStep, callbackDeployLibrary) {
|
|
const address = library.address
|
|
if (address) {
|
|
return callback(null, address)
|
|
}
|
|
const bytecode = library.evm.bytecode.object
|
|
if (bytecode.indexOf('_') >= 0) {
|
|
linkBytecode(library, contracts, (err, bytecode) => {
|
|
if (err) callback(err)
|
|
else {
|
|
library.evm.bytecode.object = bytecode
|
|
deployLibrary(libraryName, libraryShortName, library, contracts, callback, callbackStep, callbackDeployLibrary)
|
|
}
|
|
}, callbackStep, callbackDeployLibrary)
|
|
} else {
|
|
callbackStep(`creation of library ${libraryName} pending...`)
|
|
const data = { dataHex: bytecode, funAbi: { type: 'constructor' }, funArgs: [], contractBytecode: bytecode, contractName: libraryShortName, contractABI: library.abi }
|
|
callbackDeployLibrary({ data: data, useCall: false }, (err, txResult) => {
|
|
if (err) {
|
|
return callback(err)
|
|
}
|
|
const address = txResult.receipt.contractAddress
|
|
library.address = address
|
|
callback(err, address)
|
|
})
|
|
}
|
|
}
|
|
|
|
export function linkLibraryStandardFromlinkReferences (libraryName, address, bytecode, linkReferences) {
|
|
for (const file in linkReferences) {
|
|
for (const libName in linkReferences[file]) {
|
|
if (libraryName === libName) {
|
|
bytecode = setLibraryAddress(address, bytecode, linkReferences[file][libName])
|
|
}
|
|
}
|
|
}
|
|
return bytecode
|
|
}
|
|
|
|
export function linkLibraryStandard (libraryName, address, bytecode, contract) {
|
|
return linkLibraryStandardFromlinkReferences(libraryName, address, bytecode, contract.evm.bytecode.linkReferences)
|
|
}
|
|
|
|
export function setLibraryAddress (address, bytecodeToLink, positions) {
|
|
if (positions) {
|
|
for (const pos of positions) {
|
|
const regpos = bytecodeToLink.match(new RegExp(`(.{${2 * pos.start}})(.{${2 * pos.length}})(.*)`))
|
|
if (regpos) {
|
|
bytecodeToLink = regpos[1] + address + regpos[3]
|
|
}
|
|
}
|
|
}
|
|
return bytecodeToLink
|
|
}
|
|
|
|
export function linkLibrary (libraryName, address, bytecodeToLink) {
|
|
return linkBytecodeSolc(bytecodeToLink, { [libraryName]: addHexPrefix(address) })
|
|
}
|
|
|
|
export function decodeResponse (response, fnabi) {
|
|
// Only decode if there supposed to be fields
|
|
if (fnabi.outputs && fnabi.outputs.length > 0) {
|
|
try {
|
|
let i
|
|
const outputTypes = []
|
|
for (i = 0; i < fnabi.outputs.length; i++) {
|
|
const type = fnabi.outputs[i].type
|
|
outputTypes.push(type.indexOf('tuple') === 0 ? makeFullTypeDefinition(fnabi.outputs[i]) : type)
|
|
}
|
|
if (!response || !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
|
|
// decode data
|
|
const abiCoder = new ethers.utils.AbiCoder()
|
|
const decodedObj = abiCoder.decode(outputTypes, response)
|
|
|
|
const json = {}
|
|
for (i = 0; i < outputTypes.length; i++) {
|
|
const name = fnabi.outputs[i].name
|
|
json[i] = outputTypes[i] + ': ' + (name ? name + ' ' + decodedObj[i] : decodedObj[i])
|
|
}
|
|
|
|
return json
|
|
} catch (e) {
|
|
return { error: 'Failed to decode output: ' + e }
|
|
}
|
|
}
|
|
return {}
|
|
}
|
|
|
|
export function parseFunctionParams (params) {
|
|
let args = []
|
|
// Check if parameter string starts with array or string
|
|
let startIndex = isArrayOrStringStart(params, 0) ? -1 : 0
|
|
for (let i = 0; i < params.length; i++) {
|
|
// 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
|
|
}
|
|
// 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')
|
|
}
|
|
}
|
|
} else if (params.charAt(i) === '[') { // If an array/struct opening bracket is received
|
|
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--
|
|
}
|
|
// 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')
|
|
}
|
|
}
|
|
// 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 = 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))
|
|
}
|
|
}
|
|
args = args.map(e => {
|
|
if (!Array.isArray(e)) {
|
|
return e.trim()
|
|
} else {
|
|
return e
|
|
}
|
|
})
|
|
return args
|
|
}
|
|
|
|
export function isArrayOrStringStart (str, index) {
|
|
return str.charAt(index) === '"' || str.charAt(index) === '['
|
|
}
|
|
|