Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add react-helmet and DocumentHead component #66

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-draggable": "4.4.5",
"react-helmet": "6.1.0",
"react-markdown": "8.0.5",
"react-responsive-carousel": "3.2.23",
"react-router-dom": "6.8.0",
Expand All @@ -31,6 +32,7 @@
"@types/fontfaceobserver": "2.1.0",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/react-helmet": "6.1.9",
"@types/react-router-dom": "5.3.3",
"@types/react-transition-group": "4.4.5",
"@types/styled-components": "5.1.25",
Expand Down
24 changes: 12 additions & 12 deletions public/blog-posts/oracle-manipulation-101-math-edition.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $x*y=L^2 \hspace{1cm}(1)$

where $L$ is called Liquidity. This $L$ is modified only when someone adds or removes the token balance and is constant otherwise.

![img/blog-posts/oracle-manipulation-101-math/1.png](img/blog-posts/oracle-manipulation-101-math/1.png)
![img/blog-posts/oracle-manipulation-101-math-edition/1.png](img/blog-posts/oracle-manipulation-101-math-edition/1.png)

Anyone can swap token A for token B or vice versa on this pool, modifying the balances $x$ and $y$ in the pool according to $(1)$. You can visualize this behaviour in the Figure ([source](https://medium.com/block-journal/uniswap-understanding-the-decentralised-ethereum-exchange-5ee5d7878996)).

Expand Down Expand Up @@ -119,7 +119,7 @@ L_N,\quad p_N<price

the terms $x_{offset}$ and $y_{offset}$ are only centring the equation in the corresponding range, as you can see in the Figure from the whitepaper:

![img/blog-posts/oracle-manipulation-101-math/2.png](img/blog-posts/oracle-manipulation-101-math/2.png)
![img/blog-posts/oracle-manipulation-101-math-edition/2.png](img/blog-posts/oracle-manipulation-101-math-edition/2.png)

### Understanding Liquidity

Expand All @@ -143,7 +143,7 @@ Liquidity is not straightforward to compute now, as its formula depends on the p

You can find the full code implementation in [this link](https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol#L120). You can play around with different values [here](https://colab.research.google.com/drive/1RwpF-lKq968mvsyL0jgyw9rO_cTqYxPl?usp=sharing). Also, you can find examples [here](https://github.com/atiselsts/uniswap-v3-liquidity-math) and [here](http://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf).

![img/blog-posts/oracle-manipulation-101-math/3.jpg](img/blog-posts/oracle-manipulation-101-math/3.jpg)
![img/blog-posts/oracle-manipulation-101-math-edition/3.jpg](img/blog-posts/oracle-manipulation-101-math-edition/3.jpg)

### Price Manipulation

Expand Down Expand Up @@ -208,7 +208,7 @@ Uniswap knows its role as a decentralized on-chain price source and has built it

$TWAP$ stands for time-weighted average price. It's a geometric average price for a pool over a fixed interval of time and is what we query from the current implementation of the Uniswap v3 Oracle library. It's also a standard trading tool, as seen in the red line in the Figure.

![img/blog-posts/oracle-manipulation-101-math/4.png](img/blog-posts/oracle-manipulation-101-math/4.png)
![img/blog-posts/oracle-manipulation-101-math-edition/4.png](img/blog-posts/oracle-manipulation-101-math-edition/4.png)

> ℹ️ Given 2 numbers, $a_1$ and $a_2$:
> Arithmetic mean: $\frac{a_1+a_2}{2}$
Expand Down Expand Up @@ -244,7 +244,7 @@ You can gain more by playing around at [this link](https://colab.research.google
- Using longer TWAPs will make movements exponentially harder.
- Moving the price over several blocks reduces the costs exponentially.

![img/blog-posts/oracle-manipulation-101-math/5.png](img/blog-posts/oracle-manipulation-101-math/5.png)
![img/blog-posts/oracle-manipulation-101-math-edition/5.png](img/blog-posts/oracle-manipulation-101-math-edition/5.png)

> ℹ️ To manipulate a $TWAP$ to the desired price, an attacker needs to move the spot much more so that the average falls on target. The longer the $TWAP$ length $N$ is relative to the attack's $M$, the harder it is to manipulate. That is why longer $TWAPs$ are suggested for a safer query.
>
Expand All @@ -262,7 +262,7 @@ We will exclude trading fees for simplicity of reading, but you can trivially ad

## Math for Attack Scheme pre PoS

![img/blog-posts/oracle-manipulation-101-math/6.png](img/blog-posts/oracle-manipulation-101-math/6.png)
![img/blog-posts/oracle-manipulation-101-math-edition/6.png](img/blog-posts/oracle-manipulation-101-math-edition/6.png)

The regular scheme for attacking a lending market is the following:

Expand Down Expand Up @@ -306,7 +306,7 @@ $Profit = \Delta x_{out} - \Delta x_{in} = min[fP_fy(\frac{L\sqrt{P_f}-x}{L\sqrt

You can play around simulating the arbitrage scenario in [this link](https://colab.research.google.com/drive/1RwpF-lKq968mvsyL0jgyw9rO_cTqYxPl?usp=sharing). You can see in the Figure below that the optimal attack in this scenario will correspond to using all capital from the manipulation to borrow up to the available reserves (no $\Delta y_{sell}$ left). It's possible to find this optimal price analytically as a function of the reserves, which LPs can use to define safe semi Full-Range positions. Notice this graph does not take TWAP into account and is only valid for markets which query the spot price.

![img/blog-posts/oracle-manipulation-101-math/7.png](img/blog-posts/oracle-manipulation-101-math/7.png)
![img/blog-posts/oracle-manipulation-101-math-edition/7.png](img/blog-posts/oracle-manipulation-101-math-edition/7.png)

To include the $TWAP$ parameters in the analysis, we should compute the Cost of Manipulation $C_{manipulation}^*$ with the spot price added using Eq. $(3)$ while keeping the $TWAP$ price to obtain the stolen amount. We can also simulate this and check that manipulation cost increase radically to the point where single-block attacks are never profitable. Notice that the $TWAP$ is not an on-off switch and has different levels, which we can measure with the ratio $\frac{Length_{attack}}{Length_{TWAP}}\simeq \frac{M}{N}$, with $N$ the approximate number of blocks in the $TWAP$ and $M$ the number of blocks the manipulation lasted.

Expand All @@ -322,21 +322,21 @@ Two main factors can endanger $TWAP$-based oracle liquidity:

1. Bad liquidity positions in Uniswap v3: as we mentioned, a pool is, in most cases, easier to manipulate when liquidity is concentrated rather than over the Full Range. Price manipulation costs zero over regions with no liquidity.

![img/blog-posts/oracle-manipulation-101-math/8.png](img/blog-posts/oracle-manipulation-101-math/8.png)
![img/blog-posts/oracle-manipulation-101-math-edition/8.png](img/blog-posts/oracle-manipulation-101-math-edition/8.png)

1. No liquidity in secondary markets: there is no way for arbitrage to close the trade effectively. As we mentioned, the absence of arbitrage makes manipulation back to the initial price possible (the attacker recovers capital used for price manipulation). It also unlocks multi-block attacks (requires less upfront capital).

Both issues are typical for small projects. This is, for instance, what happened to the stablecoin FLOAT in Rari (see the FLOAT incident in Rari [here](https://etherscan.io/address/0xa2ce300cc17601fc660bac4eeb79bdd9ae61a0e5) and [here](https://www.defilatam.com/rekt/us-1-4-m-ataque-al-pool-90-de-rari-y-una-leccion-de-oracles-en-lending-para-aprendices)): liquidity was deployed only over the 1.16-1.74 USDC per FLOAT in Uniswap, which meant that manipulation cost was zero outside this range. As there was no liquidity in secondary markets, the attacker could wait for a few blocks and significantly impact the registered $TWAP$. Then, they proceeded to empty over $1M USD from the Pool 90 Fuse for only 10k FLOAT.

![img/blog-posts/oracle-manipulation-101-math/9.jpg](img/blog-posts/oracle-manipulation-101-math/9.jpg)
![img/blog-posts/oracle-manipulation-101-math-edition/9.jpg](img/blog-posts/oracle-manipulation-101-math-edition/9.jpg)

> ⚠️ These attacks are the most common for small projects. Attacks in these contexts are hard to distinguish from rug pulls. A lending market can protect itself by reverting the borrowing if the difference between $TWAP$ and spot price is large, but as time passes, the $TWAP$ will get close, and basic checks will pass. Both users and lending markets should be aware of these risks when using or listing low-liquidity tokens. PRICE will include additional methods to mitigate this risk.

## Math for Attack Scheme post-PoS

After the Merge, big stakers have a [high chance](https://alrevuelta.github.io/posts/ethereum-mev-multiblock) of proposing multiple blocks in a row, which makes manipulation back to the initial price possible and significantly lowers the attack cost. It also makes TWAPs cheaper to move, as the attacker can maintain the manipulated price for longer.

![img/blog-posts/oracle-manipulation-101-math/10.jpg](img/blog-posts/oracle-manipulation-101-math/10.jpg)
![img/blog-posts/oracle-manipulation-101-math-edition/10.jpg](img/blog-posts/oracle-manipulation-101-math-edition/10.jpg)

Suppose the validator has $n>2$ consecutive blocks. In that case, the attacker can manipulate over $n-1$ blocks to reduce the initial capital required. In the final block $n$, they can exercise partial manipulation back to the initial price (or near it). As we have shown in Eq. (1), the final spot price to manipulate a $TWAP$ becomes closer to the initial price as the number of proposed blocks increases ($M$ in the equation). It's straightforward to show that the attack cost decreases enormously with this parameter. When protecting an oracle, we must be ready for the worst-case scenario, i.e. the post-PoS multi-block attack.

Expand Down Expand Up @@ -364,9 +364,9 @@ You can play around with a simulation for this attack [here](https://colab.resea

The equilibrium price is a function of $a_{colateral}$. The higher this capital, the lower the target $TWAP$ (but also, the less profit). For significant enough price manipulations, $a_{left}$ is sufficient to be profitable, and $a_{colateral}$ might be unnecessary. This dependence with $a_{colateral}$ complicates the use of almost Full Range positions as a more efficient alternative to Full Range positions.

![img/blog-posts/oracle-manipulation-101-math/11.png](img/blog-posts/oracle-manipulation-101-math/11.png)
![img/blog-posts/oracle-manipulation-101-math-edition/11.png](img/blog-posts/oracle-manipulation-101-math-edition/11.png)

![img/blog-posts/oracle-manipulation-101-math/12.png](img/blog-posts/oracle-manipulation-101-math/12.png)
![img/blog-posts/oracle-manipulation-101-math-edition/12.png](img/blog-posts/oracle-manipulation-101-math-edition/12.png)

This scheme requires an additional up-front capital $a_{back}$ , which is trivially recovered by manipulating back, but it's also the heaviest capital. The up-front cost falls exponentially with the attack length (number of consecutive blocks to propose). The longer the $Lenght_{TWAP}$ the market uses relative to the attack length $Length_{attack}$, the more serious this capital becomes.

Expand Down
21 changes: 21 additions & 0 deletions src/components/common/DocumentHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Helmet } from 'react-helmet';

interface Props {
name?: string;
description?: string;
image?: string;
}

export const DocumentHead = ({ name, description, image }: Props) => {
return (
<Helmet>
<title>{name} - Wonderland</title>
{description && <meta name='description' content={description} />}

<meta property='og:url' content={`https://defi.sucks${location.pathname}`} />
<meta property='og:title' content={`${name} - Wonderland`} />
{description && <meta property='og:description' content={description} />}
{image && <meta property='og:image' content={`https://defi.sucks/${image}`} />}
</Helmet>
);
};
1 change: 1 addition & 0 deletions src/components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './AnimationIn';
export * from './Ball';
export * from './Button';
export * from './DisplayText';
export * from './DocumentHead';
export * from './GradientTitle';
export * from './Link';
export * from './Ring';
Expand Down
4 changes: 2 additions & 2 deletions src/data/blog.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "The current status of on-chain storage proofs",
"date": "11/17/23",
"tags": ["Cryptography", "Verification", "Cross-chain"],
"image": "img/blog-posts/liveness-2-and-beyond/alice-lq.jpg"
"image": "img/blog-posts/liveness-2-and-beyond/cover.jpg"
},
{
"id": "a-mev-racing-story",
Expand All @@ -29,7 +29,7 @@
"description": "What does a Uniswap v3 oracle manipulation look like?",
"date": "12/26/22",
"tags": ["Price", "Oracles", "Uniswap V3", "Math"],
"image": "img/blog-posts/oracle-manipulation-101-math/cover.jpg"
"image": "img/blog-posts/oracle-manipulation-101-math-edition/cover.jpg"
},
{
"id": "rip-oracles",
Expand Down
3 changes: 3 additions & 0 deletions src/pages/Creations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import { Divider } from './ProjectsList/ProjectsList.styles';
import { partnerProjects, publicGoods } from '~/data/projects.json';
import VIDEO_CHROME from '~/assets/videos/creations.webm';
import VIDEO_SAFARI from '~/assets/videos/creations.mp4';
import { DocumentHead } from '~/components/common';

export function Creations() {
return (
<>
<DocumentHead name='Creations' />

<Container>
<BackgroundContainer>
<BG_1 type='2' align='center' />
Expand Down
8 changes: 3 additions & 5 deletions src/pages/Ethos/Ethos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import styled from 'styled-components';

import { PageContent } from '~/components/app';
import { ApproachSection } from './ApproachSection';
import { Ball, MOBILE_MAX_WIDTH, SPACING_192, SPACING_512, SPACING_700 } from '~/components/common';
import { Ball, DocumentHead, MOBILE_MAX_WIDTH, SPACING_192, SPACING_512, SPACING_700 } from '~/components/common';
import TextSection from './TextSection';
import Cone from '~/assets/cone.png';
import HoopTop from '~/assets/hoop-top.png';
Expand Down Expand Up @@ -39,10 +39,6 @@ const SBall = styled(Ball)`
}
`;

const MobileTitleContainer = styled.div`
transform: rotate(3deg);
`;

const BackgroundContainer = styled.div`
position: relative;
width: ${SPACING_512};
Expand Down Expand Up @@ -80,6 +76,8 @@ const STitleContainer = styled(TitleContainer)`
export function Ethos() {
return (
<PageContent>
<DocumentHead name='Ethos' />

<HeroDivider>
<STitleContainer>
<video autoPlay loop muted playsInline>
Expand Down
4 changes: 3 additions & 1 deletion src/pages/Insights/Insights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import VIDEO_CHROME from '~/assets/videos/insights.webm';
import VIDEO_SAFARI from '~/assets/videos/insights.mp4';
import { TitleContainer } from '../Landing/HeroSection';
import { MOBILE_MAX_WIDTH } from '~/components/common';
import { DocumentHead, MOBILE_MAX_WIDTH } from '~/components/common';
import StarIcon from '/img/footer/star-icon.svg';
import styled from 'styled-components';

Expand All @@ -47,6 +47,8 @@ export function Insights() {

return (
<PageContainer>
<DocumentHead name='Insights' />

<Title>
<BlogTitleContainer>
<video autoPlay loop muted playsInline>
Expand Down
26 changes: 20 additions & 6 deletions src/pages/Insights/Posts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,48 @@ import ReactMarkdown from 'react-markdown';
import { CSSTransition } from 'react-transition-group';

import { Background, Title, Date, Content, BackgroundImage } from './Posts.styles';
import { DocumentHead } from '~/components/common';
import posts from '~/data/blog.json';

interface BlogData {
id: string;
name: string;
description: string;
date: string;
tags: string[];
image: string;
}

export function Posts() {
const { id } = useParams();
const [blog, setBlog] = useState('');
const [name, setName] = useState('');
const [date, setDate] = useState('');
const [blogData, setBlogData] = useState<BlogData>();

useEffect(() => {
fetch(`/blog-posts/${id}.md`)
.then((response) => response.text())
.then((data) => {
const post = posts.filter((post) => post.id == id);
setName(post[0].name);
setDate(post[0].date);

setBlogData(post[0]);
setBlog(data);
});
}, []);

return (
<CSSTransition in={!!blog} classNames='fade' timeout={200} appear unmountOnExit>
<>
<Title>{name}</Title>
<DocumentHead
name={blogData?.name}
description={blogData?.description}
image={`blog-posts/${blogData?.id}/cover.jpg`}
/>

<Title>{blogData?.name}</Title>
<BackgroundImage type='3' align='center' />
<Background>
<Content>
<Date>{date}</Date>
<Date>{blogData?.date}</Date>
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex, rehypeRaw]}>
{blog}
</ReactMarkdown>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Landing/Intro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled from 'styled-components';
import { Footer, Navbar, StarsBackground } from '~/containers';
import { Landing } from './Landing';
import { Intro } from './IntroMask/Intro';
import { MOBILE_MAX_WIDTH } from '~/components/common';
import { DocumentHead, MOBILE_MAX_WIDTH } from '~/components/common';
import { useStateContext } from '~/hooks/useStateContext';

export interface StyledContainerProps {
Expand Down
2 changes: 2 additions & 0 deletions src/pages/Squad/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { DocumentHead } from '~/components/common';
import { SquadSection } from './SquadGrid.tsx/index.js';
import { BackgroundImg, Container } from './Squad.styles';

export function Squad() {
return (
<>
<DocumentHead name='Squad' />
<BackgroundImg type='3' align='center' />
<Container>
<SquadSection />
Expand Down
29 changes: 28 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,13 @@
dependencies:
"@types/react" "*"

"@types/react-helmet@6.1.9":
version "6.1.9"
resolved "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.9.tgz#e79e0def2ad4047cb67e83c5be7cfb3d2121615a"
integrity sha512-nuOeTefP4yPTWHvjGksCBKb/4hsgJxSX7aSTjTIDFXJIkZ6Wo2Y4/cmE1FO9OlYBrHjKOer/0zLwY7s4qiQBtw==
dependencies:
"@types/react" "*"

"@types/react-router-dom@5.3.3":
version "5.3.3"
resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
Expand Down Expand Up @@ -2695,7 +2702,7 @@ prettier@2.8.4:
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==

prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.8.1:
prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
Expand Down Expand Up @@ -2742,6 +2749,21 @@ react-easy-swipe@^0.0.21:
dependencies:
prop-types "^15.5.8"

react-fast-compare@^3.1.1:
version "3.2.2"
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==

react-helmet@6.1.0:
version "6.1.0"
resolved "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
dependencies:
object-assign "^4.1.1"
prop-types "^15.7.2"
react-fast-compare "^3.1.1"
react-side-effect "^2.1.0"

react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down Expand Up @@ -2802,6 +2824,11 @@ react-router@6.8.0:
dependencies:
"@remix-run/router" "1.3.1"

react-side-effect@^2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a"
integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==

react-transition-group@4.4.5:
version "4.4.5"
resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
Expand Down