ZeroEx: TransformERC20, TokenSpender (#2545)

* `@0x/contracts-utils`: Convert more 0.6 contracts

* `@0x/contracts-erc20`: Add solidity 0.6 contracts.

* `@0x/utils`: Add new `ZeroExRevertErrors` revert types

* `@0x/contracts-zero-ex`: Introduce the `TransformERC20` feature.

* `@0x/subproviders`: Update ganache-core.
`@0x/web3-wrapper`: Update ganache-core.

* `@0x/contracts-zero-ex`: Make `TokenSpender`'s puppet contract a distinct contract type and rename `getTokenSpenderPuppet()` to `getAllowanceTarget()`

* `@0x/zero-ex`: Rebase and use "slot" instead of "offset" language in storage buckets.

* `@0x/web3-wrapper`: Add `getAccountNonceAsync()` to `Web3Wrapper`

* `@0x/contracts-zero-ex`: Revamp TransformERC20.

* `@0x/contracts-zero-ex`: Remove `payable` from `IERC20Transformer.transform()` and disable hex capitalization linter rule because of prettier conflicts.

* `@0x/contracts-zero-ex`: Use `immutable` owner in `Puppet` instead of `Ownable`.

* `@x/utils`: Address review feedback.

* `@0x/contracts-zero-ex`: Address review feedback.

* `@0x/contracts-utils`: Address review feedback.

* `@0x/contracts-zero-ex`: Return deployment nonce in `transform()`.

* `@0x/contracts-zero-ex`: Finish returning deployment nonce in `transform()`.

* `@0x/contracts-zero-ex`: Fix doc-gen bug.

* `@0x/contracts-zero-ex`: Address review comments.

* `@0x/utils`: Add `NegativeTransformERC20OutputERror`

* `@0x/contracts-zero-ex`: Revert if the taker's output amount decreases.

Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
Lawrence Forman
2020-05-20 22:47:21 -04:00
committed by GitHub
parent b23f1eb145
commit 2fce332ed7
87 changed files with 5613 additions and 260 deletions

View File

@@ -0,0 +1,82 @@
import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, hexUtils, StringRevertError } from '@0x/utils';
import { artifacts } from './artifacts';
import { AllowanceTargetContract, TestCallTargetContract, TestCallTargetEvents } from './wrappers';
blockchainTests.resets('AllowanceTarget', env => {
let owner: string;
let authority: string;
let allowanceTarget: AllowanceTargetContract;
let callTarget: TestCallTargetContract;
before(async () => {
[owner, authority] = await env.getAccountAddressesAsync();
allowanceTarget = await AllowanceTargetContract.deployFrom0xArtifactAsync(
artifacts.AllowanceTarget,
env.provider,
env.txDefaults,
artifacts,
);
await allowanceTarget.addAuthorizedAddress(authority).awaitTransactionSuccessAsync();
callTarget = await TestCallTargetContract.deployFrom0xArtifactAsync(
artifacts.TestCallTarget,
env.provider,
env.txDefaults,
artifacts,
);
});
const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678');
const REVERTING_DATA = '0x1337';
describe('executeCall()', () => {
it('non-authority cannot call executeCall()', async () => {
const notAuthority = randomAddress();
const tx = allowanceTarget
.executeCall(randomAddress(), hexUtils.random())
.callAsync({ from: notAuthority });
return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthority));
});
it('authority can call executeCall()', async () => {
const targetData = hexUtils.random(128);
const receipt = await allowanceTarget
.executeCall(callTarget.address, targetData)
.awaitTransactionSuccessAsync({ from: authority });
verifyEventsFromLogs(
receipt.logs,
[
{
context: callTarget.address,
sender: allowanceTarget.address,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('AllowanceTarget returns call result', async () => {
const result = await allowanceTarget
.executeCall(callTarget.address, hexUtils.random(128))
.callAsync({ from: authority });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
it('AllowanceTarget returns raw call revert', async () => {
const tx = allowanceTarget.executeCall(callTarget.address, REVERTING_DATA).callAsync({ from: authority });
return expect(tx).to.revertWith(new StringRevertError('TestCallTarget/REVERT'));
});
it('AllowanceTarget cannot receive ETH', async () => {
const tx = env.web3Wrapper.sendTransactionAsync({
to: allowanceTarget.address,
from: owner,
value: 0,
});
return expect(tx).to.eventually.be.rejected();
});
});
});

View File

@@ -5,16 +5,25 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json';
import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
import * as IFeature from '../test/generated-artifacts/IFeature.json';
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json';
import * as ITransformERC20 from '../test/generated-artifacts/ITransformERC20.json';
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
import * as LibERC20Transformer from '../test/generated-artifacts/LibERC20Transformer.json';
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
@@ -22,14 +31,28 @@ import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErr
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
import * as LibStorage from '../test/generated-artifacts/LibStorage.json';
import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json';
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json';
import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json';
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json';
import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json';
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
export const artifacts = {
ZeroEx: ZeroEx as ContractArtifact,
@@ -37,14 +60,26 @@ export const artifacts = {
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact,
LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact,
LibWalletRichErrors: LibWalletRichErrors as ContractArtifact,
AllowanceTarget: AllowanceTarget as ContractArtifact,
FlashWallet: FlashWallet as ContractArtifact,
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
IFlashWallet: IFlashWallet as ContractArtifact,
Bootstrap: Bootstrap as ContractArtifact,
IBootstrap: IBootstrap as ContractArtifact,
IFeature: IFeature as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
ITokenSpender: ITokenSpender as ContractArtifact,
ITransformERC20: ITransformERC20 as ContractArtifact,
Ownable: Ownable as ContractArtifact,
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
TokenSpender: TokenSpender as ContractArtifact,
TransformERC20: TransformERC20 as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact,
FullMigration: FullMigration as ContractArtifact,
InitialMigration: InitialMigration as ContractArtifact,
LibBootstrap: LibBootstrap as ContractArtifact,
LibMigrate: LibMigrate as ContractArtifact,
@@ -52,10 +87,21 @@ export const artifacts = {
LibProxyStorage: LibProxyStorage as ContractArtifact,
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
LibStorage: LibStorage as ContractArtifact,
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact,
IERC20Transformer: IERC20Transformer as ContractArtifact,
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
TestCallTarget: TestCallTarget as ContractArtifact,
TestFullMigration: TestFullMigration as ContractArtifact,
TestInitialMigration: TestInitialMigration as ContractArtifact,
TestMigrator: TestMigrator as ContractArtifact,
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
TestTokenSpender: TestTokenSpender as ContractArtifact,
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
TestTransformERC20: TestTransformERC20 as ContractArtifact,
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
};

View File

@@ -0,0 +1,141 @@
import {
blockchainTests,
expect,
getRandomInteger,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import {
TestTokenSpenderERC20TokenContract,
TestTokenSpenderERC20TokenEvents,
TokenSpenderContract,
ZeroExContract,
} from '../wrappers';
blockchainTests.resets('TokenSpender feature', env => {
let zeroEx: ZeroExContract;
let feature: TokenSpenderContract;
let token: TestTokenSpenderERC20TokenContract;
let allowanceTarget: string;
before(async () => {
const [owner] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
tokenSpender: await TokenSpenderContract.deployFrom0xArtifactAsync(
artifacts.TestTokenSpender,
env.provider,
env.txDefaults,
artifacts,
),
});
feature = new TokenSpenderContract(zeroEx.address, env.provider, env.txDefaults, abis);
token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestTokenSpenderERC20Token,
env.provider,
env.txDefaults,
artifacts,
);
allowanceTarget = await feature.getAllowanceTarget().callAsync();
});
describe('_spendERC20Tokens()', () => {
const EMPTY_RETURN_AMOUNT = 1337;
const FALSE_RETURN_AMOUNT = 1338;
const REVERT_RETURN_AMOUNT = 1339;
it('_spendERC20Tokens() successfully calls compliant ERC20 token', async () => {
const tokenFrom = randomAddress();
const tokenTo = randomAddress();
const tokenAmount = new BigNumber(123456);
const receipt = await feature
._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
.awaitTransactionSuccessAsync();
verifyEventsFromLogs(
receipt.logs,
[
{
sender: allowanceTarget,
from: tokenFrom,
to: tokenTo,
amount: tokenAmount,
},
],
TestTokenSpenderERC20TokenEvents.TransferFromCalled,
);
});
it('_spendERC20Tokens() successfully calls non-compliant ERC20 token', async () => {
const tokenFrom = randomAddress();
const tokenTo = randomAddress();
const tokenAmount = new BigNumber(EMPTY_RETURN_AMOUNT);
const receipt = await feature
._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
.awaitTransactionSuccessAsync();
verifyEventsFromLogs(
receipt.logs,
[
{
sender: allowanceTarget,
from: tokenFrom,
to: tokenTo,
amount: tokenAmount,
},
],
TestTokenSpenderERC20TokenEvents.TransferFromCalled,
);
});
it('_spendERC20Tokens() reverts if ERC20 token reverts', async () => {
const tokenFrom = randomAddress();
const tokenTo = randomAddress();
const tokenAmount = new BigNumber(REVERT_RETURN_AMOUNT);
const tx = feature
._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
.awaitTransactionSuccessAsync();
const expectedError = new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
token.address,
tokenFrom,
tokenTo,
tokenAmount,
new StringRevertError('TestTokenSpenderERC20Token/Revert').encode(),
);
return expect(tx).to.revertWith(expectedError);
});
it('_spendERC20Tokens() reverts if ERC20 token returns false', async () => {
const tokenFrom = randomAddress();
const tokenTo = randomAddress();
const tokenAmount = new BigNumber(FALSE_RETURN_AMOUNT);
const tx = feature
._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
token.address,
tokenFrom,
tokenTo,
tokenAmount,
hexUtils.leftPad(0),
),
);
});
});
describe('getSpendableERC20BalanceOf()', () => {
it("returns the minimum of the owner's balance and allowance", async () => {
const balance = getRandomInteger(1, '1e18');
const allowance = getRandomInteger(1, '1e18');
const tokenOwner = randomAddress();
await token
.setBalanceAndAllowanceOf(tokenOwner, balance, allowanceTarget, allowance)
.awaitTransactionSuccessAsync();
const spendableBalance = await feature.getSpendableERC20BalanceOf(token.address, tokenOwner).callAsync();
expect(spendableBalance).to.bignumber.eq(BigNumber.min(balance, allowance));
});
});
});

View File

@@ -0,0 +1,526 @@
import {
blockchainTests,
constants,
expect,
getRandomInteger,
getRandomPortion,
Numberish,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import {
FlashWalletContract,
ITokenSpenderContract,
TestMintableERC20TokenContract,
TestMintTokenERC20TransformerContract,
TestMintTokenERC20TransformerEvents,
TransformERC20Contract,
TransformERC20Events,
ZeroExContract,
} from '../wrappers';
blockchainTests.resets('TransformERC20 feature', env => {
let taker: string;
let transformerDeployer: string;
let zeroEx: ZeroExContract;
let feature: TransformERC20Contract;
let wallet: FlashWalletContract;
let allowanceTarget: string;
before(async () => {
let owner;
[owner, taker, transformerDeployer] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
transformERC20: await TransformERC20Contract.deployFrom0xArtifactAsync(
artifacts.TestTransformERC20,
env.provider,
env.txDefaults,
artifacts,
transformerDeployer,
),
});
feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis);
wallet = new FlashWalletContract(await feature.getTransformWallet().callAsync(), env.provider, env.txDefaults);
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
.getAllowanceTarget()
.callAsync();
});
const { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } = constants;
describe('wallets', () => {
it('createTransformWallet() replaces the current wallet', async () => {
const newWalletAddress = await feature.createTransformWallet().callAsync();
expect(newWalletAddress).to.not.eq(wallet.address);
await feature.createTransformWallet().awaitTransactionSuccessAsync();
return expect(feature.getTransformWallet().callAsync()).to.eventually.eq(newWalletAddress);
});
});
describe('_transformERC20()', () => {
let inputToken: TestMintableERC20TokenContract;
let outputToken: TestMintableERC20TokenContract;
let mintTransformer: TestMintTokenERC20TransformerContract;
let rlpNonce: string;
before(async () => {
inputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestMintableERC20Token,
env.provider,
env.txDefaults,
artifacts,
);
outputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestMintableERC20Token,
env.provider,
env.txDefaults,
artifacts,
);
rlpNonce = await getRLPEncodedAccountNonceAsync(env.web3Wrapper, transformerDeployer);
mintTransformer = await TestMintTokenERC20TransformerContract.deployFrom0xArtifactAsync(
artifacts.TestMintTokenERC20Transformer,
env.provider,
{
...env.txDefaults,
from: transformerDeployer,
},
artifacts,
);
await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
});
interface Transformation {
transformer: string;
data: string;
}
const transformDataEncoder = AbiEncoder.create([
{
name: 'data',
type: 'tuple',
components: [
{ name: 'inputToken', type: 'address' },
{ name: 'outputToken', type: 'address' },
{ name: 'burnAmount', type: 'uint256' },
{ name: 'mintAmount', type: 'uint256' },
{ name: 'feeAmount', type: 'uint256' },
{ name: 'deploymentNonce', type: 'bytes' },
],
},
]);
function createMintTokenTransformation(
opts: Partial<{
transformer: string;
outputTokenAddress: string;
inputTokenAddress: string;
inputTokenBurnAmunt: Numberish;
outputTokenMintAmount: Numberish;
outputTokenFeeAmount: Numberish;
rlpNonce: string;
}> = {},
): Transformation {
const _opts = {
rlpNonce,
outputTokenAddress: outputToken.address,
inputTokenAddress: inputToken.address,
inputTokenBurnAmunt: ZERO_AMOUNT,
outputTokenMintAmount: ZERO_AMOUNT,
outputTokenFeeAmount: ZERO_AMOUNT,
transformer: mintTransformer.address,
...opts,
};
return {
transformer: _opts.transformer,
data: transformDataEncoder.encode([
{
inputToken: _opts.inputTokenAddress,
outputToken: _opts.outputTokenAddress,
burnAmount: _opts.inputTokenBurnAmunt,
mintAmount: _opts.outputTokenMintAmount,
feeAmount: _opts.outputTokenFeeAmount,
deploymentNonce: _opts.rlpNonce,
},
]),
};
}
it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
inputTokenAmount,
outputTokenAmount: outputTokenMintAmount,
inputToken: inputToken.address,
outputToken: outputToken.address,
},
],
TransformERC20Events.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash,
taker,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
inputTokenBalance: inputTokenAmount,
ethBalance: callValue,
},
],
TestMintTokenERC20TransformerEvents.MintTransform,
);
});
const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount, with ETH", async () => {
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = outputTokenMintAmount.times(2);
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
outputTokenAddress: ETH_TOKEN_ADDRESS,
});
const startingOutputTokenBalance = await env.web3Wrapper.getBalanceInWeiAsync(taker);
const receipt = await feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
ETH_TOKEN_ADDRESS,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
inputTokenAmount,
outputTokenAmount: outputTokenMintAmount,
inputToken: inputToken.address,
outputToken: ETH_TOKEN_ADDRESS,
},
],
TransformERC20Events.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash,
taker,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
inputTokenBalance: inputTokenAmount,
ethBalance: callValue,
},
],
TestMintTokenERC20TransformerEvents.MintTransform,
);
expect(await env.web3Wrapper.getBalanceInWeiAsync(taker)).to.bignumber.eq(
startingOutputTokenBalance.plus(outputTokenMintAmount),
);
});
it("succeeds if taker's output token balance increases by more than minOutputTokenAmount", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount.plus(1);
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
inputTokenAmount,
outputTokenAmount: outputTokenMintAmount,
inputToken: inputToken.address,
outputToken: outputToken.address,
},
],
TransformERC20Events.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash,
taker,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
inputTokenBalance: inputTokenAmount,
ethBalance: callValue,
},
],
TestMintTokenERC20TransformerEvents.MintTransform,
);
});
it("throws if taker's output token balance increases by less than minOutputTokenAmount", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount.minus(1);
const callValue = getRandomInteger(1, '1e18');
const tx = feature
._transformERC20(
hexUtils.random(),
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[
createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
}),
],
)
.awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error(
outputToken.address,
outputTokenMintAmount,
minOutputTokenAmount,
);
return expect(tx).to.revertWith(expectedError);
});
it("throws if taker's output token balance decreases", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = ZERO_AMOUNT;
const outputTokenFeeAmount = 1;
const callValue = getRandomInteger(1, '1e18');
const tx = feature
._transformERC20(
hexUtils.random(),
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[
createMintTokenTransformation({
outputTokenFeeAmount,
inputTokenBurnAmunt: inputTokenAmount,
}),
],
)
.awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError(
outputToken.address,
outputTokenFeeAmount,
);
return expect(tx).to.revertWith(expectedError);
});
it('can call multiple transformers', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
// Split the total minting between two transformers.
const transformations = [
createMintTokenTransformation({
inputTokenBurnAmunt: 1,
outputTokenMintAmount: 1,
}),
createMintTokenTransformation({
inputTokenBurnAmunt: inputTokenAmount.minus(1),
outputTokenMintAmount: outputTokenMintAmount.minus(1),
}),
];
const receipt = await feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash,
taker,
context: wallet.address,
caller: zeroEx.address,
data: transformations[0].data,
inputTokenBalance: inputTokenAmount,
ethBalance: callValue,
},
{
callDataHash,
taker,
context: wallet.address,
caller: zeroEx.address,
data: transformations[1].data,
inputTokenBalance: inputTokenAmount.minus(1),
ethBalance: callValue,
},
],
TestMintTokenERC20TransformerEvents.MintTransform,
);
});
it('fails with third-party transformer', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformations = [createMintTokenTransformation({ transformer: randomAddress() })];
const tx = feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(NULL_BYTES));
});
it('fails with incorrect transformer RLP nonce', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const badRlpNonce = '0x00';
const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })];
const tx = feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.UnauthorizedTransformerError(
transformations[0].transformer,
badRlpNonce,
),
);
});
it('fails with invalid transformer RLP nonce', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const badRlpNonce = '0x010203040506';
const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })];
const tx = feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(badRlpNonce));
});
});
});

View File

@@ -0,0 +1,211 @@
import {
blockchainTests,
constants,
expect,
getRandomInteger,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { hexUtils, OwnableRevertErrors, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { FlashWalletContract, TestCallTargetContract, TestCallTargetEvents } from './wrappers';
blockchainTests.resets('FlashWallet', env => {
let owner: string;
let wallet: FlashWalletContract;
let callTarget: TestCallTargetContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
wallet = await FlashWalletContract.deployFrom0xArtifactAsync(
artifacts.FlashWallet,
env.provider,
{
...env.txDefaults,
from: owner,
},
artifacts,
);
callTarget = await TestCallTargetContract.deployFrom0xArtifactAsync(
artifacts.TestCallTarget,
env.provider,
env.txDefaults,
artifacts,
);
});
const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678');
const REVERTING_DATA = '0x1337';
it('owned by deployer', () => {
return expect(wallet.owner().callAsync()).to.eventually.eq(owner);
});
describe('executeCall()', () => {
it('non-owner cannot call executeCall()', async () => {
const notOwner = randomAddress();
const tx = wallet
.executeCall(randomAddress(), hexUtils.random(), getRandomInteger(0, '100e18'))
.callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner));
});
it('owner can call executeCall()', async () => {
const targetData = hexUtils.random(128);
const receipt = await wallet
.executeCall(callTarget.address, targetData, constants.ZERO_AMOUNT)
.awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[
{
context: callTarget.address,
sender: wallet.address,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('owner can call executeCall() with attached ETH', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await wallet
.executeCall(callTarget.address, targetData, callValue)
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: callTarget.address,
sender: wallet.address,
data: targetData,
value: callValue,
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('owner can call executeCall() can transfer less ETH than attached', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await wallet
.executeCall(callTarget.address, targetData, callValue.minus(1))
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: callTarget.address,
sender: wallet.address,
data: targetData,
value: callValue.minus(1),
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('wallet returns call result', async () => {
const result = await wallet
.executeCall(callTarget.address, hexUtils.random(128), constants.ZERO_AMOUNT)
.callAsync({ from: owner });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
it('wallet wraps call revert', async () => {
const tx = wallet
.executeCall(callTarget.address, REVERTING_DATA, constants.ZERO_AMOUNT)
.callAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Wallet.WalletExecuteCallFailedError(
wallet.address,
callTarget.address,
REVERTING_DATA,
constants.ZERO_AMOUNT,
new StringRevertError('TestCallTarget/REVERT').encode(),
),
);
});
it('wallet can receive ETH', async () => {
await env.web3Wrapper.sendTransactionAsync({
to: wallet.address,
from: owner,
value: 1,
});
const bal = await env.web3Wrapper.getBalanceInWeiAsync(wallet.address);
expect(bal).to.bignumber.eq(1);
});
});
describe('executeDelegateCall()', () => {
it('non-owner cannot call executeDelegateCall()', async () => {
const notOwner = randomAddress();
const tx = wallet.executeDelegateCall(randomAddress(), hexUtils.random()).callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner));
});
it('owner can call executeDelegateCall()', async () => {
const targetData = hexUtils.random(128);
const receipt = await wallet
.executeDelegateCall(callTarget.address, targetData)
.awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[
{
context: wallet.address,
sender: owner,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('executeDelegateCall() is payable', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await wallet
.executeDelegateCall(callTarget.address, targetData)
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: wallet.address,
sender: owner,
data: targetData,
value: callValue,
},
],
TestCallTargetEvents.CallTargetCalled,
);
});
it('wallet returns call result', async () => {
const result = await wallet
.executeDelegateCall(callTarget.address, hexUtils.random(128))
.callAsync({ from: owner });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
it('wallet wraps call revert', async () => {
const tx = wallet.executeDelegateCall(callTarget.address, REVERTING_DATA).callAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Wallet.WalletExecuteDelegateCallFailedError(
wallet.address,
callTarget.address,
REVERTING_DATA,
new StringRevertError('TestCallTarget/REVERT').encode(),
),
);
});
});
});

View File

@@ -0,0 +1,165 @@
import { BaseContract } from '@0x/base-contract';
import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils';
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { DataItem, MethodAbi } from 'ethereum-types';
import * as _ from 'lodash';
import { artifacts } from './artifacts';
import { abis } from './utils/abis';
import { deployFullFeaturesAsync, FullFeatures, toFeatureAdddresses } from './utils/migration';
import {
AllowanceTargetContract,
IOwnableContract,
ITokenSpenderContract,
ITransformERC20Contract,
TestFullMigrationContract,
ZeroExContract,
} from './wrappers';
const { NULL_ADDRESS } = constants;
blockchainTests.resets('Full migration', env => {
let owner: string;
let zeroEx: ZeroExContract;
let features: FullFeatures;
let migrator: TestFullMigrationContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
features = await deployFullFeaturesAsync(env.provider, env.txDefaults);
migrator = await TestFullMigrationContract.deployFrom0xArtifactAsync(
artifacts.TestFullMigration,
env.provider,
env.txDefaults,
artifacts,
env.txDefaults.from as string,
);
const deployCall = migrator.deploy(owner, toFeatureAdddresses(features));
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
await deployCall.awaitTransactionSuccessAsync();
});
it('ZeroEx has the correct owner', async () => {
const ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
const actualOwner = await ownable.owner().callAsync();
expect(actualOwner).to.eq(owner);
});
it('FullMigration contract self-destructs', async () => {
const dieRecipient = await migrator.dieRecipient().callAsync();
expect(dieRecipient).to.eq(owner);
});
it('Non-deployer cannot call deploy()', async () => {
const notDeployer = randomAddress();
const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer });
return expect(tx).to.revertWith('FullMigration/INVALID_SENDER');
});
const FEATURE_FNS = {
TokenSpender: {
contractType: ITokenSpenderContract,
fns: ['_spendERC20Tokens'],
},
TransformERC20: {
contractType: ITransformERC20Contract,
fns: ['transformERC20', '_transformERC20', 'createTransformWallet', 'getTransformWallet'],
},
};
function createFakeInputs(inputs: DataItem[] | DataItem): any | any[] {
if ((inputs as DataItem[]).length !== undefined) {
return (inputs as DataItem[]).map(i => createFakeInputs(i));
}
const item = inputs as DataItem;
// TODO(dorothy-zbornak): Support fixed-length arrays.
if (/\[]$/.test(item.type)) {
return _.times(_.random(0, 8), () =>
createFakeInputs({
...item,
type: item.type.substring(0, item.type.length - 2),
}),
);
}
if (/^tuple$/.test(item.type)) {
const tuple = {} as any;
for (const comp of item.components as DataItem[]) {
tuple[comp.name] = createFakeInputs(comp);
}
return tuple;
}
if (item.type === 'address') {
return randomAddress();
}
if (item.type === 'byte') {
return hexUtils.random(1);
}
if (/^bytes$/.test(item.type)) {
return hexUtils.random(_.random(0, 128));
}
if (/^bytes\d+$/.test(item.type)) {
return hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10));
}
if (/^uint\d+$/.test(item.type)) {
return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8));
}
if (/^int\d+$/.test(item.type)) {
return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8))
.div(2)
.times(_.sample([-1, 1])!);
}
throw new Error(`Unhandled input type: '${item.type}'`);
}
for (const [featureName, featureInfo] of Object.entries(FEATURE_FNS)) {
describe(`${featureName} feature`, () => {
let contract: BaseContract & { getSelector(name: string): string };
before(async () => {
contract = new featureInfo.contractType(zeroEx.address, env.provider, env.txDefaults, abis);
});
for (const fn of featureInfo.fns) {
it(`${fn} is registered`, async () => {
const selector = contract.getSelector(fn);
const impl = await zeroEx.getFunctionImplementation(selector).callAsync();
expect(impl).to.not.eq(NULL_ADDRESS);
});
if (fn.startsWith('_')) {
it(`${fn} cannot be called from outside`, async () => {
const method = contract.abi.find(
d => d.type === 'function' && (d as MethodAbi).name === fn,
) as MethodAbi;
const inputs = createFakeInputs(method.inputs);
const tx = (contract as any)[fn](...inputs).callAsync();
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Common.OnlyCallableBySelfError(env.txDefaults.from),
);
});
}
}
});
}
describe("TokenSpender's allowance target", () => {
let allowanceTarget: AllowanceTargetContract;
before(async () => {
const contract = new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults);
allowanceTarget = new AllowanceTargetContract(
await contract.getAllowanceTarget().callAsync(),
env.provider,
env.txDefaults,
);
});
it('is owned by owner', async () => {
return expect(allowanceTarget.owner().callAsync()).to.become(owner);
});
it('Proxy is authorized', async () => {
return expect(allowanceTarget.authorized(zeroEx.address).callAsync()).to.become(true);
});
});
});

View File

@@ -2,6 +2,7 @@ import { blockchainTests, expect, randomAddress } from '@0x/contracts-test-utils
import { ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { BootstrapFeatures, deployBootstrapFeaturesAsync, toFeatureAdddresses } from './utils/migration';
import {
IBootstrapContract,
InitialMigrationContract,
@@ -15,9 +16,11 @@ blockchainTests.resets('Initial migration', env => {
let zeroEx: ZeroExContract;
let migrator: TestInitialMigrationContract;
let bootstrapFeature: IBootstrapContract;
let features: BootstrapFeatures;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
features = await deployBootstrapFeaturesAsync(env.provider, env.txDefaults);
migrator = await TestInitialMigrationContract.deployFrom0xArtifactAsync(
artifacts.TestInitialMigration,
env.provider,
@@ -31,7 +34,7 @@ blockchainTests.resets('Initial migration', env => {
env.txDefaults,
{},
);
const deployCall = migrator.deploy(owner);
const deployCall = migrator.deploy(owner, toFeatureAdddresses(features));
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
await deployCall.awaitTransactionSuccessAsync();
});
@@ -43,7 +46,7 @@ blockchainTests.resets('Initial migration', env => {
it('Non-deployer cannot call deploy()', async () => {
const notDeployer = randomAddress();
const tx = migrator.deploy(owner).callAsync({ from: notDeployer });
const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer });
return expect(tx).to.revertWith('InitialMigration/INVALID_SENDER');
});
@@ -67,8 +70,8 @@ blockchainTests.resets('Initial migration', env => {
});
it('Bootstrap feature self destructs after deployment', async () => {
const codeSize = await migrator.getCodeSizeOf(bootstrapFeature.address).callAsync();
expect(codeSize).to.bignumber.eq(0);
const doesExist = await env.web3Wrapper.doesContractExistAtAddressAsync(bootstrapFeature.address);
expect(doesExist).to.eq(false);
});
});

View File

@@ -0,0 +1,5 @@
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
export const abis = _.mapValues(artifacts, v => v.compilerOutput.abi);

View File

@@ -1,15 +1,53 @@
import { BaseContract } from '@0x/base-contract';
import { SupportedProvider } from '@0x/subproviders';
import { TxData } from 'ethereum-types';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { InitialMigrationContract, ZeroExContract } from '../wrappers';
import {
FullMigrationContract,
InitialMigrationContract,
OwnableContract,
SimpleFunctionRegistryContract,
TokenSpenderContract,
TransformERC20Contract,
ZeroExContract,
} from '../wrappers';
// tslint:disable: completed-docs
export interface BootstrapFeatures {
registry: SimpleFunctionRegistryContract;
ownable: OwnableContract;
}
export async function deployBootstrapFeaturesAsync(
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<BootstrapFeatures> = {},
): Promise<BootstrapFeatures> {
return {
registry:
features.registry ||
(await SimpleFunctionRegistryContract.deployFrom0xArtifactAsync(
artifacts.SimpleFunctionRegistry,
provider,
txDefaults,
artifacts,
)),
ownable:
features.ownable ||
(await OwnableContract.deployFrom0xArtifactAsync(artifacts.Ownable, provider, txDefaults, artifacts)),
};
}
export async function initialMigrateAsync(
owner: string,
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<BootstrapFeatures> = {},
): Promise<ZeroExContract> {
const _features = await deployBootstrapFeaturesAsync(provider, txDefaults, features);
const migrator = await InitialMigrationContract.deployFrom0xArtifactAsync(
artifacts.InitialMigration,
provider,
@@ -17,8 +55,74 @@ export async function initialMigrateAsync(
artifacts,
txDefaults.from as string,
);
const deployCall = migrator.deploy(owner);
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features));
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
await deployCall.awaitTransactionSuccessAsync();
return zeroEx;
}
export interface FullFeatures extends BootstrapFeatures {
tokenSpender: TokenSpenderContract;
transformERC20: TransformERC20Contract;
}
export interface FullMigrationOpts {
transformDeployer: string;
}
export async function deployFullFeaturesAsync(
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {},
): Promise<FullFeatures> {
return {
...(await deployBootstrapFeaturesAsync(provider, txDefaults)),
tokenSpender:
features.tokenSpender ||
(await TokenSpenderContract.deployFrom0xArtifactAsync(
artifacts.TokenSpender,
provider,
txDefaults,
artifacts,
)),
transformERC20:
features.transformERC20 ||
(await TransformERC20Contract.deployFrom0xArtifactAsync(
artifacts.TransformERC20,
provider,
txDefaults,
artifacts,
opts.transformDeployer || (txDefaults.from as string),
)),
};
}
export async function fullMigrateAsync(
owner: string,
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {},
): Promise<ZeroExContract> {
const _features = await deployFullFeaturesAsync(provider, txDefaults, features, opts);
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
artifacts.FullMigration,
provider,
txDefaults,
artifacts,
txDefaults.from as string,
);
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features));
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
await deployCall.awaitTransactionSuccessAsync();
return zeroEx;
}
// tslint:disable:space-before-function-parent one-line
export function toFeatureAdddresses<T extends BootstrapFeatures | FullFeatures | (BootstrapFeatures & FullFeatures)>(
features: T,
): { [name in keyof T]: string } {
// TS can't figure this out.
return _.mapValues(features, (c: BaseContract) => c.address) as any;
}

View File

@@ -3,16 +3,25 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/allowance_target';
export * from '../test/generated-wrappers/bootstrap';
export * from '../test/generated-wrappers/fixin_common';
export * from '../test/generated-wrappers/flash_wallet';
export * from '../test/generated-wrappers/full_migration';
export * from '../test/generated-wrappers/i_allowance_target';
export * from '../test/generated-wrappers/i_bootstrap';
export * from '../test/generated-wrappers/i_erc20_transformer';
export * from '../test/generated-wrappers/i_feature';
export * from '../test/generated-wrappers/i_flash_wallet';
export * from '../test/generated-wrappers/i_ownable';
export * from '../test/generated-wrappers/i_simple_function_registry';
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
export * from '../test/generated-wrappers/i_token_spender';
export * from '../test/generated-wrappers/i_transform_erc20';
export * from '../test/generated-wrappers/initial_migration';
export * from '../test/generated-wrappers/lib_bootstrap';
export * from '../test/generated-wrappers/lib_common_rich_errors';
export * from '../test/generated-wrappers/lib_erc20_transformer';
export * from '../test/generated-wrappers/lib_migrate';
export * from '../test/generated-wrappers/lib_ownable_rich_errors';
export * from '../test/generated-wrappers/lib_ownable_storage';
@@ -20,12 +29,26 @@ export * from '../test/generated-wrappers/lib_proxy_rich_errors';
export * from '../test/generated-wrappers/lib_proxy_storage';
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
export * from '../test/generated-wrappers/lib_spender_rich_errors';
export * from '../test/generated-wrappers/lib_storage';
export * from '../test/generated-wrappers/lib_token_spender_storage';
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
export * from '../test/generated-wrappers/lib_wallet_rich_errors';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/simple_function_registry';
export * from '../test/generated-wrappers/test_call_target';
export * from '../test/generated-wrappers/test_full_migration';
export * from '../test/generated-wrappers/test_initial_migration';
export * from '../test/generated-wrappers/test_migrator';
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';
export * from '../test/generated-wrappers/test_mintable_erc20_token';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl1';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
export * from '../test/generated-wrappers/test_token_spender';
export * from '../test/generated-wrappers/test_token_spender_erc20_token';
export * from '../test/generated-wrappers/test_transform_erc20';
export * from '../test/generated-wrappers/test_zero_ex_feature';
export * from '../test/generated-wrappers/token_spender';
export * from '../test/generated-wrappers/transform_erc20';
export * from '../test/generated-wrappers/zero_ex';