Migrate erc20-contracts to foundry (#664)

* Strip erc20 package of legacy nonsense and add foundry basics

* Make foundry build

* Remove obsoleted test/UntransferrableDummyERC20Token.sol contract

* Remove obsoleted ERC20 lib variant contracts

* Remove obsoleted DummyMultipleReturnERC20Token and DummyNoReturnERC20Token contracts

* Move test contract to dedicated folder
and remove obsoleted TypeScript contract wrappers

* Remove src/interfaces/IEtherToken.sol only used in
v3 staking which is being obsoleted [skip ci]

* Add foundry test for token

* Migrate ZRX token tests to foundry

* Fix paths to erc20 contracts

* Remove obsoleted references

* Pin erc20-contracts package on treasury

* Ignore foundry imports in link checker

* Run only forge tests for erc20 contracts

* Remove DummyERC20Token and its dependencies

* Merge IERC20TokenV06 and IERC20TokenV08
into range pragma to cover solidity 0.6.5 to 0.8.x

* Merge IEtherTokenV06 and IEtherTokenV08
into range pragma to cover solidity 0.6.5 to 0.8.x

* Migrate weth9 tests to foundry

* Upload code coverage for erc20 package

* Update changelog

* Fix review comments

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>

---------

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>
This commit is contained in:
Elena
2023-02-19 20:04:24 +02:00
committed by GitHub
parent f2f835591e
commit 9f30823d70
172 changed files with 945 additions and 2876 deletions

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6.5;
pragma experimental ABIEncoderV2;
import "forge-std/Test.sol";
import "../src/v06/WETH9V06.sol";
contract WETH9V06Test is Test {
address payable internal owner = payable(vm.addr(1));
address payable internal user = payable(vm.addr(2));
WETH9V06 internal etherToken;
function setUp() public {
vm.deal(owner, 1e20);
vm.deal(user, 1e20);
etherToken = new WETH9V06();
}
function testShouldRevertIfCallerAttemptsToDepositMoreThanTheirBalance() public {
vm.prank(user);
vm.expectRevert();
etherToken.deposit{value: 1e20 + 1}();
}
function testShouldConvertDepositedETHToWrappedETH() public {
vm.prank(user);
etherToken.deposit{value: 1e20}();
vm.stopPrank();
assertEq(etherToken.balanceOf(user), 1e20);
assertEq(address(etherToken).balance, 1e20);
}
function testShouldRevertIfCallerAttemptsToWithdrawMoreThanTheirBalance() public {
vm.prank(user);
etherToken.deposit{value: 1e20}();
vm.expectRevert();
etherToken.withdraw(1e20 + 1);
}
function testShouldConvertWithdrawWrappedETHToETH() public {
vm.prank(user);
etherToken.deposit{value: 1e20}();
vm.prank(user);
etherToken.withdraw(100);
vm.stopPrank();
assertEq(etherToken.balanceOf(user), 1e20 - 100);
assertEq(address(etherToken).balance, 1e20 - 100);
assertEq(user.balance, 100);
}
function testShouldConvertSentETHToWrappedETH() public {
vm.prank(user);
address(etherToken).call{value: 1e20}(new bytes(0));
vm.stopPrank();
assertEq(etherToken.balanceOf(user), 1e20);
assertEq(address(etherToken).balance, 1e20);
}
}

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.8.17;
import "forge-std/Test.sol";
import "../src/IERC20Token.sol";
contract ZRXTokenTest is Test {
address payable internal owner = payable(vm.addr(1));
address payable internal user = payable(vm.addr(2));
address payable internal anotherUser = payable(vm.addr(3));
uint256 internal totalSupply = 1_000_000_000 * 1e18;
IERC20Token zrxToken;
function setUp() public {
vm.deal(owner, 1e20);
vm.deal(user, 1e20);
vm.prank(owner);
bytes memory _bytecode = vm.getCode("./out/ZRXToken.sol/ZRXToken.json");
address _address;
assembly {
_address := create(0, add(_bytecode, 0x20), mload(_bytecode))
}
vm.stopPrank();
zrxToken = IERC20Token(address(_address));
}
function testShouldHave18Decimals() public {
assertEq(zrxToken.decimals(), 18);
}
function testShouldHaveTotalSupplyOf1Billion() public {
assertEq(zrxToken.totalSupply(), totalSupply);
}
function testShouldInitializeOwnerBalanceToTotalSupply() public {
assertEq(zrxToken.balanceOf(owner), totalSupply);
}
function testShouldTransferBalanceCorrectly() public {
vm.prank(owner);
zrxToken.transfer(user, 100);
assertEq(zrxToken.balanceOf(user), 100);
assertEq(zrxToken.balanceOf(owner), totalSupply - 100);
}
function testShouldReturnTrueOnAZeroValueTransfer() public {
vm.prank(owner);
bool success = zrxToken.transfer(user, 0);
assertTrue(success);
}
function testShouldReturnTrueOnAZeroValueTransferByUserWithZeroBalance() public {
vm.prank(anotherUser);
bool success = zrxToken.transfer(user, 0);
assertTrue(success);
}
function testShouldReturnFalseIfSenderHasInsufficientBalance() public {
vm.prank(owner);
zrxToken.approve(user, totalSupply + 1);
vm.stopPrank();
bool success = zrxToken.transferFrom(owner, user, totalSupply + 1);
assertEq(success, false);
}
function testShouldReturnFalseIfRecipientHasInsufficientAllowance() public {
vm.prank(owner);
zrxToken.approve(user, totalSupply - 1);
vm.stopPrank();
bool success = zrxToken.transferFrom(owner, user, totalSupply);
assertEq(success, false);
}
function testShouldReturnTrueOnAZeroValueApprovedTransfer() public {
vm.prank(user);
bool success = zrxToken.transferFrom(owner, user, 0);
assertEq(success, true);
}
function testShouldNotModifySenderAllowanceIfSetToUINT256Max() public {
vm.prank(owner);
zrxToken.approve(user, type(uint256).max);
vm.stopPrank();
zrxToken.transferFrom(owner, user, 100);
assertEq(zrxToken.allowance(owner, user), type(uint256).max);
}
function testShouldTransferCorrectlyWhenSufficientAllowance() public {
vm.prank(owner);
zrxToken.approve(user, 1000 * 1e18);
vm.stopPrank();
vm.prank(user);
zrxToken.transferFrom(owner, user, 100 * 1e18);
assertEq(zrxToken.allowance(owner, user), 900 * 1e18);
assertEq(zrxToken.balanceOf(user), 100 * 1e18);
assertEq(zrxToken.balanceOf(owner), totalSupply - 100 * 1e18);
}
}

View File

@@ -1,51 +0,0 @@
/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
import { ContractArtifact } from 'ethereum-types';
import * as DummyERC20Token from '../test/generated-artifacts/DummyERC20Token.json';
import * as DummyMultipleReturnERC20Token from '../test/generated-artifacts/DummyMultipleReturnERC20Token.json';
import * as DummyNoReturnERC20Token from '../test/generated-artifacts/DummyNoReturnERC20Token.json';
import * as ERC20Token from '../test/generated-artifacts/ERC20Token.json';
import * as IERC20Token from '../test/generated-artifacts/IERC20Token.json';
import * as IERC20TokenV06 from '../test/generated-artifacts/IERC20TokenV06.json';
import * as IERC20TokenV08 from '../test/generated-artifacts/IERC20TokenV08.json';
import * as IEtherToken from '../test/generated-artifacts/IEtherToken.json';
import * as IEtherTokenV06 from '../test/generated-artifacts/IEtherTokenV06.json';
import * as IEtherTokenV08 from '../test/generated-artifacts/IEtherTokenV08.json';
import * as LibERC20Token from '../test/generated-artifacts/LibERC20Token.json';
import * as LibERC20TokenV06 from '../test/generated-artifacts/LibERC20TokenV06.json';
import * as LibERC20TokenV08 from '../test/generated-artifacts/LibERC20TokenV08.json';
import * as MintableERC20Token from '../test/generated-artifacts/MintableERC20Token.json';
import * as TestLibERC20Token from '../test/generated-artifacts/TestLibERC20Token.json';
import * as TestLibERC20TokenTarget from '../test/generated-artifacts/TestLibERC20TokenTarget.json';
import * as UnlimitedAllowanceERC20Token from '../test/generated-artifacts/UnlimitedAllowanceERC20Token.json';
import * as UntransferrableDummyERC20Token from '../test/generated-artifacts/UntransferrableDummyERC20Token.json';
import * as WETH9 from '../test/generated-artifacts/WETH9.json';
import * as WETH9V06 from '../test/generated-artifacts/WETH9V06.json';
import * as ZRXToken from '../test/generated-artifacts/ZRXToken.json';
export const artifacts = {
ERC20Token: ERC20Token as ContractArtifact,
LibERC20Token: LibERC20Token as ContractArtifact,
MintableERC20Token: MintableERC20Token as ContractArtifact,
UnlimitedAllowanceERC20Token: UnlimitedAllowanceERC20Token as ContractArtifact,
WETH9: WETH9 as ContractArtifact,
ZRXToken: ZRXToken as any as ContractArtifact,
IERC20Token: IERC20Token as ContractArtifact,
IEtherToken: IEtherToken as ContractArtifact,
IERC20TokenV06: IERC20TokenV06 as ContractArtifact,
IEtherTokenV06: IEtherTokenV06 as ContractArtifact,
LibERC20TokenV06: LibERC20TokenV06 as ContractArtifact,
WETH9V06: WETH9V06 as ContractArtifact,
IERC20TokenV08: IERC20TokenV08 as ContractArtifact,
IEtherTokenV08: IEtherTokenV08 as ContractArtifact,
LibERC20TokenV08: LibERC20TokenV08 as ContractArtifact,
DummyERC20Token: DummyERC20Token as ContractArtifact,
DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,
DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact,
TestLibERC20Token: TestLibERC20Token as ContractArtifact,
TestLibERC20TokenTarget: TestLibERC20TokenTarget as ContractArtifact,
UntransferrableDummyERC20Token: UntransferrableDummyERC20Token as ContractArtifact,
};

View File

@@ -1,335 +0,0 @@
import {
blockchainTests,
constants,
expect,
getRandomInteger,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { hexUtils, RawRevertError, StringRevertError } from '@0x/utils';
import { TestLibERC20TokenContract, TestLibERC20TokenTargetEvents } from './wrappers';
import { artifacts } from './artifacts';
blockchainTests('LibERC20Token', env => {
let testContract: TestLibERC20TokenContract;
const REVERT_STRING = 'WHOOPSIE';
const ENCODED_REVERT = new StringRevertError(REVERT_STRING).encode();
const ENCODED_TRUE = hexUtils.leftPad(1);
const ENCODED_FALSE = hexUtils.leftPad(0);
const ENCODED_TWO = hexUtils.leftPad(2);
const ENCODED_SHORT_TRUE = hexUtils.leftPad(2, 31);
const ENCODED_LONG_TRUE = hexUtils.leftPad(2, 33);
before(async () => {
testContract = await TestLibERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestLibERC20Token,
env.provider,
env.txDefaults,
artifacts,
);
});
describe('approve()', () => {
it('calls the target with the correct arguments', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const { logs } = await testContract
.testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled);
});
it('succeeds if the target returns true', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
await testContract
.testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
});
it('succeeds if the target returns nothing', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
await testContract
.testApprove(false, ENCODED_REVERT, constants.NULL_BYTES, spender, allowance)
.awaitTransactionSuccessAsync();
});
it('fails if the target returns false', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(false, ENCODED_REVERT, ENCODED_FALSE, spender, allowance)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(false, ENCODED_REVERT, ENCODED_TWO, spender, allowance)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(false, ENCODED_REVERT, ENCODED_LONG_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(true, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract
.testApprove(true, constants.NULL_BYTES, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync();
return expect(tx).to.be.rejectedWith('revert');
});
});
describe('transfer()', () => {
it('calls the target with the correct arguments', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync();
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled);
});
it('succeeds if the target returns true', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync();
});
it('succeeds if the target returns nothing', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract
.testTransfer(false, ENCODED_REVERT, constants.NULL_BYTES, to, amount)
.awaitTransactionSuccessAsync();
});
it('fails if the target returns false', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_FALSE, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_TWO, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(false, ENCODED_REVERT, ENCODED_LONG_TRUE, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(true, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransfer(true, constants.NULL_BYTES, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync();
return expect(tx).to.be.rejectedWith('revert');
});
});
describe('transferFrom()', () => {
it('calls the target with the correct arguments', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled);
});
it('succeeds if the target returns true', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
});
it('succeeds if the target returns nothing', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract
.testTransferFrom(false, ENCODED_REVERT, constants.NULL_BYTES, owner, to, amount)
.awaitTransactionSuccessAsync();
});
it('fails if the target returns false', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_FALSE, owner, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_TWO, owner, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(false, ENCODED_REVERT, ENCODED_LONG_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(true, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract
.testTransferFrom(true, constants.NULL_BYTES, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync();
return expect(tx).to.be.rejectedWith('revert');
});
});
describe('decimals()', () => {
const DEFAULT_DECIMALS = 18;
const ENCODED_ZERO = hexUtils.leftPad(0);
const ENCODED_SHORT_ZERO = hexUtils.leftPad(0, 31);
const ENCODED_LONG_ZERO = hexUtils.leftPad(0, 33);
const randomDecimals = () => Math.floor(Math.random() * 256) + 1;
it('returns the number of decimals defined by the token', async () => {
const decimals = randomDecimals();
const encodedDecimals = hexUtils.leftPad(decimals);
const result = await testContract.testDecimals(false, ENCODED_REVERT, encodedDecimals).callAsync();
return expect(result).to.bignumber.eq(decimals);
});
it('returns 0 if the token returns 0', async () => {
const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_ZERO).callAsync();
return expect(result).to.bignumber.eq(0);
});
it('returns 18 if the token returns less than 32 bytes', async () => {
const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_SHORT_ZERO).callAsync();
return expect(result).to.bignumber.eq(DEFAULT_DECIMALS);
});
it('returns 18 if the token returns greater than 32 bytes', async () => {
const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_LONG_ZERO).callAsync();
return expect(result).to.bignumber.eq(DEFAULT_DECIMALS);
});
it('returns 18 if the token reverts', async () => {
const result = await testContract.testDecimals(true, ENCODED_REVERT, ENCODED_ZERO).callAsync();
return expect(result).to.bignumber.eq(DEFAULT_DECIMALS);
});
});
});

View File

@@ -1,187 +0,0 @@
import { chaiSetup, constants, 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 { DummyERC20TokenContract } from './wrappers';
import { artifacts } from './artifacts';
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,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
constants.DUMMY_TOKEN_DECIMALS,
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.mint(MAX_MINT_VALUE).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('transfer', () => {
it('should revert if owner has insufficient balance', async () => {
const ownerBalance = await token.balanceOf(owner).callAsync();
const amountToTransfer = ownerBalance.plus(1);
return expect(token.transfer(spender, amountToTransfer).callAsync({ from: owner })).to.revertWith(
RevertReason.Erc20InsufficientBalance,
);
});
it('should transfer balance from sender to receiver', async () => {
const receiver = spender;
const initOwnerBalance = await token.balanceOf(owner).callAsync();
const amountToTransfer = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.transfer(receiver, amountToTransfer).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const finalOwnerBalance = await token.balanceOf(owner).callAsync();
const finalReceiverBalance = await token.balanceOf(receiver).callAsync();
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(spender, new BigNumber(0)).callAsync({
from: owner,
});
expect(didReturnTrue).to.be.true();
});
});
describe('transferFrom', () => {
it('should revert if owner has insufficient balance', async () => {
const ownerBalance = await token.balanceOf(owner).callAsync();
const amountToTransfer = ownerBalance.plus(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.approve(spender, amountToTransfer).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
return expect(
token.transferFrom(owner, spender, amountToTransfer).callAsync({
from: spender,
}),
).to.revertWith(RevertReason.Erc20InsufficientBalance);
});
it('should revert if spender has insufficient allowance', async () => {
const ownerBalance = await token.balanceOf(owner).callAsync();
const amountToTransfer = ownerBalance;
const spenderAllowance = await token.allowance(owner, spender).callAsync();
const isSpenderAllowanceInsufficient = spenderAllowance.comparedTo(amountToTransfer) < 0;
expect(isSpenderAllowanceInsufficient).to.be.true();
return expect(
token.transferFrom(owner, spender, amountToTransfer).callAsync({
from: spender,
}),
).to.revertWith(RevertReason.Erc20InsufficientAllowance);
});
it('should return true on a 0 value transfer', async () => {
const amountToTransfer = new BigNumber(0);
const didReturnTrue = await token.transferFrom(owner, spender, amountToTransfer).callAsync({
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(owner).callAsync();
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
await web3Wrapper.awaitTransactionSuccessAsync(
await token.approve(spender, initSpenderAllowance).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newSpenderAllowance = await token.allowance(owner, spender).callAsync();
expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
});
it('should transfer the correct balances if spender has sufficient allowance', async () => {
const initOwnerBalance = await token.balanceOf(owner).callAsync();
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
await web3Wrapper.awaitTransactionSuccessAsync(
await token.approve(spender, initSpenderAllowance).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newOwnerBalance = await token.balanceOf(owner).callAsync();
const newSpenderBalance = await token.balanceOf(spender).callAsync();
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(owner).callAsync();
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
await web3Wrapper.awaitTransactionSuccessAsync(
await token.approve(spender, initSpenderAllowance).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newSpenderAllowance = await token.allowance(owner, spender).callAsync();
expect(newSpenderAllowance).to.be.bignumber.equal(0);
});
});
});

View File

@@ -1,149 +0,0 @@
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 { WETH9Contract } from './wrappers';
import { artifacts } from './artifacts';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('EtherToken', () => {
let account: string;
const gasPrice = new BigNumber(constants.DEFAULT_GAS_PRICE);
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,
{
...txDefaults,
gasPrice,
},
artifacts,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('deposit', () => {
it('should revert 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(account).callAsync();
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(account).callAsync();
expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
});
});
describe('withdraw', () => {
it('should revert if caller attempts to withdraw greater than caller balance', async () => {
const initEthTokenBalance = await etherToken.balanceOf(account).callAsync();
const ethTokensToWithdraw = initEthTokenBalance.plus(1);
return expectTransactionFailedWithoutReasonAsync(
etherToken.withdraw(ethTokensToWithdraw).sendTransactionAsync(),
);
});
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(account).callAsync();
const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account);
const ethTokensToWithdraw = initEthTokenBalance;
expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
const txHash = await etherToken.withdraw(ethTokensToWithdraw).sendTransactionAsync({
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(account).callAsync();
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(account).callAsync();
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(account).callAsync();
expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas)));
expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit));
});
});
});

View File

@@ -1,26 +0,0 @@
/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/dummy_erc20_token';
export * from '../test/generated-wrappers/dummy_multiple_return_erc20_token';
export * from '../test/generated-wrappers/dummy_no_return_erc20_token';
export * from '../test/generated-wrappers/erc20_token';
export * from '../test/generated-wrappers/i_erc20_token';
export * from '../test/generated-wrappers/i_erc20_token_v06';
export * from '../test/generated-wrappers/i_erc20_token_v08';
export * from '../test/generated-wrappers/i_ether_token';
export * from '../test/generated-wrappers/i_ether_token_v06';
export * from '../test/generated-wrappers/i_ether_token_v08';
export * from '../test/generated-wrappers/lib_erc20_token';
export * from '../test/generated-wrappers/lib_erc20_token_v06';
export * from '../test/generated-wrappers/lib_erc20_token_v08';
export * from '../test/generated-wrappers/mintable_erc20_token';
export * from '../test/generated-wrappers/test_lib_erc20_token';
export * from '../test/generated-wrappers/test_lib_erc20_token_target';
export * from '../test/generated-wrappers/unlimited_allowance_erc20_token';
export * from '../test/generated-wrappers/untransferrable_dummy_erc20_token';
export * from '../test/generated-wrappers/weth9';
export * from '../test/generated-wrappers/weth9v06';
export * from '../test/generated-wrappers/zrx_token';

View File

@@ -1,210 +0,0 @@
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 { ZRXTokenContract } from './wrappers';
import { artifacts } from './artifacts';
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,
artifacts,
);
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(owner).callAsync();
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(owner).callAsync();
const amountToTransfer = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.transfer(receiver, amountToTransfer).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const finalOwnerBalance = await zrxToken.balanceOf(owner).callAsync();
const finalReceiverBalance = await zrxToken.balanceOf(receiver).callAsync();
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(spender, new BigNumber(0)).callAsync({
from: owner,
});
expect(didReturnTrue).to.be.true();
});
});
describe('transferFrom', () => {
it('should return false if owner has insufficient balance', async () => {
const ownerBalance = await zrxToken.balanceOf(owner).callAsync();
const amountToTransfer = ownerBalance.plus(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.approve(spender, amountToTransfer).sendTransactionAsync({
from: owner,
gas: constants.MAX_TOKEN_APPROVE_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const didReturnTrue = await zrxToken.transferFrom(owner, spender, amountToTransfer).callAsync({
from: spender,
});
expect(didReturnTrue).to.be.false();
});
it('should return false if spender has insufficient allowance', async () => {
const ownerBalance = await zrxToken.balanceOf(owner).callAsync();
const amountToTransfer = ownerBalance;
const spenderAllowance = await zrxToken.allowance(owner, spender).callAsync();
const isSpenderAllowanceInsufficient = spenderAllowance.comparedTo(amountToTransfer) < 0;
expect(isSpenderAllowanceInsufficient).to.be.true();
const didReturnTrue = await zrxToken.transferFrom(owner, spender, amountToTransfer).callAsync({
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(owner, spender, amountToTransfer).callAsync({
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(owner).callAsync();
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = MAX_UINT;
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.approve(spender, initSpenderAllowance).sendTransactionAsync({
from: owner,
gas: constants.MAX_TOKEN_APPROVE_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newSpenderAllowance = await zrxToken.allowance(owner, spender).callAsync();
expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance);
});
it('should transfer the correct balances if spender has sufficient allowance', async () => {
const initOwnerBalance = await zrxToken.balanceOf(owner).callAsync();
const initSpenderBalance = await zrxToken.balanceOf(spender).callAsync();
const amountToTransfer = initOwnerBalance;
const initSpenderAllowance = initOwnerBalance;
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.approve(spender, initSpenderAllowance).sendTransactionAsync(),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newOwnerBalance = await zrxToken.balanceOf(owner).callAsync();
const newSpenderBalance = await zrxToken.balanceOf(spender).callAsync();
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(owner).callAsync();
const amountToTransfer = initOwnerBalance;
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.approve(spender, amountToTransfer).sendTransactionAsync(),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await zrxToken.transferFrom(owner, spender, amountToTransfer).sendTransactionAsync({
from: spender,
gas: constants.MAX_TOKEN_TRANSFERFROM_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newSpenderAllowance = await zrxToken.allowance(owner, spender).callAsync();
expect(newSpenderAllowance).to.be.bignumber.equal(0);
});
});
});