Compare commits
23 Commits
504197e83c
...
d976f13a04
| Author | SHA1 | Date | |
|---|---|---|---|
| d976f13a04 | |||
| ee6c9ec710 | |||
|
|
81170fd8ec | ||
| 52aec1ae9d | |||
| 64b9be5ae8 | |||
| d64f98c0d4 | |||
| 41ec092f09 | |||
| b584399d91 | |||
| 6e0012e9d6 | |||
| 8c8ca58607 | |||
| bd1f418e11 | |||
| c04744d0c8 | |||
| f086e9bac4 | |||
| 98050890ed | |||
| aaf1c0fd46 | |||
| d28e33612e | |||
| a4be82cbd9 | |||
| 8b35e86904 | |||
| d197935fae | |||
| 07f6b3cefe | |||
| 7b9661bda7 | |||
|
|
5b0d24ccba | ||
|
|
9873dfc76b |
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
|
## 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
|
### 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
|
### 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. 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 { 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, register, army } = usePlayer();
|
const { isRegistered, army } = usePlayer();
|
||||||
|
const { openRegistrationModal } = useModal();
|
||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
return isRegistered ? `SLAY THE MOLOCH` :
|
return isRegistered ? `SLAY THE MOLOCH` :
|
||||||
@ -33,8 +35,8 @@ const Header = () => {
|
|||||||
|
|
||||||
const onRegister = useCallback(() => {
|
const onRegister = useCallback(() => {
|
||||||
if (isRegistered) return
|
if (isRegistered) return
|
||||||
register();
|
openRegistrationModal()
|
||||||
}, [isRegistered, register])
|
}, [isRegistered, openRegistrationModal])
|
||||||
|
|
||||||
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>
|
||||||
|
|||||||
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 { 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"] });
|
||||||
@ -42,7 +44,9 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<PlayerProvider>
|
<PlayerProvider>
|
||||||
<Component {...pageProps} />
|
<ModalProvider>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</ModalProvider>
|
||||||
</PlayerProvider>
|
</PlayerProvider>
|
||||||
</RainbowKitProvider>
|
</RainbowKitProvider>
|
||||||
</QueryClientProvider>
|
</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 { useAccount, useReadContract, useWriteContract } from 'wagmi'
|
||||||
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
||||||
import { parseEther } from 'viem'
|
import { Hash, parseEther } from 'viem'
|
||||||
import contractAddress from '../../contract_address'
|
import contracts 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
|
||||||
@ -26,7 +28,7 @@ export interface PlayerContextType {
|
|||||||
player: null | Player,
|
player: null | Player,
|
||||||
army: null | Army,
|
army: null | Army,
|
||||||
balance: bigint,
|
balance: bigint,
|
||||||
register: () => void,
|
register: (arg: "ETH" | "RGCVII") => void,
|
||||||
raid: () => void,
|
raid: () => void,
|
||||||
addUnit: (unit: UnitType) => void
|
addUnit: (unit: UnitType) => void
|
||||||
}
|
}
|
||||||
@ -44,11 +46,16 @@ 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,
|
||||||
@ -56,7 +63,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 5,
|
refetchInterval: 15,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -66,7 +73,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
functionName: 'balanceOf',
|
functionName: 'balanceOf',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
refetchInterval: 5,
|
refetchInterval: 15,
|
||||||
enabled: isConnected
|
enabled: isConnected
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -95,14 +102,42 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
console.log(balance, player, army)
|
console.log(balance, player, army)
|
||||||
|
|
||||||
const register = useCallback(() => {
|
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
||||||
writeContract({
|
if (arg === 'ETH') {
|
||||||
abi,
|
writeContract({
|
||||||
address: contractAddress,
|
abi,
|
||||||
functionName: 'register',
|
address: contractAddress,
|
||||||
value: parseEther("0.00005"),
|
functionName: 'register_eth',
|
||||||
})
|
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({
|
||||||
@ -132,6 +167,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
addUnit
|
addUnit
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
|
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
||||||
</PlayerContext.Provider>
|
</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 {
|
: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;
|
||||||
@ -40,8 +41,8 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: var(--accent-color);
|
background-color: var(--bg-color-button);
|
||||||
color: var(--bg-color);
|
color: var(--text-color);
|
||||||
border: 2px solid var(--border-color);
|
border: 2px solid var(--border-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -49,6 +50,7 @@ button {
|
|||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: var(--hover-color);
|
background-color: var(--hover-color);
|
||||||
|
color: var(--bg-color-button);
|
||||||
}
|
}
|
||||||
|
|
||||||
header,
|
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
|
#!/bin/bash
|
||||||
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
|
# YOUR WALLET, change to you account below:
|
||||||
cast rpc anvil_mine
|
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 {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 {
|
contract RaidGeldScript is Script, Constants {
|
||||||
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();
|
raidgeld = new RaidGeld(DAO_TOKEN, POOL);
|
||||||
vm.stopBroadcast();
|
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/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 {
|
contract RaidGeld is ERC20, Ownable, Constants {
|
||||||
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 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 => 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(
|
||||||
@ -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
|
constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) {
|
||||||
function register() external payable {
|
daoToken = ERC20(_daoToken);
|
||||||
require(
|
pool = _pool;
|
||||||
players[msg.sender].created_at == 0,
|
BUY_IN_DAO_TOKEN_AMOUNT = 50 * 10 ** daoToken.decimals();
|
||||||
"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(msg.sender, INITIAL_GELD);
|
_mint(player, INITIAL_GELD);
|
||||||
|
|
||||||
// Set initial states
|
// Set initial states
|
||||||
players[msg.sender] = Player({
|
players[msg.sender] = Player({
|
||||||
total_minted: INITIAL_GELD,
|
total_minted: INITIAL_GELD,
|
||||||
@ -71,14 +81,44 @@ contract RaidGeld is ERC20, Ownable {
|
|||||||
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
|
// Allows the owner to withdraw DAO tokens
|
||||||
function withdraw() external onlyOwner {
|
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
|
// Manual minting for itchy fingers
|
||||||
@ -206,3 +246,40 @@ contract RaidGeld is ERC20, Ownable {
|
|||||||
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,15 +2,20 @@
|
|||||||
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,
|
||||||
@ -33,13 +38,24 @@ contract raid_geldTest is Test {
|
|||||||
owner = address(0x126);
|
owner = address(0x126);
|
||||||
player1 = address(0x123);
|
player1 = address(0x123);
|
||||||
vm.deal(owner, 10 ether);
|
vm.deal(owner, 10 ether);
|
||||||
vm.deal(player1, 10 ether);
|
fundAccount(player1);
|
||||||
vm.prank(owner);
|
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 {
|
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 {
|
function test_00_no_fallback() public {
|
||||||
@ -54,27 +70,28 @@ contract raid_geldTest is Test {
|
|||||||
payable(address(raid_geld)).transfer(0.1 ether);
|
payable(address(raid_geld)).transfer(0.1 ether);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_02_registration() public {
|
function test_02_1_registrationWithEth() public {
|
||||||
vm.startPrank(player1);
|
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
|
// 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
|
||||||
assertEq(
|
uint256 contractBalance2 = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||||
address(raid_geld).balance,
|
uint256 userBalance2 = address(player1).balance;
|
||||||
initialBalance + raid_geld.BUY_IN_AMOUNT()
|
|
||||||
);
|
// 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
|
// Verify player is set initially
|
||||||
Player memory player = raid_geld.getPlayer(player1);
|
Player memory player = raid_geld.getPlayer(player1);
|
||||||
@ -90,40 +107,61 @@ contract raid_geldTest is Test {
|
|||||||
assertEq(army.champion.level, 0);
|
assertEq(army.champion.level, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_03_funds_can_be_withdrawn() public {
|
function test_02_2_registrationWithDaoToken() public {
|
||||||
uint256 initialBalance = owner.balance;
|
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(
|
||||||
|
raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_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_03_dao_token_can_be_withdrawn() public {
|
||||||
|
uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||||
|
|
||||||
// Switch to Player 1 and register it
|
// Switch to Player 1 and register it
|
||||||
vm.startPrank(player1);
|
vm.startPrank(player1);
|
||||||
|
|
||||||
// Making sure event is emitted when player is registered
|
// doesnt test player emitted event because other events get emitted before it
|
||||||
vm.expectEmit(address(raid_geld));
|
registerPlayerWithDaoToken();
|
||||||
|
|
||||||
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 = owner.balance;
|
uint256 newBalance = raid_geld.daoToken().balanceOf(address(owner));
|
||||||
uint256 newContractBalance = address(raid_geld).balance;
|
uint256 newContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||||
|
|
||||||
// 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
|
||||||
assertEq(newBalance, initialBalance + raid_geld.BUY_IN_AMOUNT());
|
assertGt(newBalance, initialBalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -138,7 +176,6 @@ contract raid_geldTest is Test {
|
|||||||
|
|
||||||
// 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();
|
||||||
@ -151,7 +188,6 @@ contract raid_geldTest is Test {
|
|||||||
|
|
||||||
// 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/RaidGeld.sol";
|
import {Army, Raider} from "../src/RaidGeldStructs.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