aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/tokens/test
diff options
context:
space:
mode:
authorLeonid Logvinov <logvinov.leon@gmail.com>2018-12-05 21:23:35 +0800
committerFred Carlsen <fred@sjelfull.no>2018-12-13 01:19:06 +0800
commitea8f35b74315fb81246082f56ca5f5fd79077d05 (patch)
tree8b11a3134c14e14dacab113c0a0a4f3ae6482da9 /contracts/tokens/test
parent9365a3bc753a4351ead3159cdecc431096529a2b (diff)
downloaddexon-0x-contracts-ea8f35b74315fb81246082f56ca5f5fd79077d05.tar.gz
dexon-0x-contracts-ea8f35b74315fb81246082f56ca5f5fd79077d05.tar.zst
dexon-0x-contracts-ea8f35b74315fb81246082f56ca5f5fd79077d05.zip
Refactor out tokens from core contracts package
Diffstat (limited to 'contracts/tokens/test')
-rw-r--r--contracts/tokens/test/erc721_token.ts282
-rw-r--r--contracts/tokens/test/global_hooks.ts17
-rw-r--r--contracts/tokens/test/unlimited_allowance_token.ts194
-rw-r--r--contracts/tokens/test/weth9.ts142
-rw-r--r--contracts/tokens/test/zrx_token.ts203
5 files changed, 838 insertions, 0 deletions
diff --git a/contracts/tokens/test/erc721_token.ts b/contracts/tokens/test/erc721_token.ts
new file mode 100644
index 000000000..13332cd35
--- /dev/null
+++ b/contracts/tokens/test/erc721_token.ts
@@ -0,0 +1,282 @@
+import {
+ chaiSetup,
+ constants,
+ expectTransactionFailedAsync,
+ expectTransactionFailedWithoutReasonAsync,
+ LogDecoder,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import { LogWithDecodedArgs } from 'ethereum-types';
+
+import {
+ artifacts,
+ DummyERC721ReceiverContract,
+ DummyERC721ReceiverTokenReceivedEventArgs,
+ DummyERC721TokenContract,
+ DummyERC721TokenTransferEventArgs,
+ InvalidERC721ReceiverContract,
+} from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+// tslint:disable:no-unnecessary-type-assertion
+describe('ERC721Token', () => {
+ let owner: string;
+ let spender: string;
+ let token: DummyERC721TokenContract;
+ let erc721Receiver: DummyERC721ReceiverContract;
+ let logDecoder: LogDecoder;
+ const tokenId = new BigNumber(1);
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = accounts[0];
+ spender = accounts[1];
+ token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC721Receiver,
+ provider,
+ txDefaults,
+ );
+ logDecoder = new LogDecoder(web3Wrapper, artifacts);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.mint.sendTransactionAsync(owner, tokenId, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('transferFrom', () => {
+ it('should revert if the tokenId is not owner', async () => {
+ const from = owner;
+ const to = erc721Receiver.address;
+ const unownedTokenId = new BigNumber(2);
+ await expectTransactionFailedAsync(
+ token.transferFrom.sendTransactionAsync(from, to, unownedTokenId),
+ RevertReason.Erc721ZeroOwner,
+ );
+ });
+ it('should revert if transferring to a null address', async () => {
+ const from = owner;
+ const to = constants.NULL_ADDRESS;
+ await expectTransactionFailedAsync(
+ token.transferFrom.sendTransactionAsync(from, to, tokenId),
+ RevertReason.Erc721ZeroToAddress,
+ );
+ });
+ it('should revert if the from address does not own the token', async () => {
+ const from = spender;
+ const to = erc721Receiver.address;
+ await expectTransactionFailedAsync(
+ token.transferFrom.sendTransactionAsync(from, to, tokenId),
+ RevertReason.Erc721OwnerMismatch,
+ );
+ });
+ it('should revert if spender does not own the token, is not approved, and is not approved for all', async () => {
+ const from = owner;
+ const to = erc721Receiver.address;
+ await expectTransactionFailedAsync(
+ token.transferFrom.sendTransactionAsync(from, to, tokenId, { from: spender }),
+ RevertReason.Erc721InvalidSpender,
+ );
+ });
+ it('should transfer the token if called by owner', async () => {
+ const from = owner;
+ const to = erc721Receiver.address;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.transferFrom.sendTransactionAsync(from, to, tokenId),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ expect(log.args._from).to.be.equal(from);
+ expect(log.args._to).to.be.equal(to);
+ expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
+ });
+ it('should transfer the token if spender is approved for all', async () => {
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.setApprovalForAll.sendTransactionAsync(spender, isApproved),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const from = owner;
+ const to = erc721Receiver.address;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.transferFrom.sendTransactionAsync(from, to, tokenId),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ expect(log.args._from).to.be.equal(from);
+ expect(log.args._to).to.be.equal(to);
+ expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
+ });
+ it('should transfer the token if spender is individually approved', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.approve.sendTransactionAsync(spender, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const from = owner;
+ const to = erc721Receiver.address;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.transferFrom.sendTransactionAsync(from, to, tokenId),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+
+ const approvedAddress = await token.getApproved.callAsync(tokenId);
+ expect(approvedAddress).to.be.equal(constants.NULL_ADDRESS);
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ expect(log.args._from).to.be.equal(from);
+ expect(log.args._to).to.be.equal(to);
+ expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
+ });
+ });
+ describe('safeTransferFrom without data', () => {
+ it('should transfer token to a non-contract address if called by owner', async () => {
+ const from = owner;
+ const to = spender;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ expect(log.args._from).to.be.equal(from);
+ expect(log.args._to).to.be.equal(to);
+ expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
+ });
+ it('should revert if transferring to a contract address without onERC721Received', async () => {
+ const contract = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const from = owner;
+ const to = contract.address;
+ await expectTransactionFailedWithoutReasonAsync(
+ token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
+ );
+ });
+ it('should revert if onERC721Received does not return the correct value', async () => {
+ const invalidErc721Receiver = await InvalidERC721ReceiverContract.deployFrom0xArtifactAsync(
+ artifacts.InvalidERC721Receiver,
+ provider,
+ txDefaults,
+ );
+ const from = owner;
+ const to = invalidErc721Receiver.address;
+ await expectTransactionFailedAsync(
+ token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
+ RevertReason.Erc721InvalidSelector,
+ );
+ });
+ it('should transfer to contract and call onERC721Received with correct return value', async () => {
+ const from = owner;
+ const to = erc721Receiver.address;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const transferLog = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
+ expect(transferLog.args._from).to.be.equal(from);
+ expect(transferLog.args._to).to.be.equal(to);
+ expect(transferLog.args._tokenId).to.be.bignumber.equal(tokenId);
+ expect(receiverLog.args.operator).to.be.equal(owner);
+ expect(receiverLog.args.from).to.be.equal(from);
+ expect(receiverLog.args.tokenId).to.be.bignumber.equal(tokenId);
+ expect(receiverLog.args.data).to.be.equal(constants.NULL_BYTES);
+ });
+ });
+ describe('safeTransferFrom with data', () => {
+ const data = '0x0102030405060708090a0b0c0d0e0f';
+ it('should transfer token to a non-contract address if called by owner', async () => {
+ const from = owner;
+ const to = spender;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ expect(log.args._from).to.be.equal(from);
+ expect(log.args._to).to.be.equal(to);
+ expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
+ });
+ it('should revert if transferring to a contract address without onERC721Received', async () => {
+ const contract = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const from = owner;
+ const to = contract.address;
+ await expectTransactionFailedWithoutReasonAsync(
+ token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
+ );
+ });
+ it('should revert if onERC721Received does not return the correct value', async () => {
+ const invalidErc721Receiver = await InvalidERC721ReceiverContract.deployFrom0xArtifactAsync(
+ artifacts.InvalidERC721Receiver,
+ provider,
+ txDefaults,
+ );
+ const from = owner;
+ const to = invalidErc721Receiver.address;
+ await expectTransactionFailedAsync(
+ token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
+ RevertReason.Erc721InvalidSelector,
+ );
+ });
+ it('should transfer to contract and call onERC721Received with correct return value', async () => {
+ const from = owner;
+ const to = erc721Receiver.address;
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
+ );
+ const newOwner = await token.ownerOf.callAsync(tokenId);
+ expect(newOwner).to.be.equal(to);
+ const transferLog = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
+ const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
+ expect(transferLog.args._from).to.be.equal(from);
+ expect(transferLog.args._to).to.be.equal(to);
+ expect(transferLog.args._tokenId).to.be.bignumber.equal(tokenId);
+ expect(receiverLog.args.operator).to.be.equal(owner);
+ expect(receiverLog.args.from).to.be.equal(from);
+ expect(receiverLog.args.tokenId).to.be.bignumber.equal(tokenId);
+ expect(receiverLog.args.data).to.be.equal(data);
+ });
+ });
+});
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/tokens/test/global_hooks.ts b/contracts/tokens/test/global_hooks.ts
new file mode 100644
index 000000000..f8ace376a
--- /dev/null
+++ b/contracts/tokens/test/global_hooks.ts
@@ -0,0 +1,17 @@
+import { env, EnvVars } from '@0x/dev-utils';
+
+import { coverage, profiler, provider } from '@0x/contracts-test-utils';
+before('start web3 provider', () => {
+ provider.start();
+});
+after('generate coverage report', async () => {
+ if (env.parseBoolean(EnvVars.SolidityCoverage)) {
+ const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
+ await coverageSubprovider.writeCoverageAsync();
+ }
+ if (env.parseBoolean(EnvVars.SolidityProfiler)) {
+ const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
+ await profilerSubprovider.writeProfilerOutputAsync();
+ }
+ provider.stop();
+});
diff --git a/contracts/tokens/test/unlimited_allowance_token.ts b/contracts/tokens/test/unlimited_allowance_token.ts
new file mode 100644
index 000000000..6d5a29b23
--- /dev/null
+++ b/contracts/tokens/test/unlimited_allowance_token.ts
@@ -0,0 +1,194 @@
+import {
+ chaiSetup,
+ constants,
+ expectContractCallFailedAsync,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+
+import { artifacts, DummyERC20TokenContract } from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('UnlimitedAllowanceToken', () => {
+ let owner: string;
+ let spender: string;
+ const MAX_MINT_VALUE = new BigNumber(10000000000000000000000);
+ let token: DummyERC20TokenContract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = accounts[0];
+ spender = accounts[1];
+ token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC20Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ constants.DUMMY_TOKEN_DECIMALS,
+ constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.mint.sendTransactionAsync(MAX_MINT_VALUE, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('transfer', () => {
+ it('should throw if owner has insufficient balance', async () => {
+ const ownerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = ownerBalance.plus(1);
+ return expectContractCallFailedAsync(
+ token.transfer.callAsync(spender, amountToTransfer, { from: owner }),
+ RevertReason.Erc20InsufficientBalance,
+ );
+ });
+
+ it('should transfer balance from sender to receiver', async () => {
+ const receiver = spender;
+ const initOwnerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.transfer.sendTransactionAsync(receiver, amountToTransfer, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const finalOwnerBalance = await token.balanceOf.callAsync(owner);
+ const finalReceiverBalance = await token.balanceOf.callAsync(receiver);
+
+ const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer);
+ const expectedFinalReceiverBalance = amountToTransfer;
+ expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance);
+ expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance);
+ });
+
+ it('should return true on a 0 value transfer', async () => {
+ const didReturnTrue = await token.transfer.callAsync(spender, new BigNumber(0), {
+ from: owner,
+ });
+ expect(didReturnTrue).to.be.true();
+ });
+ });
+
+ describe('transferFrom', () => {
+ it('should throw if owner has insufficient balance', async () => {
+ const ownerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = ownerBalance.plus(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.approve.sendTransactionAsync(spender, amountToTransfer, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ return expectContractCallFailedAsync(
+ token.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ }),
+ RevertReason.Erc20InsufficientBalance,
+ );
+ });
+
+ it('should throw if spender has insufficient allowance', async () => {
+ const ownerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = ownerBalance;
+
+ const spenderAllowance = await token.allowance.callAsync(owner, spender);
+ const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
+ expect(isSpenderAllowanceInsufficient).to.be.true();
+
+ return expectContractCallFailedAsync(
+ token.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ }),
+ RevertReason.Erc20InsufficientAllowance,
+ );
+ });
+
+ it('should return true on a 0 value transfer', async () => {
+ const amountToTransfer = new BigNumber(0);
+ const didReturnTrue = await token.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
+ expect(didReturnTrue).to.be.true();
+ });
+
+ it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => {
+ const initOwnerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = initOwnerBalance;
+ const initSpenderAllowance = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.approve.sendTransactionAsync(spender, initSpenderAllowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newSpenderAllowance = await token.allowance.callAsync(owner, spender);
+ expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
+ });
+
+ it('should transfer the correct balances if spender has sufficient allowance', async () => {
+ const initOwnerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = initOwnerBalance;
+ const initSpenderAllowance = initOwnerBalance;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.approve.sendTransactionAsync(spender, initSpenderAllowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newOwnerBalance = await token.balanceOf.callAsync(owner);
+ const newSpenderBalance = await token.balanceOf.callAsync(spender);
+
+ expect(newOwnerBalance).to.be.bignumber.equal(0);
+ expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance);
+ });
+
+ it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => {
+ const initOwnerBalance = await token.balanceOf.callAsync(owner);
+ const amountToTransfer = initOwnerBalance;
+ const initSpenderAllowance = initOwnerBalance;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.approve.sendTransactionAsync(spender, initSpenderAllowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await token.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newSpenderAllowance = await token.allowance.callAsync(owner, spender);
+ expect(newSpenderAllowance).to.be.bignumber.equal(0);
+ });
+ });
+});
diff --git a/contracts/tokens/test/weth9.ts b/contracts/tokens/test/weth9.ts
new file mode 100644
index 000000000..6a3948e2c
--- /dev/null
+++ b/contracts/tokens/test/weth9.ts
@@ -0,0 +1,142 @@
+import {
+ chaiSetup,
+ constants,
+ expectInsufficientFundsAsync,
+ expectTransactionFailedWithoutReasonAsync,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+
+import { artifacts, WETH9Contract } from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('EtherToken', () => {
+ let account: string;
+ const gasPrice = Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 9);
+ let etherToken: WETH9Contract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ account = accounts[0];
+
+ etherToken = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, {
+ gasPrice,
+ ...txDefaults,
+ });
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('deposit', () => {
+ it('should throw if caller attempts to deposit more Ether than caller balance', async () => {
+ const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const ethToDeposit = initEthBalance.plus(1);
+
+ return expectInsufficientFundsAsync(etherToken.deposit.sendTransactionAsync({ value: ethToDeposit }));
+ });
+
+ it('should convert deposited Ether to wrapped Ether tokens', async () => {
+ const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const initEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+
+ const ethToDeposit = new BigNumber(Web3Wrapper.toWei(new BigNumber(1)));
+
+ const txHash = await etherToken.deposit.sendTransactionAsync({ value: ethToDeposit });
+ const receipt = await web3Wrapper.awaitTransactionSuccessAsync(
+ txHash,
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
+ const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const finalEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+
+ expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
+ expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
+ });
+ });
+
+ describe('withdraw', () => {
+ it('should throw if caller attempts to withdraw greater than caller balance', async () => {
+ const initEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+ const ethTokensToWithdraw = initEthTokenBalance.plus(1);
+
+ return expectTransactionFailedWithoutReasonAsync(
+ etherToken.withdraw.sendTransactionAsync(ethTokensToWithdraw),
+ );
+ });
+
+ it('should convert ether tokens to ether with sufficient balance', async () => {
+ const ethToDeposit = new BigNumber(Web3Wrapper.toWei(new BigNumber(1)));
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await etherToken.deposit.sendTransactionAsync({ value: ethToDeposit }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const initEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+ const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const ethTokensToWithdraw = initEthTokenBalance;
+ expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
+ const txHash = await etherToken.withdraw.sendTransactionAsync(ethTokensToWithdraw, {
+ gas: constants.MAX_ETHERTOKEN_WITHDRAW_GAS,
+ });
+ const receipt = await web3Wrapper.awaitTransactionSuccessAsync(
+ txHash,
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
+ const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const finalEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+
+ expect(finalEthBalance).to.be.bignumber.equal(
+ initEthBalance.plus(ethTokensToWithdraw.minus(ethSpentOnGas)),
+ );
+ expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.minus(ethTokensToWithdraw));
+ });
+ });
+
+ describe('fallback', () => {
+ it('should convert sent ether to ether tokens', async () => {
+ const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const initEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+
+ const ethToDeposit = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18);
+
+ const txHash = await web3Wrapper.sendTransactionAsync({
+ from: account,
+ to: etherToken.address,
+ value: ethToDeposit,
+ gasPrice,
+ });
+
+ const receipt = await web3Wrapper.awaitTransactionSuccessAsync(
+ txHash,
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
+ const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
+ const finalEthTokenBalance = await etherToken.balanceOf.callAsync(account);
+
+ expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
+ expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
+ });
+ });
+});
diff --git a/contracts/tokens/test/zrx_token.ts b/contracts/tokens/test/zrx_token.ts
new file mode 100644
index 000000000..5dc8447f6
--- /dev/null
+++ b/contracts/tokens/test/zrx_token.ts
@@ -0,0 +1,203 @@
+import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+
+import { artifacts, ZRXTokenContract } from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('ZRXToken', () => {
+ let owner: string;
+ let spender: string;
+ let MAX_UINT: BigNumber;
+ let zrxToken: ZRXTokenContract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = accounts[0];
+ spender = accounts[1];
+ zrxToken = await ZRXTokenContract.deployFrom0xArtifactAsync(artifacts.ZRXToken, provider, txDefaults);
+ MAX_UINT = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('constants', () => {
+ it('should have 18 decimals', async () => {
+ const decimals = new BigNumber(await zrxToken.decimals.callAsync());
+ const expectedDecimals = 18;
+ expect(decimals).to.be.bignumber.equal(expectedDecimals);
+ });
+
+ it('should have a total supply of 1 billion tokens', async () => {
+ const totalSupply = new BigNumber(await zrxToken.totalSupply.callAsync());
+ const expectedTotalSupply = 1000000000;
+ expect(Web3Wrapper.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply);
+ });
+
+ it('should be named 0x Protocol Token', async () => {
+ const name = await zrxToken.name.callAsync();
+ const expectedName = '0x Protocol Token';
+ expect(name).to.be.equal(expectedName);
+ });
+
+ it('should have the symbol ZRX', async () => {
+ const symbol = await zrxToken.symbol.callAsync();
+ const expectedSymbol = 'ZRX';
+ expect(symbol).to.be.equal(expectedSymbol);
+ });
+ });
+
+ describe('constructor', () => {
+ it('should initialize owner balance to totalSupply', async () => {
+ const ownerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const totalSupply = new BigNumber(await zrxToken.totalSupply.callAsync());
+ expect(totalSupply).to.be.bignumber.equal(ownerBalance);
+ });
+ });
+
+ describe('transfer', () => {
+ it('should transfer balance from sender to receiver', async () => {
+ const receiver = spender;
+ const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const amountToTransfer = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transfer.sendTransactionAsync(receiver, amountToTransfer, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const finalOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const finalReceiverBalance = await zrxToken.balanceOf.callAsync(receiver);
+
+ const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer);
+ const expectedFinalReceiverBalance = amountToTransfer;
+ expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance);
+ expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance);
+ });
+
+ it('should return true on a 0 value transfer', async () => {
+ const didReturnTrue = await zrxToken.transfer.callAsync(spender, new BigNumber(0), {
+ from: owner,
+ });
+ expect(didReturnTrue).to.be.true();
+ });
+ });
+
+ describe('transferFrom', () => {
+ it('should return false if owner has insufficient balance', async () => {
+ const ownerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const amountToTransfer = ownerBalance.plus(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer, {
+ from: owner,
+ gas: constants.MAX_TOKEN_APPROVE_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
+ expect(didReturnTrue).to.be.false();
+ });
+
+ it('should return false if spender has insufficient allowance', async () => {
+ const ownerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const amountToTransfer = ownerBalance;
+
+ const spenderAllowance = await zrxToken.allowance.callAsync(owner, spender);
+ const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
+ expect(isSpenderAllowanceInsufficient).to.be.true();
+
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
+ expect(didReturnTrue).to.be.false();
+ });
+
+ it('should return true on a 0 value transfer', async () => {
+ const amountToTransfer = new BigNumber(0);
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
+ expect(didReturnTrue).to.be.true();
+ });
+
+ it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => {
+ const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const amountToTransfer = initOwnerBalance;
+ const initSpenderAllowance = MAX_UINT;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance, {
+ from: owner,
+ gas: constants.MAX_TOKEN_APPROVE_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newSpenderAllowance = await zrxToken.allowance.callAsync(owner, spender);
+ expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
+ });
+
+ it('should transfer the correct balances if spender has sufficient allowance', async () => {
+ const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const initSpenderBalance = await zrxToken.balanceOf.callAsync(spender);
+ const amountToTransfer = initOwnerBalance;
+ const initSpenderAllowance = initOwnerBalance;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const newSpenderBalance = await zrxToken.balanceOf.callAsync(spender);
+
+ expect(newOwnerBalance).to.be.bignumber.equal(0);
+ expect(newSpenderBalance).to.be.bignumber.equal(initSpenderBalance.plus(initOwnerBalance));
+ });
+
+ it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => {
+ const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner);
+ const amountToTransfer = initOwnerBalance;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newSpenderAllowance = await zrxToken.allowance.callAsync(owner, spender);
+ expect(newSpenderAllowance).to.be.bignumber.equal(0);
+ });
+ });
+});