commit e76bc7029751c17c4997e3ce1b5847d645e5a40e Author: Tim Olson <> Date: Sat Aug 19 18:13:43 2023 -0400 start diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..897b3d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +cache/ +out/ +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..888d42d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/docs/order_types.md b/docs/order_types.md new file mode 100644 index 0000000..50dea97 --- /dev/null +++ b/docs/order_types.md @@ -0,0 +1,41 @@ +# Order Types +## TWAP +* time-to-expiry is required +* number of tranches > 1 +* optional lower and upper price bounds + +## DCA +* similar to a TWAP, but the tranches are divided by value instead of quantity + +## Timed Entry +* specific time when an order is triggered +* can be market/limit TWAP, whatever + +## Limit + +## Stop +* enabled on a price condition instead of at a specific time + * how to enforce this in the contract? would need a price history of when the stop was touched. perhaps this is a use case for a "chain" order where one set of constraints doesn't invoke a trade but instead creates a subsequent order + +## Ladder +* split into many traches across a range of prices +* required number of tranches +* required upper and lower bounds of the ladder + + +# Conditions + +* current price above/below +* historical price touched above/below (e.g. trailing stop): keep an updated data structure of swing highs & lows. this structure must only be updated once before the relevant observation rolls off the back of the window. "management gas" +* volume above/below +* historical volume touched above/below: this could work like historical price swing high/lows if we first bucket the volumes into sizes <= the pool observation window +* per-swap slippage constraint + + +# Gas + +* an amount for gas is reserved ahead of time +* excess gas is kept +* Dexible has a gas refund delay, saying: + > `get/setLockoutBlocks` Retrieves or sets the number of blocks a trader must wait before withdrawing their gas deposit. This is to prevent traders from front-running a relay that submitted an order in order to circumvent paying for the execution or forcing a failed txn. + diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..6c68e02 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,9 @@ +[profile.default] +libs = ['lib'] +remappings = [ + '@openzeppelin/contracts/=lib/openzeppelin/contracts/', + '@uniswap/v3-core/=lib/uniswap/v3-core/', + '@uniswap/v3-periphery/=lib/uniswap/v3-periphery/', +] +optimizer=true +optimizer_runs=999999999 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..74cfb77 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 diff --git a/src/IERC20Metadata.sol b/src/IERC20Metadata.sol new file mode 100644 index 0000000..afe19f9 --- /dev/null +++ b/src/IERC20Metadata.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// the version of OpenZeppelin required by Uniswap v3 doesn't have IERC20Metadata yet, so we copy it here. +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/src/MockERC20.sol b/src/MockERC20.sol new file mode 100644 index 0000000..783f371 --- /dev/null +++ b/src/MockERC20.sol @@ -0,0 +1,17 @@ +import "openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./IERC20Metadata.sol"; + + +contract MockERC20 is ERC20, IERC20Metadata { + + constructor(string name, string symbol, uint8 decimals) + ERC20(name, symbol) + { + _setupDecimals(decimals); + } + + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } +} diff --git a/test/TestSinglePool.sol b/test/TestSinglePool.sol new file mode 100644 index 0000000..a68279b --- /dev/null +++ b/test/TestSinglePool.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.7.6; + +import "forge-std/console2.sol"; +import "../src/MockERC20.sol"; +import "uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.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 TestSinglePool { + + IUniswapV3Factory public factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); + INonfungiblePositionManager public nfpm = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + ISwapRouter public swapper = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + IUniswapV3Pool public immutable pool; + uint24 public fee; + MockERC20 public WETH; + MockERC20 public USDC; + MockERC20 public token0; // either WETH or USDC depending on the order in the pool + MockERC20 public token1; + + function setUp() public { + MockERC20 weth = MockERC20('Mock Wrapped Ethereum', 'WETH', 18); + MockERC20 usdc = MockERC20('Mock USD Coin', 'USDC', 6); + uint24 fee_ = 500; + fee = fee_; + WETH = weth; + USDC = usdc; + IUniswapV3Pool pool_ = UniswapV3Pool(factory.createPool(address(weth), address(usdc), fee_)); + pool = pool_; + token0 = pool_.token0(); + token1 = pool_.token1(); + } + + // struct MintParams { + // address token0; + // address token1; + // uint24 fee; + // int24 tickLower; + // int24 tickUpper; + // uint256 amount0Desired; + // uint256 amount1Desired; + // uint256 amount0Min; + // uint256 amount1Min; + // address recipient; + // uint256 deadline; + // } + + function stake(uint160 liquidity, uint24 lower, uint24 upper) public { + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(lower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(upper); + (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(liquidity, sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96); + token0.mint(amount0); + token1.mint(amount1); + INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams( + address(token0), address(token1), fee, lower, upper, amount0, amount1, 0, 0, msg.sender, block.timestamp + ); + nfpm.mint(params); + } + + + function swap() public { + + } +} +