abi-gen/Py: fix incorrect method return types and other small issues (#2345)

* .gitignore gen'd Python staking contract wrappers

* abi-gen/test-cli: check Python type hints in lint

* sra_client.py: Update doc for replicating examples

* abi-gen/Py: fix call() return type incl. tx hash

Previously, generated wrappers for contract methods were including type
hints that suggested that a call() (as opposed to a send_transaction())
might return either the underlying return type or a transaction hash.
This doesn't make sense because a call() will never return a TX hash.
Now, the type hint just has the return type of the underlying method.

* abi-gen: fix test_cli:lint checking wrong code

test_cli:lint is meant to be a rudimentary test of the code generated by
abi-gen.  However, previously, this script was incorporated into `yarn
lint`, and in CircleCI `static-tests` runs independently of `build`.
Consequently, the runs of test_cli:lint were checking the OLD code,
which was previously generated and checked in to git, NOT the code
generated with the version of abi-gen represented by the git repo.  Now,
test_cli:lint happens during `yarn test` rather than `yarn lint`,
because `yarn test` IS dependent on `yarn build`.

* contract_wrappers.py: fix misplaced doc

Previously, the routines `order_to_jsdict()` and `jsdict_to_order()`
were moved from contract_wrappers.exchange.types to
contract_wrappers.order_conversions.  However, the module-level
docstring describing those routines was accidentally left behind in
exchange.types.

* abi-gen/Py: stop documenting return types for TXs

Previously the send_transaction() interface included docstring
documentation for the return types of the contract method, but that
doesn't make any sense because send_transaction() returns a transaction
hash rather than any actual return values.

* abi-gen/Py: stop gen'ing send_tx for const methods

* abi-gen/Py: add build_tx to contract methods

* abi-gen/Py: fix incorrect method return types

Fixes #2298 .

* abi-gen/Py: rm validator arg to no-input methods

* abi-gen: mv Py Handlebars helpers to own module

Move all existing Python-related Handlebars helpers to the newly created
python_handlebars_helpers module.

* abi-gen: refactor internal interface

No functionality is changed.  Sole purpose of this commit is to
facilitate an upcoming commit.

* abi-gen: refactor internal interface

No functionality is changed.  Sole purpose of this commit is to
facilitate an upcoming commit.

* abi-gen/Py: name tuples w/internalType, not hash

Use the new `internalType` field on the `DataItem`s in the contract
artifact to give generated tuple classes a better name than just hashing
their component field names.

* Fix CI errors

* abi-gen/Py/wrapper: make internal member private

* Update CHANGELOGs
This commit is contained in:
F. Eugene Aumson 2019-11-15 18:27:45 -05:00 committed by GitHub
parent 9e3cc379ed
commit df97b20913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 679 additions and 847 deletions

2
.gitignore vendored
View File

@ -175,6 +175,8 @@ python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_validator/__in
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_wallet/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_wallet/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/multi_asset_proxy/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/multi_asset_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_validator/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_validator/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/static_call_proxy/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/static_call_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/weth9/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/weth9/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_token/__init__.py python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_token/__init__.py

View File

@ -5,6 +5,26 @@
{ {
"note": "Refactored TS wrapper templates to result in a more succint interface. See https://github.com/0xProject/0x-monorepo/pull/2325 for details.", "note": "Refactored TS wrapper templates to result in a more succint interface. See https://github.com/0xProject/0x-monorepo/pull/2325 for details.",
"pr": 2284 "pr": 2284
},
{
"note": "Python: Corrected return types and values for call() interface to generated method wrappers. (Fixes #2298)",
"pr": 2345
},
{
"note": "Python: Stopped generating send_transaction() interface for constant (view/pure) methods",
"pr": 2345
},
{
"note": "Python: Added a build_transaction() interface to contract method classes",
"pr": 2345
},
{
"note": "Python: Removed `validator` argument to contract method classes for methods that don't have any inputs",
"pr": 2345
},
{
"note": "Python: Changed the names of generated tuples to use the `internalType` field in the ABI, if it's present, resulting in human readable struct names rather than hashes of component field names.",
"pr": 2345
} }
] ]
}, },

View File

@ -8,7 +8,7 @@
"main": "lib/src/index.js", "main": "lib/src/index.js",
"types": "lib/src/index.d.ts", "types": "lib/src/index.d.ts",
"scripts": { "scripts": {
"lint": "tslint --format stylish --project . && yarn test_cli:lint", "lint": "tslint --format stylish --project .",
"fix": "tslint --fix --format stylish --project . && yarn lint-contracts", "fix": "tslint --fix --format stylish --project . && yarn lint-contracts",
"clean": "shx rm -rf lib && yarn test_cli:clean", "clean": "shx rm -rf lib && yarn test_cli:clean",
"build": "tsc -b && yarn generate_contract_wrappers && yarn prettier_contract_wrappers && yarn test_cli:build", "build": "tsc -b && yarn generate_contract_wrappers && yarn prettier_contract_wrappers && yarn test_cli:build",
@ -18,15 +18,16 @@
"run_mocha": "(uname -s | grep -q Darwin && echo 'HACK! skipping mocha run due to https://github.com/0xProject/0x-monorepo/issues/2000') || mocha --require source-map-support/register --require make-promises-safe lib/test/*_test.js --timeout 100000 --bail --exit", "run_mocha": "(uname -s | grep -q Darwin && echo 'HACK! skipping mocha run due to https://github.com/0xProject/0x-monorepo/issues/2000') || mocha --require source-map-support/register --require make-promises-safe lib/test/*_test.js --timeout 100000 --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"test_cli": "run-s test_cli:test_typescript diff_contract_wrappers", "test_cli": "run-p test_cli:test_typescript diff_contract_wrappers test_cli:lint",
"test_cli:clean": "rm -rf test-cli/test_typescript/lib", "test_cli:clean": "rm -rf test-cli/test_typescript/lib",
"test_cli:build": "tsc --project test-cli/tsconfig.json", "test_cli:build": "tsc --project test-cli/tsconfig.json",
"test_cli:test_typescript": "mocha --require source-map-support/register --require make-promises-safe test-cli/test_typescript/lib/**/*_test.js --timeout 100000 --bail --exit", "test_cli:test_typescript": "mocha --require source-map-support/register --require make-promises-safe test-cli/test_typescript/lib/**/*_test.js --timeout 100000 --bail --exit",
"test_cli:lint": "run-p test_cli:lint_solidity test_cli:lint_python # test_cli:lint_typescript # HACK: typescript lint disabled because prettier fails", "test_cli:lint": "run-p test_cli:lint_solidity test_cli:lint_python # test_cli:lint_typescript # HACK: typescript lint disabled because prettier fails",
"test_cli:lint_solidity": "solhint -c ../../contracts/.solhint.json test-cli/fixtures/contracts/*.sol", "test_cli:lint_solidity": "solhint -c ../../contracts/.solhint.json test-cli/fixtures/contracts/*.sol",
"test_cli:lint_typescript": "prettier --check ./test-cli/output/typescript/* --config ../../.prettierrc", "test_cli:lint_typescript": "prettier --check ./test-cli/output/typescript/* --config ../../.prettierrc",
"test_cli:lint_python": "pip install -r test-cli/fixtures/python-requirements.txt && run-p test_cli:lint_python:black test_cli:lint_python:pylint", "test_cli:lint_python": "pip install -r test-cli/fixtures/python-requirements.txt && run-p test_cli:lint_python:black test_cli:lint_python:mypy test_cli:lint_python:pylint",
"test_cli:lint_python:black": "black --line-length=79 --check ./test-cli/output/python/*", "test_cli:lint_python:black": "black --line-length=79 --check ./test-cli/output/python/*",
"test_cli:lint_python:mypy": "MYPYPATH=./stubs mypy ./test-cli/output/python",
"test_cli:lint_python:pylint": "PYTHONPATH=../../python-packages/contract_wrappers/src pylint --rcfile=test-cli/fixtures/pylintrc ./test-cli/output/python/*", "test_cli:lint_python:pylint": "PYTHONPATH=../../python-packages/contract_wrappers/src pylint --rcfile=test-cli/fixtures/pylintrc ./test-cli/output/python/*",
"rebuild_and_test": "run-s build test", "rebuild_and_test": "run-s build test",
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",

View File

@ -4,22 +4,14 @@ import chalk from 'chalk';
import * as changeCase from 'change-case'; import * as changeCase from 'change-case';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import * as cliFormat from 'cli-format'; import * as cliFormat from 'cli-format';
import { import { AbiDefinition, ConstructorAbi, ContractAbi, DevdocOutput, EventAbi, MethodAbi } from 'ethereum-types';
AbiDefinition,
ConstructorAbi,
ContractAbi,
DataItem,
DevdocOutput,
EventAbi,
MethodAbi,
} from 'ethereum-types';
import { sync as globSync } from 'glob'; import { sync as globSync } from 'glob';
import * as Handlebars from 'handlebars'; import * as Handlebars from 'handlebars';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as mkdirp from 'mkdirp'; import * as mkdirp from 'mkdirp';
import toposort = require('toposort');
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import { registerPythonHelpers } from './python_handlebars_helpers';
import { ContextData, ContractsBackend, ParamKind } from './types'; import { ContextData, ContractsBackend, ParamKind } from './types';
import { utils } from './utils'; import { utils } from './utils';
@ -146,154 +138,6 @@ function registerTypeScriptHelpers(): void {
); );
} }
function registerPythonHelpers(): void {
Handlebars.registerHelper('equal', (lhs: any, rhs: any) => {
return lhs === rhs;
});
Handlebars.registerHelper('safeString', (str: string) => new Handlebars.SafeString(str));
Handlebars.registerHelper('parameterType', utils.solTypeToPyType.bind(utils));
Handlebars.registerHelper('returnType', utils.solTypeToPyType.bind(utils));
Handlebars.registerHelper('toPythonIdentifier', utils.toPythonIdentifier.bind(utils));
Handlebars.registerHelper('sanitizeDevdocDetails', (_methodName: string, devdocDetails: string, indent: number) => {
// wrap to 80 columns, assuming given indent, so that generated
// docstrings can pass pycodestyle checks. also, replace repeated
// spaces, likely caused by leading indents in the Solidity, because
// they cause repeated spaces in the output, and in particular they may
// cause repeated spaces at the beginning of a line in the docstring,
// which leads to "unexpected indent" errors when generating
// documentation.
if (devdocDetails === undefined || devdocDetails.length === 0) {
return '';
}
const columnsPerRow = 80;
return new Handlebars.SafeString(
`\n${cliFormat.wrap(devdocDetails.replace(/ +/g, ' ') || '', {
paddingLeft: ' '.repeat(indent),
width: columnsPerRow,
ansi: false,
})}\n`,
);
});
Handlebars.registerHelper('makeParameterDocstringRole', (name: string, description: string, indent: number) => {
let docstring = `:param ${name}:`;
if (description && description.length > 0) {
docstring = `${docstring} ${description}`;
}
return new Handlebars.SafeString(utils.wrapPythonDocstringRole(docstring, indent));
});
Handlebars.registerHelper(
'makeReturnDocstringRole',
(description: string, indent: number) =>
new Handlebars.SafeString(
utils.wrapPythonDocstringRole(`:returns: ${description.replace(/ +/g, ' ')}`, indent),
),
);
Handlebars.registerHelper(
'makeEventParameterDocstringRole',
(eventName: string, indent: number) =>
new Handlebars.SafeString(
utils.wrapPythonDocstringRole(
`:param tx_hash: hash of transaction emitting ${eventName} event`,
indent,
),
),
);
Handlebars.registerHelper('tupleDefinitions', (abisJSON: string) => {
const abis: AbiDefinition[] = JSON.parse(abisJSON);
// build an array of objects, each of which has one key, the Python
// name of a tuple, with a string value holding the body of a Python
// class representing that tuple. Using a key-value object conveniently
// filters duplicate references to the same tuple.
const tupleBodies: { [pythonTupleName: string]: string } = {};
// build an array of tuple dependencies, whose format conforms to the
// expected input to toposort, a function to do a topological sort,
// which will help us declare tuples in the proper order, avoiding
// references to tuples that haven't been declared yet.
const tupleDependencies: Array<[string, string]> = [];
for (const abi of abis) {
let parameters: DataItem[] = [];
if (abi.hasOwnProperty('inputs')) {
// HACK(feuGeneA): using "as MethodAbi" below, but abi
// could just as well be ConstructorAbi, EventAbi, etc. We
// just need to tell the TypeScript compiler that it's NOT
// FallbackAbi, or else it would complain, "Property
// 'inputs' does not exist on type 'AbiDefinition'.
// Property 'inputs' does not exist on type
// 'FallbackAbi'.", despite the enclosing if statement.
// tslint:disable:no-unnecessary-type-assertion
parameters = parameters.concat((abi as MethodAbi).inputs);
}
if (abi.hasOwnProperty('outputs')) {
// HACK(feuGeneA): same as described above, except here we
// KNOW that it's a MethodAbi, given the enclosing if
// statement, because that's the only AbiDefinition subtype
// that actually has an outputs field.
parameters = parameters.concat((abi as MethodAbi).outputs);
}
for (const parameter of parameters) {
utils.extractTuples(parameter, tupleBodies, tupleDependencies);
}
}
// build up a list of tuples to declare. the order they're pushed into
// this array is the order they will be declared.
const tuplesToDeclare = [];
// first push the ones that have dependencies
tuplesToDeclare.push(...toposort(tupleDependencies));
// then push any remaining bodies (the ones that DON'T have
// dependencies)
for (const pythonTupleName in tupleBodies) {
if (!tuplesToDeclare.includes(pythonTupleName)) {
tuplesToDeclare.push(pythonTupleName);
}
}
// now iterate over those ordered tuples-to-declare, and prefix the
// corresponding class bodies with their class headers, to form full
// class declarations.
const tupleDeclarations = [];
for (const pythonTupleName of tuplesToDeclare) {
if (tupleBodies[pythonTupleName]) {
tupleDeclarations.push(
`class ${pythonTupleName}(TypedDict):\n """Python representation of a tuple or struct.\n\n Solidity compiler output does not include the names of structs that appear\n in method definitions. A tuple found in an ABI may have been written in\n Solidity as a literal, anonymous tuple, or it may have been written as a\n named \`struct\`:code:, but there is no way to tell from the compiler\n output. This class represents a tuple that appeared in a method\n definition. Its name is derived from a hash of that tuple's field names,\n and every method whose ABI refers to a tuple with that same list of field\n names will have a generated wrapper method that refers to this class.\n\n Any members of type \`bytes\`:code: should be encoded as UTF-8, which can be\n accomplished via \`str.encode("utf_8")\`:code:\n """${
tupleBodies[pythonTupleName]
}`,
);
}
}
// finally, join the class declarations together for the output file
return new Handlebars.SafeString(tupleDeclarations.join('\n\n\n'));
});
Handlebars.registerHelper('docBytesIfNecessary', (abisJSON: string) => {
const abis: AbiDefinition[] = JSON.parse(abisJSON);
// see if any ABIs accept params of type bytes, and if so then emit
// explanatory documentation string.
for (const abi of abis) {
if (abi.hasOwnProperty('inputs')) {
// HACK(feuGeneA): using "as MethodAbi" below, but abi
// could just as well be ConstructorAbi, EventAbi, etc. We
// just need to tell the TypeScript compiler that it's NOT
// FallbackAbi, or else it would complain, "Property
// 'inputs' does not exist on type 'AbiDefinition'.
// Property 'inputs' does not exist on type
// 'FallbackAbi'.", despite the enclosing if statement.
// tslint:disable:no-unnecessary-type-assertion
if ((abi as MethodAbi).inputs) {
for (const input of (abi as MethodAbi).inputs) {
if (input.type === 'bytes') {
return new Handlebars.SafeString(
'\n\n All method parameters of type `bytes`:code: should be encoded as UTF-8,\n which can be accomplished via `str.encode("utf_8")`:code:.\n ',
);
}
}
}
}
}
return '';
});
Handlebars.registerHelper(
'toPythonClassname',
(sourceName: string) => new Handlebars.SafeString(changeCase.pascal(sourceName)),
);
}
if (args.language === 'TypeScript') { if (args.language === 'TypeScript') {
registerTypeScriptHelpers(); registerTypeScriptHelpers();
} else if (args.language === 'Python') { } else if (args.language === 'Python') {

View File

@ -0,0 +1,205 @@
import * as changeCase from 'change-case';
import * as cliFormat from 'cli-format';
import * as Handlebars from 'handlebars';
import toposort = require('toposort');
import { AbiDefinition, DataItem, MethodAbi } from 'ethereum-types';
import { utils } from './utils';
/**
* Register all Python-related Handlebars helpers
*/
export function registerPythonHelpers(): void {
Handlebars.registerHelper('equal', (lhs: any, rhs: any) => {
return lhs === rhs;
});
Handlebars.registerHelper('safeString', (str: string) => new Handlebars.SafeString(str));
Handlebars.registerHelper('parameterType', utils.solTypeToPyType.bind(utils));
Handlebars.registerHelper('returnType', utils.solTypeToPyType.bind(utils));
Handlebars.registerHelper('toPythonIdentifier', utils.toPythonIdentifier.bind(utils));
Handlebars.registerHelper('sanitizeDevdocDetails', (_methodName: string, devdocDetails: string, indent: number) => {
// wrap to 80 columns, assuming given indent, so that generated
// docstrings can pass pycodestyle checks. also, replace repeated
// spaces, likely caused by leading indents in the Solidity, because
// they cause repeated spaces in the output, and in particular they may
// cause repeated spaces at the beginning of a line in the docstring,
// which leads to "unexpected indent" errors when generating
// documentation.
if (devdocDetails === undefined || devdocDetails.length === 0) {
return '';
}
const columnsPerRow = 80;
return new Handlebars.SafeString(
`\n${cliFormat.wrap(devdocDetails.replace(/ +/g, ' ') || '', {
paddingLeft: ' '.repeat(indent),
width: columnsPerRow,
ansi: false,
})}\n`,
);
});
Handlebars.registerHelper('makeParameterDocstringRole', (name: string, description: string, indent: number) => {
let docstring = `:param ${name}:`;
if (description && description.length > 0) {
docstring = `${docstring} ${description}`;
}
return new Handlebars.SafeString(utils.wrapPythonDocstringRole(docstring, indent));
});
Handlebars.registerHelper(
'makeReturnDocstringRole',
(description: string, indent: number) =>
new Handlebars.SafeString(
utils.wrapPythonDocstringRole(`:returns: ${description.replace(/ +/g, ' ')}`, indent),
),
);
Handlebars.registerHelper(
'makeEventParameterDocstringRole',
(eventName: string, indent: number) =>
new Handlebars.SafeString(
utils.wrapPythonDocstringRole(
`:param tx_hash: hash of transaction emitting ${eventName} event`,
indent,
),
),
);
Handlebars.registerHelper('tupleDefinitions', (abisJSON: string) => {
const abis: AbiDefinition[] = JSON.parse(abisJSON);
// build an array of objects, each of which has one key, the Python
// name of a tuple, with a string value holding the body of a Python
// class representing that tuple. Using a key-value object conveniently
// filters duplicate references to the same tuple.
const tupleBodies: { [pythonTupleName: string]: string } = {};
// build an array of tuple dependencies, whose format conforms to the
// expected input to toposort, a function to do a topological sort,
// which will help us declare tuples in the proper order, avoiding
// references to tuples that haven't been declared yet.
const tupleDependencies: Array<[string, string]> = [];
for (const abi of abis) {
let parameters: DataItem[] = [];
if (abi.hasOwnProperty('inputs')) {
// HACK(feuGeneA): using "as MethodAbi" below, but abi
// could just as well be ConstructorAbi, EventAbi, etc. We
// just need to tell the TypeScript compiler that it's NOT
// FallbackAbi, or else it would complain, "Property
// 'inputs' does not exist on type 'AbiDefinition'.
// Property 'inputs' does not exist on type
// 'FallbackAbi'.", despite the enclosing if statement.
// tslint:disable:no-unnecessary-type-assertion
parameters = parameters.concat((abi as MethodAbi).inputs);
}
if (abi.hasOwnProperty('outputs')) {
// HACK(feuGeneA): same as described above, except here we
// KNOW that it's a MethodAbi, given the enclosing if
// statement, because that's the only AbiDefinition subtype
// that actually has an outputs field.
parameters = parameters.concat((abi as MethodAbi).outputs);
}
for (const parameter of parameters) {
utils.extractTuples(parameter, tupleBodies, tupleDependencies);
}
}
// build up a list of tuples to declare. the order they're pushed into
// this array is the order they will be declared.
const tuplesToDeclare = [];
// first push the ones that have dependencies
tuplesToDeclare.push(...toposort(tupleDependencies));
// then push any remaining bodies (the ones that DON'T have
// dependencies)
for (const pythonTupleName in tupleBodies) {
if (!tuplesToDeclare.includes(pythonTupleName)) {
tuplesToDeclare.push(pythonTupleName);
}
}
// now iterate over those ordered tuples-to-declare, and prefix the
// corresponding class bodies with their class headers, to form full
// class declarations.
const tupleDeclarations = [];
for (const pythonTupleName of tuplesToDeclare) {
if (tupleBodies[pythonTupleName]) {
tupleDeclarations.push(
`class ${pythonTupleName}(TypedDict):\n """Python representation of a tuple or struct.\n\n Solidity compiler output does not include the names of structs that appear\n in method definitions. A tuple found in an ABI may have been written in\n Solidity as a literal, anonymous tuple, or it may have been written as a\n named \`struct\`:code:, but there is no way to tell from the compiler\n output. This class represents a tuple that appeared in a method\n definition. Its name is derived from a hash of that tuple's field names,\n and every method whose ABI refers to a tuple with that same list of field\n names will have a generated wrapper method that refers to this class.\n\n Any members of type \`bytes\`:code: should be encoded as UTF-8, which can be\n accomplished via \`str.encode("utf_8")\`:code:\n """${
tupleBodies[pythonTupleName]
}`,
);
}
}
// finally, join the class declarations together for the output file
return new Handlebars.SafeString(tupleDeclarations.join('\n\n\n'));
});
Handlebars.registerHelper('docBytesIfNecessary', (abisJSON: string) => {
const abis: AbiDefinition[] = JSON.parse(abisJSON);
// see if any ABIs accept params of type bytes, and if so then emit
// explanatory documentation string.
for (const abi of abis) {
if (abi.hasOwnProperty('inputs')) {
// HACK(feuGeneA): using "as MethodAbi" below, but abi
// could just as well be ConstructorAbi, EventAbi, etc. We
// just need to tell the TypeScript compiler that it's NOT
// FallbackAbi, or else it would complain, "Property
// 'inputs' does not exist on type 'AbiDefinition'.
// Property 'inputs' does not exist on type
// 'FallbackAbi'.", despite the enclosing if statement.
// tslint:disable:no-unnecessary-type-assertion
if ((abi as MethodAbi).inputs) {
for (const input of (abi as MethodAbi).inputs) {
if (input.type === 'bytes') {
return new Handlebars.SafeString(
'\n\n All method parameters of type `bytes`:code: should be encoded as UTF-8,\n which can be accomplished via `str.encode("utf_8")`:code:.\n ',
);
}
}
}
}
}
return '';
});
Handlebars.registerHelper(
'toPythonClassname',
(sourceName: string) => new Handlebars.SafeString(changeCase.pascal(sourceName)),
);
Handlebars.registerHelper(
'makeOutputsValue',
/**
* Produces a Python expression representing the return value from a
* Solidity function.
* @param pythonVariable the name of the Python variable holding the value
* to be used to populate the output expression.
* @param abiOutputs the "outputs" object of the function's ABI.
*/
(pythonVariable: string, abiOutputs: DataItem[]) => {
if (abiOutputs.length === 1) {
return new Handlebars.SafeString(solValueToPyValue(pythonVariable, abiOutputs[0]));
} else {
let tupleValue = '(';
for (let i = 0; i < abiOutputs.length; i++) {
tupleValue += `${pythonVariable}[${i}],`;
}
tupleValue += ')';
return new Handlebars.SafeString(tupleValue);
}
},
);
}
function solValueToPyValue(pythonVariable: string, abiItem: DataItem): string {
const pythonTypeName = utils.solTypeToPyType(abiItem);
if (pythonTypeName.match(/List\[.*\]/) !== null) {
return `[${solValueToPyValue('element', {
...abiItem,
type: abiItem.type.replace('[]', ''),
})} for element in ${pythonVariable}]`;
} else {
let pyValue = `${pythonTypeName}(`;
if (abiItem.components) {
let i = 0;
for (const component of abiItem.components) {
pyValue += `${component.name}=${pythonVariable}[${i}],`;
i++;
}
} else {
pyValue += pythonVariable;
}
pyValue += ')';
return pyValue;
}
}

View File

@ -102,11 +102,14 @@ export const utils = {
throw new Error(`Unknown Solidity type found: ${solType}`); throw new Error(`Unknown Solidity type found: ${solType}`);
} }
}, },
solTypeToPyType(solType: string, components?: DataItem[]): string { solTypeToPyType(dataItem: DataItem): string {
const solType = dataItem.type;
const trailingArrayRegex = /\[\d*\]$/; const trailingArrayRegex = /\[\d*\]$/;
if (solType.match(trailingArrayRegex)) { if (solType.match(trailingArrayRegex)) {
const arrayItemSolType = solType.replace(trailingArrayRegex, ''); const arrayItemPyType = utils.solTypeToPyType({
const arrayItemPyType = utils.solTypeToPyType(arrayItemSolType, components); ...dataItem,
type: dataItem.type.replace(trailingArrayRegex, ''),
});
const arrayPyType = `List[${arrayItemPyType}]`; const arrayPyType = `List[${arrayItemPyType}]`;
return arrayPyType; return arrayPyType;
} else { } else {
@ -125,7 +128,7 @@ export const utils = {
} }
const TUPLE_TYPE_REGEX = '^tuple$'; const TUPLE_TYPE_REGEX = '^tuple$';
if (solType.match(TUPLE_TYPE_REGEX)) { if (solType.match(TUPLE_TYPE_REGEX)) {
return utils.makePythonTupleName(components as DataItem[]); return utils.makePythonTupleName(dataItem);
} }
throw new Error(`Unknown Solidity type found: ${solType}`); throw new Error(`Unknown Solidity type found: ${solType}`);
} }
@ -187,13 +190,21 @@ export const utils = {
* simply concatenate all of the names of the components, and convert that * simply concatenate all of the names of the components, and convert that
* concatenation into PascalCase to conform to Python convention. * concatenation into PascalCase to conform to Python convention.
*/ */
makePythonTupleName(tupleComponents: DataItem[]): string { makePythonTupleName(tuple: DataItem): string {
const lengthOfHashSuffix = 8; if (tuple.internalType !== undefined) {
return `Tuple0x${createHash('MD5') return tuple.internalType
.update(_.map(tupleComponents, component => component.name).join('_')) .replace('struct ', '')
.digest() .replace('.', '')
.toString('hex') .replace('[]', '');
.substring(0, lengthOfHashSuffix)}`; } else {
const tupleComponents = tuple.components;
const lengthOfHashSuffix = 8;
return `Tuple0x${createHash('MD5')
.update(_.map(tupleComponents, component => component.name).join('_'))
.digest()
.toString('hex')
.substring(0, lengthOfHashSuffix)}`;
}
}, },
/** /**
* @returns a string that is a Python code snippet that's intended to be * @returns a string that is a Python code snippet that's intended to be
@ -203,10 +214,7 @@ export const utils = {
makePythonTupleClassBody(tupleComponents: DataItem[]): string { makePythonTupleClassBody(tupleComponents: DataItem[]): string {
let toReturn: string = ''; let toReturn: string = '';
for (const tupleComponent of tupleComponents) { for (const tupleComponent of tupleComponents) {
toReturn = `${toReturn}\n\n ${tupleComponent.name}: ${utils.solTypeToPyType( toReturn = `${toReturn}\n\n ${tupleComponent.name}: ${utils.solTypeToPyType(tupleComponent)}`;
tupleComponent.type,
tupleComponent.components,
)}`;
} }
toReturn = `${toReturn}`; toReturn = `${toReturn}`;
return toReturn; return toReturn;
@ -361,12 +369,12 @@ export const utils = {
// Argument of type 'DataItem[] | undefined' is not assignable to parameter of type 'DataItem[]'. // Argument of type 'DataItem[] | undefined' is not assignable to parameter of type 'DataItem[]'.
// Type 'undefined' is not assignable to type 'DataItem[]' // Type 'undefined' is not assignable to type 'DataItem[]'
// when the code below tries to access tupleDataItem.components. // when the code below tries to access tupleDataItem.components.
const pythonTupleName = utils.makePythonTupleName(tupleDataItem.components); const pythonTupleName = utils.makePythonTupleName(tupleDataItem);
tupleBodies[pythonTupleName] = utils.makePythonTupleClassBody(tupleDataItem.components); tupleBodies[pythonTupleName] = utils.makePythonTupleClassBody(tupleDataItem.components);
for (const component of tupleDataItem.components) { for (const component of tupleDataItem.components) {
if (component.type === 'tuple' || component.type === 'tuple[]') { if (component.type === 'tuple' || component.type === 'tuple[]') {
tupleDependencies.push([ tupleDependencies.push([
utils.makePythonTupleName((component as TupleDataItem).components), // tslint:disable-line:no-unnecessary-type-assertion utils.makePythonTupleName(component as TupleDataItem), // tslint:disable-line:no-unnecessary-type-assertion
pythonTupleName, pythonTupleName,
]); ]);
utils.extractTuples(component, tupleBodies, tupleDependencies); utils.extractTuples(component, tupleBodies, tupleDependencies);

View File

@ -0,0 +1 @@
class Account: ...

View File

@ -0,0 +1,3 @@
class LocalAccount:
address: str
...

View File

@ -0,0 +1 @@
class HexBytes: ...

View File

@ -0,0 +1,67 @@
from typing import Any, Callable, Dict, List, Optional, Union
from hexbytes import HexBytes
from eth_account.local import LocalAccount
from web3 import datastructures
from web3.contract import Contract
from web3.providers.base import BaseProvider
class Web3:
class HTTPProvider(BaseProvider):
...
def __init__(self, provider: BaseProvider) -> None: ...
@staticmethod
def sha3(
primitive: Optional[Union[bytes, int, None]] = None,
text: Optional[str] = None,
hexstr: Optional[str] = None
) -> bytes: ...
@staticmethod
def isAddress(address: str) -> bool: ...
class middleware_stack:
@staticmethod
def get(key: str) -> Callable: ...
def inject(
self, middleware_func: object, layer: object
) -> None: ...
...
middleware_onion: middleware_stack
class net:
version: str
...
class Eth:
defaultAccount: str
accounts: List[str]
chainId: int
...
class account:
@staticmethod
def privateKeyToAccount(private_key: str) -> LocalAccount: ...
...
@staticmethod
def getTransactionReceipt(tx_hash: Union[HexBytes, bytes]) -> Any: ...
@staticmethod
def contract(address: str, abi: Dict) -> Contract: ...
...
@staticmethod
def isAddress(address: str) -> bool: ...
...
eth: Eth
...

View File

@ -0,0 +1,17 @@
from typing import Any
class Contract:
def call(self): ...
functions: Any
events: Any
...
class ContractFunction:
def __call__(self, *args, **kwargs):
...
...

View File

@ -0,0 +1,5 @@
class NamedElementOnion:
...
class AttributeDict:
...

View File

@ -0,0 +1,2 @@
class BadFunctionCallOutput(Exception):
...

View File

@ -0,0 +1,2 @@
class BaseProvider:
...

View File

@ -115,7 +115,7 @@ class {{contractName}}:
functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi={{contractName}}.abi()).functions functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi={{contractName}}.abi()).functions
{{#each methods}} {{#each methods}}
self.{{toPythonIdentifier this.languageSpecificName}} = {{toPythonClassname this.languageSpecificName}}Method(web3_or_provider, contract_address, functions.{{this.name}}, validator) self.{{toPythonIdentifier this.languageSpecificName}} = {{toPythonClassname this.languageSpecificName}}Method(web3_or_provider, contract_address, functions.{{this.name}}{{#if this.inputs}}, validator{{/if}})
{{/each}} {{/each}}
{{/if}} {{/if}}

View File

@ -0,0 +1,11 @@
{{~#if outputs~}}
{{#if outputs.length}}
{{#singleReturnValue}}
{{#returnType outputs.[0]}}{{~/returnType~}}
{{/singleReturnValue}}
{{^singleReturnValue}}
Tuple[{{#each outputs}}{{#returnType this}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}]
{{~/singleReturnValue}}
{{else}}None
{{/if}}
{{else}}None{{/if~}}

View File

@ -2,10 +2,10 @@
class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod): class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
"""Various interfaces to the {{this.name}} method.""" """Various interfaces to the {{this.name}} method."""
def __init__(self, web3_or_provider: Union[Web3, BaseProvider], contract_address: str, contract_function: ContractFunction, validator: Validator=None): def __init__(self, web3_or_provider: Union[Web3, BaseProvider], contract_address: str, contract_function: ContractFunction{{#if inputs}}, validator: Validator=None{{/if}}):
"""Persist instance data.""" """Persist instance data."""
super().__init__(web3_or_provider, contract_address, validator) super().__init__(web3_or_provider, contract_address{{#if inputs}}, validator{{/if}})
self.underlying_method = contract_function self._underlying_method = contract_function
{{#if inputs}} {{#if inputs}}
def validate_and_normalize_inputs(self, {{> typed_params inputs=inputs}}): def validate_and_normalize_inputs(self, {{> typed_params inputs=inputs}}):
@ -26,7 +26,7 @@ class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
return ({{> params }}) return ({{> params }})
{{/if}} {{/if}}
def call(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> {{> return_type outputs=outputs type='call'~}}: def call(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> {{> call_return_type outputs=outputs type='call'~}}:
"""Execute underlying contract method via eth_call. """Execute underlying contract method via eth_call.
{{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}} {{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}}
{{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}} {{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}}
@ -42,28 +42,37 @@ class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
({{> params }}) = self.validate_and_normalize_inputs({{> params}}) ({{> params }}) = self.validate_and_normalize_inputs({{> params}})
{{/if}} {{/if}}
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method({{> params}}).call(tx_params.as_dict()) {{#hasReturnValue}}returned = {{/hasReturnValue}}self._underlying_method({{> params}}).call(tx_params.as_dict())
{{#hasReturnValue}}
return {{makeOutputsValue 'returned' outputs}}
{{/hasReturnValue}}
{{^if this.constant}}
def send_transaction(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> Union[HexBytes, bytes]: def send_transaction(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction. """Execute underlying contract method via eth_sendTransaction.
{{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}} {{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}}
{{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}} {{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}}
:param tx_params: transaction parameters :param tx_params: transaction parameters
{{#if this.constant~}}
{{#if this.devdoc.return}}
{{makeReturnDocstringRole this.devdoc.return 8}}{{/if}}
{{/if}}
""" """
{{#if inputs}} {{#if inputs}}
({{> params }}) = self.validate_and_normalize_inputs({{> params}}) ({{> params }}) = self.validate_and_normalize_inputs({{> params}})
{{/if}} {{/if}}
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method({{> params}}).transact(tx_params.as_dict()) return self._underlying_method({{> params}}).transact(tx_params.as_dict())
def build_transaction(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> dict:
"""Construct calldata to be used as input to the method."""
{{#if inputs}}
({{> params }}) = self.validate_and_normalize_inputs({{> params}})
{{/if}}
tx_params = super().normalize_tx_params(tx_params)
return self._underlying_method({{> params}}).buildTransaction(tx_params.as_dict())
{{/if}}
def estimate_gas(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> int: def estimate_gas(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> int:
"""Estimate gas consumption of method call.""" """Estimate gas consumption of method call."""
{{#if inputs}} {{#if inputs}}
({{> params }}) = self.validate_and_normalize_inputs({{> params}}) ({{> params }}) = self.validate_and_normalize_inputs({{> params}})
{{/if}} {{/if}}
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method({{> params}}).estimateGas(tx_params.as_dict()) return self._underlying_method({{> params}}).estimateGas(tx_params.as_dict())

View File

@ -4,10 +4,10 @@ Union[
{{~/if~}} {{~/if~}}
{{#if outputs.length}} {{#if outputs.length}}
{{#singleReturnValue}} {{#singleReturnValue}}
{{#returnType outputs.0.type outputs.0.components}}{{~/returnType~}} {{#returnType outputs.[0]}}{{~/returnType~}}
{{/singleReturnValue}} {{/singleReturnValue}}
{{^singleReturnValue}} {{^singleReturnValue}}
Tuple[{{#each outputs}}{{#returnType type components}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] Tuple[{{#each outputs}}{{#returnType this}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}]
{{~/singleReturnValue}} {{~/singleReturnValue}}
{{else}}None {{else}}None
{{/if}}{{^if this.constant}}, Union[HexBytes, bytes]]{{/if~}} {{/if}}{{^if this.constant}}, Union[HexBytes, bytes]]{{/if~}}

View File

@ -1,3 +1,3 @@
{{#each inputs}} {{#each inputs}}
{{toPythonIdentifier name}}: {{#parameterType type components}}{{/parameterType}}{{^if @last}}, {{/if~}} {{toPythonIdentifier name}}: {{#parameterType this}}{{/parameterType}}{{^if @last}}, {{/if~}}
{{/each~}} {{/each~}}

View File

@ -1,6 +1,8 @@
black black
eth_utils eth_utils
hexbytes hexbytes
mypy
mypy_extensions mypy_extensions
pylint pylint
web3 web3
../../python-packages/contract_wrappers

View File

@ -58,7 +58,7 @@ class PublicAddConstantMethod(ContractMethod):
): ):
"""Persist instance data.""" """Persist instance data."""
super().__init__(web3_or_provider, contract_address, validator) super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function self._underlying_method = contract_function
def validate_and_normalize_inputs(self, x: int): def validate_and_normalize_inputs(self, x: int):
"""Validate the inputs to the publicAddConstant method.""" """Validate the inputs to the publicAddConstant method."""
@ -79,19 +79,8 @@ class PublicAddConstantMethod(ContractMethod):
""" """
(x) = self.validate_and_normalize_inputs(x) (x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).call(tx_params.as_dict()) returned = self._underlying_method(x).call(tx_params.as_dict())
return int(returned)
def send_transaction(
self, x: int, tx_params: Optional[TxParams] = None
) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction.
:param tx_params: transaction parameters
"""
(x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).transact(tx_params.as_dict())
def estimate_gas( def estimate_gas(
self, x: int, tx_params: Optional[TxParams] = None self, x: int, tx_params: Optional[TxParams] = None
@ -99,7 +88,7 @@ class PublicAddConstantMethod(ContractMethod):
"""Estimate gas consumption of method call.""" """Estimate gas consumption of method call."""
(x) = self.validate_and_normalize_inputs(x) (x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).estimateGas(tx_params.as_dict()) return self._underlying_method(x).estimateGas(tx_params.as_dict())
class PublicAddOneMethod(ContractMethod): class PublicAddOneMethod(ContractMethod):
@ -114,7 +103,7 @@ class PublicAddOneMethod(ContractMethod):
): ):
"""Persist instance data.""" """Persist instance data."""
super().__init__(web3_or_provider, contract_address, validator) super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function self._underlying_method = contract_function
def validate_and_normalize_inputs(self, x: int): def validate_and_normalize_inputs(self, x: int):
"""Validate the inputs to the publicAddOne method.""" """Validate the inputs to the publicAddOne method."""
@ -133,19 +122,8 @@ class PublicAddOneMethod(ContractMethod):
""" """
(x) = self.validate_and_normalize_inputs(x) (x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).call(tx_params.as_dict()) returned = self._underlying_method(x).call(tx_params.as_dict())
return int(returned)
def send_transaction(
self, x: int, tx_params: Optional[TxParams] = None
) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction.
:param tx_params: transaction parameters
"""
(x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).transact(tx_params.as_dict())
def estimate_gas( def estimate_gas(
self, x: int, tx_params: Optional[TxParams] = None self, x: int, tx_params: Optional[TxParams] = None
@ -153,7 +131,7 @@ class PublicAddOneMethod(ContractMethod):
"""Estimate gas consumption of method call.""" """Estimate gas consumption of method call."""
(x) = self.validate_and_normalize_inputs(x) (x) = self.validate_and_normalize_inputs(x)
tx_params = super().normalize_tx_params(tx_params) tx_params = super().normalize_tx_params(tx_params)
return self.underlying_method(x).estimateGas(tx_params.as_dict()) return self._underlying_method(x).estimateGas(tx_params.as_dict())
# pylint: disable=too-many-public-methods,too-many-instance-attributes # pylint: disable=too-many-public-methods,too-many-instance-attributes

View File

@ -134,6 +134,7 @@ export interface EventAbi {
export interface DataItem { export interface DataItem {
name: string; name: string;
type: string; type: string;
internalType?: string;
components?: DataItem[]; components?: DataItem[];
} }

View File

@ -9,6 +9,11 @@
- Moved methods `jsdict_to_order()` and `order_to_jsdict()` from `zero_ex.contract_wrappers.exchange.types` to `zero_ex.contract_wrappers.order_conversions`. - Moved methods `jsdict_to_order()` and `order_to_jsdict()` from `zero_ex.contract_wrappers.exchange.types` to `zero_ex.contract_wrappers.order_conversions`.
- Changed field name `zero_ex.contract_wrappers.tx_params.TxParams.gasPrice` to `.gas_price`. - Changed field name `zero_ex.contract_wrappers.tx_params.TxParams.gasPrice` to `.gas_price`.
- Migrated to new version of 0x-contract-addresses. - Migrated to new version of 0x-contract-addresses.
- Made the `underlying_method` field on ContractMethod private by prefixing its name with an underscore.
- Corrected return types and values for call() interface to generated method wrappers. (Fixes #2298.)
- Removed `send_transaction()` method from ContractMethod instances for underlying Solidity methods that are const (view/pure).
- Added a `build_transaction()` method to instances of ContractMethod for non-const Solidity methods.
- Removed `validator` argument from ContractMethod instances for underlying Solidity methods that lack inputs.
## 1.1.0 - 2019-08-14 ## 1.1.0 - 2019-08-14

View File

@ -201,23 +201,23 @@ zero_ex.contract_wrappers.exchange.types
zero_ex.contract_wrappers.exchange: Generated Tuples zero_ex.contract_wrappers.exchange: Generated Tuples
==================================================== ====================================================
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x6ca34a6f .. autoclass:: zero_ex.contract_wrappers.exchange.LibOrderOrder
This is the generated class representing `the Order struct <https://0x.org/docs/contracts#structs-Order>`_. This is the generated class representing `the Order struct <https://0x.org/docs/contracts#structs-Order>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x735c43e3 .. autoclass:: zero_ex.contract_wrappers.exchange.LibFillResultsFillResults
This is the generated class representing `the FillResults struct <https://0x.org/docs/contracts#structs-FillResults>`_. This is the generated class representing `the FillResults struct <https://0x.org/docs/contracts#structs-FillResults>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x4c5ca29b .. autoclass:: zero_ex.contract_wrappers.exchange.LibFillResultsMatchedFillResults
This is the generated class representing `the MatchedFillResults struct <https://0x.org/docs/contracts#structs-MatchedFillResults>`_. This is the generated class representing `the MatchedFillResults struct <https://0x.org/docs/contracts#structs-MatchedFillResults>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xb1e4a1ae .. autoclass:: zero_ex.contract_wrappers.exchange.LibOrderOrderInfo
This is the generated class representing `the OrderInfo struct <https://0x.org/docs/contracts#structs-OrderInfo>`_. This is the generated class representing `the OrderInfo struct <https://0x.org/docs/contracts#structs-OrderInfo>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xdabc15fe .. autoclass:: zero_ex.contract_wrappers.exchange.LibZeroExTransactionZeroExTransaction
This is the generated class representing `the ZeroExTransaction struct <https://0x.org/docs/contracts#structs-ZeroExTransaction>`_. This is the generated class representing `the ZeroExTransaction struct <https://0x.org/docs/contracts#structs-ZeroExTransaction>`_.

View File

@ -171,7 +171,7 @@ Now we'll have our Taker fill the order.
But before filling an order, one may wish to check that it's actually fillable: But before filling an order, one may wish to check that it's actually fillable:
>>> from zero_ex.contract_wrappers.exchange.types import OrderStatus >>> from zero_ex.contract_wrappers.exchange.types import OrderStatus
>>> OrderStatus(exchange.get_order_info.call(order)[0]) >>> OrderStatus(exchange.get_order_info.call(order)["orderStatus"])
<OrderStatus.FILLABLE: 3> <OrderStatus.FILLABLE: 3>
The `takerAssetAmount`:code: parameter specifies the amount of tokens (in this The `takerAssetAmount`:code: parameter specifies the amount of tokens (in this
@ -181,13 +181,18 @@ completely, but partial fills are possible too.
One may wish to first call the method in a read-only way, to ensure that it One may wish to first call the method in a read-only way, to ensure that it
will not revert, and to validate that the return data is as expected: will not revert, and to validate that the return data is as expected:
>>> exchange.fill_order.call( >>> from pprint import pprint
>>> pprint(exchange.fill_order.call(
... order=order, ... order=order,
... taker_asset_fill_amount=order["takerAssetAmount"], ... taker_asset_fill_amount=order["takerAssetAmount"],
... signature=maker_signature, ... signature=maker_signature,
... tx_params=TxParams(from_=taker_address) ... tx_params=TxParams(from_=taker_address)
... ) ... ))
(100000000000000000, 100000000000000000, 0, 0, 0) {'makerAssetFilledAmount': 100000000000000000,
'makerFeePaid': 0,
'protocolFeePaid': 0,
'takerAssetFilledAmount': 100000000000000000,
'takerFeePaid': 0}
Finally, submit the transaction: Finally, submit the transaction:
@ -203,7 +208,6 @@ the exchange wrapper:
>>> exchange.get_fill_event(tx_hash) >>> exchange.get_fill_event(tx_hash)
(AttributeDict({'args': ...({'makerAddress': ...}), 'event': 'Fill', ...}),) (AttributeDict({'args': ...({'makerAddress': ...}), 'event': 'Fill', ...}),)
>>> from pprint import pprint
>>> pprint(exchange.get_fill_event(tx_hash)[0].args.__dict__) >>> pprint(exchange.get_fill_event(tx_hash)[0].args.__dict__)
{'feeRecipientAddress': '0x0000000000000000000000000000000000000000', {'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x...', 'makerAddress': '0x...',

View File

@ -5,20 +5,16 @@ encountered in the Exchange contract's ABI. However, they have weird names,
containing hashes of the tuple's field names, because the name of a Solidity containing hashes of the tuple's field names, because the name of a Solidity
`struct`:code: isn't conveyed through the ABI. This module provides type `struct`:code: isn't conveyed through the ABI. This module provides type
aliases with human-friendly names. aliases with human-friendly names.
Converting between the JSON wire format and the types accepted by Web3.py (eg
`bytes` vs `str`) can be onerous. This module provides conveniences for
converting Exchange structs between JSON and Python objects.
""" """
from enum import auto, Enum from enum import auto, Enum
from . import ( from . import (
Tuple0x735c43e3, LibFillResultsFillResults,
Tuple0x6ca34a6f, LibOrderOrder,
Tuple0x4c5ca29b, LibFillResultsMatchedFillResults,
Tuple0xdabc15fe, LibZeroExTransactionZeroExTransaction,
Tuple0xb1e4a1ae, LibOrderOrderInfo,
) )
@ -29,43 +25,43 @@ from . import (
# of each of these classes. # of each of these classes.
class FillResults(Tuple0x735c43e3): class FillResults(LibFillResultsFillResults):
"""The `FillResults`:code: Solidity struct. """The `FillResults`:code: Solidity struct.
Also known as Also known as
`zero_ex.contract_wrappers.exchange.Tuple0x735c43e3`:py:class:. `zero_ex.contract_wrappers.exchange.LibFillResultsFillResults`:py:class:.
""" """
class Order(Tuple0x6ca34a6f): class Order(LibOrderOrder):
"""The `Order`:code: Solidity struct. """The `Order`:code: Solidity struct.
Also known as Also known as
`zero_ex.contract_wrappers.exchange.Tuple0x6ca34a6f`:py:class:. `zero_ex.contract_wrappers.exchange.LibOrderOrder`:py:class:.
""" """
class MatchedFillResults(Tuple0x4c5ca29b): class MatchedFillResults(LibFillResultsMatchedFillResults):
"""The `MatchedFillResults`:code: Solidity struct. """The `MatchedFillResults`:code: Solidity struct.
Also known as Also known as
`zero_ex.contract_wrappers.exchange.Tuple0x4c5ca29b`:py:class:. `zero_ex.contract_wrappers.exchange.LibFillResultsMatchedFillResults`:py:class:.
""" """
class ZeroExTransaction(Tuple0xdabc15fe): class ZeroExTransaction(LibZeroExTransactionZeroExTransaction):
"""The `ZeroExTransaction`:code: Solidity struct. """The `ZeroExTransaction`:code: Solidity struct.
Also known as Also known as
`zero_ex.contract_wrappers.exchange.Tuple0xdabc15fe`:py:class:. `zero_ex.contract_wrappers.exchange.LibZeroExTransactionZeroExTransaction`:py:class:.
""" """
class OrderInfo(Tuple0xb1e4a1ae): class OrderInfo(LibOrderOrderInfo):
"""The `OrderInfo`:code: Solidity struct. """The `OrderInfo`:code: Solidity struct.
Also known as Also known as
`zero_ex.contract_wrappers.exchange.Tuple0xb1e4a1ae`:py:class:. `zero_ex.contract_wrappers.exchange.LibOrderOrderInfo`:py:class:.
""" """

View File

@ -1,4 +1,9 @@
"""Utilities to convert between JSON and Python-native objects.""" """Utilities to convert between JSON and Python-native objects.
Converting between the JSON wire format and the types accepted by Web3.py (eg
`bytes` vs `str`) can be onerous. This module provides conveniences for
converting Exchange structs between JSON and Python objects.
"""
from copy import copy from copy import copy
from typing import cast, Dict, Union from typing import cast, Dict, Union

View File

@ -91,11 +91,11 @@ def test_exchange_wrapper__fill_order(
signature=order_signature, signature=order_signature,
tx_params=TxParams(from_=taker), tx_params=TxParams(from_=taker),
) )
assert fill_results[0] == 1 assert fill_results["makerAssetFilledAmount"] == 1
assert fill_results[1] == 1 assert fill_results["takerAssetFilledAmount"] == 1
assert fill_results[2] == 0 assert fill_results["makerFeePaid"] == 0
assert fill_results[3] == 0 assert fill_results["takerFeePaid"] == 0
assert fill_results[4] == 0 assert fill_results["protocolFeePaid"] == 0
tx_hash = exchange_wrapper.fill_order.send_transaction( tx_hash = exchange_wrapper.fill_order.send_transaction(
order=order, order=order,

View File

@ -32,37 +32,12 @@ configured to connect to any JSON-RPC endpoint, on any network. The examples
below assume that Launch Kit is connected to a Ganache development network below assume that Launch Kit is connected to a Ganache development network
accessible at `http://localhost:8545`:code:. accessible at `http://localhost:8545`:code:.
To replicate this setup, one could run the following commands: These examples are automatically verified by spinning up docker images
`0xorg/ganache-cli`, `0xorg/mesh`, and `0xorg/launch-kit-backend`. You can
:: replicate this environment yourself by using `this docker-compose.yml file
<https://github.com/0xProject/0x-monorepo/blob/development/python-packages/sra_client/test/relayer/docker-compose.yml>`_.
docker run -d -p 8545:8545 0xorg/ganache-cli (Note: This will only work on Linux, because it uses `network_mode:
"host"`:code:, which only works on Linux.)
docker run -d -p 60557:60557 --network host \
-e ETHEREUM_RPC_URL=http://localhost:8545 \
-e ETHEREUM_NETWORK_ID=50 \
-e ETHEREUM_CHAIN_ID=1337 \
-e USE_BOOTSTRAP_LIST=false \
-e VERBOSITY=3 \
-e PRIVATE_KEY_PATH= \
-e BLOCK_POLLING_INTERVAL=5s \
-e P2P_LISTEN_PORT=60557
0xorg/mesh:6.0.0-beta-0xv3
docker run -d --network host \
-e RPC_URL=http://localhost:8545 \
-e CHAIN_ID=1337 \
-e FEE_RECIPIENT=0x0000000000000000000000000000000000000001 \
-e MAKER_FEE_UNIT_AMOUNT=0 \
-e TAKER_FEE_UNIT_AMOUNT=0
-e MESH_ENDPOINT=ws://localhost:60557
-e WHITELIST_ALL_TOKENS=True \
0xorg/launch-kit-ci
(Note: This will only work on Linux, because `--network host`:code: only works
on Linux. For other platforms one would have to clone `the 0x-launch-kit-backend
repository <https://github.com/0xProject/0x-launch-kit-backend>`_ and build and start
the server.)
Configure and create an API client instance Configure and create an API client instance
------------------------------------------- -------------------------------------------