diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..4c0aa9b --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,7 @@ +GeldTest:test_00_no_fallback() (gas: 16801) +GeldTest:test_01_no_receive() (gas: 17274) +GeldTest:test_02_registration() (gas: 160943) +GeldTest:test_03_fundsCanBeWithdrawn() (gas: 161313) +GeldTest:test_04_onlyOwnerCanWithdraw() (gas: 147226) +GeldTest:test_05_raid() (gas: 184786) +GeldTest:test_06_is_registered() (gas: 148625) \ No newline at end of file diff --git a/app/package-lock.json b/app/package-lock.json index 8e70c9e..ea5c5d7 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -14,7 +14,7 @@ "next": "^14.2.10", "react": "^18.3.1", "react-dom": "^18.3.1", - "viem": "2.17.0", + "viem": "^2.17.0", "wagmi": "^2.12.17" }, "devDependencies": { diff --git a/app/package.json b/app/package.json index 8d34403..53665ae 100644 --- a/app/package.json +++ b/app/package.json @@ -15,7 +15,7 @@ "next": "^14.2.10", "react": "^18.3.1", "react-dom": "^18.3.1", - "viem": "2.17.0", + "viem": "^2.17.0", "wagmi": "^2.12.17" }, "devDependencies": { diff --git a/app/src/components/Background.tsx b/app/src/components/Background.tsx index 74605ff..4209a8c 100644 --- a/app/src/components/Background.tsx +++ b/app/src/components/Background.tsx @@ -1,12 +1,13 @@ import React from "react" import styles from '../styles/Background.module.css'; +import Tower from "./Tower"; const Background = () => { return
-
+
diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index f0d65c3..8fbd9c1 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -1,12 +1,57 @@ -import React from "react" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import styles from "../styles/Header.module.css" +import { usePlayer } from "../providers/PlayerProvider"; +import { useAccount } from 'wagmi'; +import dynamic from "next/dynamic"; +import { formatUnits } from "viem"; const Header = () => { - return
-

mico's Slayery

-

1213123 million GELD

-

per second: 55.000 thousand

+ const { isConnected } = useAccount(); + const { isRegistered, register, balance } = usePlayer(); + const [count, setCount] = useState("0") + const [perSecond, setPerSecond] = useState("0") + + useEffect(() => { + if (balance != null) { + setCount(formatUnits(balance, 4)) + } + }, [balance]) + + const title = useMemo(() => { + return isRegistered ? `SLAY THE MOLOCH` : + !isConnected ? "Connect your wallet traveler ☝️ and then ..." : + "Click here to start 😈" + }, [isConnected, isRegistered]) + + const subtitle = useMemo(() => { + if (isRegistered) { + return

{count} GELD

+ } else { + return

SLAY THE MOLOCH

+ } + }, [isRegistered, count]) + + const perSecondParagraph = useMemo(() => { + return (isRegistered) ? +

per second: {perSecond}

+ : null + }, [isRegistered, perSecond]) + + const onRegister = useCallback(() => { + if (isRegistered) return + register(); + }, [isRegistered, register]) + + return
+ {count.current} {balance} +

{title}

+ {subtitle} + {perSecondParagraph}
} -export default Header +// export default Header + +export default dynamic(() => Promise.resolve(Header), { + ssr: false, +}); diff --git a/app/src/components/Tower.tsx b/app/src/components/Tower.tsx new file mode 100644 index 0000000..31418b5 --- /dev/null +++ b/app/src/components/Tower.tsx @@ -0,0 +1,7 @@ +import styles from '../styles/Background.module.css'; + +const Tower = () => { + return
+} + +export default Tower diff --git a/app/src/pages/_app.tsx b/app/src/pages/_app.tsx index 2fad182..1f69bf4 100644 --- a/app/src/pages/_app.tsx +++ b/app/src/pages/_app.tsx @@ -6,6 +6,7 @@ import { WagmiProvider } from 'wagmi'; import { RainbowKitProvider, midnightTheme } from '@rainbow-me/rainbowkit'; import { config } from '../wagmi'; import { Texturina } from 'next/font/google' +import PlayerProvider from '../providers/PlayerProvider'; const client = new QueryClient(); const font = Texturina({ weight: ['400'], subsets: ["latin"] }) @@ -23,7 +24,9 @@ function MyApp({ Component, pageProps }: AppProps) { font-family: ${font.style.fontFamily}; } `} - + + + diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx new file mode 100644 index 0000000..2fabb75 --- /dev/null +++ b/app/src/providers/PlayerProvider.tsx @@ -0,0 +1,91 @@ +import React, { createContext, ReactNode, useCallback, useContext } from 'react' +import { useAccount, useReadContract, useWriteContract } from 'wagmi' +import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json" +import { parseEther } from 'viem' + +const contractAddress = "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66" +const abi = contractAbi.abi + +export interface PlayerContextType { + isRegistered: boolean, + user: null | string, + balance: bigint, + register: () => void; +} + +const PlayerContext = createContext({ + isRegistered: false, + user: null, + balance: BigInt(0), + register: () => { }, +}); + +const PlayerProvider = ({ children }: { children: ReactNode }) => { + const { address, isConnected } = useAccount(); + const { writeContract } = useWriteContract(); + const { data: isRegistered } = useReadContract({ + address: contractAddress, + abi, + functionName: 'isRegistered', + args: [address], + query: { + enabled: isConnected, + refetchInterval: 5, + } + }); + + console.log("IS REGISTERED", isRegistered, address) + + const { data: balance, error, refetch: refetchBalance } = useReadContract({ + address: contractAddress, + abi, + functionName: 'balanceOf', + args: [address], + query: { + refetchInterval: 5, + enabled: isConnected + } + }); + + console.log(balance, error) + + const { data: playerData, refetch: refetchPlayerData } = useReadContract({ + address: contractAddress, + abi, + functionName: 'getPlayer', + args: [address], + query: { + enabled: isConnected, + refetchInterval: 15 + } + }); + + const register = useCallback(() => { + console.log("START") + const a = writeContract({ + abi, + address: contractAddress, + functionName: 'register', + value: parseEther("0.0005"), + }) + console.log(a) + }, [writeContract]) + + return ( + + {children} + + ); +} + +export const usePlayer = () => { + return useContext(PlayerContext); +} + +export default PlayerProvider + diff --git a/app/src/styles/Background.module.css b/app/src/styles/Background.module.css index 40b1cd7..e0f82d9 100644 --- a/app/src/styles/Background.module.css +++ b/app/src/styles/Background.module.css @@ -50,7 +50,7 @@ height: 240px; top: 200px; animation: thunder_hue_hard 12s linear infinite; - transition: all ease-in-out 0.2s; + transition: all ease-in-out 0.05s; &:hover { cursor: pointer; transform: scale(1.05, 1.1); diff --git a/script/Geld.s.sol b/script/RaidGeld.s.sol similarity index 62% rename from script/Geld.s.sol rename to script/RaidGeld.s.sol index 6e8f703..ea5f87c 100644 --- a/script/Geld.s.sol +++ b/script/RaidGeld.s.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; -import {Geld} from "../src/Geld.sol"; +import {RaidGeld} from "../src/RaidGeld.sol"; -contract GeldScript is Script { - Geld public geld; +contract RaidGeldScript is Script { + RaidGeld public raidgeld; function setUp() public {} function run() public { vm.startBroadcast(); - geld = new Geld(); + raidgeld = new RaidGeld(); vm.stopBroadcast(); } } diff --git a/src/Geld.sol b/src/RaidGeld.sol similarity index 84% rename from src/Geld.sol rename to src/RaidGeld.sol index 77ebbac..1e63df8 100644 --- a/src/Geld.sol +++ b/src/RaidGeld.sol @@ -21,7 +21,7 @@ struct Player { uint256 last_raided_at; } -contract Geld is ERC20, Ownable { +contract RaidGeld is ERC20, Ownable { uint8 constant DECIMALS = 4; uint256 public constant BUY_IN_AMOUNT = 0.0005 ether; uint256 public constant INITIAL_GELD = 50 * 10 ** DECIMALS; @@ -37,7 +37,7 @@ contract Geld is ERC20, Ownable { _; } - constructor() ERC20("Geld", "GELD") Ownable(msg.sender) {} + constructor() ERC20("Raid Geld", "GELD") Ownable(msg.sender) {} // This effectively registers the user function register() external payable { @@ -83,13 +83,18 @@ contract Geld is ERC20, Ownable { } // Function to get Player struct - function getPlayer() public view onlyPlayer returns (Player memory) { - return players[msg.sender]; + function getPlayer(address addr) public view onlyPlayer returns (Player memory) { + return players[addr]; } // Function to get Army struct - function getArmy() public view onlyPlayer returns (Army memory) { - return armies[msg.sender]; + function getArmy(address addr) public view onlyPlayer returns (Army memory) { + return armies[addr]; + } + + // Quick fn to check if user is registered + function isRegistered(address addr) public view returns (bool) { + return players[addr].created_at != 0; } receive() external payable { diff --git a/test/Geld.t.sol b/test/RaidGeld.t.sol similarity index 58% rename from test/Geld.t.sol rename to test/RaidGeld.t.sol index 79d09a8..8394da6 100644 --- a/test/Geld.t.sol +++ b/test/RaidGeld.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import {Geld, Army, Player} from "../src/Geld.sol"; +import {RaidGeld, Army, Player} from "../src/RaidGeld.sol"; -contract GeldTest is Test { - Geld public geld; +contract raid_geldTest is Test { + RaidGeld public raid_geld; address public player1; address public player2; address public owner; @@ -18,47 +18,47 @@ contract GeldTest is Test { vm.deal(player1, 10 ether); vm.deal(player2, 10 ether); vm.prank(owner); - geld = new Geld(); + raid_geld = new RaidGeld(); } function registerPlayer() private { - geld.register{value: geld.BUY_IN_AMOUNT()}(); + raid_geld.register{value: raid_geld.BUY_IN_AMOUNT()}(); } function test_00_no_fallback() public { vm.expectRevert(); // Send Ether with some data to trigger fallback - address(geld).call{value: 0.1 ether}("0x1234"); + address(raid_geld).call{value: 0.1 ether}("0x1234"); } function test_01_no_receive() public { vm.startPrank(player1); vm.expectRevert(); - payable(address(geld)).transfer(0.1 ether); + payable(address(raid_geld)).transfer(0.1 ether); } function test_02_registration() public { vm.startPrank(player1); - uint256 initialBalance = address(geld).balance; - uint256 initialTotalMinted = geld.total_minted(); + uint256 initialBalance = address(raid_geld).balance; + uint256 initialTotalMinted = raid_geld.total_minted(); // Send registration fee ETH to the contract registerPlayer(); - // Check that initial GELD is received by the player - assertEq(geld.balanceOf(player1), geld.INITIAL_GELD()); + // Check that initialraid_geld.is received by the player + assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD()); // Verify the contract balance is updated - assertEq(address(geld).balance, initialBalance + geld.BUY_IN_AMOUNT()); + assertEq(address(raid_geld).balance, initialBalance + raid_geld.BUY_IN_AMOUNT()); // Verify player is set initially - Player memory player = geld.getPlayer(); - assertEq(player.total_minted, geld.INITIAL_GELD()); + 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, 0); - Army memory army = geld.getArmy(); + Army memory army = raid_geld.getArmy(player1); assertEq(army.moloch_denier.level, 0); assertEq(army.apprentice.level, 0); @@ -66,7 +66,7 @@ contract GeldTest is Test { assertEq(army.champion.level, 0); // Verify that total_minted is updated - assertEq(geld.total_minted(), initialTotalMinted + geld.INITIAL_GELD()); + assertEq(raid_geld.total_minted(), initialTotalMinted + raid_geld.INITIAL_GELD()); } function test_03_fundsCanBeWithdrawn() public { @@ -78,14 +78,14 @@ contract GeldTest is Test { // Switch back to owner and withdraw funds vm.startPrank(owner); - geld.withdraw(); + raid_geld.withdraw(); uint256 newBalance = owner.balance; - uint256 newContractBalance = address(geld).balance; + uint256 newContractBalance = address(raid_geld).balance; // contract balance should be empty assertEq(newContractBalance, 0); // owner should have the extra funds - assertEq(newBalance, initialBalance + geld.BUY_IN_AMOUNT()); + assertEq(newBalance, initialBalance + raid_geld.BUY_IN_AMOUNT()); } function test_04_onlyOwnerCanWithdraw() public { @@ -95,7 +95,7 @@ contract GeldTest is Test { // attempt to withdraw with player 1, it should fail vm.expectRevert(); - geld.withdraw(); + raid_geld.withdraw(); } function test_05_raid() public { @@ -106,30 +106,39 @@ contract GeldTest is Test { vm.startPrank(player1); registerPlayer(); - uint256 balance = geld.balanceOf(player1); - uint256 total_minted = geld.total_minted(); + uint256 balance = raid_geld.balanceOf(player1); + uint256 total_minted = raid_geld.total_minted(); // Trigger raid funds minting - geld.raid(); + raid_geld.raid(); // New balance should be larger - uint256 newBalance = geld.balanceOf(player1); - uint256 newTotalMinted = geld.total_minted(); + uint256 newBalance = raid_geld.balanceOf(player1); + uint256 newTotalMinted = raid_geld.total_minted(); assertLt(balance, newBalance); assertLt(total_minted, newTotalMinted); // Expect fail if we raid again, we need to wait a bit vm.expectRevert(); - geld.raid(); + raid_geld.raid(); // After wait time passes raid should work again - vm.warp(block.timestamp + geld.RAID_WAIT()); - geld.raid(); + vm.warp(block.timestamp + raid_geld.RAID_WAIT()); + raid_geld.raid(); // Balance should reflect that - uint256 newestBalance = geld.balanceOf(player1); - uint256 newestTotalMinted = geld.total_minted(); + uint256 newestBalance = raid_geld.balanceOf(player1); + uint256 newestTotalMinted = raid_geld.total_minted(); assertLt(newTotalMinted, newestTotalMinted); assertLt(newBalance, newestBalance); } + + function test_06_is_registered() public { + bool is_registered = raid_geld.isRegistered(player1); + assertEq(is_registered, false); + vm.startPrank(player1); + registerPlayer(); + is_registered = raid_geld.isRegistered(player1); + assertEq(is_registered, true); + } }