This commit is contained in:
Zargham 2019-08-11 11:59:05 +02:00
commit a0d6bf03af
11 changed files with 431 additions and 112 deletions

View File

@ -15,15 +15,27 @@ import SupplyVsDemandChart from "./SupplyVsDemandChart";
import ResultParams from "./ResultParams"; import ResultParams from "./ResultParams";
import PriceSimulationChart from "./PriceSimulationChart"; import PriceSimulationChart from "./PriceSimulationChart";
import HelpText from "./HelpText"; import HelpText from "./HelpText";
// Text content
import {
parameterDescriptions,
simulationParameterDescriptions,
resultParameterDescriptions
} from "./parametersDescriptions";
// Utils // Utils
import { getLast, getAvg, pause } from "./utils"; import { getLast, getAvg, pause } from "./utils";
import { import {
getInitialParams, getInitialParams,
getPriceR, getPriceR,
getMinPrice,
getS,
vest_tokens,
getR,
getSlippage, getSlippage,
getTxDistribution, getTxDistribution,
getDeltaR_priceGrowth, getDeltaR_priceGrowth,
rv_U rv_U,
getMedian,
getSum
} from "./math"; } from "./math";
import { throttle } from "lodash"; import { throttle } from "lodash";
// General styles // General styles
@ -116,65 +128,17 @@ const useStyles = makeStyles((theme: Theme) =>
} }
}, },
descriptionTitle: { descriptionTitle: {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(0.5) padding: theme.spacing(0.5)
}, },
descriptionName: { descriptionBody: {
fontWeight: theme.typography.fontWeightBold color: "#dbdfe4"
},
descriptionPadding: {
padding: theme.spacing(0.5)
} }
}) })
); );
const parameterDescriptions = [
{
name: "Initial raise",
text: "Total funds raised in the hatch period of the ABC launch"
},
{
name: "Allocation to funding pool",
text:
"The percentage of the funds raised in the Hatch sale that go directly into the project funding pool to compensate future work done in the project"
},
{
name: "Hatch price",
text:
"The price paid per 'ABC token' by community members involved in hatching the project"
},
{
name: "Post-hatch price",
text:
"The price of the 'ABC token' when the curve enters the open phase and is live for public participation"
},
{
name: "Exit tribute",
text:
"The percentage of funds that are diverted to the project funding pool from community members who exit funds from the project by burning 'ABC tokens' in exchange for collateral"
}
];
const resultParameterDescriptions = [
{
name: "Total reserve",
text:
"Total DAI in the smart contract reserve at the end of the simulated period"
},
{
name: "Funds generated from initial hatch",
text:
"Fraction of the funds (theta) raised during the hatch that go directly to the cause"
},
{
name: "Funds generated from exit tributes",
text:
"Cumulative amount of exit tributes collected from only exit /sell transactions"
},
{
name: "Average slippage",
text:
"Average of the slippage of each transaction occured during the simulation period"
}
];
export default function App() { export default function App() {
const [curveParams, setCurveParams] = useState({ const [curveParams, setCurveParams] = useState({
theta: 0.35, // fraction allocated to reserve (.) theta: 0.35, // fraction allocated to reserve (.)
@ -199,7 +163,7 @@ export default function App() {
const { const {
k, // Invariant power kappa (.) k, // Invariant power kappa (.)
R0, // Initial reserve (DAI) R0, // Initial reserve (DAI)
// S0, // initial supply of tokens (token) S0, // initial supply of tokens (token)
V0 // invariant coef V0 // invariant coef
} = getInitialParams({ } = getInitialParams({
d0, d0,
@ -210,6 +174,7 @@ export default function App() {
const [priceTimeseries, setPriceTimeseries] = useState([0]); const [priceTimeseries, setPriceTimeseries] = useState([0]);
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]); const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
const [floorpriceTimeseries, setFloorpriceTimeseries] = useState([0]);
const [totalReserve, setTotalReserve] = useState(R0); const [totalReserve, setTotalReserve] = useState(R0);
const [withdrawCount, setWithdrawCount] = useState(0); const [withdrawCount, setWithdrawCount] = useState(0);
const [avgSlippage, setAvgSlippage] = useState(0); const [avgSlippage, setAvgSlippage] = useState(0);
@ -248,48 +213,100 @@ export default function App() {
async function simulateRandomDelta() { async function simulateRandomDelta() {
const R_t: number[] = [R0]; const R_t: number[] = [R0];
const S_t: number[] = [S0];
const p_t: number[] = [getPriceR({ R: R0, V0, k })]; const p_t: number[] = [getPriceR({ R: R0, V0, k })];
const wFee_t: number[] = [0]; const wFee_t: number[] = [0];
const slippage_t: number[] = []; const slippage_t: number[] = [];
const avgTxSize_t: number[] = []; const avgTxSize_t: number[] = [];
// hatchers tokens = S0[section added by Z]
const H_t: number[] = [S0]; // total hatcher tokens not vested
const floorprice_t: number[] = []; // initially the price is the floor as all tokens are hatcher tokens
// Random walk // Random walk
const numSteps = 52; const numSteps = 52;
const u_min = 0.97;
const u_max = 1.04;
const tx_spread = 10;
// vesting(should this be exposed in the app ?)
const cliff = 8; // weeks before vesting starts ~2 months
const halflife = 52; // 26 weeks, half life is ~6 months
// percentage of the hatch tokens which vest per week(since that is our timescale in the sim)
// numSteps = 52 take 8ms to run // numSteps = 52 take 8ms to run
setSimulationRunning(true); setSimulationRunning(true);
for (let t = 0; t < numSteps; t++) { for (let t = 0; t < numSteps; t++) {
const txsWeek = Math.ceil(t < 5 ? rv_U(0, 5) : rv_U(5, 2 * t)); const txsWeek = rv_U(5, 2 * t + 5);
const priceGrowth = rv_U(0.99, 1.03);
const R = getLast(R_t); const R = getLast(R_t);
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth }); const S = getLast(S_t);
const H = getLast(H_t);
// enforce the effects of the unvested tokens not being burnable
let u_lower;
if (H > S) {
u_lower = 1;
} else {
// compute the reserve if all that supply is burned
const R_ratio = getR({ S: S - H, V0, k }) / R;
u_lower = Math.max(1 - R_ratio, u_min);
}
const priceGrowth = rv_U(u_lower, u_max);
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth });
const R_next = R + deltaR; const R_next = R + deltaR;
const txs = getTxDistribution({ sum: deltaR, num: txsWeek }); const txs = getTxDistribution({
sum: deltaR,
num: txsWeek,
spread: tx_spread
});
// Compute slippage // Compute slippage
const slippage = getAvg( const slippage_txs = txs.map(txR =>
txs.map(txR => getSlippage({ R, deltaR: txR, V0, k })) getSlippage({ R, deltaR: txR, V0, k })
); );
const slippage = getMedian(slippage_txs);
const txsWithdraw = txs.filter(tx => tx < 0); const txsWithdraw = txs.filter(tx => tx < 0);
const wFees = -wFee * txsWithdraw.reduce((t, c) => t + c, 0); const wFees = -wFee * getSum(txsWithdraw);
const _avgTxSize = // txsWithdraw.reduce((t, c) => t + c, 0);
txs.reduce((t, c) => t + Math.abs(c), 0) / txs.length;
// Vest
const delta_H = vest_tokens({ week: t, H, halflife, cliff });
const H_next = H - delta_H;
// find floor price
const S_next = getS({ R, V0, k });
const floorprice_next = getMinPrice({
S: S_next,
H: S0 - H_next,
V0,
k
});
const _avgTxSize = getMedian(txsWithdraw);
R_t.push(R_next); R_t.push(R_next);
S_t.push(S_next);
H_t.push(H_next);
p_t.push(getPriceR({ R: R_next, V0, k })); p_t.push(getPriceR({ R: R_next, V0, k }));
slippage_t.push(slippage); slippage_t.push(slippage);
avgTxSize_t.push(_avgTxSize); avgTxSize_t.push(_avgTxSize);
wFee_t.push(getLast(wFee_t) + wFees); wFee_t.push(getLast(wFee_t) + wFees);
floorprice_t.push(floorprice_next);
setWithdrawCount(c => c + txsWithdraw.length); setWithdrawCount(c => c + txsWithdraw.length);
// Stop the simulation if it's no longer active // Stop the simulation if it's no longer active
if (!simulationActive || !canContinueSimulation) break; if (!simulationActive || !canContinueSimulation) break;
} }
// floorprice_t is missing one data point
floorprice_t[floorprice_t.length] = floorprice_t[floorprice_t.length - 1];
setPriceTimeseries(p_t); setPriceTimeseries(p_t);
setWithdrawFeeTimeseries(wFee_t); setWithdrawFeeTimeseries(wFee_t);
setFloorpriceTimeseries(floorprice_t);
setAvgSlippage(getAvg(slippage_t)); setAvgSlippage(getAvg(slippage_t));
setAvgTxSize(getAvg(avgTxSize_t)); setAvgTxSize(getAvg(avgTxSize_t));
setTotalReserve(getLast(R_t)); setTotalReserve(getLast(R_t));
@ -307,14 +324,17 @@ export default function App() {
const resultFields = [ const resultFields = [
{ {
label: `Total reserve`, label: `Total reserve`,
description: resultParameterDescriptions.totalReserve.text,
value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI" value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI"
}, },
{ {
label: `Funds generated from initial hatch`, label: `Funds generated from initial hatch`,
description: resultParameterDescriptions.initialHatchFunds.text,
value: Math.round(d0 * theta).toLocaleString() + " DAI" value: Math.round(d0 * theta).toLocaleString() + " DAI"
}, },
{ {
label: `Funds generated from exit tributes (${withdrawCount} txs)`, label: `Funds generated from exit tributes (${withdrawCount} txs)`,
description: resultParameterDescriptions.exitTributes.text,
value: value:
(+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() + (+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() +
" DAI" " DAI"
@ -323,6 +343,7 @@ export default function App() {
label: `Average slippage (avg tx size ${Math.round( label: `Average slippage (avg tx size ${Math.round(
avgTxSize avgTxSize
).toLocaleString()} DAI)`, ).toLocaleString()} DAI)`,
description: resultParameterDescriptions.slippage.text,
value: +(100 * avgSlippage).toFixed(3) + "%" value: +(100 * avgSlippage).toFixed(3) + "%"
} }
]; ];
@ -353,15 +374,21 @@ export default function App() {
</div> </div>
<table> <table>
<tbody> <tbody>
{parameterDescriptions.map(({ name, text }) => ( {[
parameterDescriptions.theta,
parameterDescriptions.p0,
parameterDescriptions.p1,
parameterDescriptions.wFee,
parameterDescriptions.d0
].map(({ name, text }) => (
<tr key={name}> <tr key={name}>
<td> <td>
<Typography className={classes.descriptionName}> <Typography>{name}</Typography>
{name}
</Typography>
</td> </td>
<td> <td>
<Typography>{text}</Typography> <Typography className={classes.descriptionBody}>
{text}
</Typography>
</td> </td>
</tr> </tr>
))} ))}
@ -398,13 +425,16 @@ export default function App() {
<Typography variant="h6">Preview</Typography> <Typography variant="h6">Preview</Typography>
<HelpText <HelpText
text={ text={
<Typography> <div className={classes.descriptionPadding}>
Visualization of the token bonding curve analytic function <Typography className={classes.descriptionBody}>
on a specific range of reserve [0, 4 * R0]. This result is Visualization of the token bonding curve analytic
deterministic given the current set of parameters and will function on a specific range of reserve [0, 4 * R0].
never change regardes of the campaign performance, it only This result is deterministic given the current set of
shows how the price will react to reserve changes. parameters and will never change regardes of the
</Typography> campaign performance, it only shows how the price will
react to reserve changes.
</Typography>
</div>
} }
/> />
</Box> </Box>
@ -449,14 +479,40 @@ export default function App() {
<Typography variant="h6">Simulation</Typography> <Typography variant="h6">Simulation</Typography>
<HelpText <HelpText
text={ text={
<Typography> <div className={classes.descriptionContainer}>
This chart shows a 52 week simulation of discrete <div className={classes.descriptionPadding}>
transactions interacting with the token bonding curve. <Typography className={classes.descriptionBody}>
Each transaction adds or substract reserve to the This chart shows a 52 week simulation of discrete
system, modifying the price over time. The frequency, transactions interacting with the token bonding
size and direction of each transaction is computed curve. Each transaction adds or substract reserve
from a set of bounded random functions. to the system, modifying the price over time. The
</Typography> frequency, size and direction of each transaction
is computed from a set of bounded random
functions.
</Typography>
</div>
<table>
<tbody>
{Object.values(
simulationParameterDescriptions
).map(({ name, text }) => (
<tr key={name}>
<td>
<Typography>{name}</Typography>
</td>
<td>
<Typography
className={classes.descriptionBody}
>
{text}
</Typography>
</td>
</tr>
))}
</tbody>
</table>
</div>
} }
/> />
</Box> </Box>
@ -465,6 +521,7 @@ export default function App() {
<PriceSimulationChart <PriceSimulationChart
priceTimeseries={priceTimeseries} priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries} withdrawFeeTimeseries={withdrawFeeTimeseries}
floorpriceTimeseries={floorpriceTimeseries}
p0={p0} p0={p0}
p1={p1} p1={p1}
/> />
@ -486,18 +543,18 @@ export default function App() {
</div> </div>
<table> <table>
<tbody> <tbody>
{resultParameterDescriptions.map( {Object.values(resultParameterDescriptions).map(
({ name, text }) => ( ({ name, text }) => (
<tr key={name}> <tr key={name}>
<td> <td>
<Typography <Typography>{name}</Typography>
className={classes.descriptionName}
>
{name}
</Typography>
</td> </td>
<td> <td>
<Typography>{text}</Typography> <Typography
className={classes.descriptionBody}
>
{text}
</Typography>
</td> </td>
</tr> </tr>
) )

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { InputFieldInterface, CurveParamsInterface } from "./types"; import { InputFieldInterface, CurveParamsInterface } from "./types";
import InputParams from "./InputParams"; import InputParams from "./InputParams";
import { parameterDescriptions } from "./parametersDescriptions";
export default function CurveDesignInputParams({ export default function CurveDesignInputParams({
curveParams, curveParams,
@ -42,6 +43,7 @@ export default function CurveDesignInputParams({
const inputFields: InputFieldInterface[] = [ const inputFields: InputFieldInterface[] = [
{ {
label: "Allocation to funding pool", label: "Allocation to funding pool",
description: parameterDescriptions.theta.text,
value: theta, value: theta,
setter: setTheta, setter: setTheta,
min: 0, min: 0,
@ -54,6 +56,7 @@ export default function CurveDesignInputParams({
}, },
{ {
label: "Hatch price (DAI/token)", label: "Hatch price (DAI/token)",
description: parameterDescriptions.p0.text,
value: p0, value: p0,
setter: _setP0, setter: _setP0,
min: 0.01, min: 0.01,
@ -65,6 +68,7 @@ export default function CurveDesignInputParams({
}, },
{ {
label: "Post-hatch price (DAI/token)", label: "Post-hatch price (DAI/token)",
description: parameterDescriptions.p1.text,
value: p1, value: p1,
setter: setP1, setter: setP1,
min: p0 || 0.1, min: p0 || 0.1,
@ -76,6 +80,7 @@ export default function CurveDesignInputParams({
}, },
{ {
label: "Exit tribute", label: "Exit tribute",
description: parameterDescriptions.wFee.text,
value: wFee, value: wFee,
setter: setWFee, setter: setWFee,
min: 0, min: 0,

View File

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import NumberFormat from "react-number-format"; import NumberFormat from "react-number-format";
import { InputFieldInterface } from "./types"; import { InputFieldInterface } from "./types";
import PrettoSlider from "./PrettoSlider"; import PrettoSlider from "./PrettoSlider";
import TextWithPopover from "./TextWithPopover";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -81,6 +81,7 @@ export default function InputParams({
{inputFields.map( {inputFields.map(
({ ({
label, label,
description,
value, value,
setter, setter,
min, min,
@ -103,9 +104,7 @@ export default function InputParams({
return ( return (
<Grid key={label} container spacing={0} className={classes.listBox}> <Grid key={label} container spacing={0} className={classes.listBox}>
<Grid item xs={6} className={classes.leftContainer}> <Grid item xs={6} className={classes.leftContainer}>
<Typography id={label} gutterBottom> <TextWithPopover content={label} popoverText={description} />
{label}
</Typography>
</Grid> </Grid>
<Grid item xs={2} className={classes.centerContainer}> <Grid item xs={2} className={classes.centerContainer}>

View File

@ -17,6 +17,7 @@ import { linspace } from "./utils";
const keyHorizontal = "x"; const keyHorizontal = "x";
const keyVerticalLeft = "Price (DAI/token)"; const keyVerticalLeft = "Price (DAI/token)";
const keyVerticalRight = "Total exit tributes (DAI)"; const keyVerticalRight = "Total exit tributes (DAI)";
const keyVerticalLeft2 = "Floor price (DAI/token)";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -32,11 +33,13 @@ const useStyles = makeStyles((theme: Theme) =>
function PriceSimulationChart({ function PriceSimulationChart({
priceTimeseries, priceTimeseries,
withdrawFeeTimeseries, withdrawFeeTimeseries,
floorpriceTimeseries,
p0, p0,
p1 p1
}: { }: {
priceTimeseries: number[]; priceTimeseries: number[];
withdrawFeeTimeseries: number[]; withdrawFeeTimeseries: number[];
floorpriceTimeseries: number[];
p0: number; p0: number;
p1: number; p1: number;
}) { }) {
@ -51,6 +54,7 @@ function PriceSimulationChart({
data.push({ data.push({
[keyHorizontal]: t, [keyHorizontal]: t,
[keyVerticalLeft]: priceTimeseries[t] || 0, [keyVerticalLeft]: priceTimeseries[t] || 0,
[keyVerticalLeft2]: floorpriceTimeseries[t] || 0,
[keyVerticalRight]: withdrawFeeTimeseries[t] || 0 [keyVerticalRight]: withdrawFeeTimeseries[t] || 0
}); });
} }
@ -70,8 +74,8 @@ function PriceSimulationChart({
const { textAnchor, viewBox, text } = props; const { textAnchor, viewBox, text } = props;
return ( return (
<text <text
x={viewBox.x + 10} x={viewBox.x + 8}
y={viewBox.y - 10} y={viewBox.y + 17}
fill={theme.palette.text.secondary} fill={theme.palette.text.secondary}
textAnchor={textAnchor} textAnchor={textAnchor}
> >
@ -83,10 +87,12 @@ function PriceSimulationChart({
function CustomTooltip({ active, payload, label }: any) { function CustomTooltip({ active, payload, label }: any) {
if (active) { if (active) {
const price = payload[0].value; const price = payload[0].value;
const exit = payload[1].value; const floor = payload[1].value;
const exit = payload[2].value;
const weekNum = label; const weekNum = label;
const toolTipData: string[][] = [ const toolTipData: string[][] = [
["Price", price.toFixed(2), "DAI/tk"], ["Price", price.toFixed(2), "DAI/tk"],
["Floor", floor.toFixed(2), "DAI/tk"],
["Exit t.", formatter(exit), "DAI"], ["Exit t.", formatter(exit), "DAI"],
["Week", weekNum, ""] ["Week", weekNum, ""]
]; ];
@ -148,12 +154,10 @@ function PriceSimulationChart({
{/* Capital collected from withdraw fees - AXIS */} {/* Capital collected from withdraw fees - AXIS */}
<YAxis <YAxis
yAxisId="right" yAxisId="right"
// domain={[ domain={[0, +(2 * withdrawFeeTimeseries.slice(-1)[0]).toPrecision(1)]}
// Math.floor(Math.min(...withdrawFeeTimeseries)),
// Math.ceil(Math.max(...withdrawFeeTimeseries))
// ]}
orientation="right" orientation="right"
tick={{ fill: theme.palette.text.secondary }} tick={{ fill: theme.palette.text.secondary }}
tickFormatter={formatter}
stroke={theme.palette.text.secondary} stroke={theme.palette.text.secondary}
/> />
@ -166,7 +170,20 @@ function PriceSimulationChart({
dataKey={keyVerticalLeft} dataKey={keyVerticalLeft}
stroke={theme.palette.primary.main} stroke={theme.palette.primary.main}
fill={theme.palette.primary.main} fill={theme.palette.primary.main}
fillOpacity={0.3}
strokeWidth={2}
/> />
<Area
isAnimationActive={false}
yAxisId="left"
type="monotone"
dataKey={keyVerticalLeft2}
stroke={"#adcd2e"}
fill={"#adcd2e"}
fillOpacity={0.05}
strokeWidth={2}
/>
<ReferenceLine <ReferenceLine
y={p0} y={p0}
yAxisId="left" yAxisId="left"
@ -188,9 +205,10 @@ function PriceSimulationChart({
yAxisId="right" yAxisId="right"
type="monotone" type="monotone"
dataKey={keyVerticalRight} dataKey={keyVerticalRight}
stroke={theme.palette.secondary.dark} stroke={"#0085ff"}
fill={theme.palette.secondary.dark} fill={theme.palette.secondary.dark}
fillOpacity="0.8" fillOpacity={0.5}
strokeWidth={2}
/> />
{/* <ReferenceLine {/* <ReferenceLine

View File

@ -2,6 +2,7 @@ import React from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import TextWithPopover from "./TextWithPopover";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -45,6 +46,7 @@ export default function ResultParams({
}: { }: {
resultFields: { resultFields: {
label: string; label: string;
description: string;
value: number | string; value: number | string;
}[]; }[];
}) { }) {
@ -52,12 +54,10 @@ export default function ResultParams({
return ( return (
<div className={classes.listBoxContainer}> <div className={classes.listBoxContainer}>
{resultFields.map(({ label, value }) => ( {resultFields.map(({ label, description, value }) => (
<Grid key={label} container spacing={0} className={classes.listBox}> <Grid key={label} container spacing={0} className={classes.listBox}>
<Grid item xs={8} className={classes.leftContainer}> <Grid item xs={8} className={classes.leftContainer}>
<Typography id={label} gutterBottom> <TextWithPopover content={label} popoverText={description} />
{label}
</Typography>
</Grid> </Grid>
<Grid item xs={4} className={classes.centerContainer}> <Grid item xs={4} className={classes.centerContainer}>

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { InputFieldInterface, CurveParamsInterface } from "./types"; import { InputFieldInterface, CurveParamsInterface } from "./types";
import InputParams from "./InputParams"; import InputParams from "./InputParams";
import { parameterDescriptions } from "./parametersDescriptions";
export default function CurveDesignInputParams({ export default function CurveDesignInputParams({
curveParams, curveParams,
@ -25,6 +26,7 @@ export default function CurveDesignInputParams({
const inputFields: InputFieldInterface[] = [ const inputFields: InputFieldInterface[] = [
{ {
label: "Initial raise (DAI)", label: "Initial raise (DAI)",
description: parameterDescriptions.d0.text,
value: d0, value: d0,
setter: setD0, setter: setD0,
min: 0.1e6, min: 0.1e6,

View File

@ -186,6 +186,8 @@ function SupplyVsDemandChart({
dataKey={keyVertical} dataKey={keyVertical}
stroke={theme.palette.primary.main} stroke={theme.palette.primary.main}
fill={theme.palette.primary.main} fill={theme.palette.primary.main}
fillOpacity={0.3}
strokeWidth={2}
/> />
<ReferenceLine <ReferenceLine
x={R0_round} x={R0_round}

93
src/TextWithPopover.tsx Normal file
View File

@ -0,0 +1,93 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Popover from "@material-ui/core/Popover";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
const useStyles = makeStyles(theme => ({
container: {
color: theme.palette.text.secondary,
display: "flex",
marginLeft: "6px",
fontSize: "0.9rem",
cursor: "pointer",
transition: "color ease 150ms",
"&:hover": {
color: "#c3c9d0"
}
},
popoverContainer: {
padding: theme.spacing(2),
"& > p:not(:last-child)": {
paddingBottom: theme.spacing(1),
marginBottom: theme.spacing(1),
borderBottom: "1px solid #3f5463"
}
},
paper: {
backgroundColor: "#384b59",
maxWidth: theme.breakpoints.values.md * 0.9,
[`@media screen and (max-width: ${theme.breakpoints.values.md}px)`]: {
maxWidth: "90vw"
},
padding: theme.spacing(0.5)
},
descriptionBody: {
color: "#dbdfe4"
}
}));
export default function TextWithPopover({
content,
popoverText
}: {
content: string;
popoverText: string;
}) {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event: any) {
setAnchorEl(event.currentTarget);
}
function handleClose() {
setAnchorEl(null);
}
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div className={classes.container}>
<div aria-describedby={id} onClick={handleClick}>
<Typography gutterBottom>{content}</Typography>
</div>
<Popover
PaperProps={{
className: classes.paper
}}
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
onClick={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
>
<Box className={classes.popoverContainer}>
<Typography>{content}</Typography>
<Typography className={classes.descriptionBody}>
{popoverText}
</Typography>
</Box>
</Popover>
</div>
);
}

View File

@ -23,6 +23,37 @@ export function getInitialParams({
return { k, R0, S0, V0 }; return { k, R0, S0, V0 };
} }
export function getR({ S, V0, k }: { S: number; V0: number; k: number }) {
return S ** k / V0;
}
export function getS({ R, V0, k }: { R: number; V0: number; k: number }) {
return (V0 * R) ** (1 / k);
}
// compute the price if all that supply is burned
export function getMinPrice({
S,
H,
V0,
k
}: {
S: number;
H: number;
V0: number;
k: number;
}) {
if (S === H) {
const myR = getR({ S, V0, k });
const myP = getPriceR({ R: myR, V0, k }); // numerical precision make complex numbers just suppress it
return Math.abs(myP);
} else {
// compute the reserve if all that supply is burned
const minR = getR({ S: S - H, V0, k });
return getPriceR({ R: minR, V0, k });
}
}
/** /**
* Computes the price at a specific reserve `R` * Computes the price at a specific reserve `R`
*/ */
@ -75,17 +106,47 @@ export function getDeltaR_priceGrowth({
* Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0 * Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0
* Very quick: < 10ms for 10000 txs * Very quick: < 10ms for 10000 txs
*/ */
export function getTxDistribution({ sum, num }: { sum: number; num: number }) { export function getTxDistribution({
sum,
num,
spread
}: {
sum: number;
num: number;
spread: number;
}) {
const mean = sum / num; const mean = sum / num;
const off = mean * 4; const off = mean * spread;
const x: number[] = []; const x: number[] = [];
for (let i = 0; i < num; i++) { for (let i = 0; i < num; i++) {
x[i] = randn_bm(mean - off, mean + off); x.push(randn_bm(mean - off, mean + off));
} }
return x; return x;
} }
// Minor utils export function vest_tokens({
week,
H, // unvested_hatch_tokens
halflife,
cliff
}: {
week: number;
H: number;
halflife: number;
cliff: number;
}) {
// check cliff
if (week < cliff) {
return 0;
} else {
// rate of release given half - life
const vest_fraction = 0.5 ** (1 / halflife);
// number of tokens that vest in this week
return H * (1 - vest_fraction);
}
}
// Statistics utils
/** /**
* Random variable uniformly distributed * Random variable uniformly distributed
@ -111,3 +172,15 @@ function randn_bm(min: number, max: number) {
num += min; // offset to min num += min; // offset to min
return num; return num;
} }
// Array utils
export function getMedian(arr: number[]) {
const mid = Math.floor(arr.length / 2);
const nums = [...arr].sort((a, b) => a - b);
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
}
export function getSum(arr: number[]) {
return arr.reduce((a, b) => a + b, 0);
}

View File

@ -0,0 +1,69 @@
export interface DescriptionObject {
[key: string]: { name: string; text: string };
}
export const parameterDescriptions: DescriptionObject = {
theta: {
name: "Allocation to funding pool",
text:
"The percentage of the funds raised in the Hatch sale that go directly into the project funding pool to compensate future work done in the project"
},
p0: {
name: "Hatch price",
text:
"The price paid per 'ABC token' by community members involved in hatching the project"
},
p1: {
name: "Post-hatch price",
text:
"The price of the 'ABC token' when the curve enters the open phase and is live for public participation"
},
wFee: {
name: "Exit tribute",
text:
"The percentage of funds that are diverted to the project funding pool from community members who exit funds from the project by burning 'ABC tokens' in exchange for collateral"
},
d0: {
name: "Initial raise",
text: "Total funds raised in the hatch period of the ABC launch"
}
};
export const simulationParameterDescriptions: DescriptionObject = {
price: {
name: "Price",
text: "Price of the token over time."
},
floorPrice: {
name: "Floor price",
text:
"Lower bound of the price guaranteed by the vesting of hatch tokens. It decreases over time as more hatch tokens are allowed to be traded"
},
exitTributes: {
name: "Total exit tributes",
text:
"Cumulative sum of exit tributes collected from only exit /sell transactions"
}
};
export const resultParameterDescriptions: DescriptionObject = {
totalReserve: {
name: "Total reserve",
text:
"Total DAI in the smart contract reserve at the end of the simulated period"
},
initialHatchFunds: {
name: "Funds generated from initial hatch",
text:
"Fraction of the funds (theta) raised during the hatch that go directly to the cause (analytic result)"
},
exitTributes: {
name: "Funds generated from exit tributes",
text: simulationParameterDescriptions.exitTributes.text
},
slippage: {
name: "Average slippage",
text:
"Average of the slippage of each transaction occured during the simulation period"
}
};

View File

@ -1,5 +1,6 @@
export interface InputFieldInterface { export interface InputFieldInterface {
label: string; label: string;
description: string;
value: number; value: number;
setter(newValue: any): void; setter(newValue: any): void;
min: number; min: number;