Merge branch 'development' into breakUp0xjs

* development:
  Fix ganache subprovider config
  Fix a bug in compiler config precedence
  Fix linter errors
  Fix templates
  Remove unused deployer docs configs
  Add a legacy endpoint for the deployer
  Add a check for compiler output
  Add a comment
  Put ARTIFACTS_VERSION in a config
  Improve a comment
  Remove _applyDefaultsToDeployTxDataAsync
  Add a HACK comment
  Fix linter issues
  Rename deployer to sol-compiler
  Remove deployer
  Remove deployer from 0x.js and migrations
  Configure migrations with a compiler.json
  Remove deployer from metacoin and contract tests
  Update wallet footer and add remove token functionality

# Conflicts:
#	.gitignore
#	packages/0x.js/package.json
#	packages/0x.js/src/0x.ts
#	packages/contracts/package.json
#	packages/contracts/test/multi_sig_with_time_lock.ts
#	packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts
#	packages/contracts/util/artifacts.ts
#	packages/deployer/test/deployer_test.ts
#	packages/migrations/package.json
This commit is contained in:
Fabio Berger
2018-05-11 12:32:57 +02:00
119 changed files with 924 additions and 1109 deletions

View File

@@ -0,0 +1,7 @@
.*
yarn-error.log
/src/
/scripts/
test/
tsconfig.json
/lib/src/monorepo_scripts/

View File

@@ -0,0 +1,151 @@
[
{
"timestamp": 1525477860,
"version": "0.4.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "0.4.2",
"changes": [
{
"note": "Add support for solidity 0.4.23",
"pr": 545
}
],
"timestamp": 1525428773
},
{
"version": "0.4.1",
"changes": [
{
"note": "Add support for solidity 0.4.22",
"pr": 531
}
],
"timestamp": 1524044013
},
{
"version": "0.4.0",
"changes": [
{
"note": "Changed the config key `web3Provider` to `provider` to be consistent with other tools",
"pr": 501
}
],
"timestamp": 1523462196
},
{
"version": "0.3.5",
"changes": [
{
"note": "Don't try to write contract artifact if an error occured",
"pr": 485
}
],
"timestamp": 1522673609
},
{
"version": "0.3.4",
"changes": [
{
"note": "Create solc_bin directory if does not exist before attempting to compile",
"pr": 491
}
],
"timestamp": 1522658513
},
{
"version": "0.3.1",
"changes": [
{
"note": "Add TS types for `yargs`"
}
],
"timestamp": 1521298800
},
{
"version": "0.3.0",
"changes": [
{
"note": "Add support for Solidity 0.4.20 and 0.4.21"
},
{
"note": "Replace `jsonrpcPort` config with `jsonrpcUrl`",
"pr": 426
},
{
"note": "Replace `jsonrpc-port` CLI option with `jsonrpc-url`",
"pr": 426
},
{
"note": "Export the `Compiler`",
"pr": 426
},
{
"note": "Load solc from remote source instead of having it locally",
"pr": 426
},
{
"note":
"Add `bytecode`, `runtime_bytecode`, `source_map`, `source_map_runtime` and `sources` fields to artifacts",
"pr": 426
},
{
"note": "Remove 0x-specific `migrate` command",
"pr": 426
},
{
"note":
"Allow deployer to accept a provider instead of port and host. This makes it possible to run it with in-process ganache-core",
"pr": 426
},
{
"note": "Consolidate all `console.log` calls into `logUtils` in the `@0xproject/utils` package",
"pr": 452
},
{
"note": "Add `#!/usr/bin/env node` pragma above `cli.ts` script to fix command-line error."
}
],
"timestamp": 1521298800
},
{
"version": "0.2.0",
"changes": [
{
"note": "Check dependencies when determining if contracts should be recompiled",
"pr": 408
},
{
"note":
"Improve an error message for when deployer is supplied with an incorrect number of constructor arguments",
"pr": 419
}
],
"timestamp": 1520089200
},
{
"version": "0.1.0",
"changes": [
{
"note": "Add the ability to pass in specific contracts to compile in CLI",
"pr": 400
}
],
"timestamp": 1518706800
},
{
"version": "0.0.8",
"changes": [
{
"note": "Fix publishing issue where .npmignore was not properly excluding undesired content",
"pr": 389
}
],
"timestamp": 1518102000
}
]

View File

@@ -0,0 +1,60 @@
<!--
This file is auto-generated using the monorepo-scripts package. Don't edit directly.
Edit the package's CHANGELOG.json file only.
-->
CHANGELOG
## v0.4.3 - _May 5, 2018_
* Dependencies updated
## v0.4.2 - _May 4, 2018_
* Add support for solidity 0.4.23 (#545)
## v0.4.1 - _April 18, 2018_
* Add support for solidity 0.4.22 (#531)
## v0.4.0 - _April 11, 2018_
* Changed the config key `web3Provider` to `provider` to be consistent with other tools (#501)
## v0.3.5 - _April 2, 2018_
* Don't try to write contract artifact if an error occured (#485)
## v0.3.4 - _April 2, 2018_
* Create solc_bin directory if does not exist before attempting to compile (#491)
## v0.3.1 - _March 17, 2018_
* Add TS types for `yargs`
## v0.3.0 - _March 17, 2018_
* Add support for Solidity 0.4.20 and 0.4.21
* Replace `jsonrpcPort` config with `jsonrpcUrl` (#426)
* Replace `jsonrpc-port` CLI option with `jsonrpc-url` (#426)
* Export the `Compiler` (#426)
* Load solc from remote source instead of having it locally (#426)
* Add `bytecode`, `runtime_bytecode`, `source_map`, `source_map_runtime` and `sources` fields to artifacts (#426)
* Remove 0x-specific `migrate` command (#426)
* Allow deployer to accept a provider instead of port and host. This makes it possible to run it with in-process ganache-core (#426)
* Consolidate all `console.log` calls into `logUtils` in the `@0xproject/utils` package (#452)
* Add `#!/usr/bin/env node` pragma above `cli.ts` script to fix command-line error.
## v0.2.0 - _March 3, 2018_
* Check dependencies when determining if contracts should be recompiled (#408)
* Improve an error message for when deployer is supplied with an incorrect number of constructor arguments (#419)
## v0.1.0 - _February 15, 2018_
* Add the ability to pass in specific contracts to compile in CLI (#400)
## v0.0.8 - _February 8, 2018_
* Fix publishing issue where .npmignore was not properly excluding undesired content (#389)

View File

@@ -0,0 +1,103 @@
## @0xproject/sol-compiler
This repository contains a CLI tool that facilitates compiling smart contracts.
### Read the [Documentation](https://0xproject.com/docs/sol-compiler).
## Installation
#### CLI Installation
```bash
yarn global add @0xproject/sol-compiler
```
#### API Installation
```bash
yarn add @0xproject/sol-compiler
```
If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
```json
"compilerOptions": {
"typeRoots": ["node_modules/@0xproject/typescript-typings/types", "node_modules/@types"],
}
```
**Import**
```typescript
import { Compiler } from '@0xproject/sol-compiler';
```
or
```javascript
var Compiler = require('@0xproject/sol-compiler').Compiler;
```
## Contributing
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
### Install dependencies
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
```bash
yarn config set workspaces-experimental true
```
Then install dependencies
```bash
yarn install
```
### Build
If this is your **first** time building this package, you must first build **all** packages within the monorepo. This is because packages that depend on other packages located inside this monorepo are symlinked when run from **within** the monorepo. This allows you to make changes across multiple packages without first publishing dependent packages to NPM. To build all packages, run the following from the monorepo root directory:
```bash
yarn lerna:rebuild
```
Or continuously rebuild on change:
```bash
yarn dev
```
You can also build this specific package by running the following from within its directory:
```bash
yarn build
```
or continuously rebuild on change:
```bash
yarn build:watch
```
### Clean
```bash
yarn clean
```
### Lint
```bash
yarn lint
```
### Run Tests
```bash
yarn test
```

View File

View File

@@ -0,0 +1,93 @@
{
"name": "@0xproject/sol-compiler",
"version": "0.4.3",
"description": "Solidity compiler wrapper and artifactor",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"build:watch": "tsc -w",
"build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"test": "run-s build run_mocha",
"run_mocha": "mocha lib/test/*_test.js --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"clean": "shx rm -rf lib scripts",
"migrate": "npm run build; node lib/src/cli.js migrate",
"lint": "tslint --project .",
"test:circleci": "yarn test:coverage",
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
"upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
},
"config": {
"postpublish": {
"assets": [],
"docPublishConfigs": {
"extraFileIncludes": [
"../types/src/index.ts"
],
"s3BucketPath": "s3://doc-jsons/sol-compiler/",
"s3StagingBucketPath": "s3://staging-doc-jsons/sol-compiler/"
}
}
},
"bin": {
"sol-compiler": "lib/src/cli.js"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x-monorepo.git"
},
"author": "Amir Bandeali",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x-monorepo/issues"
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-compiler/README.md",
"devDependencies": {
"@0xproject/dev-utils": "^0.4.1",
"@0xproject/monorepo-scripts": "^0.1.19",
"@0xproject/tslint-config": "^0.4.17",
"@types/mkdirp": "^0.5.2",
"@types/require-from-string": "^1.2.0",
"@types/semver": "^5.5.0",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"copyfiles": "^1.2.0",
"dirty-chai": "^2.0.1",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.8.0",
"typedoc": "0xProject/typedoc",
"types-bn": "^0.0.1",
"typescript": "2.7.1",
"web3-typescript-typings": "^0.10.2",
"zeppelin-solidity": "1.8.0"
},
"dependencies": {
"@0xproject/json-schemas": "^0.7.23",
"@0xproject/sol-resolver": "^0.0.4",
"@0xproject/types": "^0.6.3",
"@0xproject/typescript-typings": "^0.3.1",
"@0xproject/utils": "^0.6.1",
"@0xproject/web3-wrapper": "^0.6.3",
"@types/yargs": "^11.0.0",
"chalk": "^2.3.0",
"ethereumjs-util": "^5.1.1",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"require-from-string": "^2.0.1",
"semver": "^5.5.0",
"solc": "^0.4.23",
"web3": "^0.20.0",
"web3-eth-abi": "^1.0.0-beta.24",
"yargs": "^10.0.3"
},
"publishConfig": {
"access": "public"
}
}

View File

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env node
// We need the above pragma since this script will be run as a command-line tool.
import { BigNumber, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import * as path from 'path';
import * as Web3 from 'web3';
import * as yargs from 'yargs';
import { Compiler } from './compiler';
import { constants } from './utils/constants';
import { CompilerOptions } from './utils/types';
const DEFAULT_CONTRACTS_LIST = '*';
const SEPARATOR = ',';
(async () => {
const argv = yargs
.option('contracts-dir', {
type: 'string',
description: 'path of contracts directory to compile',
})
.option('artifacts-dir', {
type: 'string',
description: 'path to write contracts artifacts to',
})
.option('contracts', {
type: 'string',
description: 'comma separated list of contracts to compile',
})
.help().argv;
const contracts = _.isUndefined(argv.contracts)
? undefined
: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR);
const opts: CompilerOptions = {
contractsDir: argv.contractsDir,
artifactsDir: argv.artifactsDir,
contracts,
};
const compiler = new Compiler(opts);
await compiler.compileAsync();
})().catch(err => {
logUtils.log(err);
process.exit(1);
});

View File

@@ -0,0 +1,276 @@
import {
ContractSource,
ContractSources,
EnumerableResolver,
FallthroughResolver,
FSResolver,
NameResolver,
NPMResolver,
RelativeFSResolver,
Resolver,
URLResolver,
} from '@0xproject/sol-resolver';
import { ContractAbi } from '@0xproject/types';
import { logUtils, promisify } from '@0xproject/utils';
import chalk from 'chalk';
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
import 'isomorphic-fetch';
import * as _ from 'lodash';
import * as path from 'path';
import * as requireFromString from 'require-from-string';
import * as semver from 'semver';
import solc = require('solc');
import { binPaths } from './solc/bin_paths';
import {
createDirIfDoesNotExistAsync,
getContractArtifactIfExistsAsync,
getNormalizedErrMsg,
parseDependencies,
parseSolidityVersionRange,
} from './utils/compiler';
import { constants } from './utils/constants';
import { fsWrapper } from './utils/fs_wrapper';
import {
CompilerOptions,
ContractArtifact,
ContractNetworkData,
ContractNetworks,
ContractSourceData,
ContractSpecificSourceData,
ContractVersionData,
} from './utils/types';
import { utils } from './utils/utils';
type TYPE_ALL_FILES_IDENTIFIER = '*';
const ALL_CONTRACTS_IDENTIFIER = '*';
const ALL_FILES_IDENTIFIER = '*';
const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin');
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts');
// Solc compiler settings cannot be configured from the commandline.
// If you need this configured, please create a `compiler.json` config file
// with your desired configurations.
const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
optimizer: {
enabled: false,
},
outputSelection: {
[ALL_FILES_IDENTIFIER]: {
[ALL_CONTRACTS_IDENTIFIER]: ['abi', 'evm.bytecode.object'],
},
},
};
const CONFIG_FILE = 'compiler.json';
/**
* The Compiler facilitates compiling Solidity smart contracts and saves the results
* to artifact files.
*/
export class Compiler {
private _resolver: Resolver;
private _nameResolver: NameResolver;
private _contractsDir: string;
private _compilerSettings: solc.CompilerSettings;
private _artifactsDir: string;
private _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
/**
* Instantiates a new instance of the Compiler class.
* @return An instance of the Compiler class.
*/
constructor(opts: CompilerOptions) {
// TODO: Look for config file in parent directories if not found in current directory
const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
: {};
this._contractsDir = opts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR;
this._compilerSettings = opts.compilerSettings || config.compilerSettings || DEFAULT_COMPILER_SETTINGS;
this._artifactsDir = opts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR;
this._specifiedContracts = opts.contracts || config.contracts || ALL_CONTRACTS_IDENTIFIER;
this._nameResolver = new NameResolver(path.resolve(this._contractsDir));
const resolver = new FallthroughResolver();
resolver.appendResolver(new URLResolver());
const packagePath = path.resolve('');
resolver.appendResolver(new NPMResolver(packagePath));
resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
resolver.appendResolver(new FSResolver());
resolver.appendResolver(this._nameResolver);
this._resolver = resolver;
}
/**
* Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
*/
public async compileAsync(): Promise<void> {
await createDirIfDoesNotExistAsync(this._artifactsDir);
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
let contractNamesToCompile: string[] = [];
if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
const allContracts = this._nameResolver.getAll();
contractNamesToCompile = _.map(allContracts, contractSource =>
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
);
} else {
contractNamesToCompile = this._specifiedContracts;
}
for (const contractNameToCompile of contractNamesToCompile) {
await this._compileContractAsync(contractNameToCompile);
}
}
/**
* Compiles contract and saves artifact to artifactsDir.
* @param fileName Name of contract with '.sol' extension.
*/
private async _compileContractAsync(contractName: string): Promise<void> {
const contractSource = this._resolver.resolve(contractName);
const absoluteContractPath = path.join(this._contractsDir, contractSource.path);
const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, contractName);
const sourceTreeHashHex = `0x${this._getSourceTreeHash(absoluteContractPath).toString('hex')}`;
let shouldCompile = false;
if (_.isUndefined(currentArtifactIfExists)) {
shouldCompile = true;
} else {
const currentArtifact = currentArtifactIfExists as ContractArtifact;
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
const didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings);
const didSourceChange = currentArtifact.sourceTreeHashHex !== sourceTreeHashHex;
shouldCompile = !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
}
if (!shouldCompile) {
return;
}
const solcVersionRange = parseSolidityVersionRange(contractSource.source);
const availableCompilerVersions = _.keys(binPaths);
const solcVersion = semver.maxSatisfying(availableCompilerVersions, solcVersionRange);
const fullSolcVersion = binPaths[solcVersion];
const compilerBinFilename = path.join(SOLC_BIN_DIR, fullSolcVersion);
let solcjs: string;
const isCompilerAvailableLocally = fs.existsSync(compilerBinFilename);
if (isCompilerAvailableLocally) {
solcjs = fs.readFileSync(compilerBinFilename).toString();
} else {
logUtils.log(`Downloading ${fullSolcVersion}...`);
const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
const response = await fetch(url);
if (response.status !== 200) {
throw new Error(`Failed to load ${fullSolcVersion}`);
}
solcjs = await response.text();
fs.writeFileSync(compilerBinFilename, solcjs);
}
const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename));
logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
const source = contractSource.source;
const standardInput: solc.StandardInput = {
language: 'Solidity',
sources: {
[contractSource.path]: {
content: contractSource.source,
},
},
settings: this._compilerSettings,
};
const compiled: solc.StandardOutput = JSON.parse(
solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
const sourceCodeIfExists = this._resolver.resolve(importPath);
return { contents: sourceCodeIfExists.source };
}),
);
if (!_.isUndefined(compiled.errors)) {
const SOLIDITY_WARNING = 'warning';
const errors = _.filter(compiled.errors, entry => entry.severity !== SOLIDITY_WARNING);
const warnings = _.filter(compiled.errors, entry => entry.severity === SOLIDITY_WARNING);
if (!_.isEmpty(errors)) {
errors.forEach(error => {
const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message);
logUtils.log(chalk.red(normalizedErrMsg));
});
process.exit(1);
} else {
warnings.forEach(warning => {
const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message);
logUtils.log(chalk.yellow(normalizedWarningMsg));
});
}
}
const compiledData = compiled.contracts[contractSource.path][contractName];
if (_.isUndefined(compiledData)) {
throw new Error(
`Contract ${contractName} not found in ${
contractSource.path
}. Please make sure your contract has the same name as it's file name`,
);
}
if (!_.isUndefined(compiledData.evm)) {
if (!_.isUndefined(compiledData.evm.bytecode) && !_.isUndefined(compiledData.evm.bytecode.object)) {
compiledData.evm.bytecode.object = ethUtil.addHexPrefix(compiledData.evm.bytecode.object);
}
if (
!_.isUndefined(compiledData.evm.deployedBytecode) &&
!_.isUndefined(compiledData.evm.deployedBytecode.object)
) {
compiledData.evm.deployedBytecode.object = ethUtil.addHexPrefix(
compiledData.evm.deployedBytecode.object,
);
}
}
const sourceCodes = _.mapValues(
compiled.sources,
(_1, sourceFilePath) => this._resolver.resolve(sourceFilePath).source,
);
const contractVersion: ContractVersionData = {
compilerOutput: compiledData,
sources: compiled.sources,
sourceCodes,
sourceTreeHashHex,
compiler: {
name: 'solc',
version: solcVersion,
settings: this._compilerSettings,
},
};
let newArtifact: ContractArtifact;
if (!_.isUndefined(currentArtifactIfExists)) {
const currentArtifact = currentArtifactIfExists as ContractArtifact;
newArtifact = {
...currentArtifact,
...contractVersion,
};
} else {
newArtifact = {
schemaVersion: constants.LATEST_ARTIFACT_VERSION,
contractName,
...contractVersion,
networks: {},
};
}
const artifactString = utils.stringifyWithFormatting(newArtifact);
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
logUtils.log(`${contractName} artifact saved!`);
}
/**
* Gets the source tree hash for a file and its dependencies.
* @param fileName Name of contract file.
*/
private _getSourceTreeHash(importPath: string): Buffer {
const contractSource = this._resolver.resolve(importPath);
const dependencies = parseDependencies(contractSource);
const sourceHash = ethUtil.sha3(contractSource.source);
if (dependencies.length === 0) {
return sourceHash;
} else {
const dependencySourceTreeHashes = _.map(dependencies, (dependency: string) =>
this._getSourceTreeHash(dependency),
);
const sourceTreeHashesBuffer = Buffer.concat([sourceHash, ...dependencySourceTreeHashes]);
const sourceTreeHash = ethUtil.sha3(sourceTreeHashesBuffer);
return sourceTreeHash;
}
}
}

View File

@@ -0,0 +1,6 @@
declare module '*.json' {
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
}

View File

@@ -0,0 +1,2 @@
export { Compiler } from './compiler';
export { ContractArtifact, ContractNetworks } from './utils/types';

View File

@@ -0,0 +1,8 @@
import { postpublishUtils } from '@0xproject/monorepo-scripts';
import * as packageJSON from '../package.json';
import * as tsConfigJSON from '../tsconfig.json';
const cwd = `${__dirname}/..`;
// tslint:disable-next-line:no-floating-promises
postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd);

View File

@@ -0,0 +1,8 @@
import { postpublishUtils } from '@0xproject/monorepo-scripts';
import * as packageJSON from '../package.json';
import * as tsConfigJSON from '../tsconfig.json';
const cwd = `${__dirname}/..`;
// tslint:disable-next-line:no-floating-promises
postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd);

View File

@@ -0,0 +1,20 @@
export interface BinaryPaths {
[key: string]: string;
}
export const binPaths: BinaryPaths = {
'0.4.10': 'soljson-v0.4.10+commit.f0d539ae.js',
'0.4.11': 'soljson-v0.4.11+commit.68ef5810.js',
'0.4.12': 'soljson-v0.4.12+commit.194ff033.js',
'0.4.13': 'soljson-v0.4.13+commit.fb4cb1a.js',
'0.4.14': 'soljson-v0.4.14+commit.c2215d46.js',
'0.4.15': 'soljson-v0.4.15+commit.bbb8e64f.js',
'0.4.16': 'soljson-v0.4.16+commit.d7661dd9.js',
'0.4.17': 'soljson-v0.4.17+commit.bdeb9e52.js',
'0.4.18': 'soljson-v0.4.18+commit.9cf6e910.js',
'0.4.19': 'soljson-v0.4.19+commit.c4cbbb05.js',
'0.4.20': 'soljson-v0.4.20+commit.3155dd80.js',
'0.4.21': 'soljson-v0.4.21+commit.dfe3193c.js',
'0.4.22': 'soljson-v0.4.22+commit.4cb486ee.js',
'0.4.23': 'soljson-v0.4.23+commit.124ca40d.js',
};

View File

@@ -0,0 +1,107 @@
import { ContractSource, ContractSources } from '@0xproject/sol-resolver';
import { logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import * as path from 'path';
import * as solc from 'solc';
import { constants } from './constants';
import { fsWrapper } from './fs_wrapper';
import { ContractArtifact } from './types';
/**
* Gets contract data on network or returns if an artifact does not exist.
* @param artifactsDir Path to the artifacts directory.
* @param contractName Name of contract.
* @return Contract data on network or undefined.
*/
export async function getContractArtifactIfExistsAsync(
artifactsDir: string,
contractName: string,
): Promise<ContractArtifact | void> {
let contractArtifact;
const currentArtifactPath = `${artifactsDir}/${contractName}.json`;
try {
const opts = {
encoding: 'utf8',
};
const contractArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts);
contractArtifact = JSON.parse(contractArtifactString);
return contractArtifact;
} catch (err) {
logUtils.log(`Artifact for ${contractName} does not exist`);
return undefined;
}
}
/**
* Creates a directory if it does not already exist.
* @param artifactsDir Path to the directory.
*/
export async function createDirIfDoesNotExistAsync(dirPath: string): Promise<void> {
if (!fsWrapper.doesPathExistSync(dirPath)) {
logUtils.log(`Creating directory at ${dirPath}...`);
await fsWrapper.mkdirpAsync(dirPath);
}
}
/**
* Searches Solidity source code for compiler version range.
* @param source Source code of contract.
* @return Solc compiler version range.
*/
export function parseSolidityVersionRange(source: string): string {
const SOLIDITY_VERSION_RANGE_REGEX = /pragma\s+solidity\s+(.*);/;
const solcVersionRangeMatch = source.match(SOLIDITY_VERSION_RANGE_REGEX);
if (_.isNull(solcVersionRangeMatch)) {
throw new Error('Could not find Solidity version range in source');
}
const solcVersionRange = solcVersionRangeMatch[1];
return solcVersionRange;
}
/**
* Normalizes the path found in the error message.
* Example: converts 'base/Token.sol:6:46: Warning: Unused local variable'
* to 'Token.sol:6:46: Warning: Unused local variable'
* This is used to prevent logging the same error multiple times.
* @param errMsg An error message from the compiled output.
* @return The error message with directories truncated from the contract path.
*/
export function getNormalizedErrMsg(errMsg: string): string {
const SOLIDITY_FILE_EXTENSION_REGEX = /(.*\.sol)/;
const errPathMatch = errMsg.match(SOLIDITY_FILE_EXTENSION_REGEX);
if (_.isNull(errPathMatch)) {
throw new Error('Could not find a path in error message');
}
const errPath = errPathMatch[0];
const baseContract = path.basename(errPath);
const normalizedErrMsg = errMsg.replace(errPath, baseContract);
return normalizedErrMsg;
}
/**
* Parses the contract source code and extracts the dendencies
* @param source Contract source code
* @return List of dependendencies
*/
export function parseDependencies(contractSource: ContractSource): string[] {
// TODO: Use a proper parser
const source = contractSource.source;
const IMPORT_REGEX = /(import\s)/;
const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockChainCompany/soljitsu/blob/master/lib/shared.js
const dependencies: string[] = [];
const lines = source.split('\n');
_.forEach(lines, line => {
if (!_.isNull(line.match(IMPORT_REGEX))) {
const dependencyMatch = line.match(DEPENDENCY_PATH_REGEX);
if (!_.isNull(dependencyMatch)) {
let dependencyPath = dependencyMatch[1];
if (dependencyPath.startsWith('.')) {
dependencyPath = path.join(path.dirname(contractSource.path), dependencyPath);
}
dependencies.push(dependencyPath);
}
}
});
return dependencies;
}

View File

@@ -0,0 +1,5 @@
export const constants = {
SOLIDITY_FILE_EXTENSION: '.sol',
BASE_COMPILER_URL: 'https://ethereum.github.io/solc-bin/bin/',
LATEST_ARTIFACT_VERSION: '2.0.0',
};

View File

@@ -0,0 +1,18 @@
import { AbiDefinition, AbiType, ContractAbi, DataItem } from '@0xproject/types';
import * as _ from 'lodash';
import * as web3Abi from 'web3-eth-abi';
export const encoder = {
encodeConstructorArgsFromAbi(args: any[], abi: ContractAbi): string {
const constructorTypes: string[] = [];
_.each(abi, (element: AbiDefinition) => {
if (element.type === AbiType.Constructor) {
_.each(element.inputs, (input: DataItem) => {
constructorTypes.push(input.type);
});
}
});
const encodedParameters = web3Abi.encodeParameters(constructorTypes, args);
return encodedParameters;
},
};

View File

@@ -0,0 +1,13 @@
import { promisify } from '@0xproject/utils';
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
export const fsWrapper = {
readdirAsync: promisify<string[]>(fs.readdir),
readFileAsync: promisify<string>(fs.readFile),
writeFileAsync: promisify<undefined>(fs.writeFile),
mkdirpAsync: promisify<undefined>(mkdirp),
doesPathExistSync: fs.existsSync,
rmdirSync: fs.rmdirSync,
removeFileAsync: promisify<undefined>(fs.unlink),
};

View File

@@ -0,0 +1,79 @@
import { ContractAbi, Provider, TxData } from '@0xproject/types';
import * as solc from 'solc';
import * as Web3 from 'web3';
import * as yargs from 'yargs';
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
}
export interface ContractArtifact extends ContractVersionData {
schemaVersion: string;
contractName: string;
networks: ContractNetworks;
}
export interface ContractVersionData {
compiler: {
name: 'solc';
version: string;
settings: solc.CompilerSettings;
};
sources: {
[sourceName: string]: {
id: number;
};
};
sourceCodes: {
[sourceName: string]: string;
};
sourceTreeHashHex: string;
compilerOutput: solc.StandardContractOutput;
}
export interface ContractNetworks {
[networkId: number]: ContractNetworkData;
}
export interface ContractNetworkData {
address: string;
links: {
[linkName: string]: string;
};
constructorArgs: string;
}
export interface SolcErrors {
[key: string]: boolean;
}
export interface CompilerOptions {
contractsDir?: string;
artifactsDir?: string;
compilerSettings?: solc.CompilerSettings;
contracts?: string[] | '*';
}
export interface ContractSourceData {
[contractName: string]: ContractSpecificSourceData;
}
export interface ContractSpecificSourceData {
solcVersionRange: string;
sourceHash: Buffer;
sourceTreeHash: Buffer;
}
export interface Token {
address?: string;
name: string;
symbol: string;
decimals: number;
ipfsHash: string;
swarmHash: string;
}
export type DoneCallback = (err?: Error) => void;

View File

@@ -0,0 +1,6 @@
export const utils = {
stringifyWithFormatting(obj: any): string {
const stringifiedObj = JSON.stringify(obj, null, '\t');
return stringifiedObj;
},
};

View File

@@ -0,0 +1,45 @@
import { DoneCallback } from '@0xproject/types';
import * as chai from 'chai';
import 'mocha';
import { Compiler } from '../src/compiler';
import { fsWrapper } from '../src/utils/fs_wrapper';
import { CompilerOptions, ContractArtifact, ContractNetworkData } from '../src/utils/types';
import { exchange_binary } from './fixtures/exchange_bin';
import { constants } from './util/constants';
const expect = chai.expect;
describe('#Compiler', function() {
this.timeout(constants.timeoutMs);
const artifactsDir = `${__dirname}/fixtures/artifacts`;
const contractsDir = `${__dirname}/fixtures/contracts`;
const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
const compilerOpts: CompilerOptions = {
artifactsDir,
contractsDir,
contracts: constants.contracts,
};
const compiler = new Compiler(compilerOpts);
beforeEach((done: DoneCallback) => {
(async () => {
if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
await fsWrapper.removeFileAsync(exchangeArtifactPath);
}
await compiler.compileAsync();
done();
})().catch(done);
});
it('should create an Exchange artifact with the correct unlinked binary', async () => {
const opts = {
encoding: 'utf8',
};
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
// The last 43 bytes of the binaries are metadata which may not be equivalent
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(2, -86);
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
});
});

View File

@@ -0,0 +1,83 @@
import * as chai from 'chai';
import * as dirtyChai from 'dirty-chai';
import * as _ from 'lodash';
import 'mocha';
import {
createDirIfDoesNotExistAsync,
getNormalizedErrMsg,
parseDependencies,
parseSolidityVersionRange,
} from '../src/utils/compiler';
import { fsWrapper } from '../src/utils/fs_wrapper';
chai.use(dirtyChai);
const expect = chai.expect;
describe('Compiler utils', () => {
describe('#getNormalizedErrorMessage', () => {
it('normalizes the error message', () => {
const errMsg = 'base/Token.sol:6:46: Warning: Unused local variable';
const normalizedErrMsg = getNormalizedErrMsg(errMsg);
expect(normalizedErrMsg).to.be.equal('Token.sol:6:46: Warning: Unused local variable');
});
});
describe('#createDirIfDoesNotExistAsync', () => {
it('creates artifacts dir', async () => {
const artifactsDir = `${__dirname}/artifacts`;
expect(fsWrapper.doesPathExistSync(artifactsDir)).to.be.false();
await createDirIfDoesNotExistAsync(artifactsDir);
expect(fsWrapper.doesPathExistSync(artifactsDir)).to.be.true();
fsWrapper.rmdirSync(artifactsDir);
expect(fsWrapper.doesPathExistSync(artifactsDir)).to.be.false();
});
});
describe('#parseSolidityVersionRange', () => {
it('correctly parses the version range', () => {
expect(parseSolidityVersionRange('pragma solidity ^0.0.1;')).to.be.equal('^0.0.1');
expect(parseSolidityVersionRange('\npragma solidity 0.0.1;')).to.be.equal('0.0.1');
expect(parseSolidityVersionRange('pragma solidity <=1.0.1;')).to.be.equal('<=1.0.1');
expect(parseSolidityVersionRange('pragma solidity ~1.0.1;')).to.be.equal('~1.0.1');
});
// TODO: For now that doesn't work. This will work after we switch to a grammar-based parser
it.skip('correctly parses the version range with comments', () => {
expect(parseSolidityVersionRange('// pragma solidity ~1.0.1;\npragma solidity ~1.0.2;')).to.be.equal(
'~1.0.2',
);
});
});
describe('#parseDependencies', () => {
it('correctly parses Exchange dependencies', async () => {
const path = `${__dirname}/fixtures/contracts/Exchange.sol`;
const source = await fsWrapper.readFileAsync(path, {
encoding: 'utf8',
});
const dependencies = parseDependencies({ source, path });
const expectedDependencies = [
'zeppelin-solidity/contracts/token/ERC20/ERC20.sol',
'packages/sol-compiler/lib/test/fixtures/contracts/TokenTransferProxy.sol',
'packages/sol-compiler/lib/test/fixtures/contracts/base/SafeMath.sol',
];
_.each(expectedDependencies, expectedDepdency => {
const foundDependency = _.find(dependencies, dependency => _.endsWith(dependency, expectedDepdency));
expect(foundDependency, `${expectedDepdency} not found`).to.not.be.undefined();
});
});
it('correctly parses TokenTransferProxy dependencies', async () => {
const path = `${__dirname}/fixtures/contracts/TokenTransferProxy.sol`;
const source = await fsWrapper.readFileAsync(path, {
encoding: 'utf8',
});
expect(parseDependencies({ source, path })).to.be.deep.equal([
'zeppelin-solidity/contracts/ownership/Ownable.sol',
'zeppelin-solidity/contracts/token/ERC20/ERC20.sol',
]);
});
// TODO: For now that doesn't work. This will work after we switch to a grammar-based parser
it.skip('correctly parses commented out dependencies', async () => {
const path = '';
const source = `// import "./TokenTransferProxy.sol";`;
expect(parseDependencies({ path, source })).to.be.deep.equal([]);
});
});
});

View File

@@ -0,0 +1,603 @@
/*
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.4.14;
import {ERC20 as Token} from "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "./TokenTransferProxy.sol";
import "./base/SafeMath.sol";
/// @title Exchange - Facilitates exchange of ERC20 tokens.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract Exchange is SafeMath {
// Error Codes
enum Errors {
ORDER_EXPIRED, // Order has already expired
ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
}
string constant public VERSION = "1.0.0";
uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; // Changes to state require at least 5000 gas
address public ZRX_TOKEN_CONTRACT;
address public TOKEN_TRANSFER_PROXY_CONTRACT;
// Mappings of orderHash => amounts of takerTokenAmount filled or cancelled.
mapping (bytes32 => uint) public filled;
mapping (bytes32 => uint) public cancelled;
event LogFill(
address indexed maker,
address taker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint filledMakerTokenAmount,
uint filledTakerTokenAmount,
uint paidMakerFee,
uint paidTakerFee,
bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
bytes32 orderHash
);
event LogCancel(
address indexed maker,
address indexed feeRecipient,
address makerToken,
address takerToken,
uint cancelledMakerTokenAmount,
uint cancelledTakerTokenAmount,
bytes32 indexed tokens,
bytes32 orderHash
);
event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
struct Order {
address maker;
address taker;
address makerToken;
address takerToken;
address feeRecipient;
uint makerTokenAmount;
uint takerTokenAmount;
uint makerFee;
uint takerFee;
uint expirationTimestampInSec;
bytes32 orderHash;
}
function Exchange(address _zrxToken, address _tokenTransferProxy) {
ZRX_TOKEN_CONTRACT = _zrxToken;
TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
}
/*
* Core exchange functions
*/
/// @dev Fills the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Total amount of takerToken filled in trade.
function fillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8 v,
bytes32 r,
bytes32 s)
public
returns (uint filledTakerTokenAmount)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.taker == address(0) || order.taker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && fillTakerTokenAmount > 0);
require(isValidSignature(
order.maker,
order.orderHash,
v,
r,
s
));
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
filledTakerTokenAmount = min256(fillTakerTokenAmount, remainingTakerTokenAmount);
if (filledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
if (isRoundingError(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount)) {
LogError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), order.orderHash);
return 0;
}
if (!shouldThrowOnInsufficientBalanceOrAllowance && !isTransferable(order, filledTakerTokenAmount)) {
LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), order.orderHash);
return 0;
}
uint filledMakerTokenAmount = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
uint paidMakerFee;
uint paidTakerFee;
filled[order.orderHash] = safeAdd(filled[order.orderHash], filledTakerTokenAmount);
require(transferViaTokenTransferProxy(
order.makerToken,
order.maker,
msg.sender,
filledMakerTokenAmount
));
require(transferViaTokenTransferProxy(
order.takerToken,
msg.sender,
order.maker,
filledTakerTokenAmount
));
if (order.feeRecipient != address(0)) {
if (order.makerFee > 0) {
paidMakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
order.maker,
order.feeRecipient,
paidMakerFee
));
}
if (order.takerFee > 0) {
paidTakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.takerFee);
require(transferViaTokenTransferProxy(
ZRX_TOKEN_CONTRACT,
msg.sender,
order.feeRecipient,
paidTakerFee
));
}
}
LogFill(
order.maker,
msg.sender,
order.feeRecipient,
order.makerToken,
order.takerToken,
filledMakerTokenAmount,
filledTakerTokenAmount,
paidMakerFee,
paidTakerFee,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return filledTakerTokenAmount;
}
/// @dev Cancels the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
/// @return Amount of takerToken cancelled.
function cancelOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint cancelTakerTokenAmount)
public
returns (uint)
{
Order memory order = Order({
maker: orderAddresses[0],
taker: orderAddresses[1],
makerToken: orderAddresses[2],
takerToken: orderAddresses[3],
feeRecipient: orderAddresses[4],
makerTokenAmount: orderValues[0],
takerTokenAmount: orderValues[1],
makerFee: orderValues[2],
takerFee: orderValues[3],
expirationTimestampInSec: orderValues[4],
orderHash: getOrderHash(orderAddresses, orderValues)
});
require(order.maker == msg.sender);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);
if (block.timestamp >= order.expirationTimestampInSec) {
LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
return 0;
}
uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
if (cancelledTakerTokenAmount == 0) {
LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
return 0;
}
cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);
LogCancel(
order.maker,
order.feeRecipient,
order.makerToken,
order.takerToken,
getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
cancelledTakerTokenAmount,
keccak256(order.makerToken, order.takerToken),
order.orderHash
);
return cancelledTakerTokenAmount;
}
/*
* Wrapper functions
*/
/// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
function fillOrKillOrder(
address[5] orderAddresses,
uint[6] orderValues,
uint fillTakerTokenAmount,
uint8 v,
bytes32 r,
bytes32 s)
public
{
require(fillOrder(
orderAddresses,
orderValues,
fillTakerTokenAmount,
false,
v,
r,
s
) == fillTakerTokenAmount);
}
/// @dev Synchronously executes multiple fill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrKillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrKillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
v[i],
r[i],
s[i]
);
}
}
/// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
/// @return Total amount of fillTakerTokenAmount filled in orders.
function fillOrdersUpTo(
address[5][] orderAddresses,
uint[6][] orderValues,
uint fillTakerTokenAmount,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
returns (uint)
{
uint filledTakerTokenAmount = 0;
for (uint i = 0; i < orderAddresses.length; i++) {
require(orderAddresses[i][3] == orderAddresses[0][3]); // takerToken must be the same for each order
filledTakerTokenAmount = safeAdd(filledTakerTokenAmount, fillOrder(
orderAddresses[i],
orderValues[i],
safeSub(fillTakerTokenAmount, filledTakerTokenAmount),
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
));
if (filledTakerTokenAmount == fillTakerTokenAmount) break;
}
return filledTakerTokenAmount;
}
/// @dev Synchronously cancels multiple orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
function batchCancelOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] cancelTakerTokenAmounts)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
cancelOrder(
orderAddresses[i],
orderValues[i],
cancelTakerTokenAmounts[i]
);
}
}
/*
* Constant public functions
*/
/// @dev Calculates Keccak-256 hash of order with specified parameters.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @return Keccak-256 hash of order.
function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
public
constant
returns (bytes32)
{
return keccak256(
address(this),
orderAddresses[0], // maker
orderAddresses[1], // taker
orderAddresses[2], // makerToken
orderAddresses[3], // takerToken
orderAddresses[4], // feeRecipient
orderValues[0], // makerTokenAmount
orderValues[1], // takerTokenAmount
orderValues[2], // makerFee
orderValues[3], // takerFee
orderValues[4], // expirationTimestampInSec
orderValues[5] // salt
);
}
/// @dev Verifies that an order signature is valid.
/// @param signer address of signer.
/// @param hash Signed Keccak-256 hash.
/// @param v ECDSA signature parameter v.
/// @param r ECDSA signature parameters r.
/// @param s ECDSA signature parameters s.
/// @return Validity of order signature.
function isValidSignature(
address signer,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s)
public
constant
returns (bool)
{
return signer == ecrecover(
keccak256("\x19Ethereum Signed Message:\n32", hash),
v,
r,
s
);
}
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingError(uint numerator, uint denominator, uint target)
public
constant
returns (bool)
{
uint remainder = mulmod(target, numerator, denominator);
if (remainder == 0) return false; // No rounding error.
uint errPercentageTimes1000000 = safeDiv(
safeMul(remainder, 1000000),
safeMul(numerator, target)
);
return errPercentageTimes1000000 > 1000;
}
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmount(uint numerator, uint denominator, uint target)
public
constant
returns (uint)
{
return safeDiv(safeMul(numerator, target), denominator);
}
/// @dev Calculates the sum of values already filled and cancelled for a given order.
/// @param orderHash The Keccak-256 hash of the given order.
/// @return Sum of values already filled and cancelled.
function getUnavailableTakerTokenAmount(bytes32 orderHash)
public
constant
returns (uint)
{
return safeAdd(filled[orderHash], cancelled[orderHash]);
}
/*
* Internal functions
*/
/// @dev Transfers a token using TokenTransferProxy transferFrom function.
/// @param token Address of token to transferFrom.
/// @param from Address transfering token.
/// @param to Address receiving token.
/// @param value Amount of token to transfer.
/// @return Success of token transfer.
function transferViaTokenTransferProxy(
address token,
address from,
address to,
uint value)
internal
returns (bool)
{
return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom(token, from, to, value);
}
/// @dev Checks if any order transfers will fail.
/// @param order Order struct of params that will be checked.
/// @param fillTakerTokenAmount Desired amount of takerToken to fill.
/// @return Predicted result of transfers.
function isTransferable(Order order, uint fillTakerTokenAmount)
internal
constant // The called token contracts may attempt to change state, but will not be able to due to gas limits on getBalance and getAllowance.
returns (bool)
{
address taker = msg.sender;
uint fillMakerTokenAmount = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
if (order.feeRecipient != address(0)) {
bool isMakerTokenZRX = order.makerToken == ZRX_TOKEN_CONTRACT;
bool isTakerTokenZRX = order.takerToken == ZRX_TOKEN_CONTRACT;
uint paidMakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerFee);
uint paidTakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.takerFee);
uint requiredMakerZRX = isMakerTokenZRX ? safeAdd(fillMakerTokenAmount, paidMakerFee) : paidMakerFee;
uint requiredTakerZRX = isTakerTokenZRX ? safeAdd(fillTakerTokenAmount, paidTakerFee) : paidTakerFee;
if ( getBalance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
|| getBalance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
|| getAllowance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
) return false;
if (!isMakerTokenZRX && ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount // Don't double check makerToken if ZRX
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount)
) return false;
if (!isTakerTokenZRX && ( getBalance(order.takerToken, taker) < fillTakerTokenAmount // Don't double check takerToken if ZRX
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount)
) return false;
} else if ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount
|| getBalance(order.takerToken, taker) < fillTakerTokenAmount
|| getAllowance(order.takerToken, taker) < fillTakerTokenAmount
) return false;
return true;
}
/// @dev Get token balance of an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Token balance of owner.
function getBalance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
}
/// @dev Get allowance of token given to TokenTransferProxy by an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Allowance of token given to TokenTransferProxy by owner.
function getAllowance(address token, address owner)
internal
constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
returns (uint)
{
return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
}
}

View File

@@ -0,0 +1,115 @@
/*
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.4.14;
import { Ownable } from "zeppelin-solidity/contracts/ownership/Ownable.sol";
import { ERC20 as Token } from "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
contract TokenTransferProxy is Ownable {
/// @dev Only authorized addresses can invoke functions with this modifier.
modifier onlyAuthorized {
require(authorized[msg.sender]);
_;
}
modifier targetAuthorized(address target) {
require(authorized[target]);
_;
}
modifier targetNotAuthorized(address target) {
require(!authorized[target]);
_;
}
mapping (address => bool) public authorized;
address[] public authorities;
event LogAuthorizedAddressAdded(address indexed target, address indexed caller);
event LogAuthorizedAddressRemoved(address indexed target, address indexed caller);
/*
* Public functions
*/
/// @dev Authorizes an address.
/// @param target Address to authorize.
function addAuthorizedAddress(address target)
public
onlyOwner
targetNotAuthorized(target)
{
authorized[target] = true;
authorities.push(target);
LogAuthorizedAddressAdded(target, msg.sender);
}
/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.
function removeAuthorizedAddress(address target)
public
onlyOwner
targetAuthorized(target)
{
delete authorized[target];
for (uint i = 0; i < authorities.length; i++) {
if (authorities[i] == target) {
authorities[i] = authorities[authorities.length - 1];
authorities.length -= 1;
break;
}
}
LogAuthorizedAddressRemoved(target, msg.sender);
}
/// @dev Calls into ERC20 Token contract, invoking transferFrom.
/// @param token Address of token to transfer.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param value Amount of token to transfer.
/// @return Success of transfer.
function transferFrom(
address token,
address from,
address to,
uint value)
public
onlyAuthorized
returns (bool)
{
return Token(token).transferFrom(from, to, value);
}
/*
* Public constant functions
*/
/// @dev Gets all authorized addresses.
/// @return Array of authorized addresses.
function getAuthorizedAddresses()
public
constant
returns (address[])
{
return authorities;
}
}

View File

@@ -0,0 +1,41 @@
pragma solidity ^0.4.14;
contract SafeMath {
function safeMul(uint a, uint b) internal constant returns (uint256) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeDiv(uint a, uint b) internal constant returns (uint256) {
uint c = a / b;
return c;
}
function safeSub(uint a, uint b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function safeAdd(uint a, uint b) internal constant returns (uint256) {
uint c = a + b;
assert(c >= a);
return c;
}
function max64(uint64 a, uint64 b) internal constant returns (uint64) {
return a >= b ? a : b;
}
function min64(uint64 a, uint64 b) internal constant returns (uint64) {
return a < b ? a : b;
}
function max256(uint256 a, uint256 b) internal constant returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal constant returns (uint256) {
return a < b ? a : b;
}
}

View File

@@ -0,0 +1,38 @@
pragma solidity ^0.4.14;
contract Token {
/// @return total amount of tokens
function totalSupply() constant returns (uint supply) {}
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant returns (uint balance) {}
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint _value) returns (bool success) {}
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint _value) returns (bool success) {}
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint _value) returns (bool success) {}
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) constant returns (uint remaining) {}
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
import { BigNumber } from '@0xproject/utils';
export const constants = {
networkId: 0,
optimizerEnabled: false,
gasPrice: new BigNumber(20000000000),
timeoutMs: 30000,
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
contracts: '*' as '*',
};

View File

@@ -0,0 +1,9 @@
import { web3Factory } from '@0xproject/dev-utils';
import { Provider } from '@0xproject/types';
import * as Web3 from 'web3';
const providerConfigs = { shouldUseInProcessGanache: true };
const web3Instance = web3Factory.create(providerConfigs);
const provider: Provider = web3Instance.currentProvider;
export { provider };

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"strictFunctionTypes": false
},
"include": ["./src/**/*", "./test/**/*"]
}

View File

@@ -0,0 +1,3 @@
{
"extends": ["@0xproject/tslint-config"]
}