add PADTokenStake and PADToken.approveMax

This commit is contained in:
osmannyildiz 2024-05-12 13:40:06 +03:00
parent 29ca4239ee
commit 3240620034
12 changed files with 359 additions and 218 deletions

View File

@ -4,3 +4,4 @@ cp "./evm/ignition/deployments/chain-31337/deployed_addresses.json" "./frontend/
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#MessageBox.json" "./frontend/src/evm-output/MessageBox.artifacts.json"
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADToken.json" "./frontend/src/evm-output/PADToken.artifacts.json"
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADTokenStake.json" "./frontend/src/evm-output/PADTokenStake.artifacts.json"

View File

@ -4,16 +4,19 @@ pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract PADToken is ERC20, Ownable, ERC20Permit {
contract PADToken is ERC20, Ownable {
constructor(address initialOwner)
ERC20("PAD Token", "PAD")
Ownable(initialOwner)
ERC20Permit("PAD Token")
{}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function approveMax(address spender) public returns (bool) {
uint value = type(uint256).max;
return approve(spender, value);
}
}

View File

@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./PADToken.sol";
contract PADTokenStake {
// pool id => pool info
mapping (uint => Pool) pools;
// staker => deposits
mapping(address => Deposit[]) deposits;
uint nextPoolId = 0;
uint nextDepositId = 0;
PADToken immutable padToken;
struct Pool {
uint id;
uint lockdownDays;
uint rewardPercentage;
}
struct Deposit {
uint id;
address staker;
uint poolId;
uint amount;
uint lockedAt;
uint releasedAt;
}
constructor(PADToken _padToken) {
padToken = _padToken;
_initPools();
}
function _initPools() internal {
pools[0] = Pool({
id: nextPoolId,
lockdownDays: 30,
rewardPercentage: 25
});
nextPoolId++;
pools[1] = Pool({
id: nextPoolId,
lockdownDays: 60,
rewardPercentage: 50
});
nextPoolId++;
}
function stake(uint poolId, uint amount) external {
Pool memory pool = pools[poolId];
Deposit memory newDeposit = Deposit({
id: nextDepositId,
staker: msg.sender,
poolId: poolId,
amount: amount,
lockedAt: block.timestamp,
releasedAt: block.timestamp + (pool.lockdownDays * 1 days)
});
deposits[msg.sender].push(newDeposit);
}
function getDeposit(uint depositId) public view returns (Deposit memory) {
Deposit memory deposit;
for (uint i = 0; i < deposits[msg.sender].length; i++) {
deposit = deposits[msg.sender][i];
if (deposit.id == depositId) {
return deposit;
}
}
revert("Deposit with the given ID not found.");
}
function getWithdrawPenalty(uint depositId) public view returns (uint) {
Deposit memory deposit = getDeposit(depositId);
uint remainingMs = deposit.releasedAt - block.timestamp;
if (remainingMs <= 0) {
return 0;
}
// TODO Find a meaningful formula
return 100;
}
function withdraw(uint depositId, bool acceptPenaltyCut) external {
uint penalty = getWithdrawPenalty(depositId);
if (penalty > 0) {
require(acceptPenaltyCut, "You should accept the penalty cut.");
}
Deposit memory deposit = getDeposit(depositId);
Pool memory pool = pools[deposit.poolId];
uint amountToTransfer = (deposit.amount * pool.rewardPercentage / 100) - penalty;
padToken.transfer(deposit.staker, amountToTransfer);
}
}

View File

@ -11,7 +11,9 @@ const MainModule = buildModule("MainModule", (m) => {
const padToken = m.contract("PADToken", [initialOwner]);
return { messageBox, padToken };
const padTokenStake = m.contract("PADTokenStake", [padToken]);
return { messageBox, padToken, padTokenStake };
});
export default MainModule;

View File

@ -20,6 +20,9 @@ export function Header() {
<Link to="/pad-token" className="[&.active]:font-bold">
PAD Token
</Link>
<Link to="/pad-token-stake" className="[&.active]:font-bold">
PAD Token Stake
</Link>
</div>
</div>
<ConnectMetamaskButton />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
{
"MainModule#MessageBox": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"MainModule#PADToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
"MainModule#MessageBox": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
"MainModule#PADToken": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
"MainModule#PADTokenStake": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
}

View File

@ -16,6 +16,7 @@ import { Route as rootRoute } from './routes/__root'
// Create Virtual Routes
const PadTokenStakeLazyImport = createFileRoute('/pad-token-stake')()
const PadTokenLazyImport = createFileRoute('/pad-token')()
const MessageBoxLazyImport = createFileRoute('/message-box')()
const AboutLazyImport = createFileRoute('/about')()
@ -23,6 +24,13 @@ const IndexLazyImport = createFileRoute('/')()
// Create/Update Routes
const PadTokenStakeLazyRoute = PadTokenStakeLazyImport.update({
path: '/pad-token-stake',
getParentRoute: () => rootRoute,
} as any).lazy(() =>
import('./routes/pad-token-stake.lazy').then((d) => d.Route),
)
const PadTokenLazyRoute = PadTokenLazyImport.update({
path: '/pad-token',
getParentRoute: () => rootRoute,
@ -63,6 +71,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PadTokenLazyImport
parentRoute: typeof rootRoute
}
'/pad-token-stake': {
preLoaderRoute: typeof PadTokenStakeLazyImport
parentRoute: typeof rootRoute
}
}
}
@ -73,6 +85,7 @@ export const routeTree = rootRoute.addChildren([
AboutLazyRoute,
MessageBoxLazyRoute,
PadTokenLazyRoute,
PadTokenStakeLazyRoute,
])
/* prettier-ignore-end */

View File

@ -0,0 +1,74 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { useStore } from "../store";
import { toastError, tryWithToast } from "../utils/toast";
import { formatPadTokenAmount } from "../utils/ui";
import { padToken, padTokenStake } from "../web3";
export const Route = createLazyFileRoute("/pad-token-stake")({
component: PadTokenStakePage,
});
function PadTokenStakePage() {
const connectedAccount = useStore((state) => state.connectedAccount);
const [allowance, setAllowance] = useState("(nothingness)");
const getAllowanceAmount = async () => {
if (!connectedAccount) {
toastError("Get Current Allowance", "Connect your wallet first.");
return;
}
await tryWithToast("Get Current Allowance", async () => {
const owner = connectedAccount;
const spender = padTokenStake.options.address;
const allowance: bigint = await padToken.methods
.allowance(owner, spender)
.call();
setAllowance(formatPadTokenAmount(allowance));
});
};
const approveMax = async () => {
if (!connectedAccount) {
toastError("Approve Max", "Connect your wallet first.");
return;
}
await tryWithToast("Approve Max", async () => {
const owner = connectedAccount;
const spender = padTokenStake.options.address;
await padToken.methods.approveMax(spender).send({ from: owner });
});
};
return (
<div className="container mx-auto p-4 grid lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div className="card bg-base-200">
<div className="card-body items-start">
<h2 className="text-2xl font-bold">Allowance</h2>
<div>
<span className="font-bold">
Allowance of PADTokenStake contract, for my PAD tokens:
</span>{" "}
{allowance}
</div>
<button
type="button"
className="btn btn-primary"
onClick={() => getAllowanceAmount()}
>
Get
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => approveMax()}
>
Approve Max
</button>
</div>
</div>
</div>
);
}

View File

@ -5,5 +5,8 @@ export function shortenAddress(address: string) {
}
export function formatPadTokenAmount(amount: bigint) {
if (Number(amount) === Math.pow(2, 256)) {
return "∞ PAD";
}
return `${web3.utils.fromWei(amount, "ether").replace(/\.*$/, "")} PAD`;
}

View File

@ -1,6 +1,7 @@
import { Web3 } from "web3";
import messageBoxArtifacts from "./evm-output/MessageBox.artifacts.json";
import padTokenArtifacts from "./evm-output/PADToken.artifacts.json";
import padTokenStakeArtifacts from "./evm-output/PADTokenStake.artifacts.json";
import deployedAddresses from "./evm-output/deployed_addresses.json";
export let web3: Web3;
@ -23,3 +24,8 @@ export const padToken = new web3!.eth.Contract(
padTokenArtifacts.abi,
deployedAddresses["MainModule#PADToken"]
);
export const padTokenStake = new web3!.eth.Contract(
padTokenStakeArtifacts.abi,
deployedAddresses["MainModule#PADTokenStake"]
);