dexorder
This commit is contained in:
150
test/MirrorEnv.sol
Normal file
150
test/MirrorEnv.sol
Normal file
@@ -0,0 +1,150 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
//import "@forge-std/console2.sol";
|
||||
import "../src/more/MockERC20.sol";
|
||||
import "../src/core/Util.sol";
|
||||
import {IUniswapV3Pool} from "../lib_uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import {INonfungiblePositionManager} from "../lib_uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
|
||||
import {ISwapRouter} from "../lib_uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
|
||||
import {IERC20Metadata} from "../lib_uniswap/v3-periphery/contracts/interfaces/IERC20Metadata.sol";
|
||||
import "./MockUtil.sol";
|
||||
|
||||
|
||||
contract MirrorEnv {
|
||||
|
||||
struct MockPool {
|
||||
IUniswapV3Pool pool;
|
||||
bool inverted; // true iff the mock pool's token0/1 are flipped relative to the original pool
|
||||
}
|
||||
|
||||
// map original token addresses to their mock counterparts
|
||||
mapping(IERC20Metadata=>MockERC20) public tokens;
|
||||
// map original pool addresses to their mock counterparts
|
||||
mapping(IUniswapV3Pool=>MockPool) public pools;
|
||||
|
||||
INonfungiblePositionManager immutable public nfpm;
|
||||
ISwapRouter immutable public swapRouter;
|
||||
|
||||
constructor (INonfungiblePositionManager nfpm_, ISwapRouter swapRouter_) {
|
||||
nfpm = nfpm_;
|
||||
swapRouter = swapRouter_;
|
||||
}
|
||||
|
||||
struct TokenInfo {
|
||||
IERC20Metadata addr;
|
||||
string name;
|
||||
string symbol;
|
||||
uint8 decimals;
|
||||
}
|
||||
|
||||
function mirrorToken( TokenInfo memory info ) public returns (MockERC20 mock) {
|
||||
// console2.log('MirrorEnv.mirrorToken()');
|
||||
// console2.log(address(info.addr));
|
||||
mock = tokens[info.addr];
|
||||
if ( address(mock) == address(0) ) {
|
||||
// console2.log('creating mock token');
|
||||
// console2.log(info.name);
|
||||
// console2.log(info.symbol);
|
||||
// console2.log(info.decimals);
|
||||
mock = new MockERC20(info.name, info.symbol, info.decimals);
|
||||
// console2.log('setting tokens[]');
|
||||
tokens[info.addr] = mock;
|
||||
// console2.log('set tokens[]');
|
||||
}
|
||||
// console2.log(address(mock));
|
||||
// console2.log('mirrorToken complete');
|
||||
}
|
||||
|
||||
|
||||
struct PoolInfo {
|
||||
IUniswapV3Pool pool;
|
||||
IERC20Metadata token0;
|
||||
IERC20Metadata token1;
|
||||
uint24 fee;
|
||||
uint160 sqrtPriceX96;
|
||||
uint256 amount0;
|
||||
uint256 amount1;
|
||||
}
|
||||
|
||||
// given the original pool address, create a similar pool using mock tokens
|
||||
function mirrorPool( PoolInfo memory info ) public returns (MockPool memory mock) {
|
||||
// console2.log('MirrorEnv.mirrorPool()');
|
||||
// console2.log(address(info.pool));
|
||||
mock = pools[info.pool];
|
||||
// console2.log(address(mock.pool));
|
||||
if ( address(mock.pool) == address(0) ) {
|
||||
// console2.log('creating mirror pool');
|
||||
MockERC20 token0 = tokens[info.token0];
|
||||
MockERC20 token1 = tokens[info.token1];
|
||||
// console2.log(address(info.token0));
|
||||
// console2.log(address(token0));
|
||||
// console2.log(address(info.token1));
|
||||
// console2.log(address(token1));
|
||||
require(address(token0)!=address(0), 'token0 not mirrored');
|
||||
require(address(token1)!=address(0), 'token1 not mirrored');
|
||||
// put 100th of the total liquidity on each of the 1774545 ticks
|
||||
uint256 amount0 = info.amount0 * 1774545 / 100;
|
||||
uint256 amount1 = info.amount1 * 1774545 / 100;
|
||||
uint160 initialPrice = info.sqrtPriceX96;
|
||||
bool inverted = token0 > token1;
|
||||
// console2.log('got tokens. inverted?');
|
||||
if( inverted ) {
|
||||
(token0, token1) = (token1, token0);
|
||||
(amount0, amount1) = (amount1, amount0);
|
||||
initialPrice = uint160(2**96 * 2**96 / uint256(initialPrice));
|
||||
}
|
||||
// console2.log(inverted);
|
||||
// console2.log(address(token0));
|
||||
// console2.log(address(token1));
|
||||
// console2.log(info.fee);
|
||||
// console2.log(initialPrice);
|
||||
IUniswapV3Pool mockPool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(
|
||||
address(token0), address(token1), info.fee, initialPrice));
|
||||
mock = MockPool(mockPool, inverted);
|
||||
// console2.log('mirror pool / inverted');
|
||||
// console2.log(address(mockPool));
|
||||
// console2.log(inverted);
|
||||
pools[info.pool] = mock;
|
||||
// console2.log('staking');
|
||||
MockUtil.stakeWide( nfpm, mockPool, amount0, amount1);
|
||||
// console2.log('staked');
|
||||
}
|
||||
// console2.log('mirrored pool');
|
||||
}
|
||||
|
||||
function mirrorPools( PoolInfo[] memory pool ) public returns (MockPool[] memory mock) {
|
||||
mock = new MockPool[](pool.length);
|
||||
for( uint i=0; i<pool.length; i++ )
|
||||
mock[i] = mirrorPool(pool[i]);
|
||||
}
|
||||
|
||||
// change the price of a mock pool based on the original pool price
|
||||
function updatePool( IUniswapV3Pool pool, uint160 sqrtPriceX96 ) public returns (MockPool memory mock) {
|
||||
// console2.log('updating');
|
||||
// console2.log(address(pool));
|
||||
mock = pools[pool];
|
||||
require( address(mock.pool) != address(0), 'not mirrored' );
|
||||
if (mock.inverted) {
|
||||
// console2.log('inverting');
|
||||
// console2.log(sqrtPriceX96);
|
||||
sqrtPriceX96 = uint160(uint256(2**96 * 2**96) / uint256(sqrtPriceX96));
|
||||
}
|
||||
MockUtil.swapToPrice(swapRouter, mock.pool, sqrtPriceX96);
|
||||
// console2.log('updated pool');
|
||||
}
|
||||
|
||||
struct PoolUpdateInfo {
|
||||
IUniswapV3Pool pool;
|
||||
uint160 sqrtPriceX96;
|
||||
}
|
||||
|
||||
function updatePools( PoolUpdateInfo[] memory infos ) public returns (MockPool[] memory mock) {
|
||||
mock = new MockPool[](infos.length);
|
||||
for( uint i=0; i<infos.length; i++ ) {
|
||||
PoolUpdateInfo memory info = infos[i];
|
||||
mock[i] = updatePool(info.pool, info.sqrtPriceX96);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
143
test/MockEnv.sol
Normal file
143
test/MockEnv.sol
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "../src/more/MockERC20.sol";
|
||||
import "../src/core/Util.sol";
|
||||
import "./MockUtil.sol";
|
||||
import "../lib_uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "../lib_uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
|
||||
import "../lib_uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
|
||||
import "../src/more/FeeManagerLib.sol";
|
||||
import "../src/core/VaultImpl.sol";
|
||||
import "../src/core/VaultFactory.sol";
|
||||
import {ArbitrumRouter} from "../src/core/Router.sol";
|
||||
|
||||
|
||||
contract MockEnv {
|
||||
|
||||
IVaultFactory public factory;
|
||||
|
||||
INonfungiblePositionManager private nfpm =
|
||||
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); // Arbitrum
|
||||
|
||||
IUniswapV3Pool public pool;
|
||||
uint24 public fee;
|
||||
MockERC20 public COIN;
|
||||
MockERC20 public USD;
|
||||
address public token0; // either COIN or USD depending on the order in the pool
|
||||
address public token1;
|
||||
bool public inverted;
|
||||
|
||||
// sets up two mock coins COIN and USD, plus a uniswap v3 pool.
|
||||
function init() public {
|
||||
initNoFees();
|
||||
}
|
||||
|
||||
function initDebugFees() public {
|
||||
return init(new ArbitrumRouter(), FeeManagerLib.debugFeeManager());
|
||||
}
|
||||
|
||||
function initNoFees() public {
|
||||
return init(new ArbitrumRouter(), FeeManagerLib.freeFeeManager());
|
||||
}
|
||||
|
||||
function init(IRouter router, FeeManager feeManager) public {
|
||||
// console2.log('init MockEnv...');
|
||||
VaultImpl impl = new VaultImpl(router, feeManager, address(0));
|
||||
factory = new VaultFactory(msg.sender, address(impl), 2*60); // 2 minutes upgrade notice
|
||||
|
||||
console2.log('MockEnv: msg.sender:', msg.sender);
|
||||
// console2.log('MockEnv: tx.origin:' , tx.origin);
|
||||
COIN = new MockERC20('Mock Ethereum Hardfork', 'MEH', 18);
|
||||
console2.log('MEH');
|
||||
console2.log(address(COIN));
|
||||
USD = new MockERC20('Joke Currency XD', 'USXD', 6);
|
||||
console2.log('USXD');
|
||||
console2.log(address(USD));
|
||||
fee = 500;
|
||||
inverted = address(COIN) > address(USD);
|
||||
token0 = inverted ? address(USD) : address(COIN);
|
||||
token1 = inverted ? address(COIN) : address(USD);
|
||||
console2.log('if this is the last line before a revert then make sure to run forge with --rpc-url');
|
||||
// if this reverts here make sure Anvil is started and you are running forge with --rpc-url
|
||||
pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, oneSqrtX96()));
|
||||
console2.log('v3 pool');
|
||||
console2.log(address(pool));
|
||||
// stake a super wide range so we have liquidity everywhere.
|
||||
uint256 amount = 10_000*1774545 * 10**12; // 1774545 is the number of ticks so this is $10k liquidity per 0.1%
|
||||
stake(amount, amount, TickMath.MIN_TICK, TickMath.MAX_TICK);
|
||||
}
|
||||
|
||||
|
||||
function oneSqrtX96() public view returns (uint160) {
|
||||
return inverted ? uint160(79228162514264337593543950336000000) : uint160(79228162514264337593543); // $1.00 * 2^96 * 10^±12
|
||||
}
|
||||
|
||||
|
||||
function swapTo1() public {
|
||||
swapToPrice(oneSqrtX96());
|
||||
}
|
||||
|
||||
|
||||
function stake(uint256 amount, int24 width) public {
|
||||
require(width>0);
|
||||
(, int24 tick, , , , ,) = pool.slot0();
|
||||
stake(amount, tick-width, tick+width);
|
||||
}
|
||||
|
||||
|
||||
function stake(uint256 amount, int24 lower, int24 upper) public {
|
||||
uint256 coinAmount = amount * 10**18 / 2;
|
||||
uint256 usdAmount = amount * 10**6 / 2;
|
||||
stake(coinAmount, usdAmount, lower, upper);
|
||||
}
|
||||
|
||||
|
||||
function stake(uint256 coinAmount, uint256 usdAmount, int24 lower, int24 upper) public
|
||||
returns (
|
||||
uint256 tokenId,
|
||||
uint128 liquidity,
|
||||
uint256 amount0,
|
||||
uint256 amount1
|
||||
)
|
||||
{
|
||||
return _stake(coinAmount, usdAmount, lower, upper);
|
||||
}
|
||||
|
||||
function _stake(uint256 coinAmount, uint256 usdAmount, int24 lower, int24 upper) private
|
||||
returns (
|
||||
uint256 tokenId,
|
||||
uint128 liquidity,
|
||||
uint256 amount0,
|
||||
uint256 amount1
|
||||
)
|
||||
{
|
||||
|
||||
uint256 a0 = inverted ? usdAmount : coinAmount;
|
||||
uint256 a1 = inverted ? coinAmount : usdAmount;
|
||||
if (inverted) {
|
||||
lower = -upper;
|
||||
upper = -lower;
|
||||
}
|
||||
|
||||
(tokenId, liquidity, amount0, amount1) = MockUtil.stake(pool, a0, a1, lower, upper);
|
||||
}
|
||||
|
||||
function swap(MockERC20 inToken, MockERC20 outToken, uint256 amountIn) public returns (uint256 amountOut) {
|
||||
uint160 limit = address(inToken) == pool.token0() ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
||||
return swap(inToken, outToken, amountIn, limit);
|
||||
}
|
||||
|
||||
function swap(MockERC20 inToken, MockERC20 outToken, uint256 amountIn, uint160 sqrtPriceLimitX96) public returns (uint256 amountOut) {
|
||||
return MockUtil.swap(pool, inToken, outToken, amountIn, sqrtPriceLimitX96);
|
||||
}
|
||||
|
||||
function price() public view returns (uint160 sqrtPriceX96) {
|
||||
(sqrtPriceX96,,,,,,) = pool.slot0();
|
||||
}
|
||||
|
||||
function swapToPrice(uint160 sqrtPriceLimitX96) public {
|
||||
MockUtil.swapToPrice(pool, sqrtPriceLimitX96);
|
||||
}
|
||||
}
|
||||
174
test/MockUtil.sol
Normal file
174
test/MockUtil.sol
Normal file
@@ -0,0 +1,174 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
//import "@forge-std/console2.sol";
|
||||
import "../lib_uniswap/v3-core/contracts/libraries/TickMath.sol";
|
||||
import {IUniswapV3Pool} from "../lib_uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import {MockERC20} from "../src/more/MockERC20.sol";
|
||||
import "../lib_uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
|
||||
import "../lib_uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
|
||||
import "../src/core/Util.sol";
|
||||
import "../src/core/UniswapV3.sol";
|
||||
|
||||
|
||||
library MockUtil {
|
||||
// 200 is the largest tick spacing. We move our edges inward to prevent violating the extremum when rounding
|
||||
int24 constant public FAR_LOWER_TICK = TickMath.MIN_TICK + 200;
|
||||
int24 constant public FAR_UPPER_TICK = TickMath.MAX_TICK - 200;
|
||||
|
||||
function swap(IUniswapV3Pool pool,
|
||||
MockERC20 inToken, MockERC20 outToken, uint256 amountIn) internal
|
||||
returns (uint256 amountOut) {
|
||||
return swap(UniswapV3Arbitrum.swapRouter, pool, inToken, outToken, amountIn);
|
||||
}
|
||||
|
||||
|
||||
function swap(ISwapRouter swapper, IUniswapV3Pool pool,
|
||||
MockERC20 inToken, MockERC20 outToken, uint256 amountIn) internal
|
||||
returns (uint256 amountOut) {
|
||||
uint160 limit = address(inToken) == pool.token0() ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
||||
return swap(swapper, pool, inToken, outToken, amountIn, limit);
|
||||
}
|
||||
|
||||
function swap(IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken,
|
||||
uint256 amountIn, uint160 sqrtPriceLimitX96) internal
|
||||
returns (uint256 amountOut) {
|
||||
return swap(UniswapV3Arbitrum.swapRouter, pool, inToken, outToken, amountIn, sqrtPriceLimitX96);
|
||||
}
|
||||
|
||||
|
||||
function swap(ISwapRouter swapper, IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken,
|
||||
uint256 amountIn, uint160 sqrtPriceLimitX96) internal
|
||||
returns (uint256 amountOut) {
|
||||
inToken.approve(address(swapper), amountIn);
|
||||
// struct ExactInputSingleParams {
|
||||
// address tokenIn;
|
||||
// address tokenOut;
|
||||
// uint24 fee;
|
||||
// address recipient;
|
||||
// uint256 deadline;
|
||||
// uint256 amountIn;
|
||||
// uint256 amountOutMinimum;
|
||||
// uint160 sqrtPriceLimitX96;
|
||||
// }
|
||||
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
|
||||
address(inToken), address(outToken), pool.fee(), msg.sender, block.timestamp, amountIn, 0, sqrtPriceLimitX96
|
||||
);
|
||||
return swapper.exactInputSingle(params);
|
||||
}
|
||||
|
||||
function price(IUniswapV3Pool pool) internal view returns (uint160 sqrtPriceX96) {
|
||||
(sqrtPriceX96,,,,,,) = pool.slot0();
|
||||
}
|
||||
|
||||
|
||||
function swapToPrice(IUniswapV3Pool pool, uint160 sqrtPriceLimitX96) internal {
|
||||
return swapToPrice(UniswapV3Arbitrum.swapRouter, pool, sqrtPriceLimitX96);
|
||||
}
|
||||
|
||||
|
||||
function swapToPrice(ISwapRouter swapper, IUniswapV3Pool pool, uint160 sqrtPriceLimitX96) internal {
|
||||
// console2.log('swapToPrice');
|
||||
// console2.log(sqrtPriceLimitX96);
|
||||
uint160 curPrice = price(pool);
|
||||
// console2.log(curPrice);
|
||||
if( curPrice == sqrtPriceLimitX96 ) {
|
||||
// console2.log('no swap needed');
|
||||
return;
|
||||
}
|
||||
MockERC20 token0 = MockERC20(pool.token0());
|
||||
MockERC20 token1 = MockERC20(pool.token1());
|
||||
MockERC20 inToken = curPrice > sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1);
|
||||
MockERC20 outToken = curPrice < sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1);
|
||||
// instead of calculating how much we need, we just mint an absurd amount
|
||||
uint256 aLot = 2**100;
|
||||
inToken.mint(address(this), aLot);
|
||||
swap(swapper, pool, inToken, outToken, aLot, sqrtPriceLimitX96);
|
||||
}
|
||||
|
||||
|
||||
function stakeWide(IUniswapV3Pool pool, uint256 amount) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
|
||||
return stake(UniswapV3Arbitrum.nfpm, pool, amount/2, amount/2, FAR_LOWER_TICK, FAR_UPPER_TICK);
|
||||
}
|
||||
|
||||
function stakeWide(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 amount0, uint256 amount1) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 stakedAmount0, uint256 stakedAmount1) {
|
||||
return stake(nfpm, pool, amount0, amount1, FAR_LOWER_TICK, FAR_UPPER_TICK);
|
||||
}
|
||||
|
||||
function stake(IUniswapV3Pool pool, uint256 amount, int24 width) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
|
||||
return stake(UniswapV3Arbitrum.nfpm, pool, amount, width);
|
||||
}
|
||||
|
||||
|
||||
function stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 amount, int24 width) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
|
||||
require(width>0);
|
||||
(, int24 tick, , , , ,) = pool.slot0();
|
||||
return stake(nfpm, pool, amount/2, amount/2, tick-width, tick+width);
|
||||
}
|
||||
|
||||
|
||||
function stake(IUniswapV3Pool pool, uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
|
||||
return stake(UniswapV3Arbitrum.nfpm, pool, token0Amount, token1Amount, lower, upper);
|
||||
}
|
||||
|
||||
|
||||
function stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) internal
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)
|
||||
{
|
||||
return _stake(nfpm, pool, token0Amount, token1Amount, lower, upper);
|
||||
}
|
||||
|
||||
function _stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool,
|
||||
uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) private
|
||||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)
|
||||
{
|
||||
// console2.log('stake amounts');
|
||||
// console2.log(token0Amount);
|
||||
// console2.log(token1Amount);
|
||||
MockERC20 token0 = MockERC20(pool.token0());
|
||||
MockERC20 token1 = MockERC20(pool.token1());
|
||||
token0.mint(address(this), token0Amount);
|
||||
token0.approve(address(nfpm), token0Amount);
|
||||
// console2.log('token0 minted');
|
||||
token1.mint(address(this), token1Amount);
|
||||
token1.approve(address(nfpm), token1Amount);
|
||||
// console2.log('token1 minted');
|
||||
// struct MintParams {
|
||||
// address token0;
|
||||
// address token1;
|
||||
// uint24 fee;
|
||||
// int24 tickLower;
|
||||
// int24 tickUpper;
|
||||
// uint256 amount0Desired;
|
||||
// uint256 amount1Desired;
|
||||
// uint256 amount0Min;
|
||||
// uint256 amount1Min;
|
||||
// address recipient;
|
||||
// uint256 deadline;
|
||||
// }
|
||||
int24 ts = pool.tickSpacing();
|
||||
lower = Util.roundTick(lower, ts);
|
||||
upper = Util.roundTick(upper, ts);
|
||||
// console2.log('lower / upper');
|
||||
// console2.log(lower);
|
||||
// console2.log(upper);
|
||||
address recipient = msg.sender;
|
||||
if (recipient == address(0) ) // anvil will set msg.sender=0x0 this if there is no specific account and this breaks the NFT mint, so we assign the position to ourselves instead
|
||||
recipient = address(this);
|
||||
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams(
|
||||
address(token0), address(token1), pool.fee(), lower, upper,
|
||||
token0Amount, token1Amount, 0, 0, recipient, block.timestamp
|
||||
);
|
||||
(tokenId, liquidity, amount0, amount1) = nfpm.mint(params);
|
||||
// console2.log('minted liquidity');
|
||||
// console2.log(liquidity);
|
||||
// console2.log(amount0);
|
||||
// console2.log(amount1);
|
||||
}
|
||||
|
||||
}
|
||||
85
test/TestCancelOrder.sol
Normal file
85
test/TestCancelOrder.sol
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/Test.sol";
|
||||
import "@forge-std/console2.sol";
|
||||
import {MockEnv} from "./MockEnv.sol";
|
||||
import {VaultFactory} from "../src/core/VaultFactory.sol";
|
||||
import {Dexorder} from "../src/more/Dexorder.sol";
|
||||
import {IVault} from "../src/interface/IVault.sol";
|
||||
import "../src/core/OrderSpec.sol";
|
||||
|
||||
contract TestCancelOrder is MockEnv, Test {
|
||||
|
||||
IVault public vault;
|
||||
|
||||
// vault gets 100,000 COIN and 100,000 USD
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
vault = IVault(factory.deployVault(address(this)));
|
||||
vm.deal(payable(address(vault)), 1 ether); // native for fees
|
||||
uint256 coinAmount = 100_000 * 10 ** COIN.decimals();
|
||||
COIN.mint(address(vault), coinAmount);
|
||||
uint256 usdAmount = 100_000 * 10 ** USD.decimals();
|
||||
USD.mint(address(vault), usdAmount);
|
||||
}
|
||||
|
||||
function placeOrder() public {
|
||||
Tranche[] memory tranches = new Tranche[](3);
|
||||
tranches[0].fraction = 21845;
|
||||
tranches[0].startTimeIsRelative = true;
|
||||
tranches[0].startTime = 0;
|
||||
tranches[1].fraction = 21845;
|
||||
tranches[1].startTimeIsRelative = true;
|
||||
tranches[1].startTime = 60;
|
||||
tranches[2].fraction = 21845;
|
||||
tranches[2].startTimeIsRelative = true;
|
||||
tranches[2].startTime = 120;
|
||||
uint256 amount = 100000000000000000000;
|
||||
|
||||
SwapOrder memory order = SwapOrder(
|
||||
0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9, 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1,
|
||||
Route(Exchange.UniswapV3, 500), amount, amount/100, true, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
vault.placeDexorder(order);
|
||||
}
|
||||
|
||||
// Simple test
|
||||
|
||||
function testCancelOrder() public {
|
||||
placeOrder();
|
||||
placeOrder();
|
||||
placeOrder();
|
||||
assert( vault.numSwapOrders() == 3 );
|
||||
|
||||
vault.cancelDexorder(0);
|
||||
assert( vault.orderCanceled(0) );
|
||||
assert( !vault.orderCanceled(1) );
|
||||
assert( !vault.orderCanceled(2) );
|
||||
|
||||
vault.cancelDexorder(2);
|
||||
assert( vault.orderCanceled(0) );
|
||||
assert( !vault.orderCanceled(1) );
|
||||
assert( vault.orderCanceled(2) );
|
||||
|
||||
vault.cancelAllDexorders();
|
||||
assert( vault.orderCanceled(0) );
|
||||
assert( vault.orderCanceled(1) );
|
||||
assert( vault.orderCanceled(2) );
|
||||
|
||||
placeOrder();
|
||||
assert( vault.numSwapOrders() == 4 );
|
||||
assert( vault.orderCanceled(0) );
|
||||
assert( vault.orderCanceled(1) );
|
||||
assert( vault.orderCanceled(2) );
|
||||
assert( !vault.orderCanceled(3) );
|
||||
|
||||
vault.cancelAllDexorders();
|
||||
assert( vault.orderCanceled(0) );
|
||||
assert( vault.orderCanceled(1) );
|
||||
assert( vault.orderCanceled(2) );
|
||||
assert( vault.orderCanceled(3) );
|
||||
}
|
||||
|
||||
}
|
||||
24
test/TestCreateVault.sol
Normal file
24
test/TestCreateVault.sol
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "../src/core/VaultFactory.sol";
|
||||
import "../src/more/VaultAddress.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
import {MockEnv} from "./MockEnv.sol";
|
||||
|
||||
contract TestCreateVault is Test, MockEnv {
|
||||
|
||||
Vault public vault;
|
||||
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
console2.log('factory');
|
||||
console2.log(address(factory));
|
||||
}
|
||||
|
||||
function testCreateVault() public {
|
||||
factory.deployVault(address(this));
|
||||
}
|
||||
|
||||
}
|
||||
167
test/TestFees.sol
Normal file
167
test/TestFees.sol
Normal file
@@ -0,0 +1,167 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
import {console2} from "@forge-std/console2.sol";
|
||||
import {FullMath} from '@uniswap/v3-core/contracts/libraries/FullMath.sol';
|
||||
import {IUniswapV3Pool} from '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
|
||||
import {MockEnv, MockERC20} from "./MockEnv.sol";
|
||||
import {VaultFactory} from "../src/core/VaultFactory.sol";
|
||||
import {Dexorder} from "../src/more/Dexorder.sol";
|
||||
import {IVault} from "../src/interface/IVault.sol";
|
||||
import "../src/core/OrderSpec.sol";
|
||||
import {float} from "../src/core/IEEE754.sol";
|
||||
|
||||
/*
|
||||
contract TestFees is MockEnv, Test {
|
||||
|
||||
IVault public vault;
|
||||
Dexorder public dexorder;
|
||||
|
||||
Dexorder.FeeSched feeSched = Dexorder.FeeSched(100,1,2,3,1);
|
||||
|
||||
function getPriceX96(address poolAddress) public view returns (uint256) {
|
||||
(uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(poolAddress).slot0();
|
||||
return FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, 1<<96);
|
||||
}
|
||||
|
||||
// function logPrice() public view {
|
||||
// uint256 priceX96 = getPriceX96(address(pool));
|
||||
// console2.log("getPrice:", priceX96>>96, priceX96&(1<<96-1), priceX96);
|
||||
// console2.log("fee:", IUniswapV3Pool(address(pool)).fee());
|
||||
// }
|
||||
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
dexorder = new Dexorder();
|
||||
vm.prank(address(0));
|
||||
dexorder.SetFeeSched(feeSched);
|
||||
vault = IVault(factory.deployVault(address(this)));
|
||||
vm.deal(payable(address(vault)), 1 ether);
|
||||
}
|
||||
|
||||
uint64 constant nOrders = 5;
|
||||
uint64 constant nSingles = 2; // nSingles must be <= nOrders
|
||||
uint16 constant nTranches = 3;
|
||||
|
||||
function testFees() public {
|
||||
|
||||
require(nSingles <= nOrders, "testFees: nSingles <= nOrders violated");
|
||||
|
||||
address fillFeeAccount = dexorder.fillFeeAccount.address;
|
||||
address nativeFeeAccount = dexorder.orderFeeAccount.address;
|
||||
assert(dexorder.orderFeeAccount.address == dexorder.trancheFeeAccount.address);
|
||||
|
||||
// Set up Tranches and fund USD
|
||||
|
||||
Tranche[] memory tranches = new Tranche[](nTranches);
|
||||
for (uint i=0; i<nTranches; i++) {
|
||||
tranches[i].fraction = MAX_FRACTION / nTranches;
|
||||
tranches[i].endTime = DISTANT_FUTURE;
|
||||
tranches[i].marketOrder = true;
|
||||
}
|
||||
|
||||
uint256 trancheAmount = 1 * 10**COIN.decimals() / 10; // 0.3 COIN
|
||||
uint256 amount = trancheAmount * nTranches;
|
||||
console2.log("COIN.decimals():", COIN.decimals());
|
||||
console2.log("USD.decimals():", USD.decimals());
|
||||
console2.log("amount * nOrders:", amount * nOrders);
|
||||
COIN.mint(address(vault), amount * nOrders); // create COIN to sell
|
||||
console2.log("Mint: COIN.balanceOf:", COIN.balanceOf(address(vault)));
|
||||
|
||||
// Swap order
|
||||
|
||||
SwapOrder memory order = SwapOrder(
|
||||
address(COIN), address(USD), // sell COIN for USD
|
||||
Route(Exchange.UniswapV3, fee), amount, amount/100, true, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
|
||||
// Place order and verify fees
|
||||
|
||||
uint256 vaultBalance = address(vault).balance;
|
||||
uint256 expectedFee = (feeSched.order<<feeSched.orderExp);
|
||||
expectedFee += tranches.length*(feeSched.tranche<<feeSched.trancheExp);
|
||||
expectedFee *= nOrders;
|
||||
|
||||
// try some placeDexorder
|
||||
|
||||
for (uint8 i=0; i<nSingles; i++)
|
||||
vault.placeDexorder(order);
|
||||
|
||||
// try some placeDexorders
|
||||
|
||||
SwapOrder[] memory orders = new SwapOrder[](nOrders-nSingles);
|
||||
for (uint8 i=0; i<nOrders-nSingles; i++)
|
||||
orders[i] = order;
|
||||
vault.placeDexorders(orders, OcoMode.NO_OCO);
|
||||
|
||||
// Check fees
|
||||
|
||||
require(address(vault).balance == vaultBalance - expectedFee, "native order fee wrong");
|
||||
require(nativeFeeAccount.balance == expectedFee, "native order fee wrong");
|
||||
|
||||
// Execute and verify fees
|
||||
|
||||
// logPrice();
|
||||
uint256 expectedCOINbalance = COIN.balanceOf(address(vault));
|
||||
uint256 vaultUSDbalance = USD.balanceOf(address(vault));
|
||||
uint256 expectedUSDbalance_afterPrice = vaultUSDbalance;
|
||||
uint256 expectedUSDbalance_beforePrice = vaultUSDbalance;
|
||||
|
||||
for (uint8 trancheIndex=0; trancheIndex<nTranches; trancheIndex++)
|
||||
for (uint8 orderIndex=0; orderIndex<nOrders; orderIndex++) {
|
||||
|
||||
uint256 beforePrice = getPriceX96(address(pool));
|
||||
vault.execute(orderIndex, trancheIndex, PriceProof(0));
|
||||
uint256 afterPrice = getPriceX96(address(pool));
|
||||
require(afterPrice >= beforePrice, "testFees: beforePrice > afterPrice!");
|
||||
|
||||
expectedCOINbalance -= trancheAmount;
|
||||
require(
|
||||
COIN.balanceOf(address(vault)) == expectedCOINbalance,
|
||||
"testFees: Bad COIN balance"
|
||||
);
|
||||
console2.log("vault balances: COIN, USD:", COIN.balanceOf(address(vault)), USD.balanceOf(address(vault)));
|
||||
// logPrice();
|
||||
|
||||
// expectedUSDbalance using beforePrice
|
||||
|
||||
uint256 expectedTrancheUSD;
|
||||
uint256 dexorderFeeAmount_beforePrice;
|
||||
expectedTrancheUSD = FullMath.mulDiv(trancheAmount, 1<<96, beforePrice);
|
||||
expectedTrancheUSD = FullMath.mulDiv(expectedTrancheUSD, 1000000 - IUniswapV3Pool(address(pool)).fee(), 1000000);
|
||||
dexorderFeeAmount_beforePrice = FullMath.mulDiv(expectedTrancheUSD, feeSched.fillFeeHalfBps, 20000);
|
||||
expectedTrancheUSD -= dexorderFeeAmount_beforePrice;
|
||||
expectedUSDbalance_beforePrice += expectedTrancheUSD;
|
||||
|
||||
// expectedUSDbalance using afterPrice
|
||||
|
||||
uint256 dexorderFeeAmount_afterPrice;
|
||||
expectedTrancheUSD = FullMath.mulDiv(trancheAmount, 1<<96, afterPrice);
|
||||
expectedTrancheUSD = FullMath.mulDiv(expectedTrancheUSD, 1000000 - IUniswapV3Pool(address(pool)).fee(), 1000000);
|
||||
dexorderFeeAmount_afterPrice = FullMath.mulDiv(expectedTrancheUSD, feeSched.fillFeeHalfBps, 20000);
|
||||
expectedTrancheUSD -= dexorderFeeAmount_afterPrice;
|
||||
expectedUSDbalance_afterPrice += expectedTrancheUSD;
|
||||
|
||||
// Check that vaultUSDbalance matches expected
|
||||
|
||||
vaultUSDbalance = USD.balanceOf(address(vault));
|
||||
|
||||
console2.log("expected USD balance:", expectedUSDbalance_beforePrice);
|
||||
console2.log("expected USD balance:", expectedUSDbalance_afterPrice);
|
||||
console2.log("vault USD balance:", vaultUSDbalance);
|
||||
console2.log("fillFee USD balance:", USD.balanceOf(fillFeeAccount));
|
||||
|
||||
require(
|
||||
expectedUSDbalance_beforePrice >= vaultUSDbalance
|
||||
&& vaultUSDbalance >= expectedUSDbalance_afterPrice,
|
||||
"testFees: Bad USD balance"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
22
test/TestFullMath.py
Normal file
22
test/TestFullMath.py
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
Mask256 = 2**256-1
|
||||
MinusOne = -1
|
||||
MAXpos = Mask256 >> 1
|
||||
MAXneg = -MAXpos-1
|
||||
|
||||
print("MAXpos:", hex(MAXpos))
|
||||
print("MAXneg:", hex(MAXneg))
|
||||
|
||||
# https://github.com/Uniswap/v3-core/issues/586
|
||||
|
||||
a = 316922101631557355182318461781248010879680643072; # ~2^157
|
||||
b = 2694519998095207227803175883740; # ~2^101
|
||||
d = 79232019085396855395509160680691688; # ~2^116
|
||||
expected = 10777876804631170754249523106393912452806121; # ~2^143
|
||||
|
||||
r = a * b // d
|
||||
print("Expected issue 586", expected)
|
||||
|
||||
assert r == expected
|
||||
|
||||
pass
|
||||
98
test/TestFullMath.sol
Normal file
98
test/TestFullMath.sol
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/Test.sol";
|
||||
import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
|
||||
|
||||
// FullMath relies on wrapping behavior. However, Solidity 0.8 checks by
|
||||
// default and so FullMath will fail indicating overflow. We have modified
|
||||
// FullMath to be unchecked. These tests verify that it still operated as
|
||||
// intended.
|
||||
|
||||
contract TestFullMath is Test {
|
||||
|
||||
function setUp() public pure {
|
||||
console2.log('FullMath setup()');
|
||||
}
|
||||
|
||||
function testFullMath() public pure {
|
||||
|
||||
console2.log('FullMath testFullMath()');
|
||||
|
||||
// Constants
|
||||
|
||||
uint256 MinusOne = uint256(int256(-1));
|
||||
uint256 MAXneg = 2**255;
|
||||
uint256 MAXpos = ~MAXneg;
|
||||
|
||||
// Check Constants
|
||||
require(MAXpos == MinusOne>>1);
|
||||
require(MAXneg == MAXpos+1);
|
||||
unchecked{
|
||||
require(MinusOne+1 == 0);
|
||||
}
|
||||
|
||||
// Case 1 -- Max negative values
|
||||
|
||||
uint256 q = FullMath.mulDiv(MAXneg, MAXneg, MAXneg); // DUT
|
||||
require(q == MAXneg, "case 1 failed"); // check
|
||||
|
||||
// Case 2 -- All ones (-1) case
|
||||
|
||||
q = FullMath.mulDiv(MinusOne, MinusOne, MinusOne);
|
||||
require(q == MinusOne, "case 2 failed");
|
||||
|
||||
// Case 3 -- All max positive values case
|
||||
|
||||
q = FullMath.mulDiv(MAXpos, MAXpos, MAXpos);
|
||||
require(q == MAXpos, "case 3 failed");
|
||||
|
||||
// Case 4 -- Mixed pos and neg
|
||||
|
||||
q = FullMath.mulDiv(MAXpos, MAXneg, MAXpos);
|
||||
require(q == MAXneg, "case 4a failed");
|
||||
q = FullMath.mulDiv(MAXpos, MAXneg, MAXneg);
|
||||
require(q == MAXpos, "case 4b failed");
|
||||
q = FullMath.mulDiv(MAXpos, MinusOne, MAXpos);
|
||||
require(q == MinusOne, "case 4c failed");
|
||||
q = FullMath.mulDiv(MAXneg, MinusOne, MAXneg);
|
||||
require(q == MinusOne, "case 4d failed");
|
||||
q = FullMath.mulDiv(MAXpos, MinusOne, MinusOne);
|
||||
require(q == MAXpos, "case 4e failed");
|
||||
q = FullMath.mulDiv(MAXneg, MinusOne, MinusOne);
|
||||
require(q == MAXneg, "case 4f failed");
|
||||
|
||||
// Case 5
|
||||
// https://github.com/Uniswap/v3-core/issues/586
|
||||
|
||||
uint256 a;
|
||||
uint256 b;
|
||||
uint256 d;
|
||||
uint256 expected;
|
||||
|
||||
a = 316922101631557355182318461781248010879680643072; // ~2^157
|
||||
b = 2694519998095207227803175883740; // ~2^101
|
||||
d = 79232019085396855395509160680691688; // ~2^116
|
||||
expected = 10777876804631170754249523106393912452806121; // ~2^143
|
||||
require (FullMath.mulDiv(a, b, d) == expected, "Case 5 failed");
|
||||
|
||||
// Case 10 -- various exponents
|
||||
|
||||
uint256 aExp;
|
||||
uint256 bExp;
|
||||
uint256 dExp;
|
||||
|
||||
aExp = 255;
|
||||
bExp = 255;
|
||||
dExp = 255;
|
||||
|
||||
a = 2**aExp;
|
||||
b = 2**bExp;
|
||||
d = 2**dExp;
|
||||
expected = 2**(aExp+bExp-dExp);
|
||||
|
||||
q = FullMath.mulDiv(a,b,d); // DUT
|
||||
require(q == expected, "case 10 failed"); // check
|
||||
|
||||
}
|
||||
}
|
||||
49
test/TestIEEE754.py
Executable file
49
test/TestIEEE754.py
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import struct
|
||||
from fractions import Fraction
|
||||
|
||||
def to_float_binary(value):
|
||||
# Use 'struct.pack' to pack the value into bytes using IEEE 754 floating-point format
|
||||
# '>f' specifies big-endian single-precision float. Change to '>d' for double precision.
|
||||
packed = struct.pack('>f', value)
|
||||
# Convert the bytes to an integer and then to a binary string
|
||||
return ''.join(f'{byte:08b}' for byte in packed)
|
||||
|
||||
def to_float_bits(number) :
|
||||
float_bytes = struct.pack('>f', number)
|
||||
return struct.unpack('>I', float_bytes)[0], float_bytes
|
||||
|
||||
def fixedPoint(n, shift) :
|
||||
if shift >= 0 :
|
||||
return Fraction(n) * (1<<shift)
|
||||
else :
|
||||
return Fraction(n) / (1<<-shift)
|
||||
|
||||
def print_float_fix(number) :
|
||||
float_int32, float_bytes = to_float_bits(number)
|
||||
print (f"float hex: 0x{float_int32:08x} 0x{int(number*(1<<128)):x}")
|
||||
# print ("float hex:", float_bytes.hex(), hex(int(number*(1<<128))))
|
||||
|
||||
print("Assuming 128.128 fixed point")
|
||||
print("largest number:")
|
||||
number = fixedPoint(0xffffff, 127-24) # Largest number that converts to FP and fits in 128.128
|
||||
assert number < 1<<256
|
||||
# print (hex(1<<255))
|
||||
# print (hex(int(number)<<128))
|
||||
print_float_fix(number)
|
||||
print("negative largest number:")
|
||||
number = fixedPoint(-0xffffff, 127-24) # Largest number
|
||||
print_float_fix(number)
|
||||
print("smallest number:")
|
||||
number = fixedPoint(0x1, -128) # Smallest number
|
||||
print_float_fix(number)
|
||||
print("negative smallest number:")
|
||||
number = fixedPoint(-0x1, -128)
|
||||
print_float_fix(number)
|
||||
print()
|
||||
|
||||
# number = fixedPoint(0x7fffff, 127-23)
|
||||
# print_float_fix(number)
|
||||
# number = fixedPoint(0xffffff, 127-23)
|
||||
# print_float_fix(number)
|
||||
84
test/TestIEEE754.sol
Normal file
84
test/TestIEEE754.sol
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/Test.sol";
|
||||
import {IEEE754, float} from "../src/core/IEEE754.sol";
|
||||
|
||||
contract TestIEEE754 is Test {
|
||||
|
||||
function setUp() external pure {
|
||||
console2.log('TestFloat setup()');
|
||||
}
|
||||
|
||||
// Useful constants
|
||||
|
||||
uint256 constant internal umin = 0;
|
||||
uint256 constant internal umax = ~umin; // 0xff...
|
||||
int256 constant internal imax = int256(umax>>1); // 0x7f...
|
||||
int256 constant internal imin = ~imax; // 0x80...
|
||||
|
||||
// must be external because called as this.sanityReverts
|
||||
|
||||
function sanityReverts(int256 t) external pure {
|
||||
console2.log("sanityReverts", t);
|
||||
uint256 u;
|
||||
int256 i;
|
||||
if (t == 1) {
|
||||
u = umax+1; // Overflow
|
||||
} else if (t == 2) {
|
||||
u = umin-1; // Underflow
|
||||
} else if (t == 3) {
|
||||
i = imax+1; // Overflow
|
||||
} else if (t == 4) {
|
||||
i = imin-1; // Underflow
|
||||
} else {
|
||||
// Will not revert
|
||||
}
|
||||
}
|
||||
|
||||
function testSanity() external {
|
||||
console2.log('Float.testSanity()');
|
||||
int256 i;
|
||||
for (i=1; i<=4; i++) {
|
||||
vm.expectRevert();
|
||||
this.sanityReverts(i); // Convoluted way to check reverts
|
||||
}
|
||||
i = imax << 1; // Changes sign bit, but should not over/underflow
|
||||
i = imin << 1; // Changes sign bit, but should not over/underflow
|
||||
}
|
||||
|
||||
struct Item {
|
||||
float floatingPoint;
|
||||
uint8 fixedBits;
|
||||
int256 fixedPoint;
|
||||
}
|
||||
|
||||
function testToFixed() external pure {
|
||||
console2.log('TestIEEE754.testToFixed()');
|
||||
|
||||
Item[11] memory items = [
|
||||
Item(float.wrap(0x3f800000), 0, 1 << 0), // 1.0
|
||||
Item(float.wrap(0x3f800000), 128, 1 << 128), // 1.0
|
||||
Item(float.wrap(0x3f800000), 254, 1 << 254), // 1.0
|
||||
Item(float.wrap(0xbf800000), 128, -1 << 128), // -1.0
|
||||
Item(float.wrap(0x40000000), 128, 2 << 128), // 1.0
|
||||
Item(float.wrap(0xc0000000), 128, -2 << 128), // 1.0
|
||||
Item(float.wrap(0x00200000), 128, int256(uint256(0x1))), // smallest positive is subnormal
|
||||
Item(float.wrap(0x80200000), 128, int256(uint256(int256(-0x1)))), // smallest negative is subnormal
|
||||
Item(float.wrap(0x7effffff), 128, int256(uint256(0x7fffff8000000000000000000000000000000000000000000000000000000000))), // largest positive
|
||||
Item(float.wrap(0xff7fffff), 128, -int256(uint256(0xffffff0000000000000000000000000000000000000000000000000000000000))), // largest negative
|
||||
Item(float.wrap(0x7f7fffff), 120, int256(uint256(0xffffff0000000000000000000000000000000000000000000000000000000000)))
|
||||
];
|
||||
|
||||
for (uint i=0; i<items.length; i++) {
|
||||
console2.log("exp: %x", uint256(items[i].fixedPoint));
|
||||
int256 fixedPoint = IEEE754.toFixed(
|
||||
items[i].floatingPoint, items[i].fixedBits
|
||||
);
|
||||
console2.log("got: %x", uint256(fixedPoint));
|
||||
console2.log(IEEE754.isPositive(items[i].floatingPoint)?' positive':' negative');
|
||||
require(items[i].fixedPoint == fixedPoint);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
56
test/TestMirror.sol
Normal file
56
test/TestMirror.sol
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/Test.sol";
|
||||
import "@forge-std/console2.sol";
|
||||
import {UniswapV3Arbitrum} from "../src/core/UniswapV3.sol";
|
||||
import "./MirrorEnv.sol";
|
||||
|
||||
contract TestMirror is Test {
|
||||
|
||||
MirrorEnv public mirror;
|
||||
MirrorEnv.TokenInfo public tokenInfo0;
|
||||
MirrorEnv.TokenInfo public tokenInfo1;
|
||||
|
||||
function setUp() public virtual {
|
||||
mirror = new MirrorEnv(UniswapV3Arbitrum.nfpm, UniswapV3Arbitrum.swapRouter);
|
||||
tokenInfo0 = MirrorEnv.TokenInfo(IERC20Metadata(address(0x1234)), 'Test', 'TST', 18);
|
||||
tokenInfo1 = MirrorEnv.TokenInfo(IERC20Metadata(address(0x12345)), 'Testy', 'TSTY', 8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
contract TestMirrorToken is TestMirror {
|
||||
function testMirrorToken() public {
|
||||
mirror.mirrorToken(tokenInfo0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TestMirrorPool is TestMirror {
|
||||
|
||||
MockERC20 public mock0;
|
||||
MockERC20 public mock1;
|
||||
|
||||
function setUp() public override {
|
||||
TestMirror.setUp();
|
||||
mock0 = mirror.mirrorToken(tokenInfo0);
|
||||
mock1 = mirror.mirrorToken(tokenInfo1);
|
||||
console2.log('MirrorPool setUp');
|
||||
}
|
||||
|
||||
function testMirrorPool() public {
|
||||
MirrorEnv.PoolInfo memory poolInfo = MirrorEnv.PoolInfo(
|
||||
IUniswapV3Pool(address(0x4321)), // IUniswapV3Pool pool;
|
||||
tokenInfo0.addr, // IERC20Metadata token0;
|
||||
tokenInfo1.addr, // IERC20Metadata token1;
|
||||
3000, // uint24 fee;
|
||||
2**96, // uint160 sqrtPriceX96;
|
||||
1_000_000 * 10 ** tokenInfo0.decimals, // uint256 amount0;
|
||||
1_000_000 * 10 ** tokenInfo1.decimals // uint256 amount1;
|
||||
);
|
||||
mirror.mirrorPool(poolInfo);
|
||||
}
|
||||
}
|
||||
|
||||
152
test/TestOrder.sol
Normal file
152
test/TestOrder.sol
Normal file
@@ -0,0 +1,152 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/Test.sol";
|
||||
import "@forge-std/console2.sol";
|
||||
import {MockEnv, MockERC20} from "./MockEnv.sol";
|
||||
import {VaultFactory} from "../src/core/VaultFactory.sol";
|
||||
import {Dexorder} from "../src/more/Dexorder.sol";
|
||||
import {IVault} from "../src/interface/IVault.sol";
|
||||
import "../src/core/OrderSpec.sol";
|
||||
import {float} from "../src/core/IEEE754.sol";
|
||||
|
||||
contract TestOrder is MockEnv, Test {
|
||||
|
||||
IVault public vault;
|
||||
|
||||
// vault gets 100,000 COIN and 100,000 USD
|
||||
function setUp() public virtual {
|
||||
initNoFees();
|
||||
vault = IVault(factory.deployVault(address(this)));
|
||||
uint256 coinAmount = 100_000 * 10 ** COIN.decimals();
|
||||
COIN.mint(address(vault), coinAmount);
|
||||
uint256 usdAmount = 100_000 * 10 ** USD.decimals();
|
||||
USD.mint(address(vault), usdAmount);
|
||||
}
|
||||
|
||||
|
||||
function testPlaceOrder() public {
|
||||
placeOrder();
|
||||
}
|
||||
|
||||
|
||||
function placeOrder() public {
|
||||
Tranche[] memory tranches = new Tranche[](3);
|
||||
tranches[0].fraction = 21845;
|
||||
tranches[0].startTimeIsRelative = true;
|
||||
tranches[0].startTime = 0;
|
||||
tranches[1].fraction = 21845;
|
||||
tranches[1].startTimeIsRelative = true;
|
||||
tranches[1].startTime = 60;
|
||||
tranches[2].fraction = 21845;
|
||||
tranches[2].startTimeIsRelative = true;
|
||||
tranches[2].startTime = 120;
|
||||
uint256 amount = 100000000000000000000;
|
||||
SwapOrder memory order = SwapOrder(
|
||||
0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9, 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1,
|
||||
Route(Exchange.UniswapV3, 500), amount, amount/100, true, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
console2.logBytes(abi.encode(order));
|
||||
console2.log("testPlaceOrder: calling vault.numSwapOrders");
|
||||
console2.log(vault.numSwapOrders());
|
||||
|
||||
console2.log("testPlaceOrder: Placing order");
|
||||
vault.placeDexorder(order);
|
||||
}
|
||||
}
|
||||
|
||||
contract TestExecute is TestOrder {
|
||||
|
||||
uint256 public coinInitialBalance;
|
||||
uint256 public usdInitialBalance;
|
||||
uint64 public exactOutputOrderIndex;
|
||||
uint64 public exactInputOrderIndex;
|
||||
uint64 public limitOrderIndex;
|
||||
|
||||
function setUp() public override {
|
||||
TestOrder.setUp();
|
||||
|
||||
coinInitialBalance = 1_000_000 * 10**COIN.decimals();
|
||||
usdInitialBalance = 1_000_000 * 10**USD.decimals();
|
||||
COIN.mint(address(vault), coinInitialBalance);
|
||||
USD.mint(address(vault), usdInitialBalance);
|
||||
|
||||
// #0: Exact Output Order
|
||||
Tranche[] memory tranches = new Tranche[](1);
|
||||
tranches[0].fraction = MAX_FRACTION;
|
||||
tranches[0].endTime = DISTANT_FUTURE;
|
||||
tranches[0].marketOrder = true;
|
||||
uint256 amount = 3 * 10**USD.decimals() / 10; // 0.3 USD
|
||||
SwapOrder memory order = SwapOrder(
|
||||
address(COIN), address(USD), // sell COIN for USD
|
||||
Route(Exchange.UniswapV3, 500), amount, amount/100, false, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
exactOutputOrderIndex = vault.numSwapOrders();
|
||||
vault.placeDexorder(order);
|
||||
|
||||
// #1: Exact Input Order
|
||||
tranches = new Tranche[](1);
|
||||
tranches[0].fraction = MAX_FRACTION;
|
||||
tranches[0].endTime = DISTANT_FUTURE;
|
||||
tranches[0].marketOrder = true;
|
||||
amount = 3 * 10**COIN.decimals() / 10; // 0.3 COIN
|
||||
order = SwapOrder(
|
||||
address(COIN), address(USD), // sell COIN for USD
|
||||
Route(Exchange.UniswapV3, fee), amount, amount/100, true, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
exactInputOrderIndex = vault.numSwapOrders();
|
||||
vault.placeDexorder(order);
|
||||
|
||||
buildLimitOrder();
|
||||
|
||||
}
|
||||
|
||||
function testExecuteOrderExactOutput() public {
|
||||
vault.execute(exactOutputOrderIndex, 0, PriceProof(0));
|
||||
}
|
||||
|
||||
|
||||
function testExecuteOrderExactInput() public {
|
||||
vault.execute(exactInputOrderIndex, 0, PriceProof(0));
|
||||
}
|
||||
|
||||
|
||||
function buildLimitOrder() private {
|
||||
// #2: Limit Order
|
||||
// test selling token0 above a certain price
|
||||
Tranche[] memory tranches = new Tranche[](1);
|
||||
tranches[0].fraction = MAX_FRACTION;
|
||||
tranches[0].endTime = DISTANT_FUTURE;
|
||||
tranches[0].minLine.intercept = inverted ? float.wrap(0x5368da9b) : float.wrap(0x2b8cc066); // float 1.0001e±12
|
||||
MockERC20 token = MockERC20(token0);
|
||||
uint256 amount = 3*10**token.decimals() / 10; // selling 0.3 token0
|
||||
SwapOrder memory order = SwapOrder(
|
||||
token0, token1, // sell
|
||||
Route(Exchange.UniswapV3, fee), amount, amount/100, true, false,
|
||||
NO_CONDITIONAL_ORDER, tranches
|
||||
);
|
||||
limitOrderIndex = vault.numSwapOrders();
|
||||
vault.placeDexorder(order);
|
||||
}
|
||||
|
||||
function testExecuteLimitOrder() public {
|
||||
swapTo1();
|
||||
vm.expectRevert(bytes('LL'));
|
||||
// should revert with code 'LL' because the initial limit is above the current price
|
||||
vault.execute(limitOrderIndex, 0, PriceProof(0));
|
||||
console2.log('inverted');
|
||||
console2.log(inverted);
|
||||
console2.log('original price');
|
||||
console2.log(price());
|
||||
// better price for token0
|
||||
uint160 newPrice = oneSqrtX96()*10002/10000;
|
||||
swapToPrice(newPrice); // move price to be above our limit
|
||||
console2.log('new price');
|
||||
console2.log(newPrice);
|
||||
vault.execute(limitOrderIndex, 0, PriceProof(0)); // should work now
|
||||
}
|
||||
|
||||
}
|
||||
79
test/TestProxy.sol
Normal file
79
test/TestProxy.sol
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
|
||||
// Inplementaton shared as library
|
||||
|
||||
contract Impl {
|
||||
|
||||
// Begin storage shared with Proxy
|
||||
uint256 inc;
|
||||
// End storage
|
||||
|
||||
function proxy_target(uint256 x) external view returns(uint256) {
|
||||
return(x+inc);
|
||||
}
|
||||
}
|
||||
|
||||
// The proxy for the implementation
|
||||
|
||||
contract Proxy {
|
||||
// Begin storage shared with implementation
|
||||
uint256 inc;
|
||||
// End storage
|
||||
|
||||
Impl immutable impl; // Address of the library or implementation contract
|
||||
|
||||
constructor(Impl _impl, uint256 _inc) {
|
||||
impl = _impl;
|
||||
inc = _inc;
|
||||
}
|
||||
|
||||
fallback() external payable {
|
||||
address _impl = address(impl);
|
||||
assembly {
|
||||
// Copy the data sent to the memory at position `0`
|
||||
calldatacopy(0, 0, calldatasize())
|
||||
// Forward the call to the implementation contract with the provided input
|
||||
let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)
|
||||
// Copy the returned data
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
// Check if the call was successful and return the data or revert
|
||||
switch result
|
||||
case 0 { revert(0, returndatasize()) }
|
||||
default { return(0, returndatasize()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interface for Proxy/Implementation
|
||||
|
||||
interface IProxy {
|
||||
function proxy_target(uint256 x) external returns(uint256);
|
||||
}
|
||||
|
||||
// Test it
|
||||
|
||||
contract TestProxy is Test {
|
||||
|
||||
// Create shared implementation
|
||||
|
||||
Impl _impl = new Impl();
|
||||
|
||||
IProxy _proxy;
|
||||
IProxy _proxy2;
|
||||
|
||||
function setUp() public {
|
||||
// New proxy instance linked to _impl with IProxy interface
|
||||
_proxy = IProxy(address(new Proxy(_impl, 1))); // cannot directly cast Proxy to Iproxy
|
||||
_proxy2 = IProxy(address(new Proxy(_impl, 2))); // cannot directly cast Proxy to Iproxy
|
||||
}
|
||||
|
||||
function testProxy() public {
|
||||
require(_proxy.proxy_target(1) == 2, "fail");
|
||||
require(_proxy2.proxy_target(1) == 3, "fail");
|
||||
}
|
||||
|
||||
}
|
||||
44
test/TestReentrancy.sol
Normal file
44
test/TestReentrancy.sol
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
|
||||
contract ReentrancyContract {
|
||||
bool private locked;
|
||||
|
||||
modifier reentrancyProhibited() {
|
||||
require(!locked, "Reentrancy prohibited");
|
||||
locked = true;
|
||||
_;
|
||||
locked = false;
|
||||
}
|
||||
|
||||
uint256 private foo = 0;
|
||||
uint256 private bar = 0;
|
||||
|
||||
function reentrancyProtected() public reentrancyProhibited {
|
||||
foo++;
|
||||
}
|
||||
|
||||
function reentrancyVulnerable() public {
|
||||
bar++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
contract TestCosts is Test {
|
||||
|
||||
function setUp() public {
|
||||
}
|
||||
|
||||
uint256 constant N = 101;
|
||||
|
||||
function testReentrancy() public {
|
||||
ReentrancyContract reentrancyContract = new ReentrancyContract();
|
||||
for(uint256 i=0; i<N; i++) {
|
||||
reentrancyContract.reentrancyProtected();
|
||||
reentrancyContract.reentrancyVulnerable();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
test/TestReentrancyGuard.sol
Normal file
90
test/TestReentrancyGuard.sol
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
import "@forge-std/console2.sol";
|
||||
import "@forge-std/Script.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
import "../src/core/VaultFactory.sol";
|
||||
import "../src/interface/IVault.sol";
|
||||
import {MockEnv} from "./MockEnv.sol";
|
||||
|
||||
// Evilcoin is reentrant. Call to transfer will perform reentrant call to vault.execute()
|
||||
|
||||
contract EvilCoin is ERC20, Script {
|
||||
constructor(uint256 initialSupply) ERC20("EvilCoin", "ECOIN") {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
function transfer(address recipient, uint256 amount) public override returns (bool) {
|
||||
bool beEvil = true;
|
||||
if (beEvil) {
|
||||
console2.log("Evil: make me some mischief...");
|
||||
IVault vault = IVault(payable(msg.sender));
|
||||
console2.log("Evil: vault", address(vault));
|
||||
uint64 orderIndex;
|
||||
uint8 tranche_index;
|
||||
PriceProof memory priceProof;
|
||||
console2.log("Evil: reentrant call to execute...");
|
||||
vm.expectRevert(ReentrancyGuard.ReentrancyGuardReentrantCall.selector); // revert must match exactly
|
||||
vault.execute(orderIndex, tranche_index, priceProof);
|
||||
console2.log("Evil: mischief detected and inhibited.");
|
||||
}
|
||||
return (super.transfer(recipient, amount));
|
||||
}
|
||||
}
|
||||
|
||||
contract TestReentrancyGuard is Test, MockEnv {
|
||||
|
||||
IVault public vault;
|
||||
address payable owner = payable(address(this)); // this contract owns vault
|
||||
|
||||
receive() external payable {} // this is owner and needs to be able to receive native
|
||||
|
||||
uint256 constant runnerGib = 2**96-1; // Test runner gives Test{} some native to start;
|
||||
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
console2.log("setUp()");
|
||||
console2.log("msg.sender, balance ", msg.sender, payable(msg.sender).balance);
|
||||
console2.log("owner, balance ", owner, owner.balance);
|
||||
assert (owner == address(this));
|
||||
assert (owner.balance == runnerGib);
|
||||
|
||||
console2.log("factory, balance ", address(factory), address(factory).balance);
|
||||
|
||||
vault = factory.deployVault(owner);
|
||||
assert (vault.owner() == owner);
|
||||
console2.log("vault, balance ", address(vault), address(vault).balance);
|
||||
}
|
||||
|
||||
function testReentrancyGuard() public {
|
||||
|
||||
EvilCoin evilCoin = new EvilCoin(0); // Zero tokens to start
|
||||
|
||||
console2.log("testReentrancyGuard()");
|
||||
|
||||
// give vault some tokens
|
||||
address payable vaultAddr = payable(address(vault));
|
||||
assert(evilCoin.balanceOf(vaultAddr) == 0);
|
||||
|
||||
uint256 vaultTokens = 1000;
|
||||
uint256 withdrawTokens = 100;
|
||||
evilCoin.mint(vaultAddr, vaultTokens); // Give vault some tokens
|
||||
|
||||
console2.log("vault, balance ", address(vault), evilCoin.balanceOf(vaultAddr)) ;
|
||||
assert (evilCoin.balanceOf(vaultAddr) == vaultTokens);
|
||||
|
||||
vault.withdraw(evilCoin, withdrawTokens); // This one will trigger reentrancy
|
||||
|
||||
assert(evilCoin.balanceOf(vaultAddr) == vaultTokens - withdrawTokens);
|
||||
assert(evilCoin.balanceOf(owner) == withdrawTokens);
|
||||
console2.log("owner, balance ", owner, evilCoin.balanceOf(owner));
|
||||
|
||||
}
|
||||
}
|
||||
19
test/TestSinglePool.sol
Normal file
19
test/TestSinglePool.sol
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
import "./MockEnv.sol";
|
||||
|
||||
|
||||
contract TestSinglePool is MockEnv, Test {
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
}
|
||||
|
||||
function testSwap() public {
|
||||
COIN.mint(address(this), 1 * 10**18);
|
||||
uint256 usd = swap(COIN, USD, 1 * 10**18);
|
||||
console2.log(usd);
|
||||
}
|
||||
}
|
||||
130
test/TestVault.sol
Normal file
130
test/TestVault.sol
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import "@forge-std/console2.sol";
|
||||
import "../src/core/VaultFactory.sol";
|
||||
import "../src/more/VaultAddress.sol";
|
||||
import "@forge-std/Test.sol";
|
||||
import "../src/interface/IVault.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import {MockEnv} from "./MockEnv.sol";
|
||||
|
||||
contract TestCoin is ERC20 {
|
||||
constructor(uint256 initialSupply) ERC20("TestCoin", "TCOIN") {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
contract TestDeployVault is Test, MockEnv {
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
}
|
||||
|
||||
function testDeployVault() public {
|
||||
factory.deployVault();
|
||||
}
|
||||
}
|
||||
|
||||
contract TestVault is Test, MockEnv {
|
||||
|
||||
IVault public vault;
|
||||
address payable owner = payable(address(this)); // this contract owns vault
|
||||
|
||||
receive() external payable {} // this is owner and needs to be able to receive native
|
||||
|
||||
uint256 constant runnerGib = 2**96-1; // Test runner gives Test{} some native to start;
|
||||
|
||||
function setUp() public {
|
||||
initNoFees();
|
||||
console2.log("setUp()");
|
||||
console2.log("msg.sender, balance ", msg.sender, payable(msg.sender).balance);
|
||||
console2.log("owner, balance ", owner, owner.balance);
|
||||
assert (owner == address(this));
|
||||
assert (owner.balance == runnerGib);
|
||||
|
||||
console2.log("factory, balance ", address(factory), address(factory).balance);
|
||||
|
||||
vault = factory.deployVault(owner);
|
||||
assert (vault.owner() == owner);
|
||||
console2.log("vault, balance ", address(vault), address(vault).balance);
|
||||
}
|
||||
|
||||
function testDeterministicAddress() public view {
|
||||
console2.log(address(vault));
|
||||
address d = VaultAddress.computeAddress(address(factory), owner);
|
||||
console2.log(d);
|
||||
assert(address(vault) == d);
|
||||
}
|
||||
|
||||
function testWithdraw() public {
|
||||
|
||||
console2.log("testWithdraw()");
|
||||
console2.log("msg.sender, balance ", msg.sender, msg.sender.balance); // msg.sender == test runner
|
||||
|
||||
// get vault address and give some eth
|
||||
address payable vaultAddr = payable(address(vault));
|
||||
assert(vaultAddr.balance == 0);
|
||||
uint256 vaultNative = 1 ether;
|
||||
vm.deal(vaultAddr, vaultNative); // Give vault some native
|
||||
console2.log("vault, balance ", address(vault), address(vault).balance);
|
||||
assert (vaultAddr.balance == vaultNative);
|
||||
|
||||
// get address for withdrawTo()
|
||||
uint256 seed = uint256(keccak256(abi.encodePacked("testSeed")));
|
||||
address payable toAddr = payable(vm.addr(seed));
|
||||
|
||||
// Verify native withdrawTo()
|
||||
assert(vaultAddr.balance == vaultNative);
|
||||
assert(toAddr.balance == 0);
|
||||
vault.withdrawTo(toAddr, 100);
|
||||
assert(toAddr.balance == 100);
|
||||
assert(vaultAddr.balance == vaultNative - 100);
|
||||
|
||||
// Verify native withdraw()
|
||||
vault.withdraw(100);
|
||||
assert(vaultAddr.balance == vaultNative - 200);
|
||||
assert(owner.balance == runnerGib + 100);
|
||||
console2.log("owner, balance ", owner, owner.balance);
|
||||
|
||||
}
|
||||
|
||||
function testWithdrawERC20() public {
|
||||
|
||||
TestCoin testCoin = new TestCoin(0); // Zero tokens to start
|
||||
|
||||
console2.log("testWithdrawERC20()");
|
||||
|
||||
// give vault some tokens
|
||||
address payable vaultAddr = payable(address(vault));
|
||||
assert(testCoin.balanceOf(vaultAddr) == 0);
|
||||
|
||||
uint256 vaultTokens = 1000;
|
||||
testCoin.mint(vaultAddr, vaultTokens); // Give vault some tokens
|
||||
|
||||
console2.log("vault, balance ", address(vault), testCoin.balanceOf(vaultAddr)) ;
|
||||
assert (testCoin.balanceOf(vaultAddr) == vaultTokens);
|
||||
|
||||
// get address for withdrawTo()
|
||||
uint256 seed = uint256(keccak256(abi.encodePacked("testSeed")));
|
||||
address payable toAddr = payable(vm.addr(seed));
|
||||
|
||||
// Verify token withdrawTo()
|
||||
assert(testCoin.balanceOf(vaultAddr) == vaultTokens);
|
||||
assert(testCoin.balanceOf(toAddr) == 0);
|
||||
|
||||
vault.withdrawTo(testCoin, toAddr, 100);
|
||||
assert(testCoin.balanceOf(toAddr) == 100);
|
||||
assert(testCoin.balanceOf(vaultAddr) == vaultTokens - 100);
|
||||
|
||||
// Verify token withdraw()
|
||||
vault.withdraw(testCoin, 100);
|
||||
assert(testCoin.balanceOf(vaultAddr) == vaultTokens - 200);
|
||||
assert(testCoin.balanceOf(owner) == 100);
|
||||
console2.log("owner, balance ", owner, testCoin.balanceOf(owner));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user