timed order

This commit is contained in:
Tim Olson
2023-08-22 11:18:05 -04:00
parent 007135d232
commit 75150949dd
16 changed files with 568 additions and 124 deletions

5
docs/design.md Normal file
View File

@@ -0,0 +1,5 @@
# General Design
Creating a separate contract for each user address allows users to deposit coins into their "account" using standard ERC20 sends without extra approvals. Withdrawals require a contract call, but again no approval step. Furthermore, this clarifies the no-custody nature of the setup, since DexOrder never has any claim to ownership of the user's contract. Of course this costs extra gas up front to create the contract for the user, but on L2's it should be minimal. What about ETH? Hmmm... The alternative is to have a single contract which keeps an accounting of everyone's everything. Deposits would require approvals and a contract call. Using separate vaults will be an easier, more secure experience for frequent traders who are more likely to be our users rather than casual, occasional traders.

1
lib/@openzeppelin Symbolic link
View File

@@ -0,0 +1 @@
openzeppelin-contracts

1
lib/@uniswap/v3-core Symbolic link
View File

@@ -0,0 +1 @@
../v3-core/contracts/

1
lib/@uniswap/v3-periphery Symbolic link
View File

@@ -0,0 +1 @@
../v3-periphery/contracts/

22
src/Constants.sol Normal file
View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
library Constants {
uint8 internal constant VERSION = 0;
IUniswapV3Factory internal constant uniswapV3Factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984);
INonfungiblePositionManager internal constant uniswapV3NonfungiblePositionManager
= INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
ISwapRouter internal constant uniswapV3SwapRouter
= ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
// Swap statuses
string internal constant SWAP_OK = ''; // fastest comparison
// other errors may be passed through from Uniswap
}

11
src/Factory.sol Normal file
View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
import "v3-core/contracts/UniswapV3Factory.sol";
import "./VaultDeployer.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
pragma abicoder v2;
contract Factory is VaultDeployer, Ownable {
}

11
src/OrderStatus.sol Normal file
View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
enum OrderStatus {
ACTIVE, // the only status while the order is still executing
CANCELED, // canceled by the owner
FILLED, // full trade amount was filled
EXPIRED // trade ended without completing its fills
}

171
src/TimedOrder.sol Normal file
View File

@@ -0,0 +1,171 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./OrderStatus.sol";
import "./UniswapSwapper.sol";
contract TimedOrder is Ownable {
struct Spec {
address tokenIn;
address tokenOut;
uint24 fee;
uint32 deadline; // uint32 is big enough to hold dates through the year 2105
uint32 leeway; // if a tranche is not traded within this number of seconds of its scheduled time, it is skipped. If 0, then a reasonable value is generated.
uint160 minSqrtPriceX96; // must be in terms of token1/token0 regardless of which token is in/out
uint160 maxSqrtPriceX96;
uint8 numTranches;
uint256 amount; // amount PER TRANCHE
bool amountIsInput;
}
struct Status {
// includes Spec but has additional status fields
OrderStatus status;
uint32 start;
uint8 tranche;
uint8 tranchesExecuted; // may be less than tranche if a tranche was skipped
uint256 filledIn;
uint256 filledOut;
}
event TimedOrderCreated (address owner, uint64 index, Spec spec);
event TimedOrderFilled (address owner, uint64 index, uint256 amountIn, uint256 amountOut);
event TimedOrderCompleted (address owner, uint64 index);
event TimedOrderError (address owner, uint64 index, string reason);
Spec[] public timedOrderSpecs;
Status[] public timedOrderStatuses;
function timedOrder(Spec memory spec) public onlyOwner returns (uint64 index) {
uint32 start = uint32(block.timestamp);
require(spec.deadline >= start);
require(spec.numTranches >= 1);
Status memory status = Status(OrderStatus.ACTIVE, start, 0, 0, 0, 0);
require(timedOrderStatuses.length < type(uint64).max);
index = uint64(timedOrderStatuses.length);
timedOrderStatuses.push(status);
uint32 trancheInterval = (spec.deadline - uint32(block.timestamp)) / spec.numTranches;
spec.leeway = spec.leeway > 0 ? spec.leeway : trancheInterval / 10;
if (spec.leeway < 60) // todo configure per chain?
spec.leeway = 60;
timedOrderSpecs.push(spec);
emit TimedOrderCreated(address(this), index, spec);
}
function cancelTimedOrder(uint64 index) public onlyOwner {
require(index < timedOrderStatuses.length);
Status storage s = timedOrderStatuses[index];
if (s.status == OrderStatus.ACTIVE)
s.status = OrderStatus.CANCELED;
}
function triggerTimedOrder(uint64 index) public returns (bool changed) {
return _triggerTimedOrder(index);
}
function triggerTimedOrders(uint64[] calldata indexes) public returns (bool[] memory changed) {
changed = new bool[](indexes.length);
for (uint256 i = 0; i < indexes.length; i++) {
changed[i] = _triggerTimedOrder(indexes[i]);
}
}
struct _TriggerTimedOrderVars {
uint32 interval;
uint32 triggerTime;
address pool;
uint160 sqrtPriceX96;
uint160 limit;
uint256 amountIn;
uint256 amountOut;
string error;
}
function _triggerTimedOrder(uint64 index) internal returns (bool changed) {
if (!(index < timedOrderStatuses.length)) // ensure valid order index
return false;
Status storage s = timedOrderStatuses[index];
if (!(s.status == OrderStatus.ACTIVE)) // ensure order is active
return false;
Spec storage c = timedOrderSpecs[index];
_TriggerTimedOrderVars memory v;
// compute trigger times. try to find a tranche which starts before this block but hasnt expired yet
v.interval = (c.deadline - s.start) / c.numTranches;
v.triggerTime = s.start + s.tranche * v.interval;
while (s.tranche < c.numTranches) {
if (v.triggerTime > block.timestamp)
return false; // not time yet to trigger
if (block.timestamp <= v.triggerTime + c.leeway)
break; // triggerTime <= block.timestamp <= triggerTime + intervalLeeway
// we have not yet found a tranche which hasn't expired
s.tranche++;
v.triggerTime += v.interval;
}
if (_checkCompleted(index, s, c.numTranches))
return true;
// we have found a valid tranche
// check prices
v.pool = Constants.uniswapV3Factory.getPool(c.tokenIn, c.tokenOut, c.fee);
(v.sqrtPriceX96, , , , , ,) = IUniswapV3Pool(v.pool).slot0();
require(v.sqrtPriceX96 >= c.minSqrtPriceX96);
require(v.sqrtPriceX96 <= c.maxSqrtPriceX96);
// todo swap
v.limit = c.tokenIn < c.tokenOut ? c.minSqrtPriceX96 : c.maxSqrtPriceX96;
if (c.amountIsInput) {
v.amountIn = c.amount;
(v.error, v.amountOut) = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams(
v.pool, c.tokenIn, c.tokenOut, c.fee, c.amount, v.limit));
if(!_checkSwapError(index, v.error))
return false;
}
else {
v.amountOut = c.amount;
(v.error, v.amountIn) = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams(
v.pool, c.tokenIn, c.tokenOut, c.fee, c.amount, v.limit));
if(!_checkSwapError(index, v.error))
return false;
}
s.filledIn += v.amountIn;
s.filledOut += v.amountOut;
s.tranchesExecuted++;
s.tranche++;
emit TimedOrderFilled(address(this), index, v.amountIn, v.amountOut);
_checkCompleted(index, s, c.numTranches);
return true;
}
function _checkCompleted(uint64 index, Status storage s, uint8 numTranches) internal returns (bool completed) {
if (s.tranche >= numTranches) {
// last tranche has finished
s.status = s.tranchesExecuted == numTranches ? OrderStatus.FILLED : OrderStatus.EXPIRED;
emit TimedOrderCompleted(address(this), index);
return true;
}
return false;
}
function _checkSwapError( uint64 index, string memory status ) internal returns (bool ok) {
if( bytes(status).length == 0 )
return true;
emit TimedOrderError(address(this), index, status);
return false;
}
}

79
src/UniswapSwapper.sol Normal file
View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "./Constants.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
library UniswapSwapper {
struct SwapParams {
address pool;
address tokenIn;
address tokenOut;
uint24 fee;
uint256 amount;
uint160 sqrtPriceLimitX96;
}
function swapExactInput(SwapParams memory params) internal returns (string memory error, uint256 amountOut)
{
// struct ExactInputSingleParams {
// address tokenIn;
// address tokenOut;
// uint24 fee;
// address recipient;
// uint256 deadline;
// uint256 amountIn;
// uint256 amountOutMinimum;
// uint160 sqrtPriceLimitX96;
// }
try Constants.uniswapV3SwapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({
tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: address(this), // todo return directly to wallet?
deadline: block.timestamp, amountIn: params.amount, amountOutMinimum: 0, sqrtPriceLimitX96: params.sqrtPriceLimitX96
})) returns (uint256 filledOut) {
amountOut = filledOut;
error = Constants.SWAP_OK;
}
catch Error(string memory reason) {
amountOut = 0;
error = reason;
}
}
function swapExactOutput(SwapParams memory params) internal returns (string memory error, uint256 amountIn)
{
// struct ExactOutputSingleParams {
// address tokenIn;
// address tokenOut;
// uint24 fee;
// address recipient;
// uint256 deadline;
// uint256 amountOut;
// uint256 amountInMaximum;
// uint160 sqrtPriceLimitX96;
// }
address t = address(this);
uint256 balance = IERC20(params.tokenIn).balanceOf(t);
if( balance == 0 ) {
// todo dust?
return ('IIA', 0);
}
try Constants.uniswapV3SwapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({
tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: t, // todo return directly to wallet?
deadline: block.timestamp, amountOut: params.amount, amountInMaximum: balance, // todo use only the committed allocation?
sqrtPriceLimitX96: params.sqrtPriceLimitX96
})) returns (uint256 filledIn) {
amountIn = filledIn;
error = Constants.SWAP_OK;
}
catch Error(string memory reason) {
amountIn = 0;
error = reason;
}
}
}

44
src/Vault.sol Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "./Constants.sol";
import "./interface/IVaultDeployer.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Vault {
uint8 public immutable version;
address public immutable owner;
constructor()
{
(address owner_) = IVaultDeployer(msg.sender).parameters();
version = Constants.VERSION;
owner = owner_;
}
function transfer(address payable recipient, uint256 amount) public {
require(msg.sender == owner);
recipient.transfer(amount);
}
function transfer(uint256 amount) public {
require(msg.sender == owner);
msg.sender.transfer(amount);
}
function withdraw(IERC20 token, uint256 amount) public {
_withdraw(token, msg.sender, amount);
}
function withdraw(IERC20 token, address recipient, uint256 amount) public {
_withdraw(token, recipient, amount);
}
function _withdraw(IERC20 token, address recipient, uint256 amount) internal {
require(msg.sender == owner);
token.transfer(recipient, amount);
}
}

32
src/VaultAddress.sol Normal file
View File

@@ -0,0 +1,32 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "./Constants.sol";
library VaultAddress {
// keccak-256 hash of the Vault's bytecode (not the deployed bytecode but the initialization bytecode)
// can paste into:
// https://emn178.github.io/online-tools/keccak_256.html
bytes32 internal constant VAULT_INIT_CODE_HASH = 0xbf043f7035d5aa3be2b3c94df5b256fbe24675689327af4ab71c48194c463031;
// the contract being constructed must not have any constructor arguments or the determinism will be broken. instead, use a callback to
// get construction arguments
// Uniswap example
// https://github.com/Uniswap/v3-periphery/blob/6cce88e63e176af1ddb6cc56e029110289622317/contracts/libraries/PoolAddress.sol#L33C5-L47C6
function computeAddress(address factory, address owner) internal pure returns (address vault) {
vault = address(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(owner)),
VAULT_INIT_CODE_HASH
)
)
)
);
}
}

20
src/VaultDeployer.sol Normal file
View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
import "./Vault.sol";
pragma abicoder v2;
contract VaultDeployer {
struct Parameters {
address owner;
}
Parameters public parameters;
function deployVault(address owner) public returns (address vault) {
parameters = Parameters(owner);
vault = address(new Vault{salt: keccak256(abi.encode(owner))}());
delete parameters;
}
}

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
interface IVaultDeployer {
function parameters() external view returns (address owner);
}

134
test/MockEnv.sol Normal file
View File

@@ -0,0 +1,134 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
pragma abicoder v2;
import "forge-std/console2.sol";
import "../src/MockERC20.sol";
import "../src/Util.sol";
import "../src/Constants.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
contract MockEnv {
INonfungiblePositionManager public nfpm = Constants.uniswapV3NonfungiblePositionManager;
ISwapRouter public swapper = Constants.uniswapV3SwapRouter;
IUniswapV3Pool public pool;
uint24 public fee;
MockERC20 public COIN;
MockERC20 public USD;
address public token0; // either WETH or USDC depending on the order in the pool
address public token1;
bool public inverted;
function init() public {
COIN = new MockERC20('Mock Coin', 'MOCK', 18);
USD = new MockERC20('Universally Supported Dollars', 'USD', 6);
fee = 500;
inverted = address(COIN) > address(USD);
token0 = inverted ? address(USD) : address(COIN);
token1 = inverted ? address(COIN) : address(USD);
uint160 initialPrice = 1 * 2**96;
// if this reverts here make sure Anvil is started and you are running forge with --rpc-url
pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, initialPrice));
int24 ts = pool.tickSpacing();
(, int24 lower, , , , ,) = pool.slot0();
int24 upper = lower;
for (int8 i = 0; i < 10; i++) {
lower -= ts;
upper += ts;
stake(1 * 10 ** COIN.decimals(), 1000 * 10 ** USD.decimals(), lower, upper);
}
}
function stake(uint128 liquidity_, int24 lower, int24 upper) public
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(lower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(upper);
(uint160 sqrtPriceX96, , , , , ,) = pool.slot0();
(amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, liquidity_);
return _stake(amount0, amount1, 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
)
{
COIN.mint(address(this), coinAmount);
COIN.approve(address(nfpm), coinAmount);
USD.mint(address(this), usdAmount);
USD.approve(address(nfpm), usdAmount);
// 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);
(uint256 a0, uint256 a1) = inverted ? (usdAmount, coinAmount) : (coinAmount, usdAmount);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams(
token0, token1, fee, lower, upper, a0, a1, 0, 0, msg.sender, block.timestamp
);
return nfpm.mint(params);
}
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) {
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), fee, msg.sender, block.timestamp, amountIn, 0, sqrtPriceLimitX96
);
return swapper.exactInputSingle(params);
}
}

View File

@@ -4,137 +4,17 @@ pragma abicoder v2;
import "forge-std/console2.sol";
import "forge-std/Test.sol";
import "../src/MockERC20.sol";
import "../src/Util.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "./MockEnv.sol";
contract TestSinglePool is Test {
INonfungiblePositionManager public nfpm = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
ISwapRouter public swapper = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
IUniswapV3Pool public pool;
uint24 public fee;
MockERC20 public COIN;
MockERC20 public USD;
address public token0; // either WETH or USDC depending on the order in the pool
address public token1;
bool public inverted;
contract TestSinglePool is MockEnv, Test {
function setUp() public {
COIN = new MockERC20('Mock Coin', 'MOCK', 18);
USD = new MockERC20('Universally Supported Dollars', 'USD', 6);
fee = 500;
inverted = address(COIN) > address(USD);
token0 = inverted ? address(USD) : address(COIN);
token1 = inverted ? address(COIN) : address(USD);
uint160 initialPrice = 1 * 2**96;
// if this reverts here make sure Anvil is started and you are running forge with --rpc-url
pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, initialPrice));
int24 ts = pool.tickSpacing();
(, int24 lower, , , , ,) = pool.slot0();
int24 upper = lower;
for (int8 i = 0; i < 10; i++) {
lower -= ts;
upper += ts;
stake(1 * 10 ** COIN.decimals(), 1000 * 10 ** USD.decimals(), lower, upper);
}
init();
}
function stake(uint128 liquidity_, int24 lower, int24 upper) public
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(lower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(upper);
(uint160 sqrtPriceX96, , , , , ,) = pool.slot0();
(amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, liquidity_);
return _stake(amount0, amount1, lower, upper);
}
function testSwap() public {
function testSwap() public {
COIN.mint(address(this), 1 * 10**18);
uint256 usd = swap(COIN, USD, 1 * 10**18);
console2.log(usd);
}
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
)
{
COIN.mint(address(this), coinAmount);
COIN.approve(address(nfpm), coinAmount);
USD.mint(address(this), usdAmount);
USD.approve(address(nfpm), usdAmount);
// 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);
(uint256 a0, uint256 a1) = inverted ? (usdAmount, coinAmount) : (coinAmount, usdAmount);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams(
token0, token1, fee, lower, upper, a0, a1, 0, 0, msg.sender, block.timestamp
);
return nfpm.mint(params);
}
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) {
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), fee, msg.sender, block.timestamp, amountIn, 0, sqrtPriceLimitX96
);
return swapper.exactInputSingle(params);
}
}

25
test/TestVault.sol Normal file
View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.7.6;
import "forge-std/console2.sol";
import "../src/Factory.sol";
import "../src/VaultAddress.sol";
pragma abicoder v2;
contract TestVault {
Factory public factory;
Vault public vault;
function setUp() public {
factory = new Factory();
vault = Vault(factory.deployVault(address(this)));
}
function testDeterministicAddress() public {
console2.log(address(vault));
address d = VaultAddress.computeAddress(address(factory), address(this));
console2.log(d);
assert(address(vault) == d);
}
}