Extend api.util (#4979)
* cleanupValue * abiUnencode & abiSignature * Export new functions
This commit is contained in:
parent
7e87e9e8ad
commit
e1f2ccd138
@ -17,6 +17,10 @@
|
|||||||
import Abi from '~/abi';
|
import Abi from '~/abi';
|
||||||
import Func from '~/abi/spec/function';
|
import Func from '~/abi/spec/function';
|
||||||
|
|
||||||
|
import { abiDecode } from './decode';
|
||||||
|
import { cleanupValue } from './format';
|
||||||
|
import { sha3 } from './sha3';
|
||||||
|
|
||||||
export function encodeMethodCallAbi (methodAbi = {}, values = []) {
|
export function encodeMethodCallAbi (methodAbi = {}, values = []) {
|
||||||
const func = new Func(methodAbi);
|
const func = new Func(methodAbi);
|
||||||
const tokens = Abi.encodeTokens(func.inputParamTypes(), values);
|
const tokens = Abi.encodeTokens(func.inputParamTypes(), values);
|
||||||
@ -36,3 +40,30 @@ export function abiEncode (methodName, inputTypes, data) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function abiUnencode (abi, data) {
|
||||||
|
const callsig = data.substr(2, 8);
|
||||||
|
const op = abi.find((field) => {
|
||||||
|
return field.type === 'function' &&
|
||||||
|
abiSignature(field.name, field.inputs.map((input) => input.type)).substr(2, 8) === callsig;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!op) {
|
||||||
|
console.warn(`Unknown function ID: ${callsig}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let argsByIndex = abiDecode(op.inputs.map((field) => field.type), '0x' + data.substr(10))
|
||||||
|
.map((value, index) => cleanupValue(value, op.inputs[index].type));
|
||||||
|
const argsByName = op.inputs.reduce((result, field, index) => {
|
||||||
|
result[field.name] = argsByIndex[index];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return [op.name, argsByName, argsByIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function abiSignature (name, inputs) {
|
||||||
|
return sha3(`${name}(${inputs.join()})`);
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { abiEncode, encodeMethodCallAbi } from './encode';
|
import { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode';
|
||||||
|
|
||||||
const ABI = {
|
const ABI = {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
@ -51,7 +51,11 @@ describe('api/util/encode', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('encodes variable values', () => {
|
it('encodes variable values', () => {
|
||||||
expect(abiEncode('hintUrl', ['bytes32', 'string'], ['0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'http://foo.bar/'])).to.equal(`0x${VARIABLE}`);
|
expect(
|
||||||
|
abiEncode(
|
||||||
|
'hintUrl', ['bytes32', 'string'], ['0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'http://foo.bar/']
|
||||||
|
)
|
||||||
|
).to.equal(`0x${VARIABLE}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('encodes only the data with null name', () => {
|
it('encodes only the data with null name', () => {
|
||||||
@ -60,4 +64,27 @@ describe('api/util/encode', () => {
|
|||||||
).to.equal(`0x${RESULT.substr(8)}`);
|
).to.equal(`0x${RESULT.substr(8)}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('abiUnencode', () => {
|
||||||
|
it('decodes data correctly from abi', () => {
|
||||||
|
expect(
|
||||||
|
abiUnencode([{
|
||||||
|
name: 'test',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [
|
||||||
|
{ type: 'uint', name: 'arga' }
|
||||||
|
]
|
||||||
|
}], '0x1acb6f7700000000000000000000000000000038')
|
||||||
|
).to.deep.equal(['test', { arga: 56 }, [56]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same example as in abi/util/signature.spec.js
|
||||||
|
describe('abiSignature', () => {
|
||||||
|
it('encodes baz(uint32,bool) correctly', () => {
|
||||||
|
expect(
|
||||||
|
abiSignature('baz', ['uint32', 'bool'])
|
||||||
|
).to.equal('0xcdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,38 @@ export function bytesToHex (bytes) {
|
|||||||
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join('');
|
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cleanupValue (value, type) {
|
||||||
|
// TODO: make work with arbitrary depth arrays
|
||||||
|
if (value instanceof Array && type.match(/bytes[0-9]+/)) {
|
||||||
|
// figure out if it's an ASCII string hiding in there:
|
||||||
|
let ascii = '';
|
||||||
|
|
||||||
|
for (let index = 0, ended = false; index < value.length && ascii !== null; ++index) {
|
||||||
|
const val = value[index];
|
||||||
|
|
||||||
|
if (val === 0) {
|
||||||
|
ended = true;
|
||||||
|
} else {
|
||||||
|
ascii += String.fromCharCode(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ended && val !== 0) || (!ended && (val < 32 || val >= 128))) {
|
||||||
|
ascii = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ascii === null
|
||||||
|
? bytesToHex(value)
|
||||||
|
: ascii;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.substr(0, 4) === 'uint' && +type.substr(4) <= 48) {
|
||||||
|
value = +value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export function hexToBytes (hex) {
|
export function hexToBytes (hex) {
|
||||||
const raw = toHex(hex).slice(2);
|
const raw = toHex(hex).slice(2);
|
||||||
const bytes = [];
|
const bytes = [];
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { bytesToHex, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format';
|
import { bytesToHex, cleanupValue, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format';
|
||||||
|
|
||||||
describe('api/util/format', () => {
|
describe('api/util/format', () => {
|
||||||
describe('bytesToHex', () => {
|
describe('bytesToHex', () => {
|
||||||
@ -27,6 +27,28 @@ describe('api/util/format', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cleanupValue', () => {
|
||||||
|
it('returns unknown values as the original', () => {
|
||||||
|
expect(cleanupValue('original', 'unknown')).to.equal('original');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns ascii arrays as ascii', () => {
|
||||||
|
expect(cleanupValue([97, 115, 99, 105, 105, 0], 'bytes32')).to.equal('ascii');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns non-ascii arrays as hex strings', () => {
|
||||||
|
expect(cleanupValue([97, 200, 0, 0], 'bytes4')).to.equal('0x61c80000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns uint (>48) as the original', () => {
|
||||||
|
expect(cleanupValue('original', 'uint49')).to.equal('original');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns uint (<=48) as the number value', () => {
|
||||||
|
expect(cleanupValue('12345', 'uint48')).to.equal(12345);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('hexToBytes', () => {
|
describe('hexToBytes', () => {
|
||||||
it('correctly converts an empty string', () => {
|
it('correctly converts an empty string', () => {
|
||||||
expect(hexToBytes('')).to.deep.equal([]);
|
expect(hexToBytes('')).to.deep.equal([]);
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
||||||
import { abiDecode, decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
import { abiDecode, decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
||||||
import { abiEncode, encodeMethodCallAbi } from './encode';
|
import { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode';
|
||||||
import { bytesToHex, hexToAscii, asciiToHex } from './format';
|
import { bytesToHex, hexToAscii, asciiToHex, cleanupValue } from './format';
|
||||||
import { fromWei, toWei } from './wei';
|
import { fromWei, toWei } from './wei';
|
||||||
import { sha3 } from './sha3';
|
import { sha3 } from './sha3';
|
||||||
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
||||||
@ -26,6 +26,9 @@ import { createIdentityImg } from './identity';
|
|||||||
export default {
|
export default {
|
||||||
abiDecode,
|
abiDecode,
|
||||||
abiEncode,
|
abiEncode,
|
||||||
|
abiUnencode,
|
||||||
|
abiSignature,
|
||||||
|
cleanupValue,
|
||||||
isAddressValid,
|
isAddressValid,
|
||||||
isArray,
|
isArray,
|
||||||
isFunction,
|
isFunction,
|
||||||
|
Loading…
Reference in New Issue
Block a user