Skip to content

Commit dc32c18

Browse files
feat: basic limit orders (#2649)
1 parent 8a84e96 commit dc32c18

40 files changed

+2135
-515
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,4 @@
157157
"budgetPercentIncreaseRed": 20,
158158
"showDetails": true
159159
}
160-
}
160+
}

pages/_app.page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ const StakingMigrateModal = dynamic(() =>
8585
(module) => module.StakingMigrateModal
8686
)
8787
);
88+
const CancelCowOrderModal = dynamic(() =>
89+
import('src/components/transactions/CancelCowOrder/CancelCowOrderModal').then(
90+
(module) => module.CancelCowOrderModal
91+
)
92+
);
8893
const ReadOnlyModal = dynamic(() =>
8994
import('src/components/WalletConnection/ReadOnlyModal').then((module) => module.ReadOnlyModal)
9095
);
@@ -177,6 +182,7 @@ export default function MyApp(props: MyAppProps) {
177182
<BridgeModal />
178183
<ReadOnlyModal />
179184
<CowOrderToast />
185+
<CancelCowOrderModal />
180186
</GasStationProvider>
181187
</AppDataProvider>
182188
</SharedDependenciesProvider>

src/architecture/FPMath.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FixedPointNumber, Numberish } from './FixedPointNumber';
2+
3+
export class FPMath {
4+
static MaxUint256: FixedPointNumber = new FixedPointNumber(
5+
BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
6+
BigInt(0)
7+
);
8+
9+
static ZERO = new FixedPointNumber(BigInt(0), BigInt(0));
10+
11+
static ONE = new FixedPointNumber(BigInt(1), BigInt(0));
12+
13+
static MINUS_ONE = new FixedPointNumber(BigInt(-1), BigInt(0));
14+
15+
private static maxScale(a: FixedPointNumber, b: FixedPointNumber): bigint {
16+
return a.scale > b.scale ? a.scale : b.scale;
17+
}
18+
19+
static min(a: FixedPointNumber, b: FixedPointNumber): FixedPointNumber {
20+
const maxScale = FPMath.maxScale(a, b);
21+
const scaledA = a.scaleUp(maxScale);
22+
const scaledB = b.scaleUp(maxScale);
23+
return scaledA.value < scaledB.value ? a : b;
24+
}
25+
26+
static max(a: FixedPointNumber, b: FixedPointNumber): FixedPointNumber {
27+
const maxScale = FPMath.maxScale(a, b);
28+
const scaledA = a.scaleUp(maxScale);
29+
const scaledB = b.scaleUp(maxScale);
30+
return scaledA.value > scaledB.value ? a : b;
31+
}
32+
33+
static toFP(value: Numberish, _scale: Numberish): FixedPointNumber {
34+
const scale = Number(_scale);
35+
const stringValue = value.toString();
36+
const [integerPart, decimalPart] = stringValue.split('.');
37+
if (!decimalPart) {
38+
const zeroes = '0'.repeat(scale);
39+
return new FixedPointNumber(integerPart.concat(zeroes), scale);
40+
}
41+
const roundedDecimalPart =
42+
decimalPart.length > scale ? decimalPart.slice(0, scale) : decimalPart;
43+
const zeroes = '0'.repeat(scale - roundedDecimalPart.length);
44+
return new FixedPointNumber(integerPart.concat(roundedDecimalPart, zeroes), scale);
45+
}
46+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import invariant from 'tiny-invariant';
2+
3+
export type Numberish = number | string | bigint;
4+
export type FixedPointNumberValue<T extends FixedPointNumber = FixedPointNumber> = Numberish | T;
5+
6+
export enum RoundMode {
7+
ROUND_DOWN,
8+
ROUND_UP,
9+
ROUND_HALF_UP,
10+
ROUND_HALF_DOWN,
11+
}
12+
13+
interface IFixedPointNumber {
14+
add(value: FixedPointNumberValue): IFixedPointNumber;
15+
sub(value: FixedPointNumberValue): IFixedPointNumber;
16+
mul(value: FixedPointNumberValue): IFixedPointNumber;
17+
div(value: FixedPointNumberValue): IFixedPointNumber;
18+
pow(exp: FixedPointNumberValue): IFixedPointNumber;
19+
scaleUp(scale: number): IFixedPointNumber;
20+
scaleDown(scale: number, rounding: RoundMode): IFixedPointNumber;
21+
format(decimals?: number): string;
22+
}
23+
24+
const countDecimals = (n: number) => {
25+
if (Math.floor(n) === n) return 0;
26+
return n.toString().split('.')[1].length || 0;
27+
};
28+
29+
export class FixedPointNumber implements IFixedPointNumber {
30+
readonly value: bigint;
31+
readonly scale: bigint;
32+
33+
constructor(value: FixedPointNumberValue, scale?: Numberish) {
34+
if (value instanceof FixedPointNumber) {
35+
this.value = value.value;
36+
this.scale = value.scale;
37+
} else {
38+
invariant(
39+
scale !== undefined,
40+
'scale must be provided if initializing from other than a FixedPointNumber'
41+
);
42+
this.value = BigInt(value);
43+
this.scale = BigInt(scale);
44+
}
45+
}
46+
47+
add(value: FixedPointNumberValue): FixedPointNumber {
48+
if (!(value instanceof FixedPointNumber)) {
49+
const wrappedValue = BigInt(value);
50+
return new FixedPointNumber(wrappedValue + this.value, this.scale);
51+
}
52+
if (this.scale === value.scale) {
53+
return new FixedPointNumber(this.value + value.value, this.scale);
54+
}
55+
if (this.scale > value.scale) {
56+
return new FixedPointNumber(this.value + value.scaleUp(this.scale).value, this.scale);
57+
}
58+
return new FixedPointNumber(this.scaleUp(value.scale).value + value.value, value.scale);
59+
}
60+
61+
sub(value: FixedPointNumberValue): FixedPointNumber {
62+
if (!(value instanceof FixedPointNumber)) {
63+
const wrappedValue = BigInt(value);
64+
return new FixedPointNumber(this.value - wrappedValue, this.scale);
65+
}
66+
if (this.scale === value.scale) {
67+
return new FixedPointNumber(this.value - value.value, this.scale);
68+
}
69+
if (this.scale > value.scale) {
70+
return new FixedPointNumber(this.value - value.scaleUp(this.scale).value, this.scale);
71+
}
72+
return new FixedPointNumber(this.scaleUp(value.scale).value - value.value, value.scale);
73+
}
74+
75+
mul(value: FixedPointNumberValue): FixedPointNumber {
76+
if (!(value instanceof FixedPointNumber)) {
77+
const wrappedValue = BigInt(value);
78+
return new FixedPointNumber(this.value * wrappedValue, this.scale * BigInt(2));
79+
}
80+
return new FixedPointNumber(this.value * value.value, this.scale + value.scale);
81+
}
82+
83+
scaleMul(value: Numberish): FixedPointNumber {
84+
const wrappedValue = BigInt(value);
85+
return new FixedPointNumber(this.value * wrappedValue, this.scale);
86+
}
87+
88+
div(value: FixedPointNumberValue): FixedPointNumber {
89+
if (!(value instanceof FixedPointNumber)) {
90+
const wrappedValue = BigInt(value);
91+
return new FixedPointNumber(this.value / wrappedValue, 0);
92+
}
93+
const dividend = this.scale < value.scale ? this.scaleUp(value.scale) : this;
94+
const dividendWithMinPrecision =
95+
dividend.scale - value.scale < BigInt(3) ? this.scaleUp(dividend.scale + BigInt(3)) : this;
96+
return new FixedPointNumber(
97+
dividendWithMinPrecision.value / value.value,
98+
dividendWithMinPrecision.scale - value.scale
99+
);
100+
}
101+
102+
scaleDiv(value: Numberish): FixedPointNumber {
103+
const wrappedValue = BigInt(value);
104+
return new FixedPointNumber(this.value / wrappedValue, this.scale);
105+
}
106+
107+
pow(exp: Numberish): FixedPointNumber {
108+
const wrappedExp = BigInt(exp);
109+
return new FixedPointNumber(this.value ** BigInt(exp), this.scale ** wrappedExp);
110+
}
111+
112+
scaleit(scale: Numberish): FixedPointNumber {
113+
const wrappedScale = BigInt(scale);
114+
if (wrappedScale === this.scale) {
115+
return this;
116+
}
117+
if (wrappedScale > this.scale) {
118+
return this.scaleUp(scale);
119+
}
120+
return this.scaleDown(scale, RoundMode.ROUND_DOWN);
121+
}
122+
123+
scaleUp(scale: Numberish): FixedPointNumber {
124+
const wrappedScale = BigInt(scale);
125+
invariant(wrappedScale >= this.scale, 'scale must be higher or equal');
126+
return new FixedPointNumber(
127+
this.value * BigInt(10) ** BigInt(wrappedScale - this.scale),
128+
scale
129+
);
130+
}
131+
132+
scaleDown(scale: Numberish, rounding: RoundMode): FixedPointNumber {
133+
const wrappedScale = BigInt(scale);
134+
if (wrappedScale === this.scale) return this;
135+
// we leave 1 extra decimal to handle rounding
136+
const preNewValue = this.value / BigInt(10) ** BigInt(this.scale - wrappedScale - BigInt(1));
137+
switch (rounding) {
138+
case RoundMode.ROUND_DOWN:
139+
return new FixedPointNumber(preNewValue / BigInt(10), wrappedScale);
140+
case RoundMode.ROUND_UP:
141+
return new FixedPointNumber(preNewValue / BigInt(10) + BigInt(1), wrappedScale);
142+
case RoundMode.ROUND_HALF_UP:
143+
return new FixedPointNumber((preNewValue + BigInt(5)) / BigInt(10), wrappedScale);
144+
case RoundMode.ROUND_HALF_DOWN:
145+
return new FixedPointNumber((preNewValue + BigInt(4)) / BigInt(10), wrappedScale);
146+
}
147+
}
148+
149+
eq(value: FixedPointNumberValue): boolean {
150+
if (value instanceof FixedPointNumber) {
151+
if (this.scale > value.scale) {
152+
return this.value === value.value * BigInt(10) * (this.scale - value.scale);
153+
}
154+
return this.value * BigInt(10) ** (value.scale - this.scale) === value.value;
155+
}
156+
return this.value === BigInt(value);
157+
}
158+
159+
lte(value: FixedPointNumberValue): boolean {
160+
if (value instanceof FixedPointNumber) {
161+
if (this.scale > value.scale) {
162+
return this.value <= value.value * BigInt(10) ** (this.scale - value.scale);
163+
}
164+
return this.value * BigInt(10) ** (value.scale - this.scale) <= value.value;
165+
}
166+
return this.value <= BigInt(value);
167+
}
168+
169+
gte(value: FixedPointNumberValue): boolean {
170+
if (value instanceof FixedPointNumber) {
171+
if (this.scale > value.scale) {
172+
return this.value >= value.value * BigInt(10) ** (this.scale - value.scale);
173+
}
174+
return this.value * BigInt(10) ** (value.scale - this.scale) >= value.value;
175+
}
176+
return this.value >= BigInt(value);
177+
}
178+
179+
lt(value: FixedPointNumberValue): boolean {
180+
if (value instanceof FixedPointNumber) {
181+
if (this.scale > value.scale) {
182+
return this.value < value.value * BigInt(10) ** (this.scale - value.scale);
183+
}
184+
return this.value * BigInt(10) ** (value.scale - this.scale) < value.value;
185+
}
186+
return this.value < BigInt(value);
187+
}
188+
189+
gt(value: FixedPointNumberValue): boolean {
190+
if (value instanceof FixedPointNumber) {
191+
if (this.scale > value.scale) {
192+
return this.value > value.value * BigInt(10) ** (this.scale - value.scale);
193+
}
194+
return this.value * BigInt(10) ** (value.scale - this.scale) > value.value;
195+
}
196+
return this.value > BigInt(value);
197+
}
198+
199+
percent(percent: number): FixedPointNumber {
200+
const decimals = countDecimals(percent);
201+
const integerPercent = BigInt(percent * 10 ** decimals);
202+
const scaledPercent = integerPercent * BigInt(10) ** this.scale;
203+
const scaledDivisor = BigInt(10) ** (this.scale + BigInt(decimals));
204+
return this.scaleMul(scaledPercent).scaleDiv(scaledDivisor);
205+
}
206+
207+
format(decimals?: number): string {
208+
const stringValue = this.value.toString();
209+
const positivePart = this.value < BigInt(0) ? stringValue.substring(1) : stringValue;
210+
const paddedPositivePart = positivePart.padStart(Number(this.scale) + 1, '0');
211+
const positiveNumbers = paddedPositivePart.length - Number(this.scale);
212+
const positiveWithDecimals =
213+
paddedPositivePart.length === positiveNumbers
214+
? paddedPositivePart.slice(0, positiveNumbers)
215+
: paddedPositivePart.slice(0, positiveNumbers) +
216+
'.' +
217+
paddedPositivePart.slice(
218+
positiveNumbers,
219+
decimals ? positiveNumbers + decimals : undefined
220+
);
221+
if (this.value < BigInt(0)) {
222+
return '-' + positiveWithDecimals;
223+
}
224+
return positiveWithDecimals;
225+
}
226+
227+
toNumber(): number {
228+
return Number(this.format());
229+
}
230+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Trans } from '@lingui/macro';
2+
3+
import { TextWithTooltip } from '../TextWithTooltip';
4+
5+
export const NetworkCostTooltip = () => {
6+
return (
7+
<TextWithTooltip variant="caption" text={<Trans>Network costs</Trans>}>
8+
<Trans>
9+
This is the cost of settling your order on-chain, including gas and any LP fees.
10+
</Trans>
11+
</TextWithTooltip>
12+
);
13+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Trans } from '@lingui/macro';
2+
3+
import { Link } from '../primitives/Link';
4+
import { TextWithTooltip } from '../TextWithTooltip';
5+
6+
export const SwapFeeTooltip = () => {
7+
return (
8+
<TextWithTooltip variant="caption" text={<Trans>Fee</Trans>}>
9+
<Trans>
10+
Fees help support the user experience and security of the Aave application.{' '}
11+
<Link
12+
href="https://aave.com/docs/developers/smart-contracts/swap-features"
13+
target="_blank"
14+
rel="noopener"
15+
>
16+
Learn more.
17+
</Link>
18+
</Trans>
19+
</TextWithTooltip>
20+
);
21+
};

0 commit comments

Comments
 (0)