Compare commits

...

23 Commits

Author SHA1 Message Date
d976f13a04 Merge pull request 'dao-inegration' (#2) from dao-inegration into main
Some checks are pending
CI / Foundry project (push) Waiting to run
Reviewed-on: #2
2024-10-29 23:32:13 +00:00
ee6c9ec710
Changed the buy in amounts in client as well
Some checks failed
CI / Foundry project (push) Has been cancelled
CI / Foundry project (pull_request) Has been cancelled
2024-10-30 00:31:06 +01:00
yellow
81170fd8ec reg price aligned, withdraw refactor
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-29 19:35:26 +01:00
52aec1ae9d
Raises RGCVII for entry to 500
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-29 12:14:30 +01:00
64b9be5ae8
Merged latest main
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-29 12:12:55 +01:00
d64f98c0d4
Fixes botched merge
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 22:10:07 +01:00
41ec092f09
Merged latest main
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 22:01:41 +01:00
b584399d91
Makes the sprockets of the loading wheel a bit nicer color
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 18:45:14 +01:00
6e0012e9d6
31337 chain id is already in gitignore, whoops
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 18:32:43 +01:00
8c8ca58607
Enables registrations with RGCVII and ETH
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 18:31:41 +01:00
bd1f418e11
Test naming fix
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 10:08:35 +01:00
c04744d0c8
Adds Swap on registration so ETH goes and swaps for RGCVII tokens
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-28 10:06:53 +01:00
f086e9bac4
Added uniswap router contracts 2024-10-28 00:25:24 +01:00
98050890ed
Removed uni v3 2024-10-27 23:18:54 +01:00
aaf1c0fd46
Swapping WETH -> RGCVII
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-27 21:20:38 +01:00
d28e33612e
forge install: v3-core
v1.0.0
2024-10-27 13:37:24 +01:00
a4be82cbd9
Added v3-periphery and remappings 2024-10-27 13:37:15 +01:00
8b35e86904
forge install: v3-periphery
v1.3.0
2024-10-27 12:57:15 +01:00
d197935fae
Prepping uniswap of received ETH -> RGCVII 2024-10-27 12:57:05 +01:00
07f6b3cefe
Merge branch 'main' into dao-inegration
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-27 12:00:24 +01:00
7b9661bda7
Allows withdrawal of DAO tokens back to owner() account
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
2024-10-27 11:52:26 +01:00
yellow
5b0d24ccba registration with dao token
Some checks failed
CI / Foundry project (push) Has been cancelled
CI / Foundry project (pull_request) Has been cancelled
2024-10-25 23:10:40 +02:00
yellow
9873dfc76b constructor 2024-10-25 22:37:40 +02:00
24 changed files with 519 additions and 290 deletions

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -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>

View 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

View 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

View File

@ -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>
<ModalProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</ModalProvider>
</PlayerProvider> </PlayerProvider>
</RainbowKitProvider> </RainbowKitProvider>
</QueryClientProvider> </QueryClientProvider>

View 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

View File

@ -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") => {
if (arg === 'ETH') {
writeContract({ writeContract({
abi, abi,
address: contractAddress, address: contractAddress,
functionName: 'register', functionName: 'register_eth',
value: parseEther("0.00005"), value: parseEther("0.00005"),
}, {
onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback])
}
}) })
}, [writeContract]) } 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>
); );
} }

View 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);
}
}

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,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

View File

@ -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
View 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;
}

View File

@ -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;
}

View File

@ -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,26 +70,58 @@ 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
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(
address(raid_geld).balance, raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT()
initialBalance + raid_geld.BUY_IN_AMOUNT()
); );
// Verify player is set initially // Verify player is set initially
@ -90,40 +138,30 @@ 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_03_dao_token_can_be_withdrawn() public {
uint256 initialBalance = owner.balance; 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();

View File

@ -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 {