vault/orderlib initial impl compiles
This commit is contained in:
4
docs/errors.md
Normal file
4
docs/errors.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
| 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. |
|
||||||
15
foundry.toml
15
foundry.toml
@@ -6,12 +6,21 @@ remappings = [
|
|||||||
'@uniswap/v3-core/=lib/v3-core/',
|
'@uniswap/v3-core/=lib/v3-core/',
|
||||||
'@uniswap/v3-periphery/=lib/v3-periphery/',
|
'@uniswap/v3-periphery/=lib/v3-periphery/',
|
||||||
]
|
]
|
||||||
optimizer=true
|
|
||||||
optimizer_runs=999999999
|
|
||||||
sizes = true
|
sizes = true
|
||||||
via_ir = false
|
|
||||||
gas_reports = ['*']
|
gas_reports = ['*']
|
||||||
gas_reports_ignore = []
|
gas_reports_ignore = []
|
||||||
|
via_ir = false
|
||||||
|
optimizer=true
|
||||||
|
optimizer_runs=999999999
|
||||||
|
|
||||||
|
[profile.default.optimizer_details]
|
||||||
|
constantOptimizer = true
|
||||||
|
yul = true
|
||||||
|
|
||||||
|
[profile.default.optimizer_details.yulDetails]
|
||||||
|
stackAllocation = true
|
||||||
|
#optimizerSteps = 'dhfoDgvulfnTUtnIf'
|
||||||
|
optimizerSteps = 'dhfoD[xarrscLMcCTU]uljmul'
|
||||||
|
|
||||||
[profile.default.rpc_endpoints]
|
[profile.default.rpc_endpoints]
|
||||||
# todo put these into a secrets file
|
# todo put these into a secrets file
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma solidity =0.7.6;
|
|||||||
|
|
||||||
import "forge-std/Script.sol";
|
import "forge-std/Script.sol";
|
||||||
import "../src/VaultDeployer.sol";
|
import "../src/VaultDeployer.sol";
|
||||||
|
import "forge-std/console2.sol";
|
||||||
|
|
||||||
contract Deploy is Script {
|
contract Deploy is Script {
|
||||||
function run() external {
|
function run() external {
|
||||||
@@ -10,5 +11,7 @@ contract Deploy is Script {
|
|||||||
vm.startBroadcast(deployerPrivateKey);
|
vm.startBroadcast(deployerPrivateKey);
|
||||||
VaultDeployer deployer = new VaultDeployer{salt:keccak256(abi.encode(1))}();
|
VaultDeployer deployer = new VaultDeployer{salt:keccak256(abi.encode(1))}();
|
||||||
vm.stopBroadcast();
|
vm.stopBroadcast();
|
||||||
|
console2.log('VaultDeployer');
|
||||||
|
console2.log(address(deployer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ pragma solidity =0.7.6;
|
|||||||
|
|
||||||
import "v3-core/contracts/UniswapV3Factory.sol";
|
import "v3-core/contracts/UniswapV3Factory.sol";
|
||||||
import "./VaultDeployer.sol";
|
import "./VaultDeployer.sol";
|
||||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
||||||
pragma abicoder v2;
|
pragma abicoder v2;
|
||||||
|
|
||||||
contract Factory is VaultDeployer, Ownable {
|
contract Factory is VaultDeployer {
|
||||||
|
// todo owner
|
||||||
}
|
}
|
||||||
|
|||||||
250
src/OrderLib.sol
250
src/OrderLib.sol
@@ -1,10 +1,23 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
pragma solidity =0.7.6;
|
pragma solidity =0.7.6;
|
||||||
|
pragma abicoder v2;
|
||||||
|
|
||||||
|
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||||
|
import "./UniswapSwapper.sol";
|
||||||
|
|
||||||
|
|
||||||
library OrderLib {
|
library OrderLib {
|
||||||
|
|
||||||
uint64 internal constant NO_CHAIN = type(uint64).max;
|
uint64 internal constant NO_CHAIN = type(uint64).max;
|
||||||
uint8 internal constant NUM_OCO_GROUPS = 6;
|
uint64 internal constant NO_OCO = type(uint64).max;
|
||||||
|
|
||||||
|
event DexorderPlaced (uint64 startOrderIndex, uint8 numOrders);
|
||||||
|
|
||||||
|
event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut);
|
||||||
|
|
||||||
|
event DexorderCompleted (uint64 orderIndex);
|
||||||
|
|
||||||
|
event DexorderError (uint64 orderIndex, string reason);
|
||||||
|
|
||||||
enum SwapOrderState {
|
enum SwapOrderState {
|
||||||
Open, Canceled, Filled, Template
|
Open, Canceled, Filled, Template
|
||||||
@@ -17,35 +30,48 @@ library OrderLib {
|
|||||||
uint256 amount;
|
uint256 amount;
|
||||||
bool amountIsInput;
|
bool amountIsInput;
|
||||||
bool outputDirectlyToOwner;
|
bool outputDirectlyToOwner;
|
||||||
Tranche[] tranches;
|
|
||||||
uint64 chainOrder; // use NO_CHAIN for no chaining. chainOrder index must be < than this order's index for safety (written first) and chainOrder state must be Template
|
uint64 chainOrder; // use NO_CHAIN for no chaining. chainOrder index must be < than this order's index for safety (written first) and chainOrder state must be Template
|
||||||
|
Tranche[] tranches;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SwapOrderStatus {
|
struct SwapOrderStatus {
|
||||||
SwapOrderState state;
|
|
||||||
SwapOrder order;
|
SwapOrder order;
|
||||||
uint256 filled;
|
SwapOrderState state;
|
||||||
uint256 net; // received after fees, conversions, taxes, etc
|
uint32 start;
|
||||||
bool[NUM_OCO_GROUPS] ocoTriggered; // if true then the group has been canceled
|
uint64 ocoGroup;
|
||||||
|
uint256 filledIn;
|
||||||
|
uint256 filledOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConstraintMode {
|
enum ConstraintMode {
|
||||||
|
Time,
|
||||||
Limit,
|
Limit,
|
||||||
Barrier,
|
|
||||||
Trailing,
|
Trailing,
|
||||||
Time
|
Barrier,
|
||||||
|
Line
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Constraint {
|
||||||
|
ConstraintMode mode;
|
||||||
|
bytes constraint; // abi-encoded constraint struct
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PriceConstraint {
|
struct PriceConstraint {
|
||||||
PriceConstraintMode mode;
|
|
||||||
bool isAbove;
|
bool isAbove;
|
||||||
bool isRatio;
|
bool isRatio;
|
||||||
uint160 valueSqrtX96;
|
uint160 valueSqrtX96;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LineConstraint {
|
||||||
|
bool isAbove;
|
||||||
|
bool isRatio;
|
||||||
|
uint32 time;
|
||||||
|
uint160 valueSqrtX96;
|
||||||
|
int160 slopeSqrtX96; // price change per second
|
||||||
|
}
|
||||||
|
|
||||||
enum TimeMode {
|
enum TimeMode {
|
||||||
Timestamp, // absolute timestamp
|
Timestamp, // absolute timestamp
|
||||||
SinceOrderStart // relative to order creation (useful for chained orders)
|
SinceOrderStart // relative to order creation (useful for chained orders)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,17 +80,207 @@ library OrderLib {
|
|||||||
uint32 time;
|
uint32 time;
|
||||||
}
|
}
|
||||||
|
|
||||||
Time constant DISTANT_PAST = Time(TimeMode.Timestamp, 0);
|
uint32 constant DISTANT_PAST = 0;
|
||||||
Time constant DISTANT_FUTURE = Time(TimeMode.Timestamp, type(uint32).max);
|
uint32 constant DISTANT_FUTURE = type(uint32).max;
|
||||||
|
|
||||||
uint8 internal constant NO_OCO = 255;
|
struct TimeConstraint {
|
||||||
|
Time earliest;
|
||||||
|
Time latest;
|
||||||
|
}
|
||||||
|
|
||||||
struct Tranche {
|
struct Tranche {
|
||||||
uint64 fraction; // 18-decimal fraction of the order amount which is available to this tranche. must be <= 1
|
uint64 fraction; // 18-decimal fraction of the order amount which is available to this tranche. must be <= 1
|
||||||
uint8 ocoGroup; // 0-5 are six valid groups, indexing ocoTriggered. use NO_OCO to disable oco functionality.
|
Constraint[] constraints;
|
||||||
Time earliest; // earliest block timestamp for execution. use DISTANT_PAST to disable
|
|
||||||
Time latest; // latest block timestamp for execution (inclusive). use DISTANT_FUTURE to disable
|
|
||||||
PriceConstraint[] constraints;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PriceProof {
|
||||||
|
// todo
|
||||||
|
uint proof;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OcoMode {
|
||||||
|
NO_OCO,
|
||||||
|
CANCEL_ON_PARTIAL_FILL,
|
||||||
|
CANCEL_ON_COMPLETION
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OcoGroup {
|
||||||
|
OcoMode mode;
|
||||||
|
uint64 startIndex; // starting orderIndex of the group
|
||||||
|
uint8 num; // number of orders in the group
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OrdersInfo {
|
||||||
|
bool _ignored; // workaround for Solidity bug where a public struct member cannot start with an array of uncertain size
|
||||||
|
SwapOrderStatus[] orders;
|
||||||
|
OcoGroup[] ocoGroups; // each indexed OCO group is an array of orderIndexes of orders in the oco group.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _placeOrder(OrdersInfo storage self, SwapOrder memory order) internal {
|
||||||
|
SwapOrder[] memory orders = new SwapOrder[](1);
|
||||||
|
orders[0] = order;
|
||||||
|
return _placeOrders(self,orders,OcoMode.NO_OCO);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _placeOrders(OrdersInfo storage self, SwapOrder[] memory orders, OcoMode ocoMode) internal {
|
||||||
|
require(orders.length < type(uint8).max);
|
||||||
|
uint64 startIndex = uint64(self.orders.length);
|
||||||
|
require(startIndex < type(uint64).max);
|
||||||
|
uint64 ocoGroup;
|
||||||
|
if( ocoMode == OcoMode.NO_OCO )
|
||||||
|
ocoGroup = NO_OCO;
|
||||||
|
else if ( ocoMode == OcoMode.CANCEL_ON_PARTIAL_FILL || ocoMode == OcoMode.CANCEL_ON_COMPLETION ){
|
||||||
|
ocoGroup = uint64(self.ocoGroups.length);
|
||||||
|
self.ocoGroups.push(OcoGroup(ocoMode, startIndex, uint8(orders.length)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
revert('OCOM');
|
||||||
|
for( uint8 o = 0; o < orders.length; o++ ) {
|
||||||
|
SwapOrder memory order = orders[o];
|
||||||
|
// we must explicitly copy into storage because Solidity doesn't implement copying the double-nested
|
||||||
|
// tranches constraints array :(
|
||||||
|
uint orderIndex = self.orders.length;
|
||||||
|
self.orders.push();
|
||||||
|
SwapOrderStatus storage status = self.orders[orderIndex];
|
||||||
|
status.order.amount = order.amount;
|
||||||
|
status.order.amountIsInput = order.amountIsInput;
|
||||||
|
status.order.tokenIn = order.tokenIn;
|
||||||
|
status.order.tokenOut = order.tokenOut;
|
||||||
|
status.order.fee = order.fee;
|
||||||
|
status.order.chainOrder = order.chainOrder;
|
||||||
|
status.order.outputDirectlyToOwner = order.outputDirectlyToOwner;
|
||||||
|
for( uint t=0; t<order.tranches.length; t++ ) {
|
||||||
|
status.order.tranches.push();
|
||||||
|
OrderLib.Tranche memory ot = order.tranches[t]; // order tranche
|
||||||
|
OrderLib.Tranche storage st = status.order.tranches[t]; // status tranche
|
||||||
|
st.fraction = ot.fraction;
|
||||||
|
for( uint c=0; c<ot.constraints.length; c++ )
|
||||||
|
st.constraints.push(ot.constraints[c]);
|
||||||
|
}
|
||||||
|
status.state = SwapOrderState.Open;
|
||||||
|
status.start = uint32(block.timestamp);
|
||||||
|
status.ocoGroup = ocoGroup;
|
||||||
|
}
|
||||||
|
emit DexorderPlaced(startIndex,uint8(orders.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// return codes:
|
||||||
|
//
|
||||||
|
// returns the zero-length string '' on success
|
||||||
|
//
|
||||||
|
// NO order is not open
|
||||||
|
// OCO order was implicitly canceled by an OCO
|
||||||
|
// NI not implemented / unknown constraint
|
||||||
|
// TE current time is too early for this tranche
|
||||||
|
// TL current time is too late for this tranche
|
||||||
|
//
|
||||||
|
function execute(OrdersInfo storage self, uint64 orderIndex, uint8 tranche_index, PriceProof memory proof) internal
|
||||||
|
returns (string memory error)
|
||||||
|
{
|
||||||
|
SwapOrderStatus storage status = self.orders[orderIndex];
|
||||||
|
if (status.state != SwapOrderState.Open)
|
||||||
|
return 'NO'; // Not Open
|
||||||
|
Tranche storage tranche = status.order.tranches[tranche_index];
|
||||||
|
uint160 sqrtPriceX96 = 0;
|
||||||
|
uint160 sqrtPriceLimitX96 = 0;
|
||||||
|
address pool = Constants.uniswapV3Factory.getPool(status.order.tokenIn, status.order.tokenOut, status.order.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)
|
||||||
|
return 'TE';
|
||||||
|
time = tc.latest.mode == TimeMode.Timestamp ? tc.latest.time : status.start + tc.latest.time;
|
||||||
|
if (time < block.timestamp)
|
||||||
|
return 'TL';
|
||||||
|
}
|
||||||
|
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 )
|
||||||
|
return 'L';
|
||||||
|
}
|
||||||
|
else if (constraint.mode == ConstraintMode.Barrier) {
|
||||||
|
return 'NI';
|
||||||
|
}
|
||||||
|
else if (constraint.mode == ConstraintMode.Trailing) {
|
||||||
|
return 'NI';
|
||||||
|
}
|
||||||
|
else if (constraint.mode == ConstraintMode.Line) {
|
||||||
|
return 'NI';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 'NI';
|
||||||
|
// unknown constraint
|
||||||
|
}
|
||||||
|
uint256 amount = status.order.amount * tranche.fraction / 10 ** 18;
|
||||||
|
uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut);
|
||||||
|
if (amount > remaining)
|
||||||
|
amount = remaining;
|
||||||
|
uint256 amountIn;
|
||||||
|
uint256 amountOut;
|
||||||
|
(error, amountIn, amountOut) = _do_execute_univ3(status.order, pool, amount, sqrtPriceLimitX96);
|
||||||
|
if( bytes(error).length == 0 ) {
|
||||||
|
status.filledIn += amountIn;
|
||||||
|
status.filledOut += amountOut;
|
||||||
|
emit DexorderSwapFilled(orderIndex, tranche_index, amountIn, amountOut);
|
||||||
|
_checkCompleted(self, orderIndex, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _do_execute_univ3( SwapOrder storage order, address pool, uint256 amount, uint160 sqrtPriceLimitX96) private
|
||||||
|
returns (string memory error, 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;
|
||||||
|
// todo swap direct to owner
|
||||||
|
if (order.amountIsInput) {
|
||||||
|
amountIn = amount;
|
||||||
|
(error, amountOut) = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams(
|
||||||
|
pool, order.tokenIn, order.tokenOut, order.fee, amount, sqrtPriceLimitX96));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
amountOut = amount;
|
||||||
|
(error, amountIn) = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams(
|
||||||
|
pool, order.tokenIn, order.tokenOut, order.fee, amount, sqrtPriceLimitX96));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkCompleted(OrdersInfo storage self, uint64 orderIndex, SwapOrderStatus storage status) internal {
|
||||||
|
uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut);
|
||||||
|
if( remaining == 0 ) { // todo dust leeway?
|
||||||
|
status.state = SwapOrderState.Filled;
|
||||||
|
emit DexorderCompleted(orderIndex);
|
||||||
|
if( status.ocoGroup != NO_OCO )
|
||||||
|
_cancelOco(self, status.ocoGroup);
|
||||||
|
}
|
||||||
|
else if( status.ocoGroup != NO_OCO && self.ocoGroups[status.ocoGroup].mode == OcoMode.CANCEL_ON_PARTIAL_FILL )
|
||||||
|
_cancelOco(self, status.ocoGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancelOco(OrdersInfo storage self, uint64 ocoIndex) internal {
|
||||||
|
OcoGroup storage group = self.ocoGroups[ocoIndex];
|
||||||
|
uint64 endIndex = group.startIndex + group.num;
|
||||||
|
for( uint64 i=group.startIndex; i<endIndex; i++ )
|
||||||
|
_cancelOrder(self, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancelOrder(OrdersInfo storage self, uint64 orderIndex) internal {
|
||||||
|
SwapOrderState state = self.orders[orderIndex].state;
|
||||||
|
if( state == SwapOrderState.Open || state == SwapOrderState.Template ) {
|
||||||
|
self.orders[orderIndex].state = SwapOrderState.Canceled;
|
||||||
|
emit DexorderCompleted(orderIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity =0.7.6;
|
|
||||||
pragma abicoder v2;
|
|
||||||
|
|
||||||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
||||||
import "v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
|
||||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
||||||
import "./OrderStatus.sol";
|
|
||||||
import "./UniswapSwapper.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TimedOrder is Ownable {
|
|
||||||
|
|
||||||
struct Spec {
|
|
||||||
address tokenIn;
|
|
||||||
address tokenOut;
|
|
||||||
uint24 fee;
|
|
||||||
uint32 deadline; // uint32 is big enough to hold dates through the year 2105
|
|
||||||
uint32 leeway; // if a tranche is not traded within this number of seconds of its scheduled time, it is skipped. If 0, then a reasonable value is generated.
|
|
||||||
uint160 minSqrtPriceX96; // must be in terms of token1/token0 regardless of which token is in/out
|
|
||||||
uint160 maxSqrtPriceX96;
|
|
||||||
uint8 numTranches;
|
|
||||||
uint256 amount; // amount PER TRANCHE
|
|
||||||
bool amountIsInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Status {
|
|
||||||
// includes Spec but has additional status fields
|
|
||||||
OrderStatus status;
|
|
||||||
uint32 start;
|
|
||||||
uint8 tranche;
|
|
||||||
uint8 tranchesExecuted; // may be less than tranche if a tranche was skipped
|
|
||||||
uint256 filledIn;
|
|
||||||
uint256 filledOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
event TimedOrderCreated (address owner, uint64 index, Spec spec);
|
|
||||||
|
|
||||||
event TimedOrderFilled (address owner, uint64 index, uint256 amountIn, uint256 amountOut);
|
|
||||||
|
|
||||||
event TimedOrderCompleted (address owner, uint64 index);
|
|
||||||
|
|
||||||
event TimedOrderError (address owner, uint64 index, string reason);
|
|
||||||
|
|
||||||
|
|
||||||
Spec[] public timedOrderSpecs;
|
|
||||||
Status[] public timedOrderStatuses;
|
|
||||||
|
|
||||||
|
|
||||||
function timedOrder(Spec memory spec) public onlyOwner returns (uint64 index) {
|
|
||||||
uint32 start = uint32(block.timestamp);
|
|
||||||
require(spec.deadline >= start);
|
|
||||||
require(spec.numTranches >= 1);
|
|
||||||
Status memory status = Status(OrderStatus.ACTIVE, start, 0, 0, 0, 0);
|
|
||||||
require(timedOrderStatuses.length < type(uint64).max);
|
|
||||||
index = uint64(timedOrderStatuses.length);
|
|
||||||
timedOrderStatuses.push(status);
|
|
||||||
uint32 trancheInterval = (spec.deadline - uint32(block.timestamp)) / spec.numTranches;
|
|
||||||
spec.leeway = spec.leeway > 0 ? spec.leeway : trancheInterval / 10;
|
|
||||||
if (spec.leeway < 60) // todo configure per chain?
|
|
||||||
spec.leeway = 60;
|
|
||||||
timedOrderSpecs.push(spec);
|
|
||||||
emit TimedOrderCreated(address(this), index, spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cancelTimedOrder(uint64 index) public onlyOwner {
|
|
||||||
require(index < timedOrderStatuses.length);
|
|
||||||
Status storage s = timedOrderStatuses[index];
|
|
||||||
if (s.status == OrderStatus.ACTIVE)
|
|
||||||
s.status = OrderStatus.CANCELED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function triggerTimedOrder(uint64 index) public returns (bool changed) {
|
|
||||||
return _triggerTimedOrder(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function triggerTimedOrders(uint64[] calldata indexes) public returns (bool[] memory changed) {
|
|
||||||
changed = new bool[](indexes.length);
|
|
||||||
for (uint256 i = 0; i < indexes.length; i++) {
|
|
||||||
changed[i] = _triggerTimedOrder(indexes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct _TriggerTimedOrderVars {
|
|
||||||
uint32 interval;
|
|
||||||
uint32 triggerTime;
|
|
||||||
address pool;
|
|
||||||
uint160 sqrtPriceX96;
|
|
||||||
uint160 limit;
|
|
||||||
uint256 amountIn;
|
|
||||||
uint256 amountOut;
|
|
||||||
string error;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _triggerTimedOrder(uint64 index) internal returns (bool changed) {
|
|
||||||
if (!(index < timedOrderStatuses.length)) // ensure valid order index
|
|
||||||
return false;
|
|
||||||
Status storage s = timedOrderStatuses[index];
|
|
||||||
if (!(s.status == OrderStatus.ACTIVE)) // ensure order is active
|
|
||||||
return false;
|
|
||||||
Spec storage c = timedOrderSpecs[index];
|
|
||||||
_TriggerTimedOrderVars memory v;
|
|
||||||
// compute trigger times. try to find a tranche which starts before this block but hasnt expired yet
|
|
||||||
v.interval = (c.deadline - s.start) / c.numTranches;
|
|
||||||
v.triggerTime = s.start + s.tranche * v.interval;
|
|
||||||
while (s.tranche < c.numTranches) {
|
|
||||||
if (v.triggerTime > block.timestamp)
|
|
||||||
return false; // not time yet to trigger
|
|
||||||
if (block.timestamp <= v.triggerTime + c.leeway)
|
|
||||||
break; // triggerTime <= block.timestamp <= triggerTime + intervalLeeway
|
|
||||||
// we have not yet found a tranche which hasn't expired
|
|
||||||
s.tranche++;
|
|
||||||
v.triggerTime += v.interval;
|
|
||||||
}
|
|
||||||
if (_checkCompleted(index, s, c.numTranches))
|
|
||||||
return true;
|
|
||||||
// we have found a valid tranche
|
|
||||||
// check prices
|
|
||||||
v.pool = Constants.uniswapV3Factory.getPool(c.tokenIn, c.tokenOut, c.fee);
|
|
||||||
(v.sqrtPriceX96, , , , , ,) = IUniswapV3Pool(v.pool).slot0();
|
|
||||||
require(v.sqrtPriceX96 >= c.minSqrtPriceX96);
|
|
||||||
require(v.sqrtPriceX96 <= c.maxSqrtPriceX96);
|
|
||||||
// todo swap
|
|
||||||
v.limit = c.tokenIn < c.tokenOut ? c.minSqrtPriceX96 : c.maxSqrtPriceX96;
|
|
||||||
if (c.amountIsInput) {
|
|
||||||
v.amountIn = c.amount;
|
|
||||||
(v.error, v.amountOut) = UniswapSwapper.swapExactInput(UniswapSwapper.SwapParams(
|
|
||||||
v.pool, c.tokenIn, c.tokenOut, c.fee, c.amount, v.limit));
|
|
||||||
if(!_checkSwapError(index, v.error))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
v.amountOut = c.amount;
|
|
||||||
(v.error, v.amountIn) = UniswapSwapper.swapExactOutput(UniswapSwapper.SwapParams(
|
|
||||||
v.pool, c.tokenIn, c.tokenOut, c.fee, c.amount, v.limit));
|
|
||||||
if(!_checkSwapError(index, v.error))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.filledIn += v.amountIn;
|
|
||||||
s.filledOut += v.amountOut;
|
|
||||||
s.tranchesExecuted++;
|
|
||||||
s.tranche++;
|
|
||||||
emit TimedOrderFilled(address(this), index, v.amountIn, v.amountOut);
|
|
||||||
_checkCompleted(index, s, c.numTranches);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _checkCompleted(uint64 index, Status storage s, uint8 numTranches) internal returns (bool completed) {
|
|
||||||
if (s.tranche >= numTranches) {
|
|
||||||
// last tranche has finished
|
|
||||||
s.status = s.tranchesExecuted == numTranches ? OrderStatus.FILLED : OrderStatus.EXPIRED;
|
|
||||||
emit TimedOrderCompleted(address(this), index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _checkSwapError( uint64 index, string memory status ) internal returns (bool ok) {
|
|
||||||
if( bytes(status).length == 0 )
|
|
||||||
return true;
|
|
||||||
emit TimedOrderError(address(this), index, status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ pragma solidity =0.7.6;
|
|||||||
pragma abicoder v2;
|
pragma abicoder v2;
|
||||||
|
|
||||||
import "./Constants.sol";
|
import "./Constants.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
|
||||||
library UniswapSwapper {
|
library UniswapSwapper {
|
||||||
|
|||||||
@@ -5,17 +5,20 @@ pragma abicoder v2;
|
|||||||
import "./Constants.sol";
|
import "./Constants.sol";
|
||||||
import "./interface/IVaultDeployer.sol";
|
import "./interface/IVaultDeployer.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "./OrderLib.sol";
|
||||||
|
|
||||||
|
|
||||||
contract Vault {
|
contract Vault {
|
||||||
|
using OrderLib for OrderLib.OrdersInfo;
|
||||||
|
|
||||||
uint8 public immutable version;
|
uint8 public immutable version;
|
||||||
address public immutable owner;
|
address public immutable owner;
|
||||||
|
OrderLib.OrdersInfo public orderList;
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
(address owner_) = IVaultDeployer(msg.sender).parameters();
|
owner = IVaultDeployer(msg.sender).parameters();
|
||||||
version = Constants.VERSION;
|
version = Constants.VERSION;
|
||||||
owner = owner_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event DexorderReceived(address, uint256);
|
event DexorderReceived(address, uint256);
|
||||||
@@ -28,12 +31,11 @@ contract Vault {
|
|||||||
_withdrawNative(msg.sender, amount);
|
_withdrawNative(msg.sender, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withdraw(address payable recipient, uint256 amount) public {
|
function withdrawTo(address payable recipient, uint256 amount) public {
|
||||||
_withdrawNative(recipient, amount);
|
_withdrawNative(recipient, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _withdrawNative(address payable reipient, uint256 amount) internal {
|
function _withdrawNative(address payable reipient, uint256 amount) internal onlyOwner {
|
||||||
require(msg.sender == owner);
|
|
||||||
reipient.transfer(amount);
|
reipient.transfer(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,13 +43,32 @@ contract Vault {
|
|||||||
_withdraw(token, msg.sender, amount);
|
_withdraw(token, msg.sender, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withdraw(IERC20 token, address recipient, uint256 amount) public {
|
function withdrawTo(IERC20 token, address recipient, uint256 amount) public {
|
||||||
_withdraw(token, recipient, amount);
|
_withdraw(token, recipient, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _withdraw(IERC20 token, address recipient, uint256 amount) internal {
|
function _withdraw(IERC20 token, address recipient, uint256 amount) internal onlyOwner {
|
||||||
require(msg.sender == owner);
|
|
||||||
token.transfer(recipient, amount);
|
token.transfer(recipient, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function placeOrder(OrderLib.SwapOrder memory order) public onlyOwner {
|
||||||
|
orderList._placeOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeOrders(OrderLib.SwapOrder[] memory orders, OrderLib.OcoMode ocoMode) public onlyOwner {
|
||||||
|
orderList._placeOrders(orders, ocoMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute(uint64 orderIndex, uint8 tranche_index, OrderLib.PriceProof memory proof) public
|
||||||
|
returns (string memory error)
|
||||||
|
{
|
||||||
|
return orderList.execute(orderIndex, tranche_index, proof);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyOwner() {
|
||||||
|
require(msg.sender == owner);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ contract VaultDeployer {
|
|||||||
|
|
||||||
Parameters public parameters;
|
Parameters public parameters;
|
||||||
|
|
||||||
function deployVault(address owner) public returns (address vault) {
|
function deployVault(address owner) public returns (address payable vault) {
|
||||||
parameters = Parameters(owner);
|
parameters = Parameters(owner);
|
||||||
vault = address(new Vault{salt: keccak256(abi.encode(owner))}());
|
vault = address(new Vault{salt: keccak256(abi.encode(owner))}());
|
||||||
delete parameters;
|
delete parameters;
|
||||||
|
|||||||
Reference in New Issue
Block a user