forked from mico/idle_moloch
Merge pull request 'dao-inegration' (#2) from dao-inegration into main
Reviewed-on: mico/idle_moloch#2
This commit is contained in:
commit
d976f13a04
33
README.md
33
README.md
@ -4,24 +4,41 @@ Idle game & shitcoin advanture dedicated to cohort VII of Raid Guild.
|
||||
|
||||
## Set up for local DEV
|
||||
|
||||
### 1. Run `anvil` to setup local RPC
|
||||
### 1. Run `anvil` to setup local RPC as a fork of base mainnet
|
||||
|
||||
`anvil --fork-url <YOUR BASE MAINNET RPC URL> --block-time 10 --chain-id 31337`
|
||||
|
||||
You can get a free rpc url by registering with https://alchemy.com and creating an app
|
||||
|
||||
Be sure to set --chain-id to 31337 if you are forking mainnet base, otherwise it will deploy with Base chain id and metamask will glitch out.
|
||||
|
||||
### 2. Deploy contract
|
||||
|
||||
Either use `./deploy_contract.sh` script (!! change contract values and set private key to $DEV_PRIVATE_KEY for it to work) or call those things by hand.
|
||||
Use `./deploy_contract.sh` script
|
||||
|
||||
This will deploy the contract and give you ETH and DAO Token (RGCVII) funds.
|
||||
|
||||
1. Make sure to change `DEV_WALLET` var to your own.
|
||||
2. Make sure you have your private key on `DEV_PRIVATE_KEY` environment variable
|
||||
|
||||
Alternatively, check the script and run the steps as you see fit.
|
||||
|
||||
### 3. Run dev app
|
||||
|
||||
Move to `app` dir, install deps via `npm install` and run `npm run dev` to start the dev server.
|
||||
|
||||
#### 3. 1. Run `cast rpc anvil_mine`
|
||||
#### 3. 1. Point Metamask to Anvil network for local dev
|
||||
|
||||
This is so time gets set on the local chain, otherwise you will start at 0 time and first mint will give you bajillion GELD.
|
||||
Add network `http://127.0.0.1:8545` with chain id `31337`
|
||||
|
||||
#### 3. 2. Point Metamask to Anvil network for local dev
|
||||
#### 3. 2. Change `app/contract_address.ts` to match your program address if needed
|
||||
|
||||
#### 3. 3. Change `app/contract_address.ts` to match your program address if needed
|
||||
#### 3. 3. Reset metamask transaction history between anvil deployments
|
||||
|
||||
### 4. Local development requires mining blocks by hand
|
||||
If u re-run `anvil` and redeploy the contract, do clear your history in Metamask under Advanced Settings, otherwise Metamask glitches because of its cache (?)
|
||||
|
||||
Call `cast rpc anvil_mine` to mine next block, otherwise it wont ever progress and time "stands still" as far as the game is concerned
|
||||
### 4. Fork tests
|
||||
|
||||
Run `forge test --rpc-url <your base mainnet rpc url>`
|
||||
|
||||
You can get a free rpc url by registering with https://alchemy.com and creating an app
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
const contractAddress = "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66";
|
||||
import { Address } from "viem"
|
||||
|
||||
const contracts: Record<string, Address> = {
|
||||
contractAddress: "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66",
|
||||
daoTokenAddress: "0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8"
|
||||
}
|
||||
|
||||
export default contractAddress
|
||||
export default contracts
|
||||
|
||||
BIN
app/public/loader/hamster.png
Normal file
BIN
app/public/loader/hamster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
app/public/loader/hamster_stand.png
Normal file
BIN
app/public/loader/hamster_stand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
BIN
app/public/loader/hamster_wheel.png
Normal file
BIN
app/public/loader/hamster_wheel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@ -5,10 +5,12 @@ import { usePlayer } from "../providers/PlayerProvider";
|
||||
import { useAccount } from 'wagmi';
|
||||
import dynamic from "next/dynamic";
|
||||
import Counter, { toReadable } from "./Counter";
|
||||
import { useModal } from "../providers/ModalProvider";
|
||||
|
||||
const Header = () => {
|
||||
const { isConnected } = useAccount();
|
||||
const { isRegistered, register, army } = usePlayer();
|
||||
const { isRegistered, army } = usePlayer();
|
||||
const { openRegistrationModal } = useModal();
|
||||
|
||||
const title = useMemo(() => {
|
||||
return isRegistered ? `SLAY THE MOLOCH` :
|
||||
@ -33,8 +35,8 @@ const Header = () => {
|
||||
|
||||
const onRegister = useCallback(() => {
|
||||
if (isRegistered) return
|
||||
register();
|
||||
}, [isRegistered, register])
|
||||
openRegistrationModal()
|
||||
}, [isRegistered, openRegistrationModal])
|
||||
|
||||
return <header onClick={onRegister} className={styles.header}>
|
||||
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>
|
||||
|
||||
26
app/src/components/RegistrationModal.tsx
Normal file
26
app/src/components/RegistrationModal.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useCallback } from "react";
|
||||
import { usePlayer } from "../providers/PlayerProvider";
|
||||
import styles from "../styles/Modal.module.css";
|
||||
|
||||
interface RegistrationModalProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (val: boolean) => void
|
||||
}
|
||||
|
||||
const RegistrationModal = ({ isOpen, setIsOpen }: RegistrationModalProps) => {
|
||||
const { register } = usePlayer()
|
||||
const onRegister = useCallback((mode: "ETH" | "RGCVII") => {
|
||||
register(mode);
|
||||
setIsOpen(false);
|
||||
}, [register, setIsOpen])
|
||||
if (!isOpen) return null;
|
||||
return <div className={styles.modal}>
|
||||
<h2>Insert coins to continue</h2>
|
||||
<div>
|
||||
<button onClick={() => onRegister("RGCVII")}>50 RGCVII</button>
|
||||
<button onClick={() => onRegister("ETH")}>0.0005 ETH</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default RegistrationModal
|
||||
31
app/src/components/WaitingForTxModal.tsx
Normal file
31
app/src/components/WaitingForTxModal.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { useEffect } from "react";
|
||||
import { Hash } from "viem"
|
||||
import { useWaitForTransactionReceipt } from "wagmi";
|
||||
import styles from "../styles/Modal.module.css"
|
||||
|
||||
interface WaitingForTxModalProps {
|
||||
hash: Hash,
|
||||
callbackFn: () => void;
|
||||
}
|
||||
|
||||
const WaitingForTxModal = ({
|
||||
hash,
|
||||
callbackFn
|
||||
}: WaitingForTxModalProps) => {
|
||||
const { isFetched } = useWaitForTransactionReceipt({ hash })
|
||||
useEffect(() => {
|
||||
if (isFetched) {
|
||||
callbackFn()
|
||||
}
|
||||
}, [isFetched, callbackFn])
|
||||
return <div className={styles.modal}>
|
||||
<div className={styles.loadingImage}>
|
||||
<div className={styles.loadingHamsterWheelStand} />
|
||||
<div className={styles.loadingHamsterWheel} />
|
||||
<div className={styles.loadingHamster} />
|
||||
</div>
|
||||
<p className={styles.loadingText}>Writing contract ...</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default WaitingForTxModal
|
||||
@ -8,6 +8,8 @@ import { RainbowKitProvider, midnightTheme } from "@rainbow-me/rainbowkit";
|
||||
import { config } from "../wagmi";
|
||||
import { Press_Start_2P, Texturina } from "next/font/google";
|
||||
import PlayerProvider from "../providers/PlayerProvider";
|
||||
import ModalProvider from '../providers/ModalProvider';
|
||||
|
||||
|
||||
const client = new QueryClient();
|
||||
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
||||
@ -42,7 +44,9 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
}
|
||||
`}</style>
|
||||
<PlayerProvider>
|
||||
<Component {...pageProps} />
|
||||
<ModalProvider>
|
||||
<Component {...pageProps} />
|
||||
</ModalProvider>
|
||||
</PlayerProvider>
|
||||
</RainbowKitProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
33
app/src/providers/ModalProvider.tsx
Normal file
33
app/src/providers/ModalProvider.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { createContext, ReactNode, useCallback, useContext, useState } from 'react'
|
||||
import RegistrationModal from '../components/RegistrationModal';
|
||||
|
||||
|
||||
export interface PlayerContextType {
|
||||
openRegistrationModal: () => void
|
||||
}
|
||||
|
||||
const ModalContext = createContext<PlayerContextType>({
|
||||
openRegistrationModal: () => { }
|
||||
});
|
||||
|
||||
const ModalProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [registrationModalOpen, setIsRegistrationModalOpen] = useState(false)
|
||||
const openRegistrationModal = useCallback(() => {
|
||||
setIsRegistrationModalOpen(true)
|
||||
}, [])
|
||||
return (
|
||||
<ModalContext.Provider value={{
|
||||
openRegistrationModal
|
||||
}}>
|
||||
{children}
|
||||
<RegistrationModal setIsOpen={setIsRegistrationModalOpen} isOpen={registrationModalOpen} />
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useModal = () => {
|
||||
return useContext(ModalContext);
|
||||
}
|
||||
|
||||
export default ModalProvider
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React, { createContext, ReactNode, useCallback, useContext, useEffect } from 'react'
|
||||
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { useAccount, useReadContract, useWriteContract } from 'wagmi'
|
||||
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
||||
import { parseEther } from 'viem'
|
||||
import contractAddress from '../../contract_address'
|
||||
import { Hash, parseEther } from 'viem'
|
||||
import contracts from '../../contract_address'
|
||||
import WaitingForTxModal from '../components/WaitingForTxModal'
|
||||
|
||||
const { contractAddress, daoTokenAddress } = contracts
|
||||
const abi = contractAbi.abi
|
||||
|
||||
export type UnitType = 0 | 1 | 2 | 3
|
||||
@ -26,7 +28,7 @@ export interface PlayerContextType {
|
||||
player: null | Player,
|
||||
army: null | Army,
|
||||
balance: bigint,
|
||||
register: () => void,
|
||||
register: (arg: "ETH" | "RGCVII") => void,
|
||||
raid: () => void,
|
||||
addUnit: (unit: UnitType) => void
|
||||
}
|
||||
@ -44,11 +46,16 @@ const PlayerContext = createContext<PlayerContextType>({
|
||||
const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { address, isConnected } = useAccount();
|
||||
const { writeContract, error } = useWriteContract();
|
||||
const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }])
|
||||
|
||||
useEffect(() => {
|
||||
console.warn(error)
|
||||
}, [error])
|
||||
|
||||
const resetHashAndCallback = useCallback(() => {
|
||||
setHashAndCallback([null, () => { }])
|
||||
}, [])
|
||||
|
||||
const { data: isRegistered } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
@ -56,7 +63,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 5,
|
||||
refetchInterval: 15,
|
||||
}
|
||||
});
|
||||
|
||||
@ -66,7 +73,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
functionName: 'balanceOf',
|
||||
args: [address],
|
||||
query: {
|
||||
refetchInterval: 5,
|
||||
refetchInterval: 15,
|
||||
enabled: isConnected
|
||||
}
|
||||
});
|
||||
@ -95,14 +102,42 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
console.log(balance, player, army)
|
||||
|
||||
const register = useCallback(() => {
|
||||
writeContract({
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register',
|
||||
value: parseEther("0.00005"),
|
||||
})
|
||||
}, [writeContract])
|
||||
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
||||
if (arg === 'ETH') {
|
||||
writeContract({
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register_eth',
|
||||
value: parseEther("0.00005"),
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback])
|
||||
}
|
||||
})
|
||||
} else if (arg === "RGCVII") {
|
||||
writeContract({
|
||||
abi,
|
||||
address: daoTokenAddress,
|
||||
functionName: 'approve',
|
||||
args: [contractAddress, parseEther("50")],
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([
|
||||
hash,
|
||||
() => writeContract({
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register_dao',
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback])
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [writeContract, resetHashAndCallback])
|
||||
|
||||
const raid = useCallback(() => {
|
||||
writeContract({
|
||||
@ -132,6 +167,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
addUnit
|
||||
}}>
|
||||
{children}
|
||||
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
67
app/src/styles/Modal.module.css
Normal file
67
app/src/styles/Modal.module.css
Normal file
@ -0,0 +1,67 @@
|
||||
.modal {
|
||||
position: fixed;
|
||||
margin: 0 auto;
|
||||
height: auto;
|
||||
background: var(--bg-color);
|
||||
border-width: 8px;
|
||||
border-image: url("/background/frame.png") 22 fill / auto space;
|
||||
padding: 44px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
& button {
|
||||
font-size: 1.4rem;
|
||||
margin: 0 11px;
|
||||
}
|
||||
& h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.loadingImage {
|
||||
position: relative;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
.loadingHamster {
|
||||
position: absolute;
|
||||
background-image: url("/loader/hamster.png");
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
animation: jump 0.2s ease infinite;
|
||||
}
|
||||
.loadingHamsterWheel {
|
||||
position: absolute;
|
||||
background-image: url("/loader/hamster_wheel.png");
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
animation: spin 3.5s linear infinite;
|
||||
}
|
||||
.loadingHamsterWheelStand {
|
||||
position: absolute;
|
||||
background-image: url("/loader/hamster_stand.png");
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
.loadingText {
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes jump {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, -10px);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
:root {
|
||||
--bg-color: #1a1a1a;
|
||||
--bg-color-button: #000;
|
||||
--text-color: #ffffff;
|
||||
--accent-color: #f00000;
|
||||
--border-color: #800000;
|
||||
@ -40,8 +41,8 @@ a:hover {
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
background-color: var(--bg-color-button);
|
||||
color: var(--text-color);
|
||||
border: 2px solid var(--border-color);
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
@ -49,6 +50,7 @@ button {
|
||||
|
||||
button:hover {
|
||||
background-color: var(--hover-color);
|
||||
color: var(--bg-color-button);
|
||||
}
|
||||
|
||||
header,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
68
broadcast/RaidGeld.s.sol/84532/run-1730194354.json
Normal file
68
broadcast/RaidGeld.s.sol/84532/run-1730194354.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,4 +1,23 @@
|
||||
#!/bin/sh
|
||||
cast rpc anvil_setBalance 0x3295CCA2d922c637d35b258fc6c9C7e471803b45 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
|
||||
forge script script/RaidGeld.s.sol:RaidGeldScript --rpc-url 127.0.0.1:8545 --broadcast --private-key $DEV_PRIVATE_KEY
|
||||
cast rpc anvil_mine
|
||||
#!/bin/bash
|
||||
|
||||
# YOUR WALLET, change to you account below:
|
||||
DEV_WALLET="0x3295CCA2d922c637d35b258fc6c9C7e471803b45"
|
||||
|
||||
DAO_OWNER="0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD"
|
||||
DAO_CONTRACT="0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8"
|
||||
|
||||
# Set balance for the dev wallet (1eth)
|
||||
cast rpc anvil_setBalance $DEV_WALLET 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
|
||||
cast rpc anvil_setBalance $DAO_OWNER 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
|
||||
|
||||
# Deploy RaidGeld
|
||||
forge script script/RaidGeld.s.sol:RaidGeldScript --rpc-url http://127.0.0.1:8545 --broadcast --private-key $DEV_PRIVATE_KEY
|
||||
|
||||
# Impersonate the DAO owner account
|
||||
cast rpc anvil_impersonateAccount $DAO_OWNER
|
||||
|
||||
# Send the mint transaction as the impersonated owner
|
||||
cast send $DAO_CONTRACT "mint(address,uint256)" $DEV_WALLET 0x00000000000000000000000000000000000000000000003635c9adc5dea00000 --from $DAO_OWNER --rpc-url http://127.0.0.1:8545 --unlocked --gas-limit 300000
|
||||
|
||||
# Stop impersonating the DAO owner
|
||||
cast rpc anvil_stopImpersonatingAccount $DAO_OWNER
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262
|
||||
Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa
|
||||
@ -3,15 +3,16 @@ pragma solidity ^0.8.13;
|
||||
|
||||
import {Script, console} from "forge-std/Script.sol";
|
||||
import {RaidGeld} from "../src/RaidGeld.sol";
|
||||
import {Constants} from "../src/Constants.sol";
|
||||
|
||||
contract RaidGeldScript is Script {
|
||||
contract RaidGeldScript is Script, Constants {
|
||||
RaidGeld public raidgeld;
|
||||
|
||||
function setUp() public {}
|
||||
|
||||
function run() public {
|
||||
vm.startBroadcast();
|
||||
raidgeld = new RaidGeld();
|
||||
raidgeld = new RaidGeld(DAO_TOKEN, POOL);
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
10
src/Constants.sol
Normal file
10
src/Constants.sol
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
contract Constants {
|
||||
//base addresses
|
||||
address public constant DAO_TOKEN = 0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8;
|
||||
address public constant POOL = 0x27004f6d0c1bB7979367D32Ba9d6DF6d61A18926;
|
||||
address public constant WETH = 0x4200000000000000000000000000000000000006;
|
||||
address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
||||
}
|
||||
109
src/RaidGeld.sol
109
src/RaidGeld.sol
@ -3,18 +3,27 @@ pragma solidity ^0.8.13;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
import {RaidGeldUtils} from "../src/RaidGeldUtils.sol";
|
||||
import {Army, Player, Raider} from "../src/RaidGeldStructs.sol";
|
||||
import "../src/Constants.sol";
|
||||
|
||||
contract RaidGeld is ERC20, Ownable {
|
||||
contract RaidGeld is ERC20, Ownable, Constants {
|
||||
uint256 public constant MANTISSA = 1e4;
|
||||
|
||||
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether;
|
||||
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
||||
|
||||
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
|
||||
uint256 public constant INITIAL_GELD = 500 * MANTISSA;
|
||||
mapping(address => Player) private players;
|
||||
mapping(address => Army) private armies;
|
||||
|
||||
// WETH
|
||||
IWETH public immutable weth = IWETH(WETH);
|
||||
// RGCVII token
|
||||
ERC20 public daoToken;
|
||||
// RGCVII pool
|
||||
address public pool;
|
||||
// Uniswap
|
||||
ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER);
|
||||
// Events
|
||||
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
||||
event RaidPerformed(
|
||||
@ -40,19 +49,20 @@ contract RaidGeld is ERC20, Ownable {
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() ERC20("Raid Geld", "GELD") Ownable(msg.sender) {}
|
||||
modifier newPlayer() {
|
||||
require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
|
||||
_;
|
||||
}
|
||||
|
||||
// This effectively registers the user
|
||||
function register() external payable {
|
||||
require(
|
||||
players[msg.sender].created_at == 0,
|
||||
"Whoops, player already exists :)"
|
||||
);
|
||||
require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");
|
||||
constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) {
|
||||
daoToken = ERC20(_daoToken);
|
||||
pool = _pool;
|
||||
BUY_IN_DAO_TOKEN_AMOUNT = 50 * 10 ** daoToken.decimals();
|
||||
}
|
||||
|
||||
function init_player(address player) private {
|
||||
// Mint some starting tokens to the player
|
||||
_mint(msg.sender, INITIAL_GELD);
|
||||
|
||||
_mint(player, INITIAL_GELD);
|
||||
// Set initial states
|
||||
players[msg.sender] = Player({
|
||||
total_minted: INITIAL_GELD,
|
||||
@ -71,14 +81,44 @@ contract RaidGeld is ERC20, Ownable {
|
||||
emit PlayerRegistered(msg.sender, INITIAL_GELD);
|
||||
}
|
||||
|
||||
// New player want to register with ETH
|
||||
function register_eth() external payable newPlayer {
|
||||
require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");
|
||||
weth.deposit{value: BUY_IN_AMOUNT}();
|
||||
weth.approve(address(router), BUY_IN_AMOUNT);
|
||||
ISwapRouter02.ExactInputSingleParams memory params = ISwapRouter02.ExactInputSingleParams({
|
||||
tokenIn: WETH,
|
||||
tokenOut: DAO_TOKEN,
|
||||
fee: 10000,
|
||||
recipient: address(this),
|
||||
amountIn: BUY_IN_AMOUNT,
|
||||
amountOutMinimum: 0,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
router.exactInputSingle(params);
|
||||
init_player(msg.sender);
|
||||
}
|
||||
|
||||
// New player wants to register with dao
|
||||
function register_dao() external payable newPlayer {
|
||||
//@notice this is not safe for arbitrary tokens, which may not follow the interface eg. USDT
|
||||
//@notice but should be fine for the DAO token
|
||||
require(
|
||||
daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens"
|
||||
);
|
||||
// Init player
|
||||
init_player(msg.sender);
|
||||
}
|
||||
|
||||
// Override for default number of decimals
|
||||
function decimals() public view virtual override returns (uint8) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Allows the owner to withdraw
|
||||
// Allows the owner to withdraw DAO tokens
|
||||
function withdraw() external onlyOwner {
|
||||
payable(owner()).transfer(address(this).balance);
|
||||
uint256 amount = daoToken.balanceOf(address(this));
|
||||
daoToken.transfer(owner(), amount);
|
||||
}
|
||||
|
||||
// Manual minting for itchy fingers
|
||||
@ -206,3 +246,40 @@ contract RaidGeld is ERC20, Ownable {
|
||||
revert("No fallback calls accepted");
|
||||
}
|
||||
}
|
||||
|
||||
interface ISwapRouter02 {
|
||||
struct ExactInputSingleParams {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint24 fee;
|
||||
address recipient;
|
||||
uint256 amountIn;
|
||||
uint256 amountOutMinimum;
|
||||
uint160 sqrtPriceLimitX96;
|
||||
}
|
||||
|
||||
function exactInputSingle(ExactInputSingleParams calldata params)
|
||||
external
|
||||
payable
|
||||
returns (uint256 amountOut);
|
||||
|
||||
struct ExactOutputSingleParams {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint24 fee;
|
||||
address recipient;
|
||||
uint256 amountOut;
|
||||
uint256 amountInMaximum;
|
||||
uint160 sqrtPriceLimitX96;
|
||||
}
|
||||
|
||||
function exactOutputSingle(ExactOutputSingleParams calldata params)
|
||||
external
|
||||
payable
|
||||
returns (uint256 amountIn);
|
||||
}
|
||||
|
||||
interface IWETH is IERC20 {
|
||||
function deposit() external payable;
|
||||
function withdraw(uint256 amount) external;
|
||||
}
|
||||
|
||||
@ -2,15 +2,20 @@
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {stdStorage, StdStorage} from "forge-std/Test.sol";
|
||||
import {RaidGeld, Army, Player} from "../src/RaidGeld.sol";
|
||||
import "../src/RaidGeldUtils.sol";
|
||||
import {Constants} from "../src/Constants.sol";
|
||||
|
||||
contract raid_geldTest is Test, Constants {
|
||||
using stdStorage for StdStorage;
|
||||
|
||||
contract raid_geldTest is Test {
|
||||
RaidGeld public raid_geld;
|
||||
address public player1;
|
||||
address public player2;
|
||||
address public owner;
|
||||
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
||||
event RaidPerformed(
|
||||
address indexed player,
|
||||
@ -33,13 +38,24 @@ contract raid_geldTest is Test {
|
||||
owner = address(0x126);
|
||||
player1 = address(0x123);
|
||||
vm.deal(owner, 10 ether);
|
||||
vm.deal(player1, 10 ether);
|
||||
fundAccount(player1);
|
||||
vm.prank(owner);
|
||||
raid_geld = new RaidGeld();
|
||||
raid_geld = new RaidGeld(DAO_TOKEN, POOL);
|
||||
raid_geld.weth().deposit{value: 5 ether}();
|
||||
}
|
||||
|
||||
function fundAccount(address _acc) private {
|
||||
vm.deal(_acc, 10 ether);
|
||||
stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(100 ether);
|
||||
}
|
||||
|
||||
function registerPlayer() private {
|
||||
raid_geld.register{value: raid_geld.BUY_IN_AMOUNT()}();
|
||||
raid_geld.register_eth{value: raid_geld.BUY_IN_AMOUNT()}();
|
||||
}
|
||||
|
||||
function registerPlayerWithDaoToken() private {
|
||||
raid_geld.daoToken().approve(address(raid_geld), raid_geld.BUY_IN_DAO_TOKEN_AMOUNT());
|
||||
raid_geld.register_dao();
|
||||
}
|
||||
|
||||
function test_00_no_fallback() public {
|
||||
@ -54,26 +70,58 @@ contract raid_geldTest is Test {
|
||||
payable(address(raid_geld)).transfer(0.1 ether);
|
||||
}
|
||||
|
||||
function test_02_registration() public {
|
||||
function test_02_1_registrationWithEth() public {
|
||||
vm.startPrank(player1);
|
||||
|
||||
uint256 initialBalance = address(raid_geld).balance;
|
||||
uint256 contractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
uint256 userBalance = address(player1).balance;
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
vm.expectEmit(address(raid_geld));
|
||||
|
||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||
|
||||
// Send registration fee ETH to the contract
|
||||
registerPlayer();
|
||||
|
||||
// Check that initialraid_geld.is received by the player
|
||||
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
||||
|
||||
// Verify the contract balance is updated
|
||||
uint256 contractBalance2 = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
uint256 userBalance2 = address(player1).balance;
|
||||
|
||||
// Contract should get DAO tokens
|
||||
assertLt(contractBalance, contractBalance2);
|
||||
// Player should lose ETH
|
||||
assertEq(userBalance2, userBalance - raid_geld.BUY_IN_AMOUNT());
|
||||
|
||||
// Verify player is set initially
|
||||
Player memory player = raid_geld.getPlayer(player1);
|
||||
assertEq(player.total_minted, raid_geld.INITIAL_GELD());
|
||||
assertEq(player.created_at, block.timestamp);
|
||||
assertEq(player.last_raided_at, block.timestamp);
|
||||
|
||||
Army memory army = raid_geld.getArmy(player1);
|
||||
|
||||
assertEq(army.moloch_denier.level, 0);
|
||||
assertEq(army.apprentice.level, 0);
|
||||
assertEq(army.anointed.level, 0);
|
||||
assertEq(army.champion.level, 0);
|
||||
}
|
||||
|
||||
function test_02_2_registrationWithDaoToken() public {
|
||||
vm.startPrank(player1);
|
||||
|
||||
uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
// doesnt test player emitted event because other events get emitted before it
|
||||
registerPlayerWithDaoToken();
|
||||
|
||||
// Check that initial raid_geld is received by the player
|
||||
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
||||
|
||||
// Verify the contract dao token balance is updated
|
||||
assertEq(
|
||||
address(raid_geld).balance,
|
||||
initialBalance + raid_geld.BUY_IN_AMOUNT()
|
||||
raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT()
|
||||
);
|
||||
|
||||
// Verify player is set initially
|
||||
@ -90,40 +138,30 @@ contract raid_geldTest is Test {
|
||||
assertEq(army.champion.level, 0);
|
||||
}
|
||||
|
||||
function test_03_funds_can_be_withdrawn() public {
|
||||
uint256 initialBalance = owner.balance;
|
||||
function test_03_dao_token_can_be_withdrawn() public {
|
||||
uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
|
||||
// Switch to Player 1 and register it
|
||||
vm.startPrank(player1);
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
vm.expectEmit(address(raid_geld));
|
||||
|
||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||
|
||||
registerPlayer();
|
||||
// doesnt test player emitted event because other events get emitted before it
|
||||
registerPlayerWithDaoToken();
|
||||
|
||||
// Switch back to owner and withdraw funds
|
||||
vm.startPrank(owner);
|
||||
raid_geld.withdraw();
|
||||
uint256 newBalance = owner.balance;
|
||||
uint256 newContractBalance = address(raid_geld).balance;
|
||||
uint256 newBalance = raid_geld.daoToken().balanceOf(address(owner));
|
||||
uint256 newContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
|
||||
// contract balance should be empty
|
||||
assertEq(newContractBalance, 0);
|
||||
// owner should have the extra funds
|
||||
assertEq(newBalance, initialBalance + raid_geld.BUY_IN_AMOUNT());
|
||||
assertGt(newBalance, initialBalance);
|
||||
}
|
||||
|
||||
function test_04_only_owner_can_withdraw() public {
|
||||
// Register player 1
|
||||
vm.startPrank(player1);
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
vm.expectEmit(address(raid_geld));
|
||||
|
||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||
|
||||
registerPlayer();
|
||||
|
||||
// attempt to withdraw with player 1, it should fail
|
||||
@ -138,7 +176,6 @@ contract raid_geldTest is Test {
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
vm.expectEmit(address(raid_geld));
|
||||
|
||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||
|
||||
registerPlayer();
|
||||
@ -151,7 +188,6 @@ contract raid_geldTest is Test {
|
||||
|
||||
// Making sure event is emitted when player is registered
|
||||
vm.expectEmit(address(raid_geld));
|
||||
|
||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||
|
||||
registerPlayer();
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {Army, Raider} from "../src/RaidGeld.sol";
|
||||
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
||||
import "../src/RaidGeldUtils.sol";
|
||||
|
||||
contract raid_geldTest is Test {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user