TimedOrder submission works

This commit is contained in:
Tim Olson
2023-12-04 17:59:57 -04:00
parent 9797632ac6
commit c9ccb89d8e
8 changed files with 138 additions and 114 deletions

View File

@@ -47,17 +47,48 @@ export const mockErc20Abi = [...erc20Abi,
'function mint(address,uint256)', 'function mint(address,uint256)',
] ]
const Route = '(uint8,uint24)' const Route = '(uint8 exchange, uint24 fee)'
const Constraint = '(uint8,bytes)' const Tranche = `(
const Tranche = `(uint64,${Constraint}[])` uint16 fraction,
const SwapOrder = `(address,address,${Route},uint256,bool,bool,uint64,${Tranche}[])` bool startTimeIsRelative,
bool endTimeIsRelative,
bool minIsBarrier,
bool maxIsBarrier,
bool marketOrder,
bool _reserved5,
bool _reserved6,
bool _reserved7,
uint8 _reserved8,
uint32 _reserved16,
uint32 startTime,
uint32 endTime,
uint32 minIntercept,
uint32 minSlope,
uint32 maxIntercept,
uint32 maxSlope
)`
const SwapOrder = `(
address tokenIn,
address tokenOut,
${Route} route,
uint256 amount,
uint256 minFillAmount,
bool amountIsInput,
bool outputDirectlyToOwner,
uint64 chainOrder,
${Tranche}[] tranches
)`
export const vaultAbi = [ export const vaultAbi = [
'function withdraw(uint256)', 'function withdraw(uint256 amount)',
'function withdrawTo(address payable,uint256)', 'function withdrawTo(address payable recipient, uint256 amount)',
'function withdraw(address,uint256)', 'function withdraw(address token, uint256 amount)',
'function withdrawTo(address,address,uint256)', 'function withdrawTo(address token, address recipient, uint256 amount)',
'function placeOrder((address,address,(uint8,uint24),uint256,bool,bool,uint64,(uint16,(uint8,bytes)[])[]))', 'function numSwapOrders() view returns (uint64 num)',
'function placeOrders((address,address,(uint8,uint24),uint256,bool,bool,uint64,(uint16,(uint8,bytes)[])[])[],uint8)', `function placeOrder(${SwapOrder})`,
'function cancelOrder(uint64)', `function placeOrders(${SwapOrder}[], uint8 ocoMode)`,
'function cancelOrder(uint64 orderIndex)',
'function cancelAll()',
// `function swapOrderStatus(uint64 orderIndex) view returns (${SwapOrderStatus} memory status)`,
'function orderCanceled(uint64 orderIndex) view returns (bool)',
] ]

View File

@@ -12,9 +12,10 @@ export function applyFills( orderStatus, filled ) {
export function encodeIEE754(value) { export function encodeIEE754(value) {
const buffer = new ArrayBuffer(2); const buffer = new ArrayBuffer(4);
new DataView(buffer).setFloat32(0, value, false /* big endian */); const view = new DataView(buffer);
return buffer; view.setFloat32(0, value, false /* big endian */);
return view.getUint32(0, false);
} }

View File

@@ -1,14 +1,18 @@
import {uint32max, uint64max} from "@/misc.js"; import {uint32max, uint64max} from "@/misc.js";
import {ethers} from "ethers"; import {encodeIEE754} from "@/blockchain/common.js";
export const MAX_FRACTION = 65535;
export const NO_CHAIN = uint64max; export const NO_CHAIN = uint64max;
export const NO_OCO = uint64max; export const NO_OCO = uint64max;
export const DISTANT_PAST = 0
export const DISTANT_FUTURE = uint32max
// struct SwapOrder { // struct SwapOrder {
// address tokenIn; // address tokenIn;
// address tokenOut; // address tokenOut;
// Route route; // Route route;
// uint256 amount; // uint256 amount;
// uint256 minFillAmount; // if a tranche has less than this amount available to fill, it is considered completed
// bool amountIsInput; // bool amountIsInput;
// bool outputDirectlyToOwner; // bool outputDirectlyToOwner;
// 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
@@ -18,24 +22,68 @@ export const NO_OCO = uint64max;
// Exchange exchange; // Exchange exchange;
// uint24 fee; // uint24 fee;
// } // }
export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput, tranches, export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput, tranches, minAmount=null,
outputToOwner = false, chainOrder = NO_CHAIN) { outputToOwner = false, chainOrder = NO_CHAIN) {
if (!tranches) if (!tranches)
tranches = [newTranche(1,[])] // todo this is just a swap: issue warning? tranches = [newTranche({marketOrder: true})] // todo this is just a swap: issue warning?
return [ if( minAmount === null )
tokenIn, tokenOut, [exchange,fee], amount, amountIsInput, outputToOwner, chainOrder, tranches minAmount = BigInt(amount) / 100n // default to min trade size of 1%
] return [tokenIn, tokenOut, [exchange, fee], amount, minAmount, amountIsInput, outputToOwner, chainOrder, tranches]
} }
// struct Tranche { // struct Tranche {
// uint64 fraction; // uint16 fraction;
// Constraint[] constraints; //
// bool startTimeIsRelative;
// bool endTimeIsRelative;
// bool minIsBarrier;
// bool maxIsBarrier;
// bool marketOrder; // if true, both min and max lines are ignored, and minIntercept is treated as a maximum slippage value (use positive numbers)
// bool _reserved5;
// bool _reserved6;
// bool _reserved7;
// uint8 _reserved8;
// uint32 _reserved16;
//
// uint32 startTime; // use DISTANT_PAST to disable
// uint32 endTime; // use DISTANT_FUTURE to disable
//
// // if intercept and slope are both 0 the line is disabled
// float minIntercept; // if marketOrder==true, this is the (positive) max slippage amount
// float minSlope;
// float maxIntercept;
// float maxSlope;
// } // }
export function newTranche(amountRatio, constraints) { export function newTranche({
return [ fraction = MAX_FRACTION,
BigInt(Math.min(65535, Math.ceil(amountRatio * 65535))), // we use ceil to make sure the sum of tranche fractions doesn't round below 1 marketOrder = false,
constraints startTimeIsRelative = false,
] startTime = DISTANT_PAST,
endTimeIsRelative = false,
endTime = DISTANT_FUTURE,
minIsBarrier = false,
minIntercept = 0,
minSlope = 0,
maxIsBarrier = false,
maxIntercept = 0,
maxSlope = 0,
} = {}) {
if( minIntercept === 0 && minSlope === 0 && maxIntercept === 0 && maxSlope === 0 )
marketOrder = true
if( marketOrder )
minIntercept = encodeIEE754(minIntercept) // this is the slippage field for market orders
else {
minIntercept = encodeIEE754(minIntercept)
minSlope = encodeIEE754(minSlope)
maxIntercept = encodeIEE754(maxIntercept)
maxSlope = encodeIEE754(maxSlope)
}
return {
fraction: Math.min(MAX_FRACTION, Math.round(fraction)), marketOrder,
startTimeIsRelative, startTime, endTimeIsRelative, endTime,
minIsBarrier, minIntercept, minSlope, maxIsBarrier, maxIntercept, maxSlope,
_reserved5: false, _reserved6: false, _reserved7: false, _reserved8: 0, _reserved16: 0,
}
} }
// enum Exchange { // enum Exchange {
@@ -47,72 +95,6 @@ export const Exchange = {
UniswapV3: 1, UniswapV3: 1,
} }
// enum ConstraintMode {
// Time, // 0
// Line, // 1
// Barrier // 2
// }
export const ConstraintMode = {
Time: 0,
Line: 1,
Barrier: 2,
}
// struct Constraint {
// ConstraintMode mode;
// bytes constraint; // abi-encoded constraint struct
// }
function encodeConstraint( constraintMode, types, values ) {
return [constraintMode, ethers.AbiCoder.defaultAbiCoder().encode(types,values)]
}
export const TimeMode = {
Timestamp:0,
SinceOrderStart:1,
}
export const DISTANT_PAST = 0
export const DISTANT_FUTURE = uint32max
// struct Time {
// TimeMode mode;
// uint32 time;
// }
// struct TimeConstraint {
// Time earliest;
// Time latest;
// }
export function newTimeConstraint(startMode, start, endMode, end) {
// absolute time
return encodeConstraint(
ConstraintMode.Time,
['uint8', 'uint32', 'uint8', 'uint32'],
[startMode, start, endMode, end]
)
}
// struct LineConstraint {
// bool isAbove;
// bool isRatio;
// uint32 time;
// uint160 valueSqrtX96;
// int160 slopeSqrtX96; // price change per second
// }
export function newLimitConstraint( isAbove, isRatio, price ) {
return newLineConstraint(isAbove, isRatio, 0, price, 0)
}
export function newLineConstraint( isAbove, isRatio, time, price, slope ) {
return encodeConstraint(
ConstraintMode.Line,
['bool', 'bool', 'uint32', 'uint160', 'int160'],
[isAbove, isRatio, time, sqrtX96(price), sqrtX96(slope)]
)
}
export function sqrtX96(value) { export function sqrtX96(value) {
return BigInt(Math.round(Math.sqrt(value * 2 ** (96*2)))) return BigInt(Math.round(Math.sqrt(value * 2 ** (96*2))))
} }

View File

@@ -29,7 +29,7 @@ import {useOrderStore} from "@/store/store";
import LimitPrice from "@/components/LimitPrice.vue"; import LimitPrice from "@/components/LimitPrice.vue";
import Order from "@/components/Order.vue"; import Order from "@/components/Order.vue";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {limitConstraint, maxFraction} from "@/orderbuild.js"; import {applyLimit, maxFraction} from "@/orderbuild.js";
import {validateRequired, validateTranches} from "@/validate.js"; import {validateRequired, validateTranches} from "@/validate.js";
const os = useOrderStore() const os = useOrderStore()
@@ -82,7 +82,7 @@ function buildTranches() {
const mf = Number(maxFraction) const mf = Number(maxFraction)
for( let i=0; i<n; i++ ) { for( let i=0; i<n; i++ ) {
// todo optional deadline // todo optional deadline
const cs = [limitConstraint(rungs.value[i])] const cs = [applyLimit(rungs.value[i])]
const fraction = Math.min(mf, Math.ceil(mf * fractions.value[i]) ) const fraction = Math.min(mf, Math.ceil(mf * fractions.value[i]) )
ts.push([fraction, cs]) ts.push([fraction, cs])
} }

View File

@@ -43,7 +43,7 @@ function placeOrder() {
const route = os.route const route = os.route
const amt = FixedNumber.fromString(os.totalAmount.toString(), {decimals: os.amountToken.decimals}).value const amt = FixedNumber.fromString(os.totalAmount.toString(), {decimals: os.amountToken.decimals}).value
const ts = props.tranches() const ts = props.tranches()
const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, os.amountIsInput, ts) const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, os.amountIsInput, ts) // todo: minAmount, outputToOwner, chainOrder
pendOrder(order) pendOrder(order)
router.push('/orders') router.push('/orders')
} }

View File

@@ -10,7 +10,7 @@
import {routeInverted, SingletonCoroutine, vAutoSelect} from "@/misc.js"; import {routeInverted, SingletonCoroutine, vAutoSelect} from "@/misc.js";
import Order from "@/components/Order.vue"; import Order from "@/components/Order.vue";
import LimitPrice from "@/components/LimitPrice.vue"; import LimitPrice from "@/components/LimitPrice.vue";
import {timesliceTranches, limitConstraint} from "@/orderbuild.js"; import {timesliceTranches, applyLimit} from "@/orderbuild.js";
import Tranches from "@/components/Tranches.vue"; import Tranches from "@/components/Tranches.vue";
import {useOrderStore} from "@/store/store.js"; import {useOrderStore} from "@/store/store.js";
@@ -18,10 +18,11 @@ const os = useOrderStore()
function buildTranches() { function buildTranches() {
const ts = timesliceTranches(); const ts = timesliceTranches();
const priceConstraint = limitConstraint(); const os = useOrderStore()
if( priceConstraint !== null ) const price = os.limitPrice
if( price )
for( let i=0; i<ts.length; i++) for( let i=0; i<ts.length; i++)
ts[i][1].push(priceConstraint) applyLimit(ts[i], price)
return ts return ts
} }

View File

@@ -43,8 +43,8 @@ export const vAutoSelect = {
input.onfocus = () => setTimeout(() => input.select(), 0) input.onfocus = () => setTimeout(() => input.select(), 0)
} }
} }
export const uint64max = 18446744073709551615n
export const uint32max = 4294967295n export const uint32max = 4294967295n
export const uint64max = 18446744073709551615n
export function tokenNumber(token, balance) { export function tokenNumber(token, balance) {
return FixedNumber.fromValue(balance, token.decimals, {decimals:token.decimals, width: 256}) return FixedNumber.fromValue(balance, token.decimals, {decimals:token.decimals, width: 256})

View File

@@ -1,17 +1,14 @@
import {routeInverted} from "@/misc.js"; import {routeInverted} from "@/misc.js";
import {newLimitConstraint, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js"; import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import {useOrderStore} from "@/store/store.js"; import {useOrderStore} from "@/store/store.js";
export const maxFraction = 65535n // by contract definition of uint16 fraction export function applyLimit(tranche, price=null) {
export function limitConstraint(price=null) {
const os = useOrderStore() const os = useOrderStore()
if( price === null ) { if( price === null ) {
price = os.limitPrice price = os.limitPrice
if (!price) if (!price)
return null return
} }
const route = os.route const route = os.route
const inverted = routeInverted(route) const inverted = routeInverted(route)
@@ -19,7 +16,15 @@ export function limitConstraint(price=null) {
const isRatio = false // todo ratios const isRatio = false // todo ratios
const decimals = 10 ** (os.tokenA.decimals - os.tokenB.decimals) const decimals = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
const limit = inverted ? decimals / price : price / decimals const limit = inverted ? decimals / price : price / decimals
return newLimitConstraint(isAbove, isRatio, limit) tranche.marketOrder = false;
if( isAbove ) {
tranche.minIntercept = limit;
tranche.minSlope = 0;
}
else {
tranche.maxIntercept = limit;
tranche.maxSlope = 0;
}
} }
export function timesliceTranches() { export function timesliceTranches() {
@@ -39,14 +44,18 @@ export function timesliceTranches() {
} else { } else {
window = Math.round(duration / n) window = Math.round(duration / n)
} }
const ceil = maxFraction % BigInt(n) ? 1n : 0n const amtPerTranche = Math.ceil(MAX_FRACTION / n)
const amtPerTranche = maxFraction / BigInt(n) + ceil
duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
const start = Math.floor(i * (duration / Math.max((n - 1), 1))) const start = Math.floor(i * (duration / Math.max((n - 1), 1)))
const end = start + window const end = start + window
const cs = [newTimeConstraint(TimeMode.SinceOrderStart, start, TimeMode.SinceOrderStart, end)] ts.push(newTranche({
ts.push([amtPerTranche, cs]) fraction: amtPerTranche,
startTimeIsRelative: true,
startTime: start,
endTimeIsRelative: true,
endTime: end,
}))
} }
return ts return ts
} }