Added getABIDecodedTransactionData and getABIDecodedReturnData to contract wrappers + test cases

This commit is contained in:
Greg Hysen 2019-07-26 23:04:34 +02:00
parent 57318c0041
commit db74db622e
9 changed files with 273 additions and 1 deletions

View File

@ -30,6 +30,7 @@
"src/ReentrancyGuard.sol",
"src/SafeMath.sol",
"src/interfaces/IOwnable.sol",
"test/TestAbi.sol",
"test/TestConstants.sol",
"test/TestLibAddressArray.sol",
"test/TestLibBytes.sol",

View File

@ -0,0 +1,141 @@
/*
Copyright 2018 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.5;
pragma experimental ABIEncoderV2;
contract TestAbi {
struct ComplexInput {
uint256 foo;
bytes bar;
string car;
}
struct ComplexOutput {
ComplexInput input;
bytes lorem;
bytes ipsum;
string dolor;
}
function noInputNoOutput()
public
pure
{
// NOP
require(true == true);
}
function noInputSimpleOutput()
public
pure
returns (uint256)
{
return 1991;
}
function simpleInputNoOutput(uint256)
public
pure
{
// NOP
require(true == true);
}
function simpleInputSimpleOutput(uint256)
public
pure
returns (uint256)
{
return 1991;
}
function complexInputComplexOutput(ComplexInput memory complexInput)
public
pure
returns (ComplexOutput memory)
{
return ComplexOutput({
input: complexInput,
lorem: hex'12345678',
ipsum: hex'87654321',
dolor: "amet"
});
}
function multiInputMultiOutput(
uint256,
bytes memory,
string memory
)
public
pure
returns (
bytes memory,
bytes memory,
string memory
)
{
return (
hex'12345678',
hex'87654321',
"amet"
);
}
function ()
external
{
address addr = address(this);
assembly {
// copy calldata to memory
calldatacopy(
0x0,
0x0,
calldatasize()
)
// execute transaction
let success := call(
gas, // send all gas.
addr, // call into this contract.
0, // don't send any ether.
0x0, // input is `txData`.
calldatasize(), // input length is that of `txData`.
0x0, // any return data goes at mem address 0x0.
0 // there is no fixed return value size.
)
// copy return data to memory
returndatacopy(
0x0,
0x0,
returndatasize()
)
// rethrow any exceptions
if iszero(success) {
revert(0, returndatasize())
}
// return call results
return(0, returndatasize())
}
}
}

View File

@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "./generated-artifacts/@(Address|IOwnable|LibBytes|Ownable|ReentrancyGuard|SafeMath|TestConstants|TestLibAddressArray|TestLibBytes|TestLogDecoding|TestLogDecodingDownstream).json",
"abis": "./generated-artifacts/@(Address|IOwnable|LibBytes|Ownable|ReentrancyGuard|SafeMath|TestAbi|TestConstants|TestLibAddressArray|TestLibBytes|TestLogDecoding|TestLogDecodingDownstream).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@ -11,6 +11,7 @@ import * as LibBytes from '../generated-artifacts/LibBytes.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
import * as ReentrancyGuard from '../generated-artifacts/ReentrancyGuard.json';
import * as SafeMath from '../generated-artifacts/SafeMath.json';
import * as TestAbi from '../generated-artifacts/TestAbi.json';
import * as TestConstants from '../generated-artifacts/TestConstants.json';
import * as TestLibAddressArray from '../generated-artifacts/TestLibAddressArray.json';
import * as TestLibBytes from '../generated-artifacts/TestLibBytes.json';
@ -28,4 +29,5 @@ export const artifacts = {
TestLibBytes: TestLibBytes as ContractArtifact,
TestLogDecoding: TestLogDecoding as ContractArtifact,
TestLogDecodingDownstream: TestLogDecodingDownstream as ContractArtifact,
TestAbi: TestAbi as ContractArtifact,
};

View File

@ -9,6 +9,7 @@ export * from '../generated-wrappers/lib_bytes';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/reentrancy_guard';
export * from '../generated-wrappers/safe_math';
export * from '../generated-wrappers/test_abi';
export * from '../generated-wrappers/test_constants';
export * from '../generated-wrappers/test_lib_address_array';
export * from '../generated-wrappers/test_lib_bytes';

View File

@ -0,0 +1,95 @@
import { chaiSetup, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types';
import { artifacts, TestAbiContract } from '../src';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe.only('TestAbi', () => {
let testAbi: TestAbiContract;
const runTestAsync = async (contractMethod: any, input: any, output: any) => {
const transaction = contractMethod.getABIEncodedTransactionData(input);
// try decoding transaction
const decodedInput = contractMethod.getABIDecodedTransactionData(transaction);
expect(decodedInput, 'decoded input').to.be.deep.equal(input);
// execute transaction
const rawOutput = await web3Wrapper.callAsync({
to: testAbi.address,
data: transaction,
});
// try decoding output
const decodedOutput = contractMethod.getABIDecodedReturnData(rawOutput);
expect(decodedOutput, 'decoded output').to.be.deep.equal(output);
};
before(async () => {
testAbi = await TestAbiContract.deployFrom0xArtifactAsync(artifacts.TestAbi, provider, txDefaults, artifacts);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('Encoding/Decoding Transaction Data and Return Values', () => {
it('should successfully encode/decode (no input / no output)', async () => {
const input = undefined;
const output = undefined;
await runTestAsync(testAbi.noInputNoOutput, input, output);
});
it('should successfully encode/decode (no input / simple output)', async () => {
const input = undefined;
const output = new BigNumber(1991);
await runTestAsync(testAbi.noInputSimpleOutput, input, output);
});
it('should successfully encode/decode (simple input / no output)', async () => {
const input = new BigNumber(1991);
const output = undefined;
await runTestAsync(testAbi.simpleInputNoOutput, input, output);
});
it('should successfully encode/decode (simple input / simple output)', async () => {
const input = new BigNumber(16);
const output = new BigNumber(1991);
await runTestAsync(testAbi.simpleInputSimpleOutput, input, output);
});
it('should successfully encode/decode (complex input / complex output)', async () => {
const input = {
foo: new BigNumber(1991),
bar: '0x1234',
car: 'zoom zoom',
};
const output = {
input: input,
lorem: '0x12345678',
ipsum: '0x87654321',
dolor: 'amet',
};
await runTestAsync(testAbi.complexInputComplexOutput, input, output);
});
it('should successfully encode/decode (complex input / complex output)', async () => {
const input = [new BigNumber(1991), '0x1234', 'zoom zoom'];
const output = ['0x12345678', '0x87654321', 'amet'];
const transaction = testAbi.multiInputMultiOutput.getABIEncodedTransactionData(
input[0] as BigNumber,
input[1] as string,
input[2] as string,
);
// try decoding transaction
const decodedInput = testAbi.multiInputMultiOutput.getABIDecodedTransactionData(transaction);
expect(decodedInput, 'decoded input').to.be.deep.equal(input);
// execute transaction
const rawOutput = await web3Wrapper.callAsync({
to: testAbi.address,
data: transaction,
});
// try decoding output
const decodedOutput = testAbi.multiInputMultiOutput.getABIDecodedReturnData(rawOutput);
expect(decodedOutput, 'decoded output').to.be.deep.equal(output);
});
});
});

View File

@ -9,6 +9,7 @@
"generated-artifacts/Ownable.json",
"generated-artifacts/ReentrancyGuard.json",
"generated-artifacts/SafeMath.json",
"generated-artifacts/TestAbi.json",
"generated-artifacts/TestConstants.json",
"generated-artifacts/TestLibAddressArray.json",
"generated-artifacts/TestLibBytes.json",

View File

@ -44,3 +44,21 @@ getABIEncodedTransactionData(
const abiEncodedTransactionData = self._strictEncodeArguments('{{this.functionSignature}}', [{{> normalized_params inputs=inputs}}]);
return abiEncodedTransactionData;
},
getABIDecodedTransactionData(
returnData: string
): ({{> return_type inputs=inputs ~}}) {
const self = this as any as {{contractName}}Contract;
const abiEncoder = self._lookupAbiEncoder('{{this.functionSignature}}');
// tslint:disable boolean-naming
const abiDecodedReturnData = abiEncoder.strictDecode<{{> return_type inputs=inputs}}>(returnData);
return abiDecodedReturnData;
},
getABIDecodedReturnData(
returnData: string
): ({{> return_type outputs=outputs ~}}) {
const self = this as any as {{contractName}}Contract;
const abiEncoder = self._lookupAbiEncoder('{{this.functionSignature}}');
// tslint:disable boolean-naming
const abiDecodedReturnData = abiEncoder.strictDecodeReturnValue<{{> return_type outputs=outputs}}>(returnData);
return abiDecodedReturnData;
},

View File

@ -34,6 +34,19 @@ export class MethodDataType extends AbstractSetDataType {
return value;
}
public strictDecode<T>(calldata: string, rules?: DecodingRules): T {
const value = super.decode(calldata, rules, this._methodSelector);
const valueAsArray: any = _.isObject(value) ? _.values(value) : [value];
switch (valueAsArray.length) {
case 0:
return undefined as any;
case 1:
return valueAsArray[0];
default:
return valueAsArray;
}
}
public encodeReturnValues(value: any, rules?: EncodingRules): string {
const returnData = this._returnDataType.encode(value, rules);
return returnData;