Compare commits
No commits in common. "d976f13a04b790ca17dd013b6d67e666e62f70a7" and "504197e83cbdf5e8328cce3e4ccaf64360629b61" have entirely different histories.
d976f13a04
...
504197e83c
33
README.md
33
README.md
@ -4,41 +4,24 @@ Idle game & shitcoin advanture dedicated to cohort VII of Raid Guild.
|
|||||||
|
|
||||||
## Set up for local DEV
|
## Set up for local DEV
|
||||||
|
|
||||||
### 1. Run `anvil` to setup local RPC as a fork of base mainnet
|
### 1. Run `anvil` to setup local RPC
|
||||||
|
|
||||||
`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
|
### 2. Deploy contract
|
||||||
|
|
||||||
Use `./deploy_contract.sh` script
|
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.
|
||||||
|
|
||||||
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
|
### 3. Run dev app
|
||||||
|
|
||||||
Move to `app` dir, install deps via `npm install` and run `npm run dev` to start the dev server.
|
Move to `app` dir, install deps via `npm install` and run `npm run dev` to start the dev server.
|
||||||
|
|
||||||
#### 3. 1. Point Metamask to Anvil network for local dev
|
#### 3. 1. Run `cast rpc anvil_mine`
|
||||||
|
|
||||||
Add network `http://127.0.0.1:8545` with chain id `31337`
|
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.
|
||||||
|
|
||||||
#### 3. 2. Change `app/contract_address.ts` to match your program address if needed
|
#### 3. 2. Point Metamask to Anvil network for local dev
|
||||||
|
|
||||||
#### 3. 3. Reset metamask transaction history between anvil deployments
|
#### 3. 3. Change `app/contract_address.ts` to match your program address if needed
|
||||||
|
|
||||||
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 (?)
|
### 4. Local development requires mining blocks by hand
|
||||||
|
|
||||||
### 4. Fork tests
|
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
|
||||||
|
|
||||||
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,8 +1,4 @@
|
|||||||
import { Address } from "viem"
|
const contractAddress = "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66";
|
||||||
|
|
||||||
const contracts: Record<string, Address> = {
|
|
||||||
contractAddress: "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66",
|
|
||||||
daoTokenAddress: "0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8"
|
|
||||||
}
|
|
||||||
|
|
||||||
export default contracts
|
export default contractAddress
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@ -5,12 +5,10 @@ import { usePlayer } from "../providers/PlayerProvider";
|
|||||||
import { useAccount } from 'wagmi';
|
import { useAccount } from 'wagmi';
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Counter, { toReadable } from "./Counter";
|
import Counter, { toReadable } from "./Counter";
|
||||||
import { useModal } from "../providers/ModalProvider";
|
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { isConnected } = useAccount();
|
const { isConnected } = useAccount();
|
||||||
const { isRegistered, army } = usePlayer();
|
const { isRegistered, register, army } = usePlayer();
|
||||||
const { openRegistrationModal } = useModal();
|
|
||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
return isRegistered ? `SLAY THE MOLOCH` :
|
return isRegistered ? `SLAY THE MOLOCH` :
|
||||||
@ -35,8 +33,8 @@ const Header = () => {
|
|||||||
|
|
||||||
const onRegister = useCallback(() => {
|
const onRegister = useCallback(() => {
|
||||||
if (isRegistered) return
|
if (isRegistered) return
|
||||||
openRegistrationModal()
|
register();
|
||||||
}, [isRegistered, openRegistrationModal])
|
}, [isRegistered, register])
|
||||||
|
|
||||||
return <header onClick={onRegister} className={styles.header}>
|
return <header onClick={onRegister} className={styles.header}>
|
||||||
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>
|
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
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,8 +8,6 @@ import { RainbowKitProvider, midnightTheme } from "@rainbow-me/rainbowkit";
|
|||||||
import { config } from "../wagmi";
|
import { config } from "../wagmi";
|
||||||
import { Press_Start_2P, Texturina } from "next/font/google";
|
import { Press_Start_2P, Texturina } from "next/font/google";
|
||||||
import PlayerProvider from "../providers/PlayerProvider";
|
import PlayerProvider from "../providers/PlayerProvider";
|
||||||
import ModalProvider from '../providers/ModalProvider';
|
|
||||||
|
|
||||||
|
|
||||||
const client = new QueryClient();
|
const client = new QueryClient();
|
||||||
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
||||||
@ -44,9 +42,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<PlayerProvider>
|
<PlayerProvider>
|
||||||
<ModalProvider>
|
<Component {...pageProps} />
|
||||||
<Component {...pageProps} />
|
|
||||||
</ModalProvider>
|
|
||||||
</PlayerProvider>
|
</PlayerProvider>
|
||||||
</RainbowKitProvider>
|
</RainbowKitProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
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,11 +1,9 @@
|
|||||||
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
|
import React, { createContext, ReactNode, useCallback, useContext, useEffect } from 'react'
|
||||||
import { useAccount, useReadContract, useWriteContract } from 'wagmi'
|
import { useAccount, useReadContract, useWriteContract } from 'wagmi'
|
||||||
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
||||||
import { Hash, parseEther } from 'viem'
|
import { parseEther } from 'viem'
|
||||||
import contracts from '../../contract_address'
|
import contractAddress from '../../contract_address'
|
||||||
import WaitingForTxModal from '../components/WaitingForTxModal'
|
|
||||||
|
|
||||||
const { contractAddress, daoTokenAddress } = contracts
|
|
||||||
const abi = contractAbi.abi
|
const abi = contractAbi.abi
|
||||||
|
|
||||||
export type UnitType = 0 | 1 | 2 | 3
|
export type UnitType = 0 | 1 | 2 | 3
|
||||||
@ -28,7 +26,7 @@ export interface PlayerContextType {
|
|||||||
player: null | Player,
|
player: null | Player,
|
||||||
army: null | Army,
|
army: null | Army,
|
||||||
balance: bigint,
|
balance: bigint,
|
||||||
register: (arg: "ETH" | "RGCVII") => void,
|
register: () => void,
|
||||||
raid: () => void,
|
raid: () => void,
|
||||||
addUnit: (unit: UnitType) => void
|
addUnit: (unit: UnitType) => void
|
||||||
}
|
}
|
||||||
@ -46,16 +44,11 @@ const PlayerContext = createContext<PlayerContextType>({
|
|||||||
const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const { address, isConnected } = useAccount();
|
const { address, isConnected } = useAccount();
|
||||||
const { writeContract, error } = useWriteContract();
|
const { writeContract, error } = useWriteContract();
|
||||||
const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.warn(error)
|
console.warn(error)
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const resetHashAndCallback = useCallback(() => {
|
|
||||||
setHashAndCallback([null, () => { }])
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const { data: isRegistered } = useReadContract({
|
const { data: isRegistered } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
@ -63,7 +56,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 15,
|
refetchInterval: 5,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,7 +66,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
functionName: 'balanceOf',
|
functionName: 'balanceOf',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
refetchInterval: 15,
|
refetchInterval: 5,
|
||||||
enabled: isConnected
|
enabled: isConnected
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -102,42 +95,14 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
console.log(balance, player, army)
|
console.log(balance, player, army)
|
||||||
|
|
||||||
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
const register = useCallback(() => {
|
||||||
if (arg === 'ETH') {
|
writeContract({
|
||||||
writeContract({
|
abi,
|
||||||
abi,
|
address: contractAddress,
|
||||||
address: contractAddress,
|
functionName: 'register',
|
||||||
functionName: 'register_eth',
|
value: parseEther("0.00005"),
|
||||||
value: parseEther("0.00005"),
|
})
|
||||||
}, {
|
}, [writeContract])
|
||||||
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(() => {
|
const raid = useCallback(() => {
|
||||||
writeContract({
|
writeContract({
|
||||||
@ -167,7 +132,6 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
addUnit
|
addUnit
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
|
||||||
</PlayerContext.Provider>
|
</PlayerContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
.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,6 +1,5 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg-color: #1a1a1a;
|
--bg-color: #1a1a1a;
|
||||||
--bg-color-button: #000;
|
|
||||||
--text-color: #ffffff;
|
--text-color: #ffffff;
|
||||||
--accent-color: #f00000;
|
--accent-color: #f00000;
|
||||||
--border-color: #800000;
|
--border-color: #800000;
|
||||||
@ -41,8 +40,8 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: var(--bg-color-button);
|
background-color: var(--accent-color);
|
||||||
color: var(--text-color);
|
color: var(--bg-color);
|
||||||
border: 2px solid var(--border-color);
|
border: 2px solid var(--border-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -50,7 +49,6 @@ button {
|
|||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: var(--hover-color);
|
background-color: var(--hover-color);
|
||||||
color: var(--bg-color-button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header,
|
header,
|
||||||
|
|||||||
68
broadcast/RaidGeld.s.sol/84532/run-1729696549.json
Normal file
68
broadcast/RaidGeld.s.sol/84532/run-1729696549.json
Normal file
File diff suppressed because one or more lines are too long
68
broadcast/RaidGeld.s.sol/84532/run-1729773387.json
Normal file
68
broadcast/RaidGeld.s.sol/84532/run-1729773387.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
68
broadcast/RaidGeld.s.sol/84532/run-latest.json
Normal file
68
broadcast/RaidGeld.s.sol/84532/run-latest.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,23 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
cast rpc anvil_setBalance 0x3295CCA2d922c637d35b258fc6c9C7e471803b45 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
|
||||||
# YOUR WALLET, change to you account below:
|
forge script script/RaidGeld.s.sol:RaidGeldScript --rpc-url 127.0.0.1:8545 --broadcast --private-key $DEV_PRIVATE_KEY
|
||||||
DEV_WALLET="0x3295CCA2d922c637d35b258fc6c9C7e471803b45"
|
cast rpc anvil_mine
|
||||||
|
|
||||||
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 8f24d6b04c92975e0795b5868aa0d783251cdeaa
|
Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262
|
||||||
@ -3,16 +3,15 @@ pragma solidity ^0.8.13;
|
|||||||
|
|
||||||
import {Script, console} from "forge-std/Script.sol";
|
import {Script, console} from "forge-std/Script.sol";
|
||||||
import {RaidGeld} from "../src/RaidGeld.sol";
|
import {RaidGeld} from "../src/RaidGeld.sol";
|
||||||
import {Constants} from "../src/Constants.sol";
|
|
||||||
|
|
||||||
contract RaidGeldScript is Script, Constants {
|
contract RaidGeldScript is Script {
|
||||||
RaidGeld public raidgeld;
|
RaidGeld public raidgeld;
|
||||||
|
|
||||||
function setUp() public {}
|
function setUp() public {}
|
||||||
|
|
||||||
function run() public {
|
function run() public {
|
||||||
vm.startBroadcast();
|
vm.startBroadcast();
|
||||||
raidgeld = new RaidGeld(DAO_TOKEN, POOL);
|
raidgeld = new RaidGeld();
|
||||||
vm.stopBroadcast();
|
vm.stopBroadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
// 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,27 +3,18 @@ pragma solidity ^0.8.13;
|
|||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
|
||||||
import {RaidGeldUtils} from "../src/RaidGeldUtils.sol";
|
import {RaidGeldUtils} from "../src/RaidGeldUtils.sol";
|
||||||
import {Army, Player, Raider} from "../src/RaidGeldStructs.sol";
|
import {Army, Player, Raider} from "../src/RaidGeldStructs.sol";
|
||||||
import "../src/Constants.sol";
|
|
||||||
|
|
||||||
contract RaidGeld is ERC20, Ownable, Constants {
|
contract RaidGeld is ERC20, Ownable {
|
||||||
uint256 public constant MANTISSA = 1e4;
|
uint256 public constant MANTISSA = 1e4;
|
||||||
|
|
||||||
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether;
|
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether;
|
||||||
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
|
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
||||||
uint256 public constant INITIAL_GELD = 500 * MANTISSA;
|
|
||||||
mapping(address => Player) private players;
|
mapping(address => Player) private players;
|
||||||
mapping(address => Army) private armies;
|
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
|
// Events
|
||||||
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
||||||
event RaidPerformed(
|
event RaidPerformed(
|
||||||
@ -49,20 +40,19 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier newPlayer() {
|
constructor() ERC20("Raid Geld", "GELD") Ownable(msg.sender) {}
|
||||||
require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) {
|
// This effectively registers the user
|
||||||
daoToken = ERC20(_daoToken);
|
function register() external payable {
|
||||||
pool = _pool;
|
require(
|
||||||
BUY_IN_DAO_TOKEN_AMOUNT = 50 * 10 ** daoToken.decimals();
|
players[msg.sender].created_at == 0,
|
||||||
}
|
"Whoops, player already exists :)"
|
||||||
|
);
|
||||||
|
require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");
|
||||||
|
|
||||||
function init_player(address player) private {
|
|
||||||
// Mint some starting tokens to the player
|
// Mint some starting tokens to the player
|
||||||
_mint(player, INITIAL_GELD);
|
_mint(msg.sender, INITIAL_GELD);
|
||||||
|
|
||||||
// Set initial states
|
// Set initial states
|
||||||
players[msg.sender] = Player({
|
players[msg.sender] = Player({
|
||||||
total_minted: INITIAL_GELD,
|
total_minted: INITIAL_GELD,
|
||||||
@ -81,44 +71,14 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
emit PlayerRegistered(msg.sender, INITIAL_GELD);
|
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
|
// Override for default number of decimals
|
||||||
function decimals() public view virtual override returns (uint8) {
|
function decimals() public view virtual override returns (uint8) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows the owner to withdraw DAO tokens
|
// Allows the owner to withdraw
|
||||||
function withdraw() external onlyOwner {
|
function withdraw() external onlyOwner {
|
||||||
uint256 amount = daoToken.balanceOf(address(this));
|
payable(owner()).transfer(address(this).balance);
|
||||||
daoToken.transfer(owner(), amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual minting for itchy fingers
|
// Manual minting for itchy fingers
|
||||||
@ -246,40 +206,3 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
revert("No fallback calls accepted");
|
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,20 +2,15 @@
|
|||||||
pragma solidity ^0.8.13;
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
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 {RaidGeld, Army, Player} from "../src/RaidGeld.sol";
|
||||||
import "../src/RaidGeldUtils.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;
|
RaidGeld public raid_geld;
|
||||||
address public player1;
|
address public player1;
|
||||||
address public player2;
|
address public player2;
|
||||||
address public owner;
|
address public owner;
|
||||||
|
|
||||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
|
||||||
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
||||||
event RaidPerformed(
|
event RaidPerformed(
|
||||||
address indexed player,
|
address indexed player,
|
||||||
@ -38,24 +33,13 @@ contract raid_geldTest is Test, Constants {
|
|||||||
owner = address(0x126);
|
owner = address(0x126);
|
||||||
player1 = address(0x123);
|
player1 = address(0x123);
|
||||||
vm.deal(owner, 10 ether);
|
vm.deal(owner, 10 ether);
|
||||||
fundAccount(player1);
|
vm.deal(player1, 10 ether);
|
||||||
vm.prank(owner);
|
vm.prank(owner);
|
||||||
raid_geld = new RaidGeld(DAO_TOKEN, POOL);
|
raid_geld = new RaidGeld();
|
||||||
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 {
|
function registerPlayer() private {
|
||||||
raid_geld.register_eth{value: raid_geld.BUY_IN_AMOUNT()}();
|
raid_geld.register{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 {
|
function test_00_no_fallback() public {
|
||||||
@ -70,58 +54,26 @@ contract raid_geldTest is Test, Constants {
|
|||||||
payable(address(raid_geld)).transfer(0.1 ether);
|
payable(address(raid_geld)).transfer(0.1 ether);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_02_1_registrationWithEth() public {
|
function test_02_registration() public {
|
||||||
vm.startPrank(player1);
|
vm.startPrank(player1);
|
||||||
|
|
||||||
uint256 contractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
uint256 initialBalance = address(raid_geld).balance;
|
||||||
uint256 userBalance = address(player1).balance;
|
|
||||||
|
|
||||||
// Making sure event is emitted when player is registered
|
// Making sure event is emitted when player is registered
|
||||||
vm.expectEmit(address(raid_geld));
|
vm.expectEmit(address(raid_geld));
|
||||||
|
|
||||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
|
// Send registration fee ETH to the contract
|
||||||
registerPlayer();
|
registerPlayer();
|
||||||
|
|
||||||
// Check that initialraid_geld.is received by the player
|
// Check that initialraid_geld.is received by the player
|
||||||
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
// Verify the contract balance is updated
|
// 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(
|
assertEq(
|
||||||
raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT()
|
address(raid_geld).balance,
|
||||||
|
initialBalance + raid_geld.BUY_IN_AMOUNT()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify player is set initially
|
// Verify player is set initially
|
||||||
@ -138,30 +90,40 @@ contract raid_geldTest is Test, Constants {
|
|||||||
assertEq(army.champion.level, 0);
|
assertEq(army.champion.level, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_03_dao_token_can_be_withdrawn() public {
|
function test_03_funds_can_be_withdrawn() public {
|
||||||
uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
uint256 initialBalance = owner.balance;
|
||||||
|
|
||||||
// Switch to Player 1 and register it
|
// Switch to Player 1 and register it
|
||||||
vm.startPrank(player1);
|
vm.startPrank(player1);
|
||||||
|
|
||||||
// doesnt test player emitted event because other events get emitted before it
|
// Making sure event is emitted when player is registered
|
||||||
registerPlayerWithDaoToken();
|
vm.expectEmit(address(raid_geld));
|
||||||
|
|
||||||
|
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
|
registerPlayer();
|
||||||
|
|
||||||
// Switch back to owner and withdraw funds
|
// Switch back to owner and withdraw funds
|
||||||
vm.startPrank(owner);
|
vm.startPrank(owner);
|
||||||
raid_geld.withdraw();
|
raid_geld.withdraw();
|
||||||
uint256 newBalance = raid_geld.daoToken().balanceOf(address(owner));
|
uint256 newBalance = owner.balance;
|
||||||
uint256 newContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
uint256 newContractBalance = address(raid_geld).balance;
|
||||||
|
|
||||||
// contract balance should be empty
|
// contract balance should be empty
|
||||||
assertEq(newContractBalance, 0);
|
assertEq(newContractBalance, 0);
|
||||||
// owner should have the extra funds
|
// owner should have the extra funds
|
||||||
assertGt(newBalance, initialBalance);
|
assertEq(newBalance, initialBalance + raid_geld.BUY_IN_AMOUNT());
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_04_only_owner_can_withdraw() public {
|
function test_04_only_owner_can_withdraw() public {
|
||||||
// Register player 1
|
// Register player 1
|
||||||
vm.startPrank(player1);
|
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();
|
registerPlayer();
|
||||||
|
|
||||||
// attempt to withdraw with player 1, it should fail
|
// attempt to withdraw with player 1, it should fail
|
||||||
@ -176,6 +138,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
|
|
||||||
// Making sure event is emitted when player is registered
|
// Making sure event is emitted when player is registered
|
||||||
vm.expectEmit(address(raid_geld));
|
vm.expectEmit(address(raid_geld));
|
||||||
|
|
||||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
registerPlayer();
|
registerPlayer();
|
||||||
@ -188,6 +151,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
|
|
||||||
// Making sure event is emitted when player is registered
|
// Making sure event is emitted when player is registered
|
||||||
vm.expectEmit(address(raid_geld));
|
vm.expectEmit(address(raid_geld));
|
||||||
|
|
||||||
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
emit PlayerRegistered(player1, raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
registerPlayer();
|
registerPlayer();
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
pragma solidity ^0.8.13;
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
import {Test, console} from "forge-std/Test.sol";
|
||||||
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
import {Army, Raider} from "../src/RaidGeld.sol";
|
||||||
import "../src/RaidGeldUtils.sol";
|
import "../src/RaidGeldUtils.sol";
|
||||||
|
|
||||||
contract raid_geldTest is Test {
|
contract raid_geldTest is Test {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user