Skip to content

Commit

Permalink
add test
Browse files Browse the repository at this point in the history
  • Loading branch information
lukema95 committed Mar 25, 2024
1 parent f0c4f8d commit effd287
Show file tree
Hide file tree
Showing 12 changed files with 853 additions and 1 deletion.
88 changes: 88 additions & 0 deletions omnichain/swap/contracts/shared/MockSystemContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@zetachain/protocol-contracts/contracts/zevm/interfaces/zContract.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IZRC20.sol";

interface SystemContractErrors {
error CallerIsNotFungibleModule();

error InvalidTarget();

error CantBeIdenticalAddresses();

error CantBeZeroAddress();
}

contract MockSystemContract is SystemContractErrors {
error TransferFailed();

mapping(uint256 => uint256) public gasPriceByChainId;
mapping(uint256 => address) public gasCoinZRC20ByChainId;
mapping(uint256 => address) public gasZetaPoolByChainId;

address public wZetaContractAddress;
address public immutable uniswapv2FactoryAddress;
address public immutable uniswapv2Router02Address;

event SystemContractDeployed();
event SetGasPrice(uint256, uint256);
event SetGasCoin(uint256, address);
event SetGasZetaPool(uint256, address);
event SetWZeta(address);

constructor(address wzeta_, address uniswapv2Factory_, address uniswapv2Router02_) {
wZetaContractAddress = wzeta_;
uniswapv2FactoryAddress = uniswapv2Factory_;
uniswapv2Router02Address = uniswapv2Router02_;
emit SystemContractDeployed();
}

// fungible module updates the gas price oracle periodically
function setGasPrice(uint256 chainID, uint256 price) external {
gasPriceByChainId[chainID] = price;
emit SetGasPrice(chainID, price);
}

function setGasCoinZRC20(uint256 chainID, address zrc20) external {
gasCoinZRC20ByChainId[chainID] = zrc20;
emit SetGasCoin(chainID, zrc20);
}

function setWZETAContractAddress(address addr) external {
wZetaContractAddress = addr;
emit SetWZeta(wZetaContractAddress);
}

// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
if (tokenA == tokenB) revert CantBeIdenticalAddresses();
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
if (token0 == address(0)) revert CantBeZeroAddress();
}

function uniswapv2PairFor(address factory, address tokenA, address tokenB) public pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(abi.encodePacked(token0, token1)),
hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash
)
)
)
)
);
}

function onCrossChainCall(uint256 chainID, address target, address zrc20, uint256 amount, bytes calldata message) external {
zContext memory context = zContext({sender: msg.sender, origin: "", chainID: chainID});
bool transfer = IZRC20(zrc20).transfer(target, amount);
if (!transfer) revert TransferFailed();
zContract(target).onCrossChainCall(context, zrc20, amount, message);
}
}
53 changes: 53 additions & 0 deletions omnichain/swap/contracts/shared/MockZRC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.7;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@zetachain/toolkit/contracts/BytesHelperLib.sol";

contract MockZRC20 is ERC20 {
address public gasFeeAddress;
uint256 public gasFee;

event Withdrawal(address indexed from, bytes to, uint256 value, uint256 gasfee, uint256 protocolFlatFee);

constructor(uint256 initialSupply, string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, initialSupply * (10 ** uint256(decimals())));
gasFeeAddress = address(this);
}

function setGasFeeAddress(address gasFeeAddress_) external {
gasFeeAddress = gasFeeAddress_;
}

function setGasFee(uint256 gasFee_) external {
gasFee = gasFee_;
}

function deposit(address to, uint256 amount) external returns (bool) {
return true;
}

function bytesToAddress(bytes calldata data, uint256 offset, uint256 size) public pure returns (address output) {
bytes memory b = data[offset:offset + size];
assembly {
output := mload(add(b, size))
}
}

function withdraw(bytes calldata to, uint256 amount) external returns (bool) {
address toAddress;
if (to.length < 32) {
toAddress = BytesHelperLib.bytesToAddress(to, 0);
} else {
toAddress = BytesHelperLib.bytesToAddress(to, 12);
}

emit Withdrawal(msg.sender, to, amount, gasFee, 0);
return transfer(toAddress, amount);
}

function withdrawGasFee() external view returns (address, uint256) {
return (gasFeeAddress, gasFee);
}
}
10 changes: 10 additions & 0 deletions omnichain/swap/contracts/shared/TestUniswapCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

// SPDX-License-Identifier: MIT
pragma solidity 0.5.16;

/**
* @dev Contracts that need to be compiled for testing purposes
*/

import "@uniswap/v2-core/contracts/UniswapV2Factory.sol";
import "@uniswap/v2-core/contracts/UniswapV2Pair.sol";
222 changes: 222 additions & 0 deletions omnichain/swap/contracts/shared/TestUniswapRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
pragma solidity =0.6.6;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';

contract TestUniswapRouter {
using SafeMath for uint;

address public immutable factory;
address public immutable WETH;

modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}

constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}

receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}

// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
address pair = IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}

(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);

}

// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}

// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}

// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}

function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}

function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}

function getAmountsOut(uint amountIn, address[] memory path)
public
view
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}

function getAmountsIn(uint amountOut, address[] memory path)
public
view
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
Loading

0 comments on commit effd287

Please sign in to comment.