Skip to content

Commit

Permalink
Merge pull request #127 from happyleow/feat/staking-ui
Browse files Browse the repository at this point in the history
Add staking UI & Subgraph
  • Loading branch information
LilaRest authored May 6, 2024
2 parents ed18699 + 8c8b57a commit dcdf970
Show file tree
Hide file tree
Showing 37 changed files with 2,652 additions and 782 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@
"prettier.documentSelectors": ["**/*.sol"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnPaste": true
"editor.formatOnPaste": true,
"slither.solcPath": "",
"slither.hiddenDetectors": []
}
Binary file modified bun.lockb
Binary file not shown.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"graph:linea-remove": "graph remove --node https://graph-deploy.linea.build/ LedgityLabs/LedgityYield",
"graph:arbitrum-gen": "cd subgraph && graph codegen ./arbitrum-subgraph.yaml && graph build ./arbitrum-subgraph.yaml",
"graph:arbitrum-deploy": "graph deploy --studio ledgity-yield-arbitrum ./subgraph/arbitrum-subgraph.yaml",
"graph:OKX_X1_Testnet-remove": "graph remove --node https://www.okx.com/api/v1/x1-testnet/index/subgraphs/name/LedgityLabs/LedgityYield/"
"graph:OKX_X1_Testnet-remove": "graph remove --node https://www.okx.com/api/v1/x1-testnet/index/subgraphs/name/LedgityLabs/LedgityYield/",
"graph:base-sepolia-gen": "cd subgraph && graph codegen ./base-sepolia_subgraph.yaml && graph build ./base-sepolia_subgraph.yaml",
"graph:base-sepolia-deploy": "cd subgraph && graph deploy --node https://api.studio.thegraph.com/deploy/ ldystaking-subgraph ./base-sepolia_subgraph.yaml"
},
"dependencies": {
"@auth/prisma-adapter": "^1.0.11",
Expand All @@ -44,6 +46,7 @@
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
Expand All @@ -60,7 +63,10 @@
"clsx": "^2.1.0",
"cookies-next": "^4.1.0",
"d3-format": "^3.1.0",
"dayjs": "^1.11.11",
"embla-carousel-react": "^8.0.2",
"graphql": "^16.8.1",
"graphql-request": "^6.1.0",
"ioredis": "^5.3.2",
"lodash.merge": "^4.6.2",
"lokijs": "^1.5.12",
Expand Down
52 changes: 27 additions & 25 deletions report.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results.
Summary
- [events-access](#events-access) (2 results) (Low)
- [events-maths](#events-maths) (3 results) (Low)
- [events-maths](#events-maths) (2 results) (Low)
- [calls-loop](#calls-loop) (1 results) (Low)
- [reentrancy-events](#reentrancy-events) (1 results) (Low)
- [reentrancy-events](#reentrancy-events) (2 results) (Low)
- [timestamp](#timestamp) (4 results) (Low)
- [costly-loop](#costly-loop) (3 results) (Informational)
- [low-level-calls](#low-level-calls) (1 results) (Informational)
Expand Down Expand Up @@ -43,18 +43,10 @@ contracts/src/LToken.sol#L283-L286
contracts/src/LToken.sol#L272-L275


- [ ] ID-4
[LDYStaking.notifyRewardAmount(uint256)](contracts/src/LDYStaking.sol#L258-L283) should emit an event for:
- [rewardRatePerSec = amount / rewardsDuration](contracts/src/LDYStaking.sol#L265)
- [rewardRatePerSec = (amount + remainingRewards) / rewardsDuration](contracts/src/LDYStaking.sol#L268)

contracts/src/LDYStaking.sol#L258-L283


## calls-loop
Impact: Low
Confidence: Medium
- [ ] ID-5
- [ ] ID-4
[LToken.getWithdrawnAmountAndFees(address,uint256)](contracts/src/LToken.sol#L603-L621) has external calls inside a loop: [ldyStaking.tierOf(account) >= 2](contracts/src/LToken.sol#L608)

contracts/src/LToken.sol#L603-L621
Expand All @@ -63,6 +55,16 @@ contracts/src/LToken.sol#L603-L621
## reentrancy-events
Impact: Low
Confidence: Medium
- [ ] ID-5
Reentrancy in [LDYStaking.notifyRewardAmount(uint256)](contracts/src/LDYStaking.sol#L265-L292):
External calls:
- [stakeRewardToken.safeTransferFrom(_msgSender(),address(this),amount)](contracts/src/LDYStaking.sol#L289)
Event emitted after the call(s):
- [NotifiedRewardAmount(amount,rewardRatePerSec)](contracts/src/LDYStaking.sol#L291)

contracts/src/LDYStaking.sol#L265-L292


- [ ] ID-6
Reentrancy in [LToken.processQueuedRequests()](contracts/src/LToken.sol#L740-L851):
External calls:
Expand All @@ -78,37 +80,37 @@ contracts/src/LToken.sol#L740-L851
Impact: Low
Confidence: Medium
- [ ] ID-7
[LDYStaking.unstake(uint256,uint256)](contracts/src/LDYStaking.sol#L187-L226) uses timestamp for comparisons
[LDYStaking._min(uint256,uint256)](contracts/src/LDYStaking.sol#L389-L391) uses timestamp for comparisons
Dangerous comparisons:
- [require(bool,string)(block.timestamp >= userStakingInfo[_msgSender()][stakeIndex].unStakeAt,not allowed unstaking in the staking period)](contracts/src/LDYStaking.sol#L193-L196)
- [x <= y](contracts/src/LDYStaking.sol#L390)

contracts/src/LDYStaking.sol#L187-L226
contracts/src/LDYStaking.sol#L389-L391


- [ ] ID-8
[LDYStaking._min(uint256,uint256)](contracts/src/LDYStaking.sol#L380-L382) uses timestamp for comparisons
[LDYStaking.unstake(uint256,uint256)](contracts/src/LDYStaking.sol#L194-L233) uses timestamp for comparisons
Dangerous comparisons:
- [x <= y](contracts/src/LDYStaking.sol#L381)
- [require(bool,string)(block.timestamp >= userStakingInfo[_msgSender()][stakeIndex].unStakeAt,not allowed unstaking in the staking period)](contracts/src/LDYStaking.sol#L200-L203)

contracts/src/LDYStaking.sol#L380-L382
contracts/src/LDYStaking.sol#L194-L233


- [ ] ID-9
[LDYStaking.setRewardsDuration(uint256)](contracts/src/LDYStaking.sol#L248-L251) uses timestamp for comparisons
[LDYStaking.notifyRewardAmount(uint256)](contracts/src/LDYStaking.sol#L265-L292) uses timestamp for comparisons
Dangerous comparisons:
- [require(bool,string)(finishAt < block.timestamp,reward duration is not finished)](contracts/src/LDYStaking.sol#L249)
- [block.timestamp >= finishAt](contracts/src/LDYStaking.sol#L271)
- [require(bool,string)(rewardRatePerSec > 0,reward rate = 0)](contracts/src/LDYStaking.sol#L278)
- [require(bool,string)(rewardRatePerSec <= (stakeRewardToken.balanceOf(address(this)) + amount - totalStaked) / rewardsDuration,reward amount > balance)](contracts/src/LDYStaking.sol#L279-L284)

contracts/src/LDYStaking.sol#L248-L251
contracts/src/LDYStaking.sol#L265-L292


- [ ] ID-10
[LDYStaking.notifyRewardAmount(uint256)](contracts/src/LDYStaking.sol#L258-L283) uses timestamp for comparisons
[LDYStaking.setRewardsDuration(uint256)](contracts/src/LDYStaking.sol#L255-L258) uses timestamp for comparisons
Dangerous comparisons:
- [block.timestamp >= finishAt](contracts/src/LDYStaking.sol#L264)
- [require(bool,string)(rewardRatePerSec > 0,reward rate = 0)](contracts/src/LDYStaking.sol#L271)
- [require(bool,string)(rewardRatePerSec <= (stakeRewardToken.balanceOf(address(this)) + amount - totalStaked) / rewardsDuration,reward amount > balance)](contracts/src/LDYStaking.sol#L272-L277)
- [require(bool,string)(finishAt < block.timestamp,reward duration is not finished)](contracts/src/LDYStaking.sol#L256)

contracts/src/LDYStaking.sol#L258-L283
contracts/src/LDYStaking.sol#L255-L258


## costly-loop
Expand Down
9 changes: 6 additions & 3 deletions src/app/app/[tab]/AppTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { twMerge } from "tailwind-merge";
import { AppPreMining } from "@/components/app/pre-mining/AppPreMining";
import { AppAirdrop } from "@/components/app/airdrop/AppAirdrop";
import { SessionProvider } from "next-auth/react";

import { AppStaking } from "@/components/app/staking/AppStaking";
interface Props {
defaultTab: string;
}
Expand All @@ -26,7 +26,6 @@ const AppTabs: FC<Props> = ({ defaultTab }) => {

const _AppTabs: FC = () => {
const { currentTab, switchTab } = useSwitchAppTab();

return (
<Tabs
value={currentTab}
Expand Down Expand Up @@ -59,7 +58,8 @@ const _AppTabs: FC = () => {
>
Pre-Mining
</TabsTrigger>
{/* <TabsTrigger value="get-usdc">Get USDC</TabsTrigger> */}

{/* <TabsTrigger value="staking">Staking</TabsTrigger> */}
<TabsTrigger value="dashboard">Dashboard</TabsTrigger>
</TabsList>
<div className="[&_>_*]:animate-fadeAndMoveIn [&_>_*]:[animation-duration:300ms] sm:px-5 max-w-[100vw]">
Expand All @@ -76,6 +76,9 @@ const _AppTabs: FC = () => {
<TabsContent value="get-usdc">
<AppGetUSDC />
</TabsContent>
<TabsContent value="staking">
<AppStaking />
</TabsContent>
<TabsContent value="dashboard">
<AppDashboard />
</TabsContent>
Expand Down
1 change: 1 addition & 0 deletions src/app/app/[tab]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
airdrop: "Multi-Airdrop",
"get-usdc": "Get USDC",
"pre-mining": "Pre-Mining",
staking: "Staking",
}[params.tab];

const description = {
Expand Down
84 changes: 84 additions & 0 deletions src/components/app/staking/AppStaking.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Card } from "@/components/ui";
import { FC, useEffect, useRef } from "react";
import { AppStakingPane } from "./AppStakingPane";
import { AppStakingDescription } from "./AppStakingDescription";
import { AppStakingPools } from "./AppStakingPools";
import { useContractAddress } from "@/hooks/useContractAddress";
import { useAccount, usePublicClient } from "wagmi";
import { zeroAddress } from "viem";
import { useReadLdyBalanceOf, useReadLdyDecimals } from "@/generated";
import { useQueryClient } from "@tanstack/react-query";
import { useGetStakingAprById } from "@/services/graph";
import { STAKING_APR_INFO_ID } from "@/constants/staking";
import { STAKING_APR_INFO_QUERY } from "@/services/graph/queries";

export const AppStaking: FC = () => {
const queryClient = useQueryClient();
const account = useAccount();
const publicClient = usePublicClient();
const ldySymbol = "LDY";
const ldyTokenAddress = useContractAddress(ldySymbol);

const { data: ldyBalance, queryKey: ldyBalanceQuery } = useReadLdyBalanceOf({
args: [account.address || zeroAddress],
});

const { data: ldyDecimals } = useReadLdyDecimals();

const {
data: stakingAprInfo,
refetch: refetchStakingAPR,
isFetching: isFetchingAPR,
} = useGetStakingAprById(STAKING_APR_INFO_ID);

// Refetch LdyBalance & APR from contract on network/wallet change
const queryKeys = [ldyBalanceQuery, [STAKING_APR_INFO_QUERY]];
useEffect(() => {
queryKeys.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
}, [account.address, publicClient]);

// Refetch stakingAPR on ldyBalance change.
useEffect(() => {
// Refetch after 3 seconds due to subgraph latency
const timeoutId = setTimeout(() => {
queryClient.invalidateQueries({ queryKey: [STAKING_APR_INFO_QUERY] });
}, 3000);
return () => clearTimeout(timeoutId);
}, [ldyBalance]);

return (
<section className="lg:w-[1080px] grid grid-cols-12 gap-5 pb-10 w-full h-full px-2">
<Card
circleIntensity={0.07}
defaultGradient={true}
className="w-full flex flex-col col-span-12 xl:col-span-6 gap-2 p-2"
>
<AppStakingPane
ldyTokenSymbol={ldySymbol}
ldyTokenAddress={ldyTokenAddress}
ldyTokenBalance={ldyBalance || 0n}
ldyTokenDecimals={ldyDecimals}
stakingAprInfo={stakingAprInfo ? stakingAprInfo.stakingAPRInfo || undefined : undefined}
/>
</Card>
<Card
circleIntensity={0.07}
defaultGradient={true}
className="w-full flex flex-col col-span-12 xl:col-span-6 gap-8 p-2"
>
<AppStakingDescription />
</Card>
<Card
circleIntensity={0.07}
defaultGradient={false}
className="w-full flex flex-col gap-8 col-span-12 before:bg-primary p-2"
>
<AppStakingPools
ldyTokenDecimals={ldyDecimals}
ldyTokenBalanceQuery={ldyBalanceQuery || []}
stakingAprInfo={stakingAprInfo ? stakingAprInfo.stakingAPRInfo || undefined : undefined}
/>
</Card>
</section>
);
};
36 changes: 36 additions & 0 deletions src/components/app/staking/AppStakingDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { FC } from "react";

export const AppStakingDescription: FC = () => {
return (
<div className="flex flex-col justify-start gap-y-2 p-4">
<div className="font-heading font-bold text-xl">YOUR PERKS</div>
<div className="font-heading font-semibold text-lg py-2">
Stake at least 1000 $LDY for 12months
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">Protocol profits sharing</span>
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">0x Withdrawal fees</span>
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">Access to Leverage Vault</span>
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">Access to L-Boost Vault</span>
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">Voting power</span>
</div>
<div className="font-medium flex items-center">
<i className="ri-checkbox-circle-line text-xl" />
<span className="mx-1">Visibility on the portfolio of RWA</span>
</div>
</div>
);
};
Loading

0 comments on commit dcdf970

Please sign in to comment.