removed PriceConstraint and implemented LineConstraint; MockDeploy copied/separated from Deploy

This commit is contained in:
Tim Olson
2023-11-01 17:56:25 -04:00
parent 7b64745438
commit 03b7ac11e3
7 changed files with 122 additions and 49 deletions

View File

@@ -11,7 +11,7 @@ anvil -f arbitrum_mock --chain-id 31337 &
ANVIL_PID=$!
sleep 2
forge script script/Deploy.sol -vvvv --fork-url http://localhost:8545 --broadcast
forge script script/DeployMock.sol -vvvv --fork-url http://localhost:8545 --broadcast
trap_ctrlc() {
echo exiting anvil

View File

@@ -1,11 +1,12 @@
| 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. |
| 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. |
| Too little received | Too little received | Uniswap v3 error when min output amount is not filled. Can happen when a limit price is very near the current price. |

View File

@@ -1,5 +1,4 @@
// SPDX-License-Identifier: UNLICENSED
// pragma solidity =0.7.6;
pragma solidity >=0.8.0;
import "forge-std/Script.sol";
@@ -13,11 +12,9 @@ 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))}(); // version 1
Factory deployer = new Factory(); // hardhat often breaks on the CREATE2 above :(
Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); // version 1
QueryHelper query = new QueryHelper();
Dexorder dexorder = new Dexorder();
// MockEnv mock = new MockEnv();
vm.stopBroadcast();
console2.log('Factory');
console2.log(address(deployer));
@@ -25,7 +22,5 @@ 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));
}
}

31
script/DeployMock.sol Normal file
View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
import "forge-std/Script.sol";
import "forge-std/console2.sol";
import "../src/QueryHelper.sol";
import "../src/Factory.sol";
import "../src/Dexorder.sol";
import "../test/MockEnv.sol";
contract DeployMock is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
// hardhat often breaks on the CREATE2 so we disable it for mock
// Factory deployer = new Factory{salt:keccak256(abi.encode(1))}(); // version 1
Factory deployer = new Factory();
QueryHelper query = new QueryHelper();
Dexorder dexorder = new Dexorder();
MockEnv mock = new MockEnv();
vm.stopBroadcast();
console2.log('Factory');
console2.log(address(deployer));
console2.log('QueryHelper');
console2.log(address(query));
console2.log('Dexorder');
console2.log(address(dexorder));
console2.log('MockEnv');
console2.log(address(mock));
}
}

View File

@@ -66,21 +66,13 @@ library OrderLib {
enum ConstraintMode {
Time,
Limit,
Trailing,
Barrier,
Line
Line,
Barrier
}
struct Constraint {
ConstraintMode mode; // type information
bytes constraint; // abi packed-encoded constraint struct: decode according to mode
}
struct PriceConstraint {
bool isAbove;
bool isRatio;
uint160 valueSqrtX96;
bytes constraint; // abi-encoded constraint struct: decode according to mode
}
struct LineConstraint {
@@ -217,35 +209,36 @@ library OrderLib {
if (time < block.timestamp)
revert('TL'); // time late
}
else if (constraint.mode == ConstraintMode.Limit) {
console2.log('limit constraint');
if( sqrtPriceX96 == 0 ) {
else if (constraint.mode == ConstraintMode.Line) {
console2.log('line constraint');
if( sqrtPriceX96 == 0 )
(sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
}
PriceConstraint memory pc = abi.decode(constraint.constraint, (PriceConstraint));
LineConstraint memory lc = abi.decode(constraint.constraint, (LineConstraint));
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 )
if( lc.isRatio )
revert('ratio not implemented'); // todo the ratio must be computed when the order is placed
// c.valueSqrtX96 = uint160(price * c.valueSqrtX96 / 2**96);
int256 limit256 = int256(uint256(lc.valueSqrtX96));
if( lc.slopeSqrtX96 != 0 ) {
limit256 += int256(block.timestamp - lc.time) * lc.slopeSqrtX96 / 2**96;
if( limit256 < 0 )
limit256 = 0;
}
console2.log(limit256);
uint160 limit = uint160(uint256(limit256));
// use <= and >= here because trading AT the limit results in 0 volume. price must exceed the limit.
if( lc.isAbove && price <= limit || !lc.isAbove && price >= limit )
revert('L');
if( sqrtPriceLimitX96 == 0 ||
pc.isAbove && pc.valueSqrtX96 < sqrtPriceLimitX96 ||
!pc.isAbove && pc.valueSqrtX96 > sqrtPriceLimitX96
lc.isAbove && limit < sqrtPriceLimitX96 ||
!lc.isAbove && limit > sqrtPriceLimitX96
)
sqrtPriceLimitX96 = pc.valueSqrtX96;
sqrtPriceLimitX96 = limit;
}
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
}

View File

@@ -142,4 +142,23 @@ contract MockEnv {
);
return swapper.exactInputSingle(params);
}
function price() public view returns (uint160 sqrtPrice) {
(sqrtPrice,,,,,,) = pool.slot0();
}
function swapToPrice(uint160 sqrtPriceLimitX96) public {
console2.log('swapToPrice');
console2.log(sqrtPriceLimitX96);
uint160 curPrice = price();
console2.log(curPrice);
if( curPrice == sqrtPriceLimitX96 )
return;
MockERC20 inToken = curPrice > sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1);
MockERC20 outToken = curPrice < sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1);
// instead of calculating how much we need, we just mint an absurd amount
uint256 aLot = 2**100;
inToken.mint(address(this), aLot);
swap(inToken, outToken, aLot, sqrtPriceLimitX96);
}
}

View File

@@ -77,7 +77,7 @@ contract TestOrder is MockEnv, Test {
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.Route(OrderLib.Exchange.UniswapV3, fee), amount, true, false,
OrderLib.NO_CHAIN, tranches
);
uint64 orderIndex = vault.numSwapOrders();
@@ -88,4 +88,38 @@ contract TestOrder is MockEnv, Test {
console2.log('executed');
}
function testExecuteLimitOrder() public {
// test selling token0 above a certain price
OrderLib.Tranche[] memory tranches = new OrderLib.Tranche[](1);
OrderLib.Constraint[] memory constraints1 = new OrderLib.Constraint[](1);
uint160 limit = price() * 10001 / 10000; // 1bp above the current price
bytes memory serialized = abi.encode( OrderLib.LineConstraint(true, false, 0, limit, 0) );
constraints1[0] = OrderLib.Constraint(OrderLib.ConstraintMode.Line, serialized);
tranches[0] = OrderLib.Tranche(type(uint16).max,constraints1);
MockERC20 token = MockERC20(token0);
uint256 amount = 3*10**token.decimals() / 10; // selling 0.3 token0
token.mint(address(vault), amount);
OrderLib.SwapOrder memory order = OrderLib.SwapOrder(
token0, token1, // sell
OrderLib.Route(OrderLib.Exchange.UniswapV3, fee), amount, true, false,
OrderLib.NO_CHAIN, tranches
);
uint64 orderIndex = vault.numSwapOrders();
vault.placeOrder(order);
console2.log('placed order');
console2.log(uint(orderIndex));
vm.expectRevert(bytes('L'));
vault.execute(orderIndex, 0, OrderLib.PriceProof(0)); // should revert with code 'L'
console2.log('successfully failed to execute below limit price');
swapToPrice(limit); // move price to exactly the limit
vm.expectRevert(bytes('L')); // the limit is violated. no liquidity can be taken without moving the price.
vault.execute(orderIndex, 0, OrderLib.PriceProof(0)); // should work now that the price is high enough
swapToPrice(limit*10001/10000); // move price to be 1bp abouve our limit
console2.log('successfully executed at limit price');
}
}