diff --git a/bin/build.sh b/bin/build.sh index 3ffb85c..05217b1 100755 --- a/bin/build.sh +++ b/bin/build.sh @@ -9,7 +9,7 @@ forge build "$@" || exit 1 VAULT_INIT_CODE_HASH=$(cast keccak $(jq -r .bytecode.object < out/Vault.sol/Vault.json)) # put the hash value into the VaultAddress.sol source file -sed -i "s/bytes32 internal constant VAULT_INIT_CODE_HASH = .*;/bytes32 internal constant VAULT_INIT_CODE_HASH = $VAULT_INIT_CODE_HASH;/" src/VaultAddress.sol +sed -i "s/VAULT_INIT_CODE_HASH = .*;/VAULT_INIT_CODE_HASH = $VAULT_INIT_CODE_HASH;/" src/VaultAddress.sol # generate a javascript file with the constant mkdir gen &> /dev/null diff --git a/bin/mock.sh b/bin/mock.sh index cbf1d9a..e44b5c2 100755 --- a/bin/mock.sh +++ b/bin/mock.sh @@ -6,7 +6,7 @@ #cd ../contract #./bin/build.sh -anvil -f arbitrum_mock --chain-id 1338 & +anvil -f arbitrum_mock --chain-id 31337 & # todo check anvil result ANVIL_PID=$! sleep 2 diff --git a/script/Deploy.sol b/script/Deploy.sol index 922508e..aeac2a6 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -12,7 +12,8 @@ contract Deploy is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); +// Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); // version 1 + Factory deployer = new Factory(); // hardhat often breaks on the CREATE2 above :( QueryHelper query = new QueryHelper(); Dexorder dexorder = new Dexorder(); MockEnv mock = new MockEnv(); diff --git a/src/Factory.sol b/src/Factory.sol index 6fb7de1..ed1c726 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -8,7 +8,6 @@ pragma abicoder v2; contract Factory is VaultDeployer { address public admin; - constructor() { admin = msg.sender; } diff --git a/src/OrderLib.sol b/src/OrderLib.sol index f3ca8e2..a33a85c 100644 --- a/src/OrderLib.sol +++ b/src/OrderLib.sol @@ -191,69 +191,80 @@ 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 tranche_index, PriceProof memory proof) internal { + function execute(OrdersInfo storage self, address owner, uint64 orderIndex, uint8 trancheIndex, PriceProof memory proof) internal { + console2.log('execute'); + console2.log(address(this)); + console2.log(uint(orderIndex)); + console2.log(uint(trancheIndex)); SwapOrderStatus storage status = self.orders[orderIndex]; if (status.state != SwapOrderState.Open) revert('NO'); // Not Open - Tranche storage tranche = status.order.tranches[tranche_index]; + Tranche storage tranche = status.order.tranches[trancheIndex]; uint160 sqrtPriceX96 = 0; - uint160 sqrtPriceLimitX96 = 0; + 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'); - } - 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) { +// 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 +// } uint256 amount = status.order.amount * tranche.fraction / type(uint16).max // the most this tranche could do - - (status.order.amountIsInput ? status.trancheFilledIn[tranche_index] : status.trancheFilledOut[tranche_index]); // minus tranche fills + - (status.order.amountIsInput ? status.trancheFilledIn[trancheIndex] : status.trancheFilledOut[trancheIndex]); // minus tranche fills // order amount remaining uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut); if (amount > remaining) // not more than the order's overall remaining amount amount = remaining; + 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[tranche_index] += amountIn; - status.trancheFilledOut[tranche_index] += amountOut; - emit DexorderSwapFilled(orderIndex, tranche_index, 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); } @@ -261,9 +272,8 @@ library OrderLib { returns (uint256 amountIn, uint256 amountOut) { // todo refactor this signature to be more low-level, taking only the in/out amounts and limit prices. doesnt need self/status/index - if (sqrtPriceLimitX96 == 0) - // check pool inversion to see if the price should be high or low - sqrtPriceLimitX96 = order.tokenIn < order.tokenOut ? 0 : type(uint160).max; + console2.log('price limit'); + console2.log(uint(sqrtPriceLimitX96)); if (order.amountIsInput) { amountIn = amount; amountOut = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams( diff --git a/src/UniswapSwapper.sol b/src/UniswapSwapper.sol index edf3965..669318b 100644 --- a/src/UniswapSwapper.sol +++ b/src/UniswapSwapper.sol @@ -5,6 +5,7 @@ pragma abicoder v2; import "./Constants.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "v3-periphery/libraries/TransferHelper.sol"; +import "v3-core/contracts/libraries/TickMath.sol"; import "forge-std/console2.sol"; @@ -33,12 +34,20 @@ library UniswapSwapper { // uint160 sqrtPriceLimitX96; // } console2.log('swapExactInput approve...'); - TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), params.amount); + console2.log(address(this)); console2.log(params.tokenIn); console2.log(params.tokenOut); console2.log(uint(params.fee)); - console2.log(address(Constants.uniswapV3SwapRouter)); + console2.log(address(params.recipient)); console2.log(params.amount); + console2.log(uint(params.sqrtPriceLimitX96)); + console2.log(address(Constants.uniswapV3SwapRouter)); + + if (params.sqrtPriceLimitX96 == 0) + params.sqrtPriceLimitX96 = params.tokenIn < params.tokenOut ? TickMath.MIN_SQRT_RATIO+1 : TickMath.MAX_SQRT_RATIO-1; + TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), params.amount); + console2.log('splx96'); + console2.log(uint(params.sqrtPriceLimitX96)); 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: 0, sqrtPriceLimitX96: params.sqrtPriceLimitX96 @@ -50,6 +59,8 @@ library UniswapSwapper { function swapExactOutput(SwapParams memory params) internal returns (uint256 amountIn) { + // TODO copy changes over from swapExactInput + // struct ExactOutputSingleParams { // address tokenIn; // address tokenOut; diff --git a/src/Vault.sol b/src/Vault.sol index d49aa58..7172869 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -16,7 +16,7 @@ contract Vault { uint8 public immutable version; address public immutable owner; - OrderLib.OrdersInfo public orderList; + OrderLib.OrdersInfo public ordersInfo; constructor() { @@ -54,23 +54,26 @@ contract Vault { token.transfer(recipient, amount); } + function numSwapOrders() external view returns (uint64 num) { + return uint64(ordersInfo.orders.length); + } function placeOrder(OrderLib.SwapOrder memory order) public onlyOwner { console2.log('Vault.placeOrder()'); - orderList._placeOrder(order); + ordersInfo._placeOrder(order); } function placeOrders(OrderLib.SwapOrder[] memory orders, OrderLib.OcoMode ocoMode) public onlyOwner { - orderList._placeOrders(orders, ocoMode); + ordersInfo._placeOrders(orders, ocoMode); } - function swapOrderStatus(uint64 orderIndex) public view returns (OrderLib.SwapOrderStatus memory status) { - return orderList.orders[orderIndex]; + function swapOrderStatus(uint64 orderIndex) external view returns (OrderLib.SwapOrderStatus memory status) { + return ordersInfo.orders[orderIndex]; } function execute(uint64 orderIndex, uint8 tranche_index, OrderLib.PriceProof memory proof) public { - orderList.execute(owner, orderIndex, tranche_index, proof); + ordersInfo.execute(owner, orderIndex, tranche_index, proof); } modifier onlyOwner() { diff --git a/src/VaultAddress.sol b/src/VaultAddress.sol index 424b38d..929f897 100644 --- a/src/VaultAddress.sol +++ b/src/VaultAddress.sol @@ -10,7 +10,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 internal constant VAULT_INIT_CODE_HASH = 0x72f8226a3666abba278cd472062b776a74be697e53a365b69f0e9bfbcfabc1b9; + bytes32 public constant VAULT_INIT_CODE_HASH = 0xaa3457854b70ea8d66f3b73269f0cf34c7e2212e4b4bd8176e8388ff223a2bd0; // 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/MockEnv.sol b/test/MockEnv.sol index 1533488..ced5e54 100644 --- a/test/MockEnv.sol +++ b/test/MockEnv.sol @@ -29,9 +29,13 @@ contract MockEnv { // 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 { + function init() public { COIN = new MockERC20('Mock Coin', 'MOCK', 18); - USD = new MockERC20('Universally Supported Dollars', 'USD', 6); + console2.log('COIN'); + console2.log(address(COIN)); + USD = new MockERC20('Universally Stable Denomination', 'USD', 6); + console2.log('USD'); + console2.log(address(USD)); fee = 500; inverted = address(COIN) > address(USD); token0 = inverted ? address(USD) : address(COIN); @@ -40,7 +44,8 @@ contract MockEnv { 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, initialPrice)); - console2.log('created v3 pool successfully'); + console2.log('v3 pool'); + console2.log(address(pool)); int24 ts = pool.tickSpacing(); (, int24 lower, , , , ,) = pool.slot0(); int24 upper = lower; diff --git a/test/TestOrder.sol b/test/TestOrder.sol index d4d9177..73b5a37 100644 --- a/test/TestOrder.sol +++ b/test/TestOrder.sol @@ -9,6 +9,8 @@ import "../src/Factory.sol"; import "../src/OrderLib.sol"; contract TestOrder is MockEnv, Test { + using OrderLib for OrderLib.OrdersInfo; + Factory public factory; Vault public vault; @@ -24,7 +26,7 @@ contract TestOrder is MockEnv, Test { } - function testOrder() public { + function testPlaceOrder() public { OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](3); OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1); constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Time, bytes(hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000046500")); @@ -44,4 +46,25 @@ contract TestOrder is MockEnv, Test { vault.placeOrder(order); } + function testExecuteOrder() public { + OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](1); + OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1); + constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Time, bytes(hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000046500")); + tranches[0] = OrderLib.Tranche(type(uint16).max,constraints1); + uint256 amount = 3*10**COIN.decimals() / 10; // 0.3 COIN + COIN.mint(address(vault), amount); // create COIN to sell + OrderLib.SwapOrder memory order = OrderLib.SwapOrder( + address(COIN), address(USD), // sell COIN for USD + OrderLib.Route(OrderLib.Exchange.UniswapV3, 500), amount, true, false, + OrderLib.NO_CHAIN, tranches + ); + uint64 orderIndex = vault.numSwapOrders(); + vault.placeOrder(order); + console2.log('placed order'); + console2.log(uint(orderIndex)); + string memory result; + vault.execute(orderIndex, 0, OrderLib.PriceProof(0)); + console2.log('executed'); + } + }