diff --git a/docs/errors.md b/docs/errors.md index 4e92735..ca739ea 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -1,5 +1,11 @@ -| 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 +| Code | Name | Description | +|------|---------------------------|------------------------------------------------------------------------| +| OCOM | Invalid OCO Mode | The OCO mode provided to placeOrder() is invalid. | +| UR | Unknown Route | The specified order route is invalid. | +| NO | Not Open | Order status state is not OPEN | +| UC | Unknown Constraint | The constraint specification did not have a recognized Constraint Mode | +| TE | Too Early | Time constraint window hasn't opened yet | +| TL | Too Late | Time constraint has expired the tranche | +| L | Limit | Price limit constraint violation | +| IIA | Insufficient Input Amount | Not enough input coin available in the vault (from Uniswap) | +| TF | Tranche Filled | The tranche has no remaining amount available to execute. | diff --git a/script/Deploy.sol b/script/Deploy.sol index 7385edf..1ad0e08 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -17,7 +17,7 @@ contract Deploy is Script { Factory deployer = new Factory(); // hardhat often breaks on the CREATE2 above :( QueryHelper query = new QueryHelper(); Dexorder dexorder = new Dexorder(); - MockEnv mock = new MockEnv(); +// MockEnv mock = new MockEnv(); vm.stopBroadcast(); console2.log('Factory'); console2.log(address(deployer)); @@ -25,7 +25,7 @@ contract Deploy is Script { console2.log(address(query)); console2.log('Dexorder'); console2.log(address(dexorder)); - console2.log('MockEnv'); // todo no mock in production deployment - console2.log(address(mock)); +// console2.log('MockEnv'); // todo no mock in production deployment +// console2.log(address(mock)); } } diff --git a/src/OrderLib.sol b/src/OrderLib.sol index 036e97d..0b5b8fc 100644 --- a/src/OrderLib.sol +++ b/src/OrderLib.sol @@ -192,7 +192,7 @@ library OrderLib { // TE current time is too early for this tranche // TL current time is too late for this tranche // - function execute(OrdersInfo storage self, address owner, uint64 orderIndex, uint8 trancheIndex, PriceProof memory proof) internal { + function execute(OrdersInfo storage self, address owner, uint64 orderIndex, uint8 trancheIndex, PriceProof memory ) internal { console2.log('execute'); console2.log(address(this)); console2.log(uint(orderIndex)); @@ -205,67 +205,86 @@ library OrderLib { uint160 sqrtPriceLimitX96 = 0; // 0 means "not set yet" and 1 is the minimum value // todo other routes address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.route.fee); -// for (uint8 c = 0; c < tranche.constraints.length; c++) { -// Constraint storage constraint = tranche.constraints[c]; -// if (constraint.mode == ConstraintMode.Time) { -// TimeConstraint memory tc = abi.decode(constraint.constraint, (TimeConstraint)); -// uint32 time = tc.earliest.mode == TimeMode.Timestamp ? tc.earliest.time : status.start + tc.earliest.time; -// if (time > block.timestamp) -// revert('TE'); // time early -// time = tc.latest.mode == TimeMode.Timestamp ? tc.latest.time : status.start + tc.latest.time; -// if (time < block.timestamp) -// revert('TL'); // time late -// } -// else if (constraint.mode == ConstraintMode.Limit) { -// if( sqrtPriceX96 == 0 ) { -// (sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); -// } -// PriceConstraint memory pc = abi.decode(constraint.constraint, (PriceConstraint)); -// uint256 price = sqrtPriceX96; -// if( pc.isRatio ) -// pc.valueSqrtX96 = uint160(price * pc.valueSqrtX96 / 2**96); // todo overflow check! -// if( pc.isAbove && price < pc.valueSqrtX96 || !pc.isAbove && price > pc.valueSqrtX96 ) -// revert('L'); -// if( sqrtPriceLimitX96 == 0 || -// pc.isAbove && pc.valueSqrtX96 < sqrtPriceLimitX96 || -// !pc.isAbove && pc.valueSqrtX96 > sqrtPriceLimitX96 -// ) -// sqrtPriceLimitX96 = pc.valueSqrtX96; -// } -// else if (constraint.mode == ConstraintMode.Barrier) { -// revert('NI'); // not implemented -// } -// else if (constraint.mode == ConstraintMode.Trailing) { -// revert('NI'); // not implemented -// } -// else if (constraint.mode == ConstraintMode.Line) { -// revert('NI'); // not implemented -// } -// else // unknown constraint -// revert('NI'); // not implemented -// } + for (uint8 c = 0; c < tranche.constraints.length; c++) { + Constraint storage constraint = tranche.constraints[c]; + if (constraint.mode == ConstraintMode.Time) { + console2.log('time constraint'); + TimeConstraint memory tc = abi.decode(constraint.constraint, (TimeConstraint)); + uint32 time = tc.earliest.mode == TimeMode.Timestamp ? tc.earliest.time : status.start + tc.earliest.time; + if (time > block.timestamp) + revert('TE'); // time early + time = tc.latest.mode == TimeMode.Timestamp ? tc.latest.time : status.start + tc.latest.time; + if (time < block.timestamp) + revert('TL'); // time late + } + else if (constraint.mode == ConstraintMode.Limit) { + console2.log('limit constraint'); + if( sqrtPriceX96 == 0 ) { + (sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); + } + PriceConstraint memory pc = abi.decode(constraint.constraint, (PriceConstraint)); + uint256 price = sqrtPriceX96; + if( pc.isRatio ) + pc.valueSqrtX96 = uint160(price * pc.valueSqrtX96 / 2**96); // todo overflow check! + if( pc.isAbove && price < pc.valueSqrtX96 || !pc.isAbove && price > pc.valueSqrtX96 ) + revert('L'); + if( sqrtPriceLimitX96 == 0 || + pc.isAbove && pc.valueSqrtX96 < sqrtPriceLimitX96 || + !pc.isAbove && pc.valueSqrtX96 > sqrtPriceLimitX96 + ) + sqrtPriceLimitX96 = pc.valueSqrtX96; + } + else if (constraint.mode == ConstraintMode.Barrier) { + console2.log('barrier constraint'); + revert('NI'); // not implemented + } + else if (constraint.mode == ConstraintMode.Trailing) { + console2.log('trailing constraint'); + revert('NI'); // not implemented + } + else if (constraint.mode == ConstraintMode.Line) { + console2.log('line constraint'); + revert('NI'); // not implemented + } + else // unknown constraint + revert('UC'); // not implemented + } + console2.log('computing amount'); + console2.log(status.order.amount); + console2.log(tranche.fraction); + console2.log(status.order.amountIsInput); + console2.log(status.filledIn); + console2.log(status.filledOut); + console2.log(status.trancheFilledIn[trancheIndex]); + console2.log(status.trancheFilledOut[trancheIndex]); uint256 amount = status.order.amount * tranche.fraction / type(uint16).max // the most this tranche could do - (status.order.amountIsInput ? status.trancheFilledIn[trancheIndex] : status.trancheFilledOut[trancheIndex]); // minus tranche fills + console2.log('amount'); + console2.log(amount); // order amount remaining + require( (status.order.amountIsInput ? status.filledIn : status.filledOut) <= status.order.amount, 'OVERFILL' ); uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut); + console2.log('remaining'); + console2.log(remaining); if (amount > remaining) // not more than the order's overall remaining amount amount = remaining; + require( amount > 0, 'TF' ); console2.log(amount); address recipient = status.order.outputDirectlyToOwner ? owner : address(this); console2.log(recipient); uint256 amountIn; uint256 amountOut; -// if( status.order.route.exchange == Exchange.UniswapV3 ) + if( status.order.route.exchange == Exchange.UniswapV3 ) (amountIn, amountOut) = _do_execute_univ3(recipient, status.order, pool, amount, sqrtPriceLimitX96); // todo other routes -// else -// revert('UR'); // unknown route -// status.filledIn += amountIn; -// status.filledOut += amountOut; -// status.trancheFilledIn[trancheIndex] += amountIn; -// status.trancheFilledOut[trancheIndex] += amountOut; -// emit DexorderSwapFilled(orderIndex, trancheIndex, amountIn, amountOut); -// _checkCompleted(self, orderIndex, status); + else + revert('UR'); // unknown route + status.filledIn += amountIn; + status.filledOut += amountOut; + status.trancheFilledIn[trancheIndex] += amountIn; + status.trancheFilledOut[trancheIndex] += amountOut; + emit DexorderSwapFilled(orderIndex, trancheIndex, amountIn, amountOut); + _checkCompleted(self, orderIndex, status); } @@ -276,13 +295,11 @@ library OrderLib { console2.log('price limit'); console2.log(uint(sqrtPriceLimitX96)); if (order.amountIsInput) { - amountIn = amount; - amountOut = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams( + (amountIn, amountOut) = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams( pool, order.tokenIn, order.tokenOut, recipient, order.route.fee, amount, sqrtPriceLimitX96)); } else { - amountOut = amount; - amountIn = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams( + (amountIn, amountOut) = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams( pool, order.tokenIn, order.tokenOut, recipient, order.route.fee, amount, sqrtPriceLimitX96)); } } diff --git a/src/QueryHelper.sol b/src/QueryHelper.sol index d088061..4de0d90 100644 --- a/src/QueryHelper.sol +++ b/src/QueryHelper.sol @@ -50,8 +50,8 @@ contract QueryHelper { // here we find the highest liquidity pool for v2 and for v3 uint24[4] memory fees = [uint24(100),500,3000,10000]; uint24 uniswapV2Fee = 0; - uint128 uniswapV2Liquidity = 0; - address uniswapV2Pool = address(0); +// uint128 uniswapV2Liquidity = 0; +// address uniswapV2Pool = address(0); uint24 uniswapV3Fee = 0; uint128 uniswapV3Liquidity = 0; address uniswapV3Pool = address(0); diff --git a/src/UniswapSwapper.sol b/src/UniswapSwapper.sol index 90cc8a4..80bfd0f 100644 --- a/src/UniswapSwapper.sol +++ b/src/UniswapSwapper.sol @@ -22,7 +22,7 @@ library UniswapSwapper { uint160 sqrtPriceLimitX96; } - function swapExactInput(SwapParams memory params) internal returns (uint256 amountOut) + function swapExactInput(SwapParams memory params) internal returns (uint256 amountIn, uint256 amountOut) { // struct ExactInputSingleParams { // address tokenIn; @@ -44,23 +44,30 @@ library UniswapSwapper { console2.log(uint(params.sqrtPriceLimitX96)); console2.log(address(Constants.uniswapV3SwapRouter)); - TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), params.amount); + amountIn = params.amount; + uint256 balance = IERC20(params.tokenIn).balanceOf(address(this)); + if( balance == 0 ) { + // todo dust? + revert('IIA'); + } + if( balance < amountIn ) + amountIn = balance; + + TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), amountIn); // if (params.sqrtPriceLimitX96 == 0) // params.sqrtPriceLimitX96 = params.tokenIn < params.tokenOut ? TickMath.MIN_SQRT_RATIO+1 : TickMath.MAX_SQRT_RATIO-1; - console2.log('splx96'); - console2.log(uint(params.sqrtPriceLimitX96)); - + console2.log('swapping...'); amountOut = Constants.uniswapV3SwapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: params.recipient, - deadline: block.timestamp, amountIn: params.amount, amountOutMinimum: 1, sqrtPriceLimitX96: params.sqrtPriceLimitX96 + deadline: block.timestamp, amountIn: amountIn, amountOutMinimum: 1, sqrtPriceLimitX96: params.sqrtPriceLimitX96 })); console2.log('swapped'); console2.log(amountOut); TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), 0); } - function swapExactOutput(SwapParams memory params) internal returns (uint256 amountIn) + function swapExactOutput(SwapParams memory params) internal returns (uint256 amountIn, uint256 amountOut) { // TODO copy changes over from swapExactInput @@ -74,8 +81,7 @@ library UniswapSwapper { // uint256 amountInMaximum; // uint160 sqrtPriceLimitX96; // } - address t = address(this); - uint256 balance = IERC20(params.tokenIn).balanceOf(t); + uint256 balance = IERC20(params.tokenIn).balanceOf(address(this)); if( balance == 0 ) { // todo dust? revert('IIA'); @@ -99,14 +105,33 @@ library UniswapSwapper { // if (params.sqrtPriceLimitX96 == 0) // params.sqrtPriceLimitX96 = params.tokenIn < params.tokenOut ? TickMath.MIN_SQRT_RATIO+1 : TickMath.MAX_SQRT_RATIO-1; - amountIn = Constants.uniswapV3SwapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ + console2.log('swapping...'); + try Constants.uniswapV3SwapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: params.recipient, deadline: block.timestamp, amountOut: params.amount, amountInMaximum: maxAmountIn, sqrtPriceLimitX96: params.sqrtPriceLimitX96 - })); + })) returns (uint256 amtIn) { + amountIn = amtIn; + amountOut = params.amount; + } + catch Error( string memory reason ) { + // todo check reason before trying exactinput + // if the input amount was insufficient, use exactInputSingle to spend whatever remains. + try Constants.uniswapV3SwapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ + tokenIn: params.tokenIn, tokenOut: params.tokenOut, fee: params.fee, recipient: params.recipient, + deadline: block.timestamp, amountIn: maxAmountIn, amountOutMinimum: 1, sqrtPriceLimitX96: params.sqrtPriceLimitX96 + })) returns (uint256 amtOut) { + amountIn = maxAmountIn; + amountOut = amtOut; + } + catch Error( string memory ) { + revert(reason); // revert on the original reason + } + } console2.log('swapped'); console2.log(amountIn); + console2.log(amountOut); TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), 0); } diff --git a/src/VaultAddress.sol b/src/VaultAddress.sol index b394ace..0b7d293 100644 --- a/src/VaultAddress.sol +++ b/src/VaultAddress.sol @@ -11,7 +11,7 @@ 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 public constant VAULT_INIT_CODE_HASH = 0x5548eccbb8c4c38711944ab3e79a6f320ae2eb4a6bb16c64215f13e7732f182c; + bytes32 public constant VAULT_INIT_CODE_HASH = 0xe8ec1ead51d2900d63e5e0aa245485e9452d619fceb9ab914ae78cdb11c4ab70; // the contract being constructed must not have any constructor arguments or the determinism will be broken. instead, use a callback to // get construction arguments diff --git a/test/TestOrder.sol b/test/TestOrder.sol index 6a8e5da..a6e8960 100644 --- a/test/TestOrder.sol +++ b/test/TestOrder.sol @@ -63,7 +63,6 @@ contract TestOrder is MockEnv, Test { vault.placeOrder(order); console2.log('placed order'); console2.log(uint(orderIndex)); - string memory result; vault.execute(orderIndex, 0, OrderLib.PriceProof(0)); console2.log('executed'); } @@ -85,7 +84,6 @@ contract TestOrder is MockEnv, Test { vault.placeOrder(order); console2.log('placed order'); console2.log(uint(orderIndex)); - string memory result; vault.execute(orderIndex, 0, OrderLib.PriceProof(0)); console2.log('executed'); } diff --git a/test/TestVault.sol b/test/TestVault.sol index d8a9749..00898af 100644 --- a/test/TestVault.sol +++ b/test/TestVault.sol @@ -22,7 +22,7 @@ contract TestVault is Test { console2.log(address(vault)); } - function testDeterministicAddress() public { + function testDeterministicAddress() public view { console2.log(address(vault)); address d = VaultAddress.computeAddress(address(factory), address(this)); console2.log(d);