@0x/contracts-asset-proxy
: Finish off UniswapBridge
tests.
This commit is contained in:
parent
b383781870
commit
c2261a6bbe
@ -21,18 +21,18 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||||
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||||
import "@0x/contracts-exchange/contracts/src/interfaces/IWallet.sol";
|
|
||||||
import "../interfaces/IUniswapExchangeFactory.sol";
|
import "../interfaces/IUniswapExchangeFactory.sol";
|
||||||
import "../interfaces/IUniswapExchange.sol";
|
import "../interfaces/IUniswapExchange.sol";
|
||||||
import "./ERC20Bridge.sol";
|
import "../interfaces/IWallet.sol";
|
||||||
|
import "../interfaces/IERC20Bridge.sol";
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable space-after-comma
|
// solhint-disable space-after-comma
|
||||||
|
// solhint-disable not-rely-on-time
|
||||||
contract UniswapBridge is
|
contract UniswapBridge is
|
||||||
ERC20Bridge,
|
IERC20Bridge,
|
||||||
IWallet
|
IWallet
|
||||||
{
|
{
|
||||||
bytes4 private constant LEGACY_WALLET_MAGIC_VALUE = 0xb0671381;
|
|
||||||
/* Mainnet addresses */
|
/* Mainnet addresses */
|
||||||
address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
|
address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
|
||||||
address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||||
@ -45,8 +45,15 @@ contract UniswapBridge is
|
|||||||
IEtherToken weth;
|
IEtherToken weth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Whether we've granted an allowance to a spender for a token.
|
// solhint-disable no-empty-blocks
|
||||||
mapping (address => mapping (address => bool)) private _hasAllowance;
|
/// @dev Payable fallback to receive ETH from uniswap.
|
||||||
|
function ()
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// @dev Whether we've granted an allowance to the exchange for a token.
|
||||||
|
mapping (address => bool) private _hasAllowance;
|
||||||
|
|
||||||
/// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
|
/// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
|
||||||
/// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress`
|
/// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress`
|
||||||
@ -83,7 +90,7 @@ contract UniswapBridge is
|
|||||||
toTokenAddress
|
toTokenAddress
|
||||||
);
|
);
|
||||||
// Grant an allowance to the exchange.
|
// Grant an allowance to the exchange.
|
||||||
_grantAllowanceForTokens(address(state.exchange), [fromTokenAddress, toTokenAddress]);
|
_grantExchangeAllowance(state.exchange);
|
||||||
// Get our balance of `fromTokenAddress` token.
|
// Get our balance of `fromTokenAddress` token.
|
||||||
state.fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
|
state.fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
|
||||||
// Get the weth contract.
|
// Get the weth contract.
|
||||||
@ -176,27 +183,16 @@ contract UniswapBridge is
|
|||||||
return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS);
|
return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Grants an unlimited allowance to `spender` for the tokens passed,
|
/// @dev Grants an unlimited allowance to the exchange for its token
|
||||||
/// if they're not WETH and we haven't already granted `spender` an
|
/// on behalf of this contract, if we haven't already done so.
|
||||||
/// allowance.
|
/// @param exchange The Uniswap token exchange.
|
||||||
/// @param spender The spender being granted an aloowance.
|
function _grantExchangeAllowance(IUniswapExchange exchange)
|
||||||
/// @param tokenAddresses Array of token addresses.
|
|
||||||
function _grantAllowanceForTokens(
|
|
||||||
address spender,
|
|
||||||
address[2] memory tokenAddresses
|
|
||||||
)
|
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
address wethAddress = address(_getWethContract());
|
address tokenAddress = exchange.toTokenAddress();
|
||||||
mapping (address => bool) storage doesSpenderHaveAllowance = _hasAllowance[spender];
|
if (!_hasAllowance[tokenAddress]) {
|
||||||
for (uint256 i = 0; i < tokenAddresses.length; ++i) {
|
IERC20Token(tokenAddress).approve(address(exchange), uint256(-1));
|
||||||
address tokenAddress = tokenAddresses[i];
|
_hasAllowance[tokenAddress] = true;
|
||||||
if (tokenAddress != wethAddress) {
|
|
||||||
if (!doesSpenderHaveAllowance[tokenAddress]) {
|
|
||||||
IERC20Token(tokenAddress).approve(spender, uint256(-1));
|
|
||||||
doesSpenderHaveAllowance[tokenAddress] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ interface IUniswapExchange {
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
|
||||||
returns (uint256 ethBought);
|
returns (uint256 ethBought);
|
||||||
|
|
||||||
/// @dev Buys at least `minTokensBought` tokens with the exchange token
|
/// @dev Buys at least `minTokensBought` tokens with the exchange token
|
||||||
@ -68,4 +67,11 @@ interface IUniswapExchange {
|
|||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 tokensBought);
|
returns (uint256 tokensBought);
|
||||||
|
|
||||||
|
/// @dev Retrieves the token that is associated with this exchange.
|
||||||
|
/// @return tokenAddress The token address.
|
||||||
|
function toTokenAddress()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address tokenAddress);
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,9 @@ import "../src/interfaces/IUniswapExchangeFactory.sol";
|
|||||||
import "../src/interfaces/IUniswapExchange.sol";
|
import "../src/interfaces/IUniswapExchange.sol";
|
||||||
|
|
||||||
|
|
||||||
|
// solhint-disable no-simple-event-func-name
|
||||||
contract TestEventsRaiser {
|
contract TestEventsRaiser {
|
||||||
|
|
||||||
event SellAllAmount(
|
|
||||||
address sellToken,
|
|
||||||
uint256 sellTokenAmount,
|
|
||||||
address buyToken,
|
|
||||||
uint256 minimumFillAmount
|
|
||||||
);
|
|
||||||
|
|
||||||
event TokenTransfer(
|
event TokenTransfer(
|
||||||
address token,
|
address token,
|
||||||
address from,
|
address from,
|
||||||
@ -56,18 +50,21 @@ contract TestEventsRaiser {
|
|||||||
);
|
);
|
||||||
|
|
||||||
event EthToTokenTransferInput(
|
event EthToTokenTransferInput(
|
||||||
|
address exchange,
|
||||||
uint256 minTokensBought,
|
uint256 minTokensBought,
|
||||||
uint256 deadline,
|
uint256 deadline,
|
||||||
address recipient
|
address recipient
|
||||||
);
|
);
|
||||||
|
|
||||||
event TokenToEthSwapInput(
|
event TokenToEthSwapInput(
|
||||||
|
address exchange,
|
||||||
uint256 tokensSold,
|
uint256 tokensSold,
|
||||||
uint256 minEthBought,
|
uint256 minEthBought,
|
||||||
uint256 deadline
|
uint256 deadline
|
||||||
);
|
);
|
||||||
|
|
||||||
event TokenToTokenTransferInput(
|
event TokenToTokenTransferInput(
|
||||||
|
address exchange,
|
||||||
uint256 tokensSold,
|
uint256 tokensSold,
|
||||||
uint256 minTokensBought,
|
uint256 minTokensBought,
|
||||||
uint256 minEthBought,
|
uint256 minEthBought,
|
||||||
@ -84,6 +81,7 @@ contract TestEventsRaiser {
|
|||||||
external
|
external
|
||||||
{
|
{
|
||||||
emit EthToTokenTransferInput(
|
emit EthToTokenTransferInput(
|
||||||
|
msg.sender,
|
||||||
minTokensBought,
|
minTokensBought,
|
||||||
deadline,
|
deadline,
|
||||||
recipient
|
recipient
|
||||||
@ -98,6 +96,7 @@ contract TestEventsRaiser {
|
|||||||
external
|
external
|
||||||
{
|
{
|
||||||
emit TokenToEthSwapInput(
|
emit TokenToEthSwapInput(
|
||||||
|
msg.sender,
|
||||||
tokensSold,
|
tokensSold,
|
||||||
minEthBought,
|
minEthBought,
|
||||||
deadline
|
deadline
|
||||||
@ -115,6 +114,7 @@ contract TestEventsRaiser {
|
|||||||
external
|
external
|
||||||
{
|
{
|
||||||
emit TokenToTokenTransferInput(
|
emit TokenToTokenTransferInput(
|
||||||
|
msg.sender,
|
||||||
tokensSold,
|
tokensSold,
|
||||||
minTokensBought,
|
minTokensBought,
|
||||||
minEthBought,
|
minEthBought,
|
||||||
@ -158,21 +158,38 @@ contract TestEventsRaiser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @dev A minimalist ERC20/WETH token.
|
/// @dev A minimalist ERC20/WETH token.
|
||||||
contract TestToken {
|
contract TestToken {
|
||||||
|
|
||||||
using LibSafeMath for uint256;
|
using LibSafeMath for uint256;
|
||||||
|
|
||||||
mapping (address => uint256) public balances;
|
mapping (address => uint256) public balances;
|
||||||
|
string private _nextRevertReason;
|
||||||
|
|
||||||
/// @dev Calls `raiseTokenTransfer()` on the caller.
|
/// @dev Set the balance for `owner`.
|
||||||
|
function setBalance(address owner)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
balances[owner] = msg.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Set the revert reason for `transfer()`,
|
||||||
|
/// `deposit()`, and `withdraw()`.
|
||||||
|
function setRevertReason(string calldata reason)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
_nextRevertReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Just calls `raiseTokenTransfer()` on the caller.
|
||||||
function transfer(address to, uint256 amount)
|
function transfer(address to, uint256 amount)
|
||||||
external
|
external
|
||||||
returns (bool)
|
returns (bool)
|
||||||
{
|
{
|
||||||
|
_revertIfReasonExists();
|
||||||
TestEventsRaiser(msg.sender).raiseTokenTransfer(msg.sender, to, amount);
|
TestEventsRaiser(msg.sender).raiseTokenTransfer(msg.sender, to, amount);
|
||||||
balances[msg.sender] = balances[msg.sender].safeSub(amount);
|
|
||||||
balances[to] = balances[to].safeAdd(amount);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,20 +202,13 @@ contract TestToken {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Set the balance for `owner`.
|
|
||||||
function setBalance(address owner, uint256 balance)
|
|
||||||
external
|
|
||||||
payable
|
|
||||||
{
|
|
||||||
balances[owner] = balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev `IWETH.deposit()` that increases balances and calls
|
/// @dev `IWETH.deposit()` that increases balances and calls
|
||||||
/// `raiseWethDeposit()` on the caller.
|
/// `raiseWethDeposit()` on the caller.
|
||||||
function deposit()
|
function deposit()
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
{
|
{
|
||||||
|
_revertIfReasonExists();
|
||||||
balances[msg.sender] += balances[msg.sender].safeAdd(msg.value);
|
balances[msg.sender] += balances[msg.sender].safeAdd(msg.value);
|
||||||
TestEventsRaiser(msg.sender).raiseWethDeposit(msg.value);
|
TestEventsRaiser(msg.sender).raiseWethDeposit(msg.value);
|
||||||
}
|
}
|
||||||
@ -208,6 +218,7 @@ contract TestToken {
|
|||||||
function withdraw(uint256 amount)
|
function withdraw(uint256 amount)
|
||||||
external
|
external
|
||||||
{
|
{
|
||||||
|
_revertIfReasonExists();
|
||||||
balances[msg.sender] = balances[msg.sender].safeSub(amount);
|
balances[msg.sender] = balances[msg.sender].safeSub(amount);
|
||||||
msg.sender.transfer(amount);
|
msg.sender.transfer(amount);
|
||||||
TestEventsRaiser(msg.sender).raiseWethWithdraw(amount);
|
TestEventsRaiser(msg.sender).raiseWethWithdraw(amount);
|
||||||
@ -221,6 +232,15 @@ contract TestToken {
|
|||||||
{
|
{
|
||||||
return balances[owner];
|
return balances[owner];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _revertIfReasonExists()
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
if (bytes(_nextRevertReason).length != 0) {
|
||||||
|
revert(_nextRevertReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -229,21 +249,18 @@ contract TestExchange is
|
|||||||
{
|
{
|
||||||
address public tokenAddress;
|
address public tokenAddress;
|
||||||
string private _nextRevertReason;
|
string private _nextRevertReason;
|
||||||
uint256 private _nextFillAmount;
|
|
||||||
|
|
||||||
constructor(address _tokenAddress) public {
|
constructor(address _tokenAddress) public {
|
||||||
tokenAddress = _tokenAddress;
|
tokenAddress = _tokenAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFillBehavior(
|
function setFillBehavior(
|
||||||
string calldata revertReason,
|
string calldata revertReason
|
||||||
uint256 fillAmount
|
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
{
|
{
|
||||||
_nextRevertReason = revertReason;
|
_nextRevertReason = revertReason;
|
||||||
_nextFillAmount = fillAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ethToTokenTransferInput(
|
function ethToTokenTransferInput(
|
||||||
@ -261,7 +278,7 @@ contract TestExchange is
|
|||||||
recipient
|
recipient
|
||||||
);
|
);
|
||||||
_revertIfReasonExists();
|
_revertIfReasonExists();
|
||||||
return _nextFillAmount;
|
return address(this).balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenToEthSwapInput(
|
function tokenToEthSwapInput(
|
||||||
@ -270,7 +287,6 @@ contract TestExchange is
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
|
||||||
returns (uint256 ethBought)
|
returns (uint256 ethBought)
|
||||||
{
|
{
|
||||||
TestEventsRaiser(msg.sender).raiseTokenToEthSwapInput(
|
TestEventsRaiser(msg.sender).raiseTokenToEthSwapInput(
|
||||||
@ -279,7 +295,9 @@ contract TestExchange is
|
|||||||
deadline
|
deadline
|
||||||
);
|
);
|
||||||
_revertIfReasonExists();
|
_revertIfReasonExists();
|
||||||
return _nextFillAmount;
|
uint256 fillAmount = address(this).balance;
|
||||||
|
msg.sender.transfer(fillAmount);
|
||||||
|
return fillAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenToTokenTransferInput(
|
function tokenToTokenTransferInput(
|
||||||
@ -302,11 +320,20 @@ contract TestExchange is
|
|||||||
toTokenAddress
|
toTokenAddress
|
||||||
);
|
);
|
||||||
_revertIfReasonExists();
|
_revertIfReasonExists();
|
||||||
return _nextFillAmount;
|
return address(this).balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTokenAddress()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address _tokenAddress)
|
||||||
|
{
|
||||||
|
return tokenAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _revertIfReasonExists()
|
function _revertIfReasonExists()
|
||||||
private
|
private
|
||||||
|
view
|
||||||
{
|
{
|
||||||
if (bytes(_nextRevertReason).length != 0) {
|
if (bytes(_nextRevertReason).length != 0) {
|
||||||
revert(_nextRevertReason);
|
revert(_nextRevertReason);
|
||||||
@ -321,50 +348,59 @@ contract TestUniswapBridge is
|
|||||||
TestEventsRaiser,
|
TestEventsRaiser,
|
||||||
UniswapBridge
|
UniswapBridge
|
||||||
{
|
{
|
||||||
|
TestToken public wethToken;
|
||||||
TestToken public wethToken = new TestToken();
|
|
||||||
// Token address to TestToken instance.
|
// Token address to TestToken instance.
|
||||||
mapping (address => TestToken) private _testTokens;
|
mapping (address => TestToken) private _testTokens;
|
||||||
// Token address to TestExchange instance.
|
// Token address to TestExchange instance.
|
||||||
mapping (address => TestExchange) private _testExchanges;
|
mapping (address => TestExchange) private _testExchanges;
|
||||||
|
|
||||||
/// @dev Set token balances for this contract.
|
constructor() public {
|
||||||
function setTokenBalances(address tokenAddress, uint256 balance)
|
wethToken = new TestToken();
|
||||||
external
|
_testTokens[address(wethToken)] = wethToken;
|
||||||
{
|
|
||||||
TestToken token = _testTokens[tokenAddress];
|
|
||||||
// Create the token if it doesn't exist.
|
|
||||||
if (address(token) == address(0)) {
|
|
||||||
_testTokens[tokenAddress] = token = new TestToken();
|
|
||||||
}
|
|
||||||
token.setBalance(address(this), balance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Set the behavior for a fill on a uniswap exchange.
|
/// @dev Sets the balance of this contract for an existing token.
|
||||||
function setExchangeFillBehavior(
|
/// The wei attached will be the balance.
|
||||||
address exchangeAddress,
|
function setTokenBalance(address tokenAddress)
|
||||||
string calldata revertReason,
|
|
||||||
uint256 fillAmount
|
|
||||||
)
|
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
{
|
{
|
||||||
createExchange(exchangeAddress).setFillBehavior.value(msg.value)(
|
TestToken token = _testTokens[tokenAddress];
|
||||||
revertReason,
|
token.deposit.value(msg.value)();
|
||||||
fillAmount
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Create an exchange for a token.
|
/// @dev Sets the revert reason for an existing token.
|
||||||
function createExchange(address tokenAddress)
|
function setTokenRevertReason(address tokenAddress, string calldata revertReason)
|
||||||
public
|
external
|
||||||
returns (TestExchange exchangeAddress)
|
|
||||||
{
|
{
|
||||||
TestExchange exchange = _testExchanges[tokenAddress];
|
TestToken token = _testTokens[tokenAddress];
|
||||||
if (address(exchange) == address(0)) {
|
token.setRevertReason(revertReason);
|
||||||
_testExchanges[tokenAddress] = exchange = new TestExchange(tokenAddress);
|
|
||||||
}
|
}
|
||||||
return exchange;
|
|
||||||
|
/// @dev Create a token and exchange (if they don't exist) for a new token
|
||||||
|
/// and sets the exchange revert and fill behavior. The wei attached
|
||||||
|
/// will be the fill amount for the exchange.
|
||||||
|
/// @param tokenAddress The token address. If zero, one will be created.
|
||||||
|
/// @param revertReason The revert reason for exchange operations.
|
||||||
|
function createTokenAndExchange(
|
||||||
|
address tokenAddress,
|
||||||
|
string calldata revertReason
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (TestToken token, TestExchange exchange)
|
||||||
|
{
|
||||||
|
token = TestToken(tokenAddress);
|
||||||
|
if (tokenAddress == address(0)) {
|
||||||
|
token = new TestToken();
|
||||||
|
}
|
||||||
|
_testTokens[address(token)] = token;
|
||||||
|
exchange = _testExchanges[address(token)];
|
||||||
|
if (address(exchange) == address(0)) {
|
||||||
|
_testExchanges[address(token)] = exchange = new TestExchange(address(token));
|
||||||
|
}
|
||||||
|
exchange.setFillBehavior.value(msg.value)(revertReason);
|
||||||
|
return (token, exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev `IUniswapExchangeFactory.getExchange`
|
/// @dev `IUniswapExchangeFactory.getExchange`
|
||||||
|
@ -2,13 +2,16 @@ import {
|
|||||||
blockchainTests,
|
blockchainTests,
|
||||||
constants,
|
constants,
|
||||||
expect,
|
expect,
|
||||||
|
filterLogs,
|
||||||
filterLogsToArguments,
|
filterLogsToArguments,
|
||||||
getRandomInteger,
|
getRandomInteger,
|
||||||
|
hexLeftPad,
|
||||||
hexRandom,
|
hexRandom,
|
||||||
Numberish,
|
Numberish,
|
||||||
randomAddress,
|
randomAddress,
|
||||||
TransactionHelper,
|
TransactionHelper,
|
||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { AssetProxyId } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { DecodedLogs } from 'ethereum-types';
|
import { DecodedLogs } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@ -16,15 +19,19 @@ import * as _ from 'lodash';
|
|||||||
import {
|
import {
|
||||||
artifacts,
|
artifacts,
|
||||||
TestUniswapBridgeContract,
|
TestUniswapBridgeContract,
|
||||||
TestUniswapBridgeEvents,
|
TestUniswapBridgeEthToTokenTransferInputEventArgs as EthToTokenTransferInputArgs,
|
||||||
TestUniswapBridgeSellAllAmountEventArgs,
|
TestUniswapBridgeEvents as ContractEvents,
|
||||||
TestUniswapBridgeTokenTransferEventArgs,
|
TestUniswapBridgeTokenApproveEventArgs as TokenApproveArgs,
|
||||||
|
TestUniswapBridgeTokenToEthSwapInputEventArgs as TokenToEthSwapInputArgs,
|
||||||
|
TestUniswapBridgeTokenToTokenTransferInputEventArgs as TokenToTokenTransferInputArgs,
|
||||||
|
TestUniswapBridgeTokenTransferEventArgs as TokenTransferArgs,
|
||||||
|
TestUniswapBridgeWethDepositEventArgs as WethDepositArgs,
|
||||||
|
TestUniswapBridgeWethWithdrawEventArgs as WethWithdrawArgs,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
blockchainTests.resets('UniswapBridge unit tests', env => {
|
blockchainTests.resets.only('UniswapBridge unit tests', env => {
|
||||||
const txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
|
const txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
|
||||||
let testContract: TestUniswapBridgeContract;
|
let testContract: TestUniswapBridgeContract;
|
||||||
let daiTokenAddress: string;
|
|
||||||
let wethTokenAddress: string;
|
let wethTokenAddress: string;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@ -34,18 +41,7 @@ blockchainTests.resets('UniswapBridge unit tests', env => {
|
|||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
);
|
);
|
||||||
[daiTokenAddress, wethTokenAddress] = await Promise.all([
|
wethTokenAddress = await testContract.wethToken.callAsync();
|
||||||
testContract.daiToken.callAsync(),
|
|
||||||
testContract.wethToken.callAsync(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deployment', () => {
|
|
||||||
it('sets Uniswap allowances to maximum', async () => {
|
|
||||||
const [wethAllowance, daiAllowance] = await testContract.getUniswapTokenAllowances.callAsync();
|
|
||||||
expect(wethAllowance).to.bignumber.eq(constants.MAX_UINT256);
|
|
||||||
expect(daiAllowance).to.bignumber.eq(constants.MAX_UINT256);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isValidSignature()', () => {
|
describe('isValidSignature()', () => {
|
||||||
@ -56,125 +52,333 @@ blockchainTests.resets('UniswapBridge unit tests', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transfer()', () => {
|
describe('withdrawTo()', () => {
|
||||||
interface TransferOpts {
|
interface WithdrawToOpts {
|
||||||
|
fromTokenAddress: string;
|
||||||
toTokenAddress: string;
|
toTokenAddress: string;
|
||||||
|
fromTokenBalance: Numberish;
|
||||||
toAddress: string;
|
toAddress: string;
|
||||||
amount: Numberish;
|
amount: Numberish;
|
||||||
fromTokenBalance: Numberish;
|
exchangeRevertReason: string;
|
||||||
revertReason: string;
|
exchangeFillAmount: Numberish;
|
||||||
fillAmount: Numberish;
|
toTokenRevertReason: string;
|
||||||
|
fromTokenRevertReason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTransferOpts(opts?: Partial<TransferOpts>): TransferOpts {
|
function createWithdrawToOpts(opts?: Partial<WithdrawToOpts>): WithdrawToOpts {
|
||||||
return {
|
return {
|
||||||
toTokenAddress: _.sampleSize([wethTokenAddress, daiTokenAddress], 1)[0],
|
fromTokenAddress: constants.NULL_ADDRESS,
|
||||||
|
toTokenAddress: constants.NULL_ADDRESS,
|
||||||
|
fromTokenBalance: getRandomInteger(1, 1e18),
|
||||||
toAddress: randomAddress(),
|
toAddress: randomAddress(),
|
||||||
amount: getRandomInteger(1, 100e18),
|
amount: getRandomInteger(1, 1e18),
|
||||||
revertReason: '',
|
exchangeRevertReason: '',
|
||||||
fillAmount: getRandomInteger(1, 100e18),
|
exchangeFillAmount: getRandomInteger(1, 1e18),
|
||||||
fromTokenBalance: getRandomInteger(1, 100e18),
|
toTokenRevertReason: '',
|
||||||
|
fromTokenRevertReason: '',
|
||||||
...opts,
|
...opts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function transferAsync(opts?: Partial<TransferOpts>): Promise<[string, DecodedLogs]> {
|
interface WithdrawToResult {
|
||||||
const _opts = createTransferOpts(opts);
|
opts: WithdrawToOpts;
|
||||||
// Set the fill behavior.
|
result: string;
|
||||||
await testContract.setFillBehavior.awaitTransactionSuccessAsync(
|
logs: DecodedLogs;
|
||||||
_opts.revertReason,
|
blockTime: number;
|
||||||
new BigNumber(_opts.fillAmount),
|
|
||||||
);
|
|
||||||
// Set the token balance for the token we're converting from.
|
|
||||||
await testContract.setTokenBalances.awaitTransactionSuccessAsync(
|
|
||||||
_opts.toTokenAddress === daiTokenAddress
|
|
||||||
? new BigNumber(_opts.fromTokenBalance)
|
|
||||||
: constants.ZERO_AMOUNT,
|
|
||||||
_opts.toTokenAddress === wethTokenAddress
|
|
||||||
? new BigNumber(_opts.fromTokenBalance)
|
|
||||||
: constants.ZERO_AMOUNT,
|
|
||||||
);
|
|
||||||
// Call transfer().
|
|
||||||
const [result, { logs }] = await txHelper.getResultAndReceiptAsync(
|
|
||||||
testContract.transfer,
|
|
||||||
'0x',
|
|
||||||
_opts.toTokenAddress,
|
|
||||||
randomAddress(),
|
|
||||||
_opts.toAddress,
|
|
||||||
new BigNumber(_opts.amount),
|
|
||||||
);
|
|
||||||
return [result, (logs as any) as DecodedLogs];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOppositeToken(tokenAddress: string): string {
|
async function withdrawToAsync(opts?: Partial<WithdrawToOpts>): Promise<WithdrawToResult> {
|
||||||
if (tokenAddress === daiTokenAddress) {
|
const _opts = createWithdrawToOpts(opts);
|
||||||
return wethTokenAddress;
|
// Create the "from" token and exchange.
|
||||||
|
[[_opts.fromTokenAddress]] = await txHelper.getResultAndReceiptAsync(
|
||||||
|
testContract.createTokenAndExchange,
|
||||||
|
_opts.fromTokenAddress,
|
||||||
|
_opts.exchangeRevertReason,
|
||||||
|
{ value: new BigNumber(_opts.exchangeFillAmount) },
|
||||||
|
);
|
||||||
|
// Create the "to" token and exchange.
|
||||||
|
[[_opts.toTokenAddress]] = await txHelper.getResultAndReceiptAsync(
|
||||||
|
testContract.createTokenAndExchange,
|
||||||
|
_opts.toTokenAddress,
|
||||||
|
_opts.exchangeRevertReason,
|
||||||
|
{ value: new BigNumber(_opts.exchangeFillAmount) },
|
||||||
|
);
|
||||||
|
await testContract.setTokenRevertReason.awaitTransactionSuccessAsync(
|
||||||
|
_opts.toTokenAddress,
|
||||||
|
_opts.toTokenRevertReason,
|
||||||
|
);
|
||||||
|
await testContract.setTokenRevertReason.awaitTransactionSuccessAsync(
|
||||||
|
_opts.fromTokenAddress,
|
||||||
|
_opts.fromTokenRevertReason,
|
||||||
|
);
|
||||||
|
// Set the token balance for the token we're converting from.
|
||||||
|
await testContract.setTokenBalance.awaitTransactionSuccessAsync(_opts.fromTokenAddress, {
|
||||||
|
value: new BigNumber(_opts.fromTokenBalance),
|
||||||
|
});
|
||||||
|
// Call withdrawTo().
|
||||||
|
const [result, receipt] = await txHelper.getResultAndReceiptAsync(
|
||||||
|
testContract.withdrawTo,
|
||||||
|
// The "to" token address.
|
||||||
|
_opts.toTokenAddress,
|
||||||
|
// The "from" address.
|
||||||
|
randomAddress(),
|
||||||
|
// The "to" address.
|
||||||
|
_opts.toAddress,
|
||||||
|
// The amount to transfer to "to"
|
||||||
|
new BigNumber(_opts.amount),
|
||||||
|
// ABI-encoded "from" token address.
|
||||||
|
hexLeftPad(_opts.fromTokenAddress),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
opts: _opts,
|
||||||
|
result,
|
||||||
|
logs: receipt.logs,
|
||||||
|
blockTime: await env.web3Wrapper.getBlockTimestampAsync(receipt.blockNumber),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return daiTokenAddress;
|
|
||||||
|
async function getExchangeForTokenAsync(tokenAddress: string): Promise<string> {
|
||||||
|
return testContract.getExchange.callAsync(tokenAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('returns magic bytes on success', async () => {
|
it('returns magic bytes on success', async () => {
|
||||||
const BRIDGE_SUCCESS_RETURN_DATA = '0xb5d40d78';
|
const { result } = await withdrawToAsync();
|
||||||
const [result] = await transferAsync();
|
expect(result).to.eq(AssetProxyId.ERC20Bridge);
|
||||||
expect(result).to.eq(BRIDGE_SUCCESS_RETURN_DATA);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls `Uniswap.sellAllAmount()`', async () => {
|
it('just transfers tokens to `to` if the same tokens are in play', async () => {
|
||||||
const opts = createTransferOpts();
|
const [[tokenAddress]] = await txHelper.getResultAndReceiptAsync(
|
||||||
const [, logs] = await transferAsync(opts);
|
testContract.createTokenAndExchange,
|
||||||
const transfers = filterLogsToArguments<TestUniswapBridgeSellAllAmountEventArgs>(
|
constants.NULL_ADDRESS,
|
||||||
logs,
|
'',
|
||||||
TestUniswapBridgeEvents.SellAllAmount,
|
|
||||||
);
|
);
|
||||||
expect(transfers.length).to.eq(1);
|
const { opts, result, logs } = await withdrawToAsync({
|
||||||
expect(transfers[0].sellToken).to.eq(getOppositeToken(opts.toTokenAddress));
|
fromTokenAddress: tokenAddress,
|
||||||
expect(transfers[0].buyToken).to.eq(opts.toTokenAddress);
|
toTokenAddress: tokenAddress,
|
||||||
expect(transfers[0].sellTokenAmount).to.bignumber.eq(opts.fromTokenBalance);
|
|
||||||
expect(transfers[0].minimumFillAmount).to.bignumber.eq(opts.amount);
|
|
||||||
});
|
});
|
||||||
|
expect(result).to.eq(AssetProxyId.ERC20Bridge);
|
||||||
it('can swap DAI for WETH', async () => {
|
const transfers = filterLogsToArguments<TokenTransferArgs>(logs, ContractEvents.TokenTransfer);
|
||||||
const opts = createTransferOpts({ toTokenAddress: wethTokenAddress });
|
|
||||||
const [, logs] = await transferAsync(opts);
|
|
||||||
const transfers = filterLogsToArguments<TestUniswapBridgeSellAllAmountEventArgs>(
|
|
||||||
logs,
|
|
||||||
TestUniswapBridgeEvents.SellAllAmount,
|
|
||||||
);
|
|
||||||
expect(transfers.length).to.eq(1);
|
expect(transfers.length).to.eq(1);
|
||||||
expect(transfers[0].sellToken).to.eq(daiTokenAddress);
|
expect(transfers[0].token).to.eq(tokenAddress);
|
||||||
expect(transfers[0].buyToken).to.eq(wethTokenAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can swap WETH for DAI', async () => {
|
|
||||||
const opts = createTransferOpts({ toTokenAddress: daiTokenAddress });
|
|
||||||
const [, logs] = await transferAsync(opts);
|
|
||||||
const transfers = filterLogsToArguments<TestUniswapBridgeSellAllAmountEventArgs>(
|
|
||||||
logs,
|
|
||||||
TestUniswapBridgeEvents.SellAllAmount,
|
|
||||||
);
|
|
||||||
expect(transfers.length).to.eq(1);
|
|
||||||
expect(transfers[0].sellToken).to.eq(wethTokenAddress);
|
|
||||||
expect(transfers[0].buyToken).to.eq(daiTokenAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transfers filled amount to `to`', async () => {
|
|
||||||
const opts = createTransferOpts();
|
|
||||||
const [, logs] = await transferAsync(opts);
|
|
||||||
const transfers = filterLogsToArguments<TestUniswapBridgeTokenTransferEventArgs>(
|
|
||||||
logs,
|
|
||||||
TestUniswapBridgeEvents.TokenTransfer,
|
|
||||||
);
|
|
||||||
expect(transfers.length).to.eq(1);
|
|
||||||
expect(transfers[0].token).to.eq(opts.toTokenAddress);
|
|
||||||
expect(transfers[0].from).to.eq(testContract.address);
|
expect(transfers[0].from).to.eq(testContract.address);
|
||||||
expect(transfers[0].to).to.eq(opts.toAddress);
|
expect(transfers[0].to).to.eq(opts.toAddress);
|
||||||
expect(transfers[0].amount).to.bignumber.eq(opts.fillAmount);
|
expect(transfers[0].amount).to.bignumber.eq(opts.amount);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if `Uniswap.sellAllAmount()` reverts', async () => {
|
describe('token -> token', () => {
|
||||||
const opts = createTransferOpts({ revertReason: 'FOOBAR' });
|
it('calls `IUniswapExchange.tokenToTokenTransferInput()', async () => {
|
||||||
const tx = transferAsync(opts);
|
const { opts, logs, blockTime } = await withdrawToAsync();
|
||||||
return expect(tx).to.revertWith(opts.revertReason);
|
const exchangeAddress = await getExchangeForTokenAsync(opts.fromTokenAddress);
|
||||||
|
const calls = filterLogsToArguments<TokenToTokenTransferInputArgs>(
|
||||||
|
logs,
|
||||||
|
ContractEvents.TokenToTokenTransferInput,
|
||||||
|
);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].exchange).to.eq(exchangeAddress);
|
||||||
|
expect(calls[0].tokensSold).to.bignumber.eq(opts.fromTokenBalance);
|
||||||
|
expect(calls[0].minTokensBought).to.bignumber.eq(0);
|
||||||
|
expect(calls[0].minEthBought).to.bignumber.eq(0);
|
||||||
|
expect(calls[0].deadline).to.bignumber.eq(blockTime);
|
||||||
|
expect(calls[0].recipient).to.eq(opts.toAddress);
|
||||||
|
expect(calls[0].toTokenAddress).to.eq(opts.toTokenAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "from" token', async () => {
|
||||||
|
const { opts, logs } = await withdrawToAsync();
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.fromTokenAddress);
|
||||||
|
expect(transfers.length).to.eq(1);
|
||||||
|
expect(transfers[0].spender).to.eq(exchangeAddress);
|
||||||
|
expect(transfers[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "from" token only once', async () => {
|
||||||
|
const { opts } = await withdrawToAsync();
|
||||||
|
const { logs } = await withdrawToAsync(opts);
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
expect(transfers.length).to.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if "from" token does not exist', async () => {
|
||||||
|
const tx = testContract.withdrawTo.awaitTransactionSuccessAsync(
|
||||||
|
randomAddress(),
|
||||||
|
randomAddress(),
|
||||||
|
randomAddress(),
|
||||||
|
getRandomInteger(1, 1e18),
|
||||||
|
hexLeftPad(randomAddress()),
|
||||||
|
);
|
||||||
|
return expect(tx).to.revertWith('NO_UNISWAP_EXCHANGE_FOR_TOKEN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if the exchange fails', async () => {
|
||||||
|
const revertReason = 'FOOBAR';
|
||||||
|
const tx = withdrawToAsync({
|
||||||
|
exchangeRevertReason: revertReason,
|
||||||
|
});
|
||||||
|
return expect(tx).to.revertWith(revertReason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('token -> ETH', () => {
|
||||||
|
it('calls `IUniswapExchange.tokenToEthSwapInput()`, `WETH.deposit()`, then `transfer()`', async () => {
|
||||||
|
const { opts, logs, blockTime } = await withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.fromTokenAddress);
|
||||||
|
let calls: any = filterLogs<TokenToEthSwapInputArgs>(logs, ContractEvents.TokenToEthSwapInput);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].args.exchange).to.eq(exchangeAddress);
|
||||||
|
expect(calls[0].args.tokensSold).to.bignumber.eq(opts.fromTokenBalance);
|
||||||
|
expect(calls[0].args.minEthBought).to.bignumber.eq(0);
|
||||||
|
expect(calls[0].args.deadline).to.bignumber.eq(blockTime);
|
||||||
|
calls = filterLogs<WethDepositArgs>(
|
||||||
|
logs.slice(calls[0].logIndex as number),
|
||||||
|
ContractEvents.WethDeposit,
|
||||||
|
);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].args.amount).to.bignumber.eq(opts.exchangeFillAmount);
|
||||||
|
calls = filterLogs<TokenTransferArgs>(
|
||||||
|
logs.slice(calls[0].logIndex as number),
|
||||||
|
ContractEvents.TokenTransfer,
|
||||||
|
);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].args.token).to.eq(opts.toTokenAddress);
|
||||||
|
expect(calls[0].args.from).to.eq(testContract.address);
|
||||||
|
expect(calls[0].args.to).to.eq(opts.toAddress);
|
||||||
|
expect(calls[0].args.amount).to.bignumber.eq(opts.exchangeFillAmount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls `IUniswapExchange.tokenToEthSwapInput()`', async () => {
|
||||||
|
const { opts, logs, blockTime } = await withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const calls = filterLogsToArguments<TokenToEthSwapInputArgs>(logs, ContractEvents.TokenToEthSwapInput);
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.fromTokenAddress);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].exchange).to.eq(exchangeAddress);
|
||||||
|
expect(calls[0].tokensSold).to.bignumber.eq(opts.fromTokenBalance);
|
||||||
|
expect(calls[0].minEthBought).to.bignumber.eq(0);
|
||||||
|
expect(calls[0].deadline).to.bignumber.eq(blockTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "from" token', async () => {
|
||||||
|
const { opts, logs } = await withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.fromTokenAddress);
|
||||||
|
expect(transfers.length).to.eq(1);
|
||||||
|
expect(transfers[0].spender).to.eq(exchangeAddress);
|
||||||
|
expect(transfers[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "from" token only once', async () => {
|
||||||
|
const { opts } = await withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const { logs } = await withdrawToAsync(opts);
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
expect(transfers.length).to.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if "from" token does not exist', async () => {
|
||||||
|
const tx = testContract.withdrawTo.awaitTransactionSuccessAsync(
|
||||||
|
randomAddress(),
|
||||||
|
randomAddress(),
|
||||||
|
randomAddress(),
|
||||||
|
getRandomInteger(1, 1e18),
|
||||||
|
hexLeftPad(wethTokenAddress),
|
||||||
|
);
|
||||||
|
return expect(tx).to.revertWith('NO_UNISWAP_EXCHANGE_FOR_TOKEN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if `WETH.deposit()` fails', async () => {
|
||||||
|
const revertReason = 'FOOBAR';
|
||||||
|
const tx = withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
toTokenRevertReason: revertReason,
|
||||||
|
});
|
||||||
|
return expect(tx).to.revertWith(revertReason);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if the exchange fails', async () => {
|
||||||
|
const revertReason = 'FOOBAR';
|
||||||
|
const tx = withdrawToAsync({
|
||||||
|
toTokenAddress: wethTokenAddress,
|
||||||
|
exchangeRevertReason: revertReason,
|
||||||
|
});
|
||||||
|
return expect(tx).to.revertWith(revertReason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ETH -> token', () => {
|
||||||
|
it('calls `WETH.withdraw()`, then `IUniswapExchange.ethToTokenTransferInput()`', async () => {
|
||||||
|
const { opts, logs, blockTime } = await withdrawToAsync({
|
||||||
|
fromTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.toTokenAddress);
|
||||||
|
let calls: any = filterLogs<WethWithdrawArgs>(logs, ContractEvents.WethWithdraw);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].args.amount).to.bignumber.eq(opts.fromTokenBalance);
|
||||||
|
calls = filterLogs<EthToTokenTransferInputArgs>(
|
||||||
|
logs.slice(calls[0].logIndex as number),
|
||||||
|
ContractEvents.EthToTokenTransferInput,
|
||||||
|
);
|
||||||
|
expect(calls.length).to.eq(1);
|
||||||
|
expect(calls[0].args.exchange).to.eq(exchangeAddress);
|
||||||
|
expect(calls[0].args.minTokensBought).to.bignumber.eq(0);
|
||||||
|
expect(calls[0].args.deadline).to.bignumber.eq(blockTime);
|
||||||
|
expect(calls[0].args.recipient).to.eq(opts.toAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "to" token', async () => {
|
||||||
|
const { opts, logs } = await withdrawToAsync({
|
||||||
|
fromTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
const exchangeAddress = await getExchangeForTokenAsync(opts.toTokenAddress);
|
||||||
|
expect(transfers.length).to.eq(1);
|
||||||
|
expect(transfers[0].spender).to.eq(exchangeAddress);
|
||||||
|
expect(transfers[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets allowance for "from" token only', async () => {
|
||||||
|
const { opts } = await withdrawToAsync({
|
||||||
|
fromTokenAddress: wethTokenAddress,
|
||||||
|
});
|
||||||
|
const { logs } = await withdrawToAsync(opts);
|
||||||
|
const transfers = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||||
|
expect(transfers.length).to.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if "to" token does not exist', async () => {
|
||||||
|
const tx = testContract.withdrawTo.awaitTransactionSuccessAsync(
|
||||||
|
wethTokenAddress,
|
||||||
|
randomAddress(),
|
||||||
|
randomAddress(),
|
||||||
|
getRandomInteger(1, 1e18),
|
||||||
|
hexLeftPad(randomAddress()),
|
||||||
|
);
|
||||||
|
return expect(tx).to.revertWith('NO_UNISWAP_EXCHANGE_FOR_TOKEN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if the `WETH.withdraw()` fails', async () => {
|
||||||
|
const revertReason = 'FOOBAR';
|
||||||
|
const tx = withdrawToAsync({
|
||||||
|
fromTokenAddress: wethTokenAddress,
|
||||||
|
fromTokenRevertReason: revertReason,
|
||||||
|
});
|
||||||
|
return expect(tx).to.revertWith(revertReason);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if the exchange fails', async () => {
|
||||||
|
const revertReason = 'FOOBAR';
|
||||||
|
const tx = withdrawToAsync({
|
||||||
|
fromTokenAddress: wethTokenAddress,
|
||||||
|
exchangeRevertReason: revertReason,
|
||||||
|
});
|
||||||
|
return expect(tx).to.revertWith(revertReason);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user