433 lines
11 KiB
Solidity
433 lines
11 KiB
Solidity
/*
|
|
|
|
Copyright 2019 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.5.9;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
|
import "../src/bridges/UniswapBridge.sol";
|
|
import "../src/interfaces/IUniswapExchangeFactory.sol";
|
|
import "../src/interfaces/IUniswapExchange.sol";
|
|
|
|
|
|
// solhint-disable no-simple-event-func-name
|
|
contract TestEventsRaiser {
|
|
|
|
event TokenTransfer(
|
|
address token,
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
);
|
|
|
|
event TokenApprove(
|
|
address spender,
|
|
uint256 allowance
|
|
);
|
|
|
|
event WethDeposit(
|
|
uint256 amount
|
|
);
|
|
|
|
event WethWithdraw(
|
|
uint256 amount
|
|
);
|
|
|
|
event EthToTokenTransferInput(
|
|
address exchange,
|
|
uint256 minTokensBought,
|
|
uint256 deadline,
|
|
address recipient
|
|
);
|
|
|
|
event TokenToEthSwapInput(
|
|
address exchange,
|
|
uint256 tokensSold,
|
|
uint256 minEthBought,
|
|
uint256 deadline
|
|
);
|
|
|
|
event TokenToTokenTransferInput(
|
|
address exchange,
|
|
uint256 tokensSold,
|
|
uint256 minTokensBought,
|
|
uint256 minEthBought,
|
|
uint256 deadline,
|
|
address recipient,
|
|
address toTokenAddress
|
|
);
|
|
|
|
function raiseEthToTokenTransferInput(
|
|
uint256 minTokensBought,
|
|
uint256 deadline,
|
|
address recipient
|
|
)
|
|
external
|
|
{
|
|
emit EthToTokenTransferInput(
|
|
msg.sender,
|
|
minTokensBought,
|
|
deadline,
|
|
recipient
|
|
);
|
|
}
|
|
|
|
function raiseTokenToEthSwapInput(
|
|
uint256 tokensSold,
|
|
uint256 minEthBought,
|
|
uint256 deadline
|
|
)
|
|
external
|
|
{
|
|
emit TokenToEthSwapInput(
|
|
msg.sender,
|
|
tokensSold,
|
|
minEthBought,
|
|
deadline
|
|
);
|
|
}
|
|
|
|
function raiseTokenToTokenTransferInput(
|
|
uint256 tokensSold,
|
|
uint256 minTokensBought,
|
|
uint256 minEthBought,
|
|
uint256 deadline,
|
|
address recipient,
|
|
address toTokenAddress
|
|
)
|
|
external
|
|
{
|
|
emit TokenToTokenTransferInput(
|
|
msg.sender,
|
|
tokensSold,
|
|
minTokensBought,
|
|
minEthBought,
|
|
deadline,
|
|
recipient,
|
|
toTokenAddress
|
|
);
|
|
}
|
|
|
|
function raiseTokenTransfer(
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
)
|
|
external
|
|
{
|
|
emit TokenTransfer(
|
|
msg.sender,
|
|
from,
|
|
to,
|
|
amount
|
|
);
|
|
}
|
|
|
|
function raiseTokenApprove(address spender, uint256 allowance)
|
|
external
|
|
{
|
|
emit TokenApprove(spender, allowance);
|
|
}
|
|
|
|
function raiseWethDeposit(uint256 amount)
|
|
external
|
|
{
|
|
emit WethDeposit(amount);
|
|
}
|
|
|
|
function raiseWethWithdraw(uint256 amount)
|
|
external
|
|
{
|
|
emit WethWithdraw(amount);
|
|
}
|
|
}
|
|
|
|
|
|
/// @dev A minimalist ERC20/WETH token.
|
|
contract TestToken {
|
|
|
|
using LibSafeMath for uint256;
|
|
|
|
mapping (address => uint256) public balances;
|
|
string private _nextRevertReason;
|
|
|
|
/// @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)
|
|
external
|
|
returns (bool)
|
|
{
|
|
_revertIfReasonExists();
|
|
TestEventsRaiser(msg.sender).raiseTokenTransfer(msg.sender, to, amount);
|
|
return true;
|
|
}
|
|
|
|
/// @dev Just calls `raiseTokenApprove()` on the caller.
|
|
function approve(address spender, uint256 allowance)
|
|
external
|
|
returns (bool)
|
|
{
|
|
TestEventsRaiser(msg.sender).raiseTokenApprove(spender, allowance);
|
|
return true;
|
|
}
|
|
|
|
/// @dev `IWETH.deposit()` that increases balances and calls
|
|
/// `raiseWethDeposit()` on the caller.
|
|
function deposit()
|
|
external
|
|
payable
|
|
{
|
|
_revertIfReasonExists();
|
|
balances[msg.sender] += balances[msg.sender].safeAdd(msg.value);
|
|
TestEventsRaiser(msg.sender).raiseWethDeposit(msg.value);
|
|
}
|
|
|
|
/// @dev `IWETH.withdraw()` that just reduces balances and calls
|
|
/// `raiseWethWithdraw()` on the caller.
|
|
function withdraw(uint256 amount)
|
|
external
|
|
{
|
|
_revertIfReasonExists();
|
|
balances[msg.sender] = balances[msg.sender].safeSub(amount);
|
|
msg.sender.transfer(amount);
|
|
TestEventsRaiser(msg.sender).raiseWethWithdraw(amount);
|
|
}
|
|
|
|
/// @dev Retrieve the balance for `owner`.
|
|
function balanceOf(address owner)
|
|
external
|
|
view
|
|
returns (uint256)
|
|
{
|
|
return balances[owner];
|
|
}
|
|
|
|
function _revertIfReasonExists()
|
|
private
|
|
view
|
|
{
|
|
if (bytes(_nextRevertReason).length != 0) {
|
|
revert(_nextRevertReason);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
contract TestExchange is
|
|
IUniswapExchange
|
|
{
|
|
address public tokenAddress;
|
|
string private _nextRevertReason;
|
|
|
|
constructor(address _tokenAddress) public {
|
|
tokenAddress = _tokenAddress;
|
|
}
|
|
|
|
function setFillBehavior(
|
|
string calldata revertReason
|
|
)
|
|
external
|
|
payable
|
|
{
|
|
_nextRevertReason = revertReason;
|
|
}
|
|
|
|
function ethToTokenTransferInput(
|
|
uint256 minTokensBought,
|
|
uint256 deadline,
|
|
address recipient
|
|
)
|
|
external
|
|
payable
|
|
returns (uint256 tokensBought)
|
|
{
|
|
TestEventsRaiser(msg.sender).raiseEthToTokenTransferInput(
|
|
minTokensBought,
|
|
deadline,
|
|
recipient
|
|
);
|
|
_revertIfReasonExists();
|
|
return address(this).balance;
|
|
}
|
|
|
|
function tokenToEthSwapInput(
|
|
uint256 tokensSold,
|
|
uint256 minEthBought,
|
|
uint256 deadline
|
|
)
|
|
external
|
|
returns (uint256 ethBought)
|
|
{
|
|
TestEventsRaiser(msg.sender).raiseTokenToEthSwapInput(
|
|
tokensSold,
|
|
minEthBought,
|
|
deadline
|
|
);
|
|
_revertIfReasonExists();
|
|
uint256 fillAmount = address(this).balance;
|
|
msg.sender.transfer(fillAmount);
|
|
return fillAmount;
|
|
}
|
|
|
|
function tokenToTokenTransferInput(
|
|
uint256 tokensSold,
|
|
uint256 minTokensBought,
|
|
uint256 minEthBought,
|
|
uint256 deadline,
|
|
address recipient,
|
|
address toTokenAddress
|
|
)
|
|
external
|
|
returns (uint256 tokensBought)
|
|
{
|
|
TestEventsRaiser(msg.sender).raiseTokenToTokenTransferInput(
|
|
tokensSold,
|
|
minTokensBought,
|
|
minEthBought,
|
|
deadline,
|
|
recipient,
|
|
toTokenAddress
|
|
);
|
|
_revertIfReasonExists();
|
|
return address(this).balance;
|
|
}
|
|
|
|
function toTokenAddress()
|
|
external
|
|
view
|
|
returns (address _tokenAddress)
|
|
{
|
|
return tokenAddress;
|
|
}
|
|
|
|
function _revertIfReasonExists()
|
|
private
|
|
view
|
|
{
|
|
if (bytes(_nextRevertReason).length != 0) {
|
|
revert(_nextRevertReason);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// @dev UniswapBridge overridden to mock tokens and implement IUniswapExchangeFactory.
|
|
contract TestUniswapBridge is
|
|
IUniswapExchangeFactory,
|
|
TestEventsRaiser,
|
|
UniswapBridge
|
|
{
|
|
TestToken public wethToken;
|
|
// Token address to TestToken instance.
|
|
mapping (address => TestToken) private _testTokens;
|
|
// Token address to TestExchange instance.
|
|
mapping (address => TestExchange) private _testExchanges;
|
|
|
|
constructor() public {
|
|
wethToken = new TestToken();
|
|
_testTokens[address(wethToken)] = wethToken;
|
|
}
|
|
|
|
/// @dev Sets the balance of this contract for an existing token.
|
|
/// The wei attached will be the balance.
|
|
function setTokenBalance(address tokenAddress)
|
|
external
|
|
payable
|
|
{
|
|
TestToken token = _testTokens[tokenAddress];
|
|
token.deposit.value(msg.value)();
|
|
}
|
|
|
|
/// @dev Sets the revert reason for an existing token.
|
|
function setTokenRevertReason(address tokenAddress, string calldata revertReason)
|
|
external
|
|
{
|
|
TestToken token = _testTokens[tokenAddress];
|
|
token.setRevertReason(revertReason);
|
|
}
|
|
|
|
/// @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`
|
|
function getExchange(address tokenAddress)
|
|
external
|
|
view
|
|
returns (IUniswapExchange)
|
|
{
|
|
return IUniswapExchange(_testExchanges[tokenAddress]);
|
|
}
|
|
|
|
// @dev Use `wethToken`.
|
|
function getWethContract()
|
|
public
|
|
view
|
|
returns (IEtherToken)
|
|
{
|
|
return IEtherToken(address(wethToken));
|
|
}
|
|
|
|
// @dev This contract will double as the Uniswap contract.
|
|
function getUniswapExchangeFactoryContract()
|
|
public
|
|
view
|
|
returns (IUniswapExchangeFactory)
|
|
{
|
|
return IUniswapExchangeFactory(address(this));
|
|
}
|
|
}
|