From 52069cfe0ba30de41cb73878f05572e9f1188168 Mon Sep 17 00:00:00 2001 From: Tim Olson <> Date: Fri, 6 Oct 2023 19:48:39 -0400 Subject: [PATCH] vault deployment, query helper, bug fixes --- .gitignore | 11 ++--- bin/deploy.sh | 3 +- bin/{reset.sh => mock.sh} | 7 +++- docs/errors.md | 9 ++-- node_modules | 1 + script/Deploy.sol | 12 +++++- src/Factory.sol | 8 +++- src/OrderLib.sol | 32 +++++++++++---- src/QueryHelper.sol | 86 +++++++++++++++++++++++++++++++++++++++ src/VaultAddress.sol | 8 +++- src/VaultDeployer.sol | 29 ++++++++++--- test/MockEnv.sol | 13 +++--- test/TestOrder.sol | 29 +++++++++++++ test/TestVault.sol | 3 +- 14 files changed, 215 insertions(+), 36 deletions(-) rename bin/{reset.sh => mock.sh} (58%) create mode 120000 node_modules create mode 100644 src/QueryHelper.sol create mode 100644 test/TestOrder.sol diff --git a/.gitignore b/.gitignore index ab1539e..cb2a76a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -cache/ -out/ -.idea/ -.env -gen/ +/cache +/out +/.idea +/.env +/gen +/lib diff --git a/bin/deploy.sh b/bin/deploy.sh index e89ffdb..9fa3e6b 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -1,2 +1,3 @@ #!/usr/bin/env bash -forge script script/Deploy.sol --fork-url http://localhost:8545 --broadcast +./bin/build.sh +forge script script/Deploy.sol -vvvv --fork-url http://localhost:8545 --broadcast diff --git a/bin/reset.sh b/bin/mock.sh similarity index 58% rename from bin/reset.sh rename to bin/mock.sh index 4e5d60d..dc60efd 100755 --- a/bin/reset.sh +++ b/bin/mock.sh @@ -5,10 +5,13 @@ #db-migrate up #cd ../contract -anvil -f arbitrum_ankr & +./bin/build.sh +anvil -f arbitrum_ankr --chain-id 1338 & +# todo check anvil result ANVIL_PID=$! sleep 2 -forge script script/Deploy.sol --fork-url http://localhost:8545 --broadcast + +forge script script/Deploy.sol -vvvv --fork-url http://localhost:8545 --broadcast trap_ctrlc() { echo diff --git a/docs/errors.md b/docs/errors.md index b9e5f80..4e92735 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -1,4 +1,5 @@ -| Code | Name | Description | -|------|--------------------|------------------------------------------------------------------------| -| UC | Unknown Constraint | The constraint specification did not have a recognized Constraint Mode | -| OCOM | Invalid OCO Mode | The OCO mode provided to placeOrder() is invalid. | \ No newline at end of file +| Code | Name | Description | +|-------|--------------------|------------------------------------------------------------------------| +| UC | Unknown Constraint | The constraint specification did not have a recognized Constraint Mode | +| OCOM | Invalid OCO Mode | The OCO mode provided to placeOrder() is invalid. | +| UR | Unknown Route | The specified order route is invalid. | \ No newline at end of file diff --git a/node_modules b/node_modules new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/node_modules @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/script/Deploy.sol b/script/Deploy.sol index eb960d0..35f3495 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -2,16 +2,24 @@ pragma solidity =0.7.6; import "forge-std/Script.sol"; -import "../src/VaultDeployer.sol"; import "forge-std/console2.sol"; +import "../src/QueryHelper.sol"; +import "../src/Factory.sol"; +import "../test/MockEnv.sol"; contract Deploy is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - VaultDeployer deployer = new VaultDeployer{salt:keccak256(abi.encode(1))}(); + Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); + QueryHelper query = new QueryHelper(); + MockEnv mock = new MockEnv(); vm.stopBroadcast(); console2.log('VaultDeployer'); console2.log(address(deployer)); + console2.log('QueryHelper'); + console2.log(address(query)); + console2.log('MockEnv'); // todo no mock in production deployment + console2.log(address(mock)); } } diff --git a/src/Factory.sol b/src/Factory.sol index d1e2083..629cff4 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -5,6 +5,12 @@ import "v3-core/contracts/UniswapV3Factory.sol"; import "./VaultDeployer.sol"; pragma abicoder v2; + contract Factory is VaultDeployer { - // todo owner + address public admin; + + + constructor() { + admin = msg.sender; + } } diff --git a/src/OrderLib.sol b/src/OrderLib.sol index 57cb804..e246232 100644 --- a/src/OrderLib.sol +++ b/src/OrderLib.sol @@ -15,18 +15,29 @@ library OrderLib { event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut); - event DexorderCompleted (uint64 orderIndex); + event DexorderCompleted (uint64 orderIndex); // todo remove? event DexorderError (uint64 orderIndex, string reason); + // todo If a tranche fails to fill, an order can stay Open forever without any active tranches. maybe replace state with a simple canceled flag enum SwapOrderState { Open, Canceled, Filled, Template } + enum Exchange { + UniswapV2, + UniswapV3 + } + + struct Route { + Exchange exchange; + uint24 fee; + } + struct SwapOrder { address tokenIn; address tokenOut; - uint24 fee; + Route route; uint256 amount; bool amountIsInput; bool outputDirectlyToOwner; @@ -138,6 +149,8 @@ library OrderLib { revert('OCOM'); for( uint8 o = 0; o < orders.length; o++ ) { SwapOrder memory order = orders[o]; + require(order.route.exchange == Exchange.UniswapV3, 'UR'); + // todo more order validation // we must explicitly copy into storage because Solidity doesn't implement copying the double-nested // tranches constraints array :( uint orderIndex = self.orders.length; @@ -147,7 +160,7 @@ library OrderLib { status.order.amountIsInput = order.amountIsInput; status.order.tokenIn = order.tokenIn; status.order.tokenOut = order.tokenOut; - status.order.fee = order.fee; + status.order.route = order.route; status.order.chainOrder = order.chainOrder; status.order.outputDirectlyToOwner = order.outputDirectlyToOwner; for( uint t=0; t uniswapV3Liquidity ) { + uniswapV3Fee = fees[f]; + uniswapV3Liquidity = liquidity; + uniswapV3Pool = address(pool); + } + } + catch { + } + } + uint8 routesCount = uniswapV3Fee > 0 ? 1 : 0 + uniswapV2Fee > 0 ? 1 : 0; + routes = new QueryHelper.RoutesResult[](routesCount); + uint8 i = 0; + // todo v2 + if( uniswapV3Fee > 0 ) + routes[i++] = QueryHelper.RoutesResult(OrderLib.Exchange.UniswapV3, uniswapV3Fee, uniswapV3Pool); + } + + function poolStatus(IUniswapV3Pool pool) public view + returns ( + int24 tick, + uint128 liquidity + ) { + (, tick,,,,,) = pool.slot0(); + liquidity = pool.liquidity(); + } +} diff --git a/src/VaultAddress.sol b/src/VaultAddress.sol index 24062c2..51f70e7 100644 --- a/src/VaultAddress.sol +++ b/src/VaultAddress.sol @@ -9,20 +9,24 @@ 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; + bytes32 internal constant VAULT_INIT_CODE_HASH = 0xdc7f6d1305b2c9951dc98f6a745290e4f2b92bf65d346db11dd284dcfcd67aef; // 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) { + return computeAddress(factory, owner, 0); + } + + function computeAddress(address factory, address owner, uint8 num) internal pure returns (address vault) { vault = address( uint256( keccak256( abi.encodePacked( hex'ff', factory, - keccak256(abi.encode(owner)), + keccak256(abi.encode(owner,num)), VAULT_INIT_CODE_HASH ) ) diff --git a/src/VaultDeployer.sol b/src/VaultDeployer.sol index 856c832..9acca9f 100644 --- a/src/VaultDeployer.sol +++ b/src/VaultDeployer.sol @@ -10,14 +10,31 @@ contract VaultDeployer { address owner; } - event VaultCreated( address deployer, address owner ); + event VaultCreated( address indexed owner, uint8 num ); Parameters public parameters; - function deployVault(address owner) public returns (address payable vault) { - parameters = Parameters(owner); - vault = address(new Vault{salt: keccak256(abi.encode(owner))}()); - delete parameters; - emit VaultCreated( address(this), owner ); + function deployVault() public returns (address payable vault) { + return _deployVault(msg.sender, 0); } + + function deployVault(uint8 num) public returns (address payable vault) { + return _deployVault(msg.sender, num); + } + + function deployVault(address owner) public returns (address payable vault) { + return _deployVault(owner, 0); + } + + function deployVault(address owner, uint8 num) public returns (address payable vault) { + return _deployVault(owner, num); + } + + function _deployVault(address owner, uint8 num) internal returns (address payable vault) { + parameters = Parameters(owner); + vault = address(new Vault{salt: keccak256(abi.encode(owner,num))}()); + delete parameters; + emit VaultCreated( owner, num ); + } + } diff --git a/test/MockEnv.sol b/test/MockEnv.sol index 59be0b7..1a1fe8a 100644 --- a/test/MockEnv.sol +++ b/test/MockEnv.sol @@ -21,19 +21,22 @@ contract MockEnv { 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 token0; // either COIN or USD depending on the order in the pool address public token1; bool public inverted; - - function init() public { + // sets up two mock coins COIN and USD, plus a uniswap v3 pool. + // the initial price is 1.000000, but since COIN has 18 decimals and USD only has 6, the raw pool price is 1e-12 + // therefore the sqrt price is 1e-6 + // 1000e12 liquidity is put into the pool at each tick spacing for 10 tick spacings to either side of $1 + function init() internal { 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; + uint160 initialPrice = uint160(79228162514264337593543); // price 1e-12 = sqrt price 1e-6 = 2**96 / 10**6 // 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(); @@ -42,7 +45,7 @@ contract MockEnv { for (int8 i = 0; i < 10; i++) { lower -= ts; upper += ts; - stake(1 * 10 ** COIN.decimals(), 1000 * 10 ** USD.decimals(), lower, upper); + stake(1000 * 10**12, lower, upper); } } diff --git a/test/TestOrder.sol b/test/TestOrder.sol new file mode 100644 index 0000000..b5395ba --- /dev/null +++ b/test/TestOrder.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.7.6; +pragma abicoder v2; + +import "./MockEnv.sol"; +import "forge-std/Test.sol"; +import "../src/Factory.sol"; + +contract TestOrder is MockEnv, Test { + Factory public factory; + Vault public vault; + + // vault gets 100,000 COIN and 100,000 USD + function setUp() public { + factory = new Factory(); + vault = Vault(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 testOrder() public { + + } + + +} diff --git a/test/TestVault.sol b/test/TestVault.sol index fe5300c..83f7cf0 100644 --- a/test/TestVault.sol +++ b/test/TestVault.sol @@ -4,9 +4,10 @@ pragma solidity =0.7.6; import "forge-std/console2.sol"; import "../src/Factory.sol"; import "../src/VaultAddress.sol"; +import "forge-std/Test.sol"; pragma abicoder v2; -contract TestVault { +contract TestVault is Test{ Factory public factory; Vault public vault;