protocol/contracts/zero-ex/test/features/token_spender_test.ts
Lawrence Forman 2fce332ed7
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>
2020-05-20 22:47:21 -04:00

142 lines
5.5 KiB
TypeScript

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));
});
});
});