Merge pull request #2443 from 0xProject/fix/abiDecoder/LogDecodingDynamicData
Fixes log decoding of dynamic data
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibAddress.sol";
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "./ERC1155.sol";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
contract MixinNonFungibleToken {
|
||||
@@ -64,7 +65,7 @@ contract MixinNonFungibleToken {
|
||||
// A base type has the NF bit but does has an index.
|
||||
return (id & TYPE_NF_BIT == TYPE_NF_BIT) && (id & NF_INDEX_MASK != 0);
|
||||
}
|
||||
|
||||
|
||||
/// @dev returns owner of a non-fungible token
|
||||
function ownerOf(uint256 id) public view returns (address) {
|
||||
return nfOwners[id];
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
/// @title ERC-1155 Multi Token Standard
|
||||
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md
|
||||
/// Note: The ERC-165 identifier for this interface is 0xd9b67a26.
|
||||
interface IERC1155 {
|
||||
|
||||
|
||||
/// @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred,
|
||||
/// including zero value transfers as well as minting or burning.
|
||||
/// Operator will always be msg.sender.
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./IERC1155.sol";
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
interface IERC1155Receiver {
|
||||
|
||||
|
||||
/// @notice Handle the receipt of a single ERC1155 token type
|
||||
/// @dev The smart contract calls this function on the recipient
|
||||
/// after a `safeTransferFrom`. This function MAY throw to revert and reject the
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "3.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Uses updated event decoding to properly decodes arrays and objects.",
|
||||
"pr": 2443
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "3.0.3",
|
||||
|
||||
@@ -121,22 +121,21 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
|
||||
}
|
||||
}
|
||||
|
||||
function assertSameOrderFromEvent(actual: any[], expected: Order): void {
|
||||
expect(actual.length === 14);
|
||||
expect(actual[0].toLowerCase()).to.be.eq(expected.makerAddress);
|
||||
expect(actual[1].toLowerCase()).to.be.eq(expected.takerAddress);
|
||||
expect(actual[2].toLowerCase()).to.be.eq(expected.feeRecipientAddress);
|
||||
expect(actual[3].toLowerCase()).to.be.eq(expected.senderAddress);
|
||||
expect(actual[4]).to.be.bignumber.eq(expected.makerAssetAmount);
|
||||
expect(actual[5]).to.be.bignumber.eq(expected.takerAssetAmount);
|
||||
expect(actual[6]).to.be.bignumber.eq(expected.makerFee);
|
||||
expect(actual[7]).to.be.bignumber.eq(expected.takerFee);
|
||||
expect(actual[8]).to.be.bignumber.eq(expected.expirationTimeSeconds);
|
||||
expect(actual[9]).to.be.bignumber.eq(expected.salt);
|
||||
expect(actual[10]).to.be.eq(expected.makerAssetData);
|
||||
expect(actual[11]).to.be.eq(expected.takerAssetData);
|
||||
expect(actual[12]).to.be.eq(expected.makerFeeAssetData);
|
||||
expect(actual[13]).to.be.eq(expected.takerFeeAssetData);
|
||||
function assertSameOrderFromEvent(actual: any, expected: Order): void {
|
||||
expect(actual.makerAddress).to.be.eq(expected.makerAddress);
|
||||
expect(actual.takerAddress).to.be.eq(expected.takerAddress);
|
||||
expect(actual.feeRecipientAddress).to.be.eq(expected.feeRecipientAddress);
|
||||
expect(actual.senderAddress).to.be.eq(expected.senderAddress);
|
||||
expect(actual.makerAssetAmount).to.bignumber.eq(expected.makerAssetAmount);
|
||||
expect(actual.takerAssetAmount).to.bignumber.eq(expected.takerAssetAmount);
|
||||
expect(actual.makerFee).to.bignumber.eq(expected.makerFee);
|
||||
expect(actual.takerFee).to.bignumber.eq(expected.takerFee);
|
||||
expect(actual.expirationTimeSeconds).to.bignumber.eq(expected.expirationTimeSeconds);
|
||||
expect(actual.salt).to.bignumber.eq(expected.salt);
|
||||
expect(actual.makerAssetData).to.eq(expected.makerAssetData);
|
||||
expect(actual.takerAssetData).to.eq(expected.takerAssetData);
|
||||
expect(actual.makerFeeAssetData).to.eq(expected.makerFeeAssetData);
|
||||
expect(actual.takerFeeAssetData).to.eq(expected.takerFeeAssetData);
|
||||
}
|
||||
|
||||
describe('fillOrKillOrder', () => {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "4.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Replaced ethers with 0x ABI Encoder for decoding logs. Fixes decoding for arrays/objects.",
|
||||
"pr": 2443
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "4.0.3",
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import {
|
||||
AbiDefinition,
|
||||
AbiType,
|
||||
DataItem,
|
||||
DecodedLogArgs,
|
||||
EventAbi,
|
||||
EventParameter,
|
||||
LogEntry,
|
||||
LogWithDecodedArgs,
|
||||
MethodAbi,
|
||||
RawLog,
|
||||
SolidityTypes,
|
||||
} from 'ethereum-types';
|
||||
import * as ethers from 'ethers';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AbiEncoder } from '.';
|
||||
import { addressUtils } from './address_utils';
|
||||
import { BigNumber } from './configured_bignumber';
|
||||
import { DecodedCalldata, SelectorToFunctionInfo } from './types';
|
||||
|
||||
/**
|
||||
@@ -56,58 +53,53 @@ export class AbiDecoder {
|
||||
* @return The decoded log if the requisite ABI was available. Otherwise the log unaltered.
|
||||
*/
|
||||
public tryToDecodeLogOrNoop<ArgsType extends DecodedLogArgs>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog {
|
||||
// Lookup event corresponding to log
|
||||
const eventId = log.topics[0];
|
||||
const numIndexedArgs = log.topics.length - 1;
|
||||
if (this._eventIds[eventId] === undefined || this._eventIds[eventId][numIndexedArgs] === undefined) {
|
||||
return log;
|
||||
}
|
||||
const event = this._eventIds[eventId][numIndexedArgs];
|
||||
const ethersInterface = new ethers.utils.Interface([event]);
|
||||
const decodedParams: DecodedLogArgs = {};
|
||||
let topicsIndex = 1;
|
||||
|
||||
let decodedData: any[];
|
||||
try {
|
||||
decodedData = ethersInterface.events[event.name].decode(log.data);
|
||||
} catch (error) {
|
||||
if (error.code === ethers.errors.INVALID_ARGUMENT) {
|
||||
// Because we index events by Method ID, and Method IDs are derived from the method
|
||||
// name and the input parameters, it's possible that the return value of the event
|
||||
// does not match our ABI. If that's the case, then ethers will throw an error
|
||||
// when we try to parse the event. We handle that case here by returning the log rather
|
||||
// than throwing an error.
|
||||
// Create decoders for indexed data
|
||||
const indexedDataDecoders = _.mapValues(_.filter(event.inputs, { indexed: true }), input =>
|
||||
// tslint:disable:next-line no-unnecessary-type-assertion
|
||||
AbiEncoder.create(input as DataItem),
|
||||
);
|
||||
|
||||
// Decode indexed data
|
||||
const decodedIndexedData = _.map(
|
||||
log.topics.slice(1), // ignore first topic, which is the event id.
|
||||
(input, i) => indexedDataDecoders[i].decode(input),
|
||||
);
|
||||
|
||||
// Decode non-indexed data
|
||||
const decodedNonIndexedData = AbiEncoder.create(_.filter(event.inputs, { indexed: false })).decodeAsArray(
|
||||
log.data,
|
||||
);
|
||||
|
||||
// Construct DecodedLogArgs struct by mapping event parameters to their respective decoded argument.
|
||||
const decodedArgs: DecodedLogArgs = {};
|
||||
let indexedOffset = 0;
|
||||
let nonIndexedOffset = 0;
|
||||
for (const param of event.inputs) {
|
||||
const value = param.indexed
|
||||
? decodedIndexedData[indexedOffset++]
|
||||
: decodedNonIndexedData[nonIndexedOffset++];
|
||||
|
||||
if (value === undefined) {
|
||||
return log;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
let didFailToDecode = false;
|
||||
_.forEach(event.inputs, (param: EventParameter, i: number) => {
|
||||
// Indexed parameters are stored in topics. Non-indexed ones in decodedData
|
||||
let value: BigNumber | string | number = param.indexed ? log.topics[topicsIndex++] : decodedData[i];
|
||||
if (value === undefined) {
|
||||
didFailToDecode = true;
|
||||
return;
|
||||
}
|
||||
if (param.type === SolidityTypes.Address) {
|
||||
const baseHex = 16;
|
||||
value = addressUtils.padZeros(new BigNumber((value as string).toLowerCase()).toString(baseHex));
|
||||
} else if (param.type === SolidityTypes.Uint256 || param.type === SolidityTypes.Uint) {
|
||||
value = new BigNumber(value);
|
||||
} else if (param.type === SolidityTypes.Uint8) {
|
||||
value = new BigNumber(value).toNumber();
|
||||
}
|
||||
decodedParams[param.name] = value;
|
||||
});
|
||||
|
||||
if (didFailToDecode) {
|
||||
return log;
|
||||
} else {
|
||||
return {
|
||||
...log,
|
||||
event: event.name,
|
||||
args: decodedParams,
|
||||
};
|
||||
decodedArgs[param.name] = value;
|
||||
}
|
||||
|
||||
// Decoding was successful. Return decoded log.
|
||||
return {
|
||||
...log,
|
||||
event: event.name,
|
||||
args: decodedArgs as ArgsType,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Decodes calldata for a known ABI.
|
||||
|
||||
Reference in New Issue
Block a user