Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Reorganize and modularize generated contract wrappers and artifacts #1105

Merged
merged 55 commits into from
Oct 16, 2018

Conversation

albrow
Copy link
Contributor

@albrow albrow commented Oct 2, 2018

Description

This PR is a huge reorganization of how we generate, store, and copy generated files related to contracts (specifically artifacts and contract wrappers generated by abi-gen).

Here's a summary of the biggest changes (some of which are not fully complete yet):

1. The contracts package is now a public package that will be published with the name @0xproject/contracts
2. All artifacts have been moved out of @0xproject/migrations and into @0xproject/contracts. Artifacts are encapsulated in an npm package and have a version number indicated by a package.json file. You can import artifacts with import { artifacts } from '@0xproject/contracts'.
3. All contract wrappers have been moved into @0xproject/contracts. Like artifacts, they are encapsulated in an npm package. You can import them with import { wrappers } from '@0xproject/contracts'.
4. Addresses of deployed contracts are no longer stored in artifact files. For tests, you can get the addresses of contracts that were deployed during migrations with the new getContractAddresses function in @0xproject/migrations. (I'm still not sure if I'm happy with this exact interface, but in any case there will be some mechanism to get the contract addresses after you run runMigrationsAsync). For contracts that have been deployed to the mainnet or various testnets, the addresses will be exposed as an object in one of our public packages (exact details TBD). This approach leads to a better separation between things that are temporary (addresses of contracts that were deployed for testing purposes) and permanent (addresses, abis, and source code of contracts that have already been deployed to mainnet or a testnet).

(A lot has changed. See below for an updated high-level description).

Testing instructions

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Prefix PR title with bracketed package name(s) corresponding to the changed package(s). For example: [sol-cov] Fixed bug.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@albrow albrow added the WIP label Oct 2, 2018
@coveralls
Copy link

coveralls commented Oct 2, 2018

Coverage Status

Coverage decreased (-0.3%) to 84.688% when pulling 5938e8a on feature/contracts-artifacts-re-org into 05bf7a8 on development.

@LogvinovLeon
Copy link
Contributor

What is the generated artifacts folder? Why can't we just use the artifacts from migrations?

"name": "contracts",
"version": "2.1.48",
"name": "@0xproject/contracts",
"version": "2.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, what's the rationale here? Lerna is going to publish patch updates of this package whenever it's deps update. Don't think it's possible to have this version track the "contract" version. Rather we should maybe add a second key called "0xVersion: "2.0.0"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking it would be cleaner to start at 2.0.0 and it shouldn't matter since this package has never been published before.

That said, you're right that in the long-term this version will probably not track the "contract" version exactly. I changed my mind and I think it's probably better to keep the same version number we had before. I'll change it back to 2.1.48.

@@ -0,0 +1,3 @@
export * from './artifacts';
import * as wrappers from './wrappers';
export { wrappers };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in contracts let's add a .npmignore file and make sure the bundle published to npmjs.com repository only includes these files and not the tests and Solidity source dirs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I was planning to sort what to include in the published package out at the end. Want to make sure everything works inside the monorepo first.

@albrow
Copy link
Contributor Author

albrow commented Oct 2, 2018

@LogvinovLeon the goal of this PR is to have all artifacts and generated contract wrappers in a single canonical location. Other packages that need those things will import the canonical package instead of copying files or calling abi-gen manually.

I'm planning to have the contracts package be that canonical location and will remove the actual artifacts from the migrations package. The reasoning for putting them in contracts instead of migrations is that it keeps the boundaries between packages more clear. If the artifacts were stored in migrations, we would need to have build and/or clean steps that either modify files in other packages or call scripts or reference files in other packages, which can lead to some unintuitive results and confusing build-order dependencies.

@fabioberger
Copy link
Contributor

Reminder: make sure to add CHANGELOG.json and CHANGELOG.md files to the contracts package.

@albrow
Copy link
Contributor Author

albrow commented Oct 3, 2018

I added a summary of major changes to this PR description. In terms of progress:

  1. The @0xproject/contracts package has been created with the structure described above.
  2. @0xproject/migrations has been updated as described above. I removed the artifacts and added the new getContractsAddress function. Migrations also operate on only one version of the contracts at a time so I removed the v1 artifacts and related migrations.
  3. @0xproject/contract-wrappers has been updated to use the new locations for artifacts and abi-gen'ed contract wrappers.

The biggest things I still need to do are:

  1. Update any other packages that rely on old locations for artifacts or abi-gen'ed contract wrappers.
  2. Clean up @0xproject/contracts and get it ready to publish. This means only exporting what we need to export, adding things to .npmignore and other config files, creating CHANGELOG.json, etc. I also want to double check bundle sizes and make sure they're manageable.
  3. Make sure all the documentation is up to date.

@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch 2 times, most recently from 4df5bb4 to 1c09da9 Compare October 4, 2018 23:42
@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch from 2f66ae8 to 66f407f Compare October 5, 2018 00:36
@albrow
Copy link
Contributor Author

albrow commented Oct 9, 2018

Updated high-level summary of changes:

  1. All artifacts have been moved out of @0xproject/migrations. Bleeding edge development artifacts have been moved to the contracts package, where they are kept private and are not imported by any other packages. On the other hand, permanent, versioned artifacts are available in the new @0xproject/contract-artifacts package. They are encapsulated in an npm package and have a version number indicated by a package.json file. You can import artifacts with import * as artifacts from '@0xproject/contract-artifacts'. (You can also import individual artifacts directly). This approach allows us to stop manually copying around artifact files to the packages that need them.
  2. Abi-gen'ed contract wrappers have been moved into the new@0xproject/abi-gen-wrappers package. Like artifacts, they are encapsulated in an npm package. You can import them with import * as wrappers from '@0xproject/abi-gen-wrappers'. (You can also import individual wrappers directly). This approach allows us to avoid re-generating the same wrappers across many different packages.
  3. Addresses of deployed contracts are no longer stored in artifact files. For tests, the runMigrationsAsync function in @0xproject/migrations simply returns the contract addresses. For contracts that have been deployed to the mainnet or various testnets, the addresses are exposed via the new @0xproject/contract-addresses package. This approach leads to a better separation between things that are temporary (addresses of contracts that were temporarily deployed, e.g., to Ganache, for testing purposes) and permanent (addresses of contracts that have already been deployed to an external network).

@albrow
Copy link
Contributor Author

albrow commented Oct 10, 2018

Okay I think I have an approach that works now. Most of what's left to do is bookkeeping:

  • Resolve merge conflicts.
  • Add README.md and CHANGELOG.json to all three new packages.
  • Update all CHANGELOG.json for any changed packages.
  • Get CI passing.
  • Move v1 contracts back into contracts package (required for sol-doc?). (Not required according to Gene).
  • Update .prettierignore, .gitignore, and other ignore files.

@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch from 608418f to 4326757 Compare October 10, 2018 06:23
import * as ZRXToken from '../artifacts/ZRXToken.json';

export {
AssetProxyOwner,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we don't have to do the (as any) as ContractArtifact hack anymore! With "resolveJsonModule": true in tsconfig.json TypeScript supports typed JSON imports. It also works with Project References, so when you watch, it will recompile when the JSON file(s) change.

artifacts.ERC20Token,
normalizedTokenAddress,
);
// TODO(albrow): Do we really still need this check? The default error
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LogvinovLeon or @fabioberger thoughts on this TODO? (Also appears a few other places).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the default error? doesContractExistAtAddressAsync returns a boolean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm asking is whether we really need the ContractWrappersError.ERC20TokenContractDoesNotExist error. If we remove this check, Web3Wrapper throws an error along the lines of "Tried to send a transaction to 0xabcde... but it is not a contract address.". Together with a stack trace, I think this makes it pretty clear what went wrong. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, let's get rid of the mapping.

import { provider, txDefaults, web3Wrapper } from './web3_wrapper';

// Those addresses come from migrations. They're deterministic so it's relatively safe to hard-code them here.
// Before we were fetching them from the TokenRegistry but now we can't as it's deprecated and removed.
// TODO(albrow): Import these from the migrations package instead of hard-coding them.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this for now. We're no worse off than we were before this PR and can always fix it later. This PR is already arguably too big as it is.

@@ -112,7 +107,7 @@ describe('Revert Validation ExchangeWrapper', () => {
makerTokenBalance,
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
expect(
return expect(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is subtle, but there was a race condition here before adding the return.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a TSLint rule for this? If not now, let's add a task to the backlog.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

➕ Good idea. I believe we already have a check for unhandled promise rejections. The problem is that expect().to.be.rejectedWith doesn't return a real Promise. Instead it returns a Chai.PromisedAssertion. There must be some way to check for it.

@@ -454,13 +458,6 @@ describe('ExchangeWrapper', () => {
})().catch(done);
});
});
describe('#getZRXTokenAddressAsync', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we removed the token registry, this test isn't really doing anything.

@@ -124,39 +112,8 @@ export abstract class ContractWrapper {
const logWithDecodedArgs = abiDecoder.tryToDecodeLogOrNoop(log);
return logWithDecodedArgs;
}
protected async _getContractAbiAndAddressFromArtifactsAsync(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't reading addresses from a file anymore, I was able to remove this function and others like it. For ContractWrappers addresses are now known at instantiation time and are synchronous, which simplifies a lot of our code.

.gitignore Outdated
packages/0x.js/src/generated_contract_wrappers/
packages/contracts/generated_contract_wrappers/
packages/contract-wrappers/src/contract_wrappers/generated/
packages/contracts/generated-wrappers/
packages/metacoin/src/contract_wrappers
packages/fill-scenarios/src/generated_contract_wrappers/
packages/order-watcher/src/generated_contract_wrappers/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these still exist? This is where the abi-gen-wrappers used to get copied to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I have a TODO item on this PR to fix .gitignore

"engines": {
"node": ">=6.12"
},
"description": "Smart contract components of 0x protocol",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"0x smart contract wrappers generated using @0xproject/abi-gen"

@@ -0,0 +1,14 @@
export * from '../wrappers/asset_proxy_owner';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the wrappers not under the src dir?

Copy link
Contributor Author

@albrow albrow Oct 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO src should be for user-editable source code. These wrappers are generated code. Does that sound right to you?

Copy link
Contributor

@fabioberger fabioberger Oct 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I think of src as where all the TS code lived that needs to be compiled into lib. Those generated contracts have disclaimers in their headers, and we could also name the directory to generated_wrappers.

Copy link
Contributor Author

@albrow albrow Oct 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Okay I changed it.

@@ -0,0 +1,64 @@
import * as _ from 'lodash';

export interface ContractAddresses {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to contractToAddress? We are trying to turn this mapped naming which mimicks the left (key) and right (value) of the interface into a convention.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I'm not sure I agree with renaming it. In this case, I think the kind of information we are passing around (the addresses of deployed contracts) is more important to call out than the underlying data structure (a mapping of contract name to contract address). For similar reasons, it seems awkward to rename getContractAddressesForNetwork to getContractToAddressForNetwork or rename the contractAddresses parameter in the constructor for ContractWrappers to contractToAddress.

To look at it from another perspective, here are a few examples of how we use the XToY naming convention in @0xproject/types:

export interface ExportNameToTypedocNames {
    [exportName: string]: string[];
}

export interface ExternalTypeToLink {
    [externalTypeName: string]: string;
}

export interface ExternalExportToLink {
    [externalExport: string]: string;
}

All of these cases represent a mapping of arbitrary X to Y, and that's where the naming convention makes the most sense to me. Since we don't know ahead of time exactly what keys the type will hold (and whether other uses of the type will have the same keys), the underlying data structure is more important to call out. In other words, the data structure itself is what defines the type, not the data it holds.

All that said, if you feel strongly about it, I can be convinced.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see your point in this case. Thanks for the thoughtful explanation, let's leave it the way it is.

@@ -109,6 +110,7 @@ export class AssetBuyer {
this.expiryBufferSeconds = expiryBufferSeconds;
this._contractWrappers = new ContractWrappers(this.provider, {
networkId,
contractAddresses: getContractAddressesForNetwork(networkId),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is optional, and the fallback is to call this same method under-the-hood, we can omit this line and remove the contract-addresses dep from this package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fragosti @BMillman19 do we want people to be able to use AssetBuyer in their own private testnets? If so, we might want to expose this config in the contructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omitted for now. We can add it back in if we think users want to configure it.

public getZRXTokenAddress(): string {
const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists);
return contractAddress;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... any reason to remove these? We could just return it no? It's a big breaking change to remove these... I know relayers rely on that. Do we want to force them to move to using contract-addresses package?

Copy link
Contributor Author

@albrow albrow Oct 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the changes in this PR, zrxTokenAddress is simply a property of ExchangeWrapper and it will always be set after instantiation. You don't have to use the contract-addresses package if you don't want to. Removing this method simplifies our API surface and will make contract-wrappers easier to use/understand. Leaving it in would mean that there are now two ways to get the address and just creates a little bit of clutter.

Given that AFAIK we don't have any mechanism for marking functions/methods as deprecated, I think we should just remove outdated code like this.

If this was the only breaking change, I would leave it in. However, we already have some breaking changes to the contract-wrappers package in this PR and this one is definitely on the easy side in terms of updating.

* @param networkId Desired networkId
* @param web3Wrapper Web3Wrapper instance to use.
* @param networkId Desired networkId.
* @param address (Optional) The address of the OrderValidator contract. If
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove (optional)

@@ -112,7 +107,7 @@ describe('Revert Validation ExchangeWrapper', () => {
makerTokenBalance,
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
expect(
return expect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a TSLint rule for this? If not now, let's add a task to the backlog.

this._contractWrappers = new ContractWrappers(provider, { networkId });
const contractWrappersConfig = {
networkId,
contractAddresses: getContractAddressesForNetwork(networkId),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omit this. It defaults to doing this internally.

{ "path": "./packages/assert" },
{ "path": "./packages/asset-buyer" },
{ "path": "./packages/base-contract" },
{ "path": "./packages/connect" },
{ "path": "./packages/contract-artifacts" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an entry here for contract-addressses?

"compilerOptions": {
"outDir": "lib",
"rootDir": ".",
"resolveJsonModule": true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried to do that I've hit a problem that TSC auto-generates types for JSON files from their content and we had some partial artifacts in some modules, but I guess now that you standardized it - it's not a problem any more. Happy about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I still had to include some type assertions in the contracts package for the reasons you mentioned. I think it only works for contract-artifacts right now because we are using the trimmed down contract artifacts there.

@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch 2 times, most recently from 653ef71 to 462bf02 Compare October 12, 2018 20:55
@@ -103,6 +103,9 @@ export class DocGenerateAndUploadUtils {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration: {
const exportClause = (node as any).exportClause;
if (_.isUndefined(exportClause)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabioberger commenting here to make sure you get a chance to review the changes I made to this file. It's easy to miss in a PR of this size.

@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch from 5472bf2 to f0e4837 Compare October 15, 2018 20:39
@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch from ca26a2b to e093864 Compare October 16, 2018 00:09
Copy link
Contributor

@BMillman19 BMillman19 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve for website and asset-buyer

@albrow albrow force-pushed the feature/contracts-artifacts-re-org branch from 7e0e126 to 5bdfad9 Compare October 16, 2018 07:24
@albrow albrow changed the title [WIP] Reorganize and modularize generated contract wrappers and artifacts Reorganize and modularize generated contract wrappers and artifacts Oct 16, 2018
@albrow albrow removed the WIP label Oct 16, 2018
@albrow
Copy link
Contributor Author

albrow commented Oct 16, 2018

Removed WIP label. Ready for final review.


You may also be interested in the
[@0xproject/contract-wrappers](../contract-wrappers/README.md) package which
includes some higher-level features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

@fabioberger fabioberger merged commit e624587 into development Oct 16, 2018
@fabioberger fabioberger deleted the feature/contracts-artifacts-re-org branch October 16, 2018 13:21
@albrow albrow mentioned this pull request Oct 18, 2018
5 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants