From 98913323f80beffe820375c735f87c6c8c6140d3 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:47:27 +0100 Subject: [PATCH 1/8] sacrifice --- script/RaidGeld.s.sol | 2 +- src/Constants.sol | 1 + src/RaidGeld.sol | 11 ++++++++++- test/RaidGeld.t.sol | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/script/RaidGeld.s.sol b/script/RaidGeld.s.sol index 1cce4ee..cacd86d 100644 --- a/script/RaidGeld.s.sol +++ b/script/RaidGeld.s.sol @@ -12,7 +12,7 @@ contract RaidGeldScript is Script, Constants { function run() public { vm.startBroadcast(); - raidgeld = new RaidGeld(DAO_TOKEN, POOL); + raidgeld = new RaidGeld(DAO_TOKEN, POOL, BAAL); vm.stopBroadcast(); } } diff --git a/src/Constants.sol b/src/Constants.sol index 58237ff..e1ea2bb 100644 --- a/src/Constants.sol +++ b/src/Constants.sol @@ -5,6 +5,7 @@ contract Constants { //base addresses address public constant DAO_TOKEN = 0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8; address public constant POOL = 0x27004f6d0c1bB7979367D32Ba9d6DF6d61A18926; + address public constant BAAL = 0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD; address public constant WETH = 0x4200000000000000000000000000000000000006; address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481; } diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 1da2f84..bb50c5e 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import {IBaal} from "lib/Baal/contracts/interfaces/IBaal.sol"; import {RaidGeldUtils} from "../src/RaidGeldUtils.sol"; import {Army, Player, Raider, Boss} from "../src/RaidGeldStructs.sol"; @@ -13,6 +14,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; uint256 public BUY_IN_DAO_TOKEN_AMOUNT; uint256 public constant INITIAL_GELD = 50 * MANTISSA; + uint256 public constant SACRIFICE_SHARE = 1e3; // 10% mapping(address => Player) private players; mapping(address => Army) private armies; mapping(address => Boss) private bosses; @@ -21,6 +23,7 @@ contract RaidGeld is ERC20, Ownable, Constants { IWETH public immutable weth = IWETH(WETH); // RGCVII token ERC20 public daoToken; + IBaal public baal; // RGCVII pool address public pool; // Uniswap @@ -62,9 +65,10 @@ contract RaidGeld is ERC20, Ownable, Constants { _; } - constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { + constructor(address _daoToken, address _pool, address _baal) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { daoToken = ERC20(_daoToken); pool = _pool; + baal = IBaal(_baal); BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); } @@ -134,6 +138,11 @@ contract RaidGeld is ERC20, Ownable, Constants { ); start_game(msg.sender); } + function sacrificeToDao(uint256 _baseAmount) private { + uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTIASSA; + address[] memory tokens = new address[](0); + baal.ragequite(address(this), amount, 0, tokens); + } // Override for default number of decimals function decimals() public view virtual override returns (uint8) { diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 1d32a09..44b2e4b 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -36,7 +36,7 @@ contract raid_geldTest is Test, Constants { vm.deal(owner, 10 ether); fundAccount(player1); vm.prank(owner); - raid_geld = new RaidGeld(DAO_TOKEN, POOL); + raid_geld = new RaidGeld(DAO_TOKEN, POOL, BAAL); raid_geld.weth().deposit{value: 5 ether}(); } -- 2.39.2 From e028e84a9d2f7d6c66342b58c88c334b7edaff98 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:48:21 +0100 Subject: [PATCH 2/8] added uni lib to git --- lib/v3-core | 1 + lib/v3-periphery | 1 + 2 files changed, 2 insertions(+) create mode 160000 lib/v3-core create mode 160000 lib/v3-periphery diff --git a/lib/v3-core b/lib/v3-core new file mode 160000 index 0000000..e3589b1 --- /dev/null +++ b/lib/v3-core @@ -0,0 +1 @@ +Subproject commit e3589b192d0be27e100cd0daaf6c97204fdb1899 diff --git a/lib/v3-periphery b/lib/v3-periphery new file mode 160000 index 0000000..80f26c8 --- /dev/null +++ b/lib/v3-periphery @@ -0,0 +1 @@ +Subproject commit 80f26c86c57b8a5e4b913f42844d4c8bd274d058 -- 2.39.2 From 06434f52814881d91444960696f9e165cabde738 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:48:27 +0100 Subject: [PATCH 3/8] forge install: Baal --- .gitmodules | 3 +++ lib/Baal | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/Baal diff --git a/.gitmodules b/.gitmodules index 690924b..eed4319 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/Baal"] + path = lib/Baal + url = https://github.com/HausDAO/Baal diff --git a/lib/Baal b/lib/Baal new file mode 160000 index 0000000..ee3d5ab --- /dev/null +++ b/lib/Baal @@ -0,0 +1 @@ +Subproject commit ee3d5ab910399c2b70e70aee2eb3c874047313ed -- 2.39.2 From f6f3e5cadf17d3a369d6b80b448d6e5835b65241 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:55:29 +0100 Subject: [PATCH 4/8] dao sacrifice --- src/RaidGeld.sol | 10 ++++++---- test/RaidGeld.t.sol | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index bb50c5e..59c0693 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -125,7 +125,8 @@ contract RaidGeld is ERC20, Ownable, Constants { amountOutMinimum: 0, sqrtPriceLimitX96: 0 }); - router.exactInputSingle(params); + uint256 daoTokenAmount = router.exactInputSingle(params); + performSacrifice(daoTokenAmount); start_game(msg.sender); } @@ -136,12 +137,13 @@ contract RaidGeld is ERC20, Ownable, Constants { require( daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens" ); + performSacrifice(BUY_IN_DAO_TOKEN_AMOUNT); start_game(msg.sender); } - function sacrificeToDao(uint256 _baseAmount) private { - uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTIASSA; + function performSacrifice(uint256 _baseAmount) private { + uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTISSA; address[] memory tokens = new address[](0); - baal.ragequite(address(this), amount, 0, tokens); + baal.ragequit(address(this), amount, 0, tokens); } // Override for default number of decimals diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 44b2e4b..4cb6d2b 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -116,8 +116,10 @@ contract raid_geldTest is Test, Constants { assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD()); // Verify the contract dao token balance is updated + uint256 expectedDaoBalance = initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT() - + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT() * raid_geld.SACRIFICE_SHARE() / raid_geld.MANTISSA(); assertEq( - raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT() + raid_geld.daoToken().balanceOf(address(raid_geld)), expectedDaoBalance ); // Verify player is set initially -- 2.39.2 From 2b6beb4b68bbf3550fec06489b192c8ca62500f7 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:59:36 +0100 Subject: [PATCH 5/8] can create a pool --- foundry.toml | 5 + script/RaidGeld.s.sol | 2 +- src/Constants.sol | 1 + src/RaidGeld.sol | 123 ++++++++++++++++++++++-- src/lib/CustomMath.sol | 69 +++++++++++++ src/lib/INonfungiblePositionManager.sol | 44 +++++++++ test/RaidGeld.t.sol | 2 +- 7 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 src/lib/CustomMath.sol create mode 100644 src/lib/INonfungiblePositionManager.sol diff --git a/foundry.toml b/foundry.toml index 25b918f..9b61783 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,3 +4,8 @@ out = "out" libs = ["lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +remappings = [ + "@uniswap/v3-core/=lib/v3-core/", + "@uniswap/v3-periphery=lib/v3-periphery/", +] \ No newline at end of file diff --git a/script/RaidGeld.s.sol b/script/RaidGeld.s.sol index cacd86d..84e52db 100644 --- a/script/RaidGeld.s.sol +++ b/script/RaidGeld.s.sol @@ -12,7 +12,7 @@ contract RaidGeldScript is Script, Constants { function run() public { vm.startBroadcast(); - raidgeld = new RaidGeld(DAO_TOKEN, POOL, BAAL); + raidgeld = new RaidGeld(DAO_TOKEN, POOL, BAAL, NFT_POSITION_MANAGER); vm.stopBroadcast(); } } diff --git a/src/Constants.sol b/src/Constants.sol index e1ea2bb..b099212 100644 --- a/src/Constants.sol +++ b/src/Constants.sol @@ -8,4 +8,5 @@ contract Constants { address public constant BAAL = 0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD; address public constant WETH = 0x4200000000000000000000000000000000000006; address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481; + address public constant NFT_POSITION_MANAGER = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1; } diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 59c0693..ec19970 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -1,13 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IBaal} from "lib/Baal/contracts/interfaces/IBaal.sol"; -import {RaidGeldUtils} from "../src/RaidGeldUtils.sol"; -import {Army, Player, Raider, Boss} from "../src/RaidGeldStructs.sol"; -import "../src/Constants.sol"; +import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; +import { IERC20, TransferHelper } from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; +import { INonfungiblePositionManager } from "./lib/INonfungiblePositionManager.sol"; +import { CustomMath} from "./lib/CustomMath.sol"; + +import {RaidGeldUtils } from "../src/RaidGeldUtils.sol"; +import { Army, Player, Raider, Boss } from "../src/RaidGeldStructs.sol"; +import { Constants} from "../src/Constants.sol"; contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant MANTISSA = 1e4; @@ -24,6 +29,12 @@ contract RaidGeld is ERC20, Ownable, Constants { // RGCVII token ERC20 public daoToken; IBaal public baal; + address public DAO; + bool poolDeployed = false; + int24 internal maxTick; + int24 internal minTick; + uint24 internal poolFee = 10000; + INonfungiblePositionManager public nonfungiblePositionManager; // RGCVII pool address public pool; // Uniswap @@ -45,13 +56,27 @@ contract RaidGeld is ERC20, Ownable, Constants { uint16 championLevel ); event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); - + event UniswapPositionCreated( + address indexed pool, + uint256 indexed positionId, + uint160 sqrtPriceX96, + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); // Modifier for functions that should only be available to registered players modifier onlyPlayer() { require(players[msg.sender].created_at != 0, "Not an initiated player"); _; } + // Modifier for dao to deploy the swap pool + modifier onlyDaoOnlyOnce() { + require(msg.sender == DAO && poolDeployed == false, "Not DAO"); + _; + poolDeployed = true; + } + modifier onlyActiveSession() { require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first"); _; @@ -65,11 +90,19 @@ contract RaidGeld is ERC20, Ownable, Constants { _; } - constructor(address _daoToken, address _pool, address _baal) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { + constructor(address _daoToken, address _pool, address _baal, address _nftPositionManager) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { daoToken = ERC20(_daoToken); pool = _pool; baal = IBaal(_baal); BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); + nonfungiblePositionManager = INonfungiblePositionManager(_nftPositionManager); + + IUniswapV3Factory factory = IUniswapV3Factory(nonfungiblePositionManager.factory()); + int24 tickSpacing = factory.feeAmountTickSpacing(poolFee); + require(tickSpacing != 0, "InvalidPoolFee"); + maxTick = (887272 / tickSpacing) * tickSpacing; + minTick = -maxTick; + } function start_game(address player) private { @@ -143,7 +176,7 @@ contract RaidGeld is ERC20, Ownable, Constants { function performSacrifice(uint256 _baseAmount) private { uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTISSA; address[] memory tokens = new address[](0); - baal.ragequit(address(this), amount, 0, tokens); + _ragequit(amount); } // Override for default number of decimals @@ -292,6 +325,80 @@ contract RaidGeld is ERC20, Ownable, Constants { BUY_IN_DAO_TOKEN_AMOUNT = newAmount; } + function deploySwapPool(uint256 _geldAmount, uint256 _daoTokenAmount) external onlyDaoOnlyOnce { + + uint256 daoBalanceBefore = daoToken.balanceOf(address(this)); + uint256 geldBalanceBefore = balanceOf(address(this)); + _mint(address(this), _geldAmount); + + bool isGeldFirst = address(this) < address(daoToken); + // Ensure correct order of tokens based on their addresses + (address token0, address token1, uint256 liquidityAmount0, uint256 liquidityAmount1) = isGeldFirst + ? (address(this), address(daoToken), _geldAmount, _daoTokenAmount) + : (address(daoToken), address(this), _daoTokenAmount, _geldAmount); + + // calculate the sqrtPriceX96 + uint160 sqrtPriceX96 = CustomMath.calculateSqrtPriceX96(liquidityAmount0, liquidityAmount1); + // Create and initialize the pool if necessary + pool = nonfungiblePositionManager.createAndInitializePoolIfNecessary(token0, token1, poolFee, sqrtPriceX96); + // console.log("pool", pool); + + // approve weth and shares with NonfungiblePositionManager (taken from univ3 docs) + TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), liquidityAmount0); + TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), liquidityAmount1); + // console.log("approve OK"); + + + // Set up mintParams with full range for volatile token + // tick upper and lower need to be a valid tick per fee (divisible by 200 for 1%) + // position receipt NFT goes to the vault + INonfungiblePositionManager.MintParams memory mintParams = INonfungiblePositionManager.MintParams({ + token0: token0, + token1: token1, + fee: poolFee, + tickLower: minTick, + tickUpper: maxTick, + amount0Desired: liquidityAmount0, + amount1Desired: liquidityAmount1, + amount0Min: 0, + amount1Min: 0, + recipient: _msgSender(), // baalVaultOnly ensures vault is the caller + deadline: block.timestamp + 15 minutes // Ensure a reasonable deadline + }); + + // Mint the position + (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) = nonfungiblePositionManager.mint( + mintParams + ); + + // Remove allowance and refund in both assets. + uint256 daoRefund = daoBalanceBefore - daoToken.balanceOf(address(this)); + if (daoRefund > 0) { + TransferHelper.safeApprove(address(daoToken), address(nonfungiblePositionManager), 0); + _ragequit(daoRefund); + } + uint256 geldRefund = geldBalanceBefore - balanceOf(address(this)); + if (geldRefund > 0) { + TransferHelper.safeApprove(address(this), address(nonfungiblePositionManager), 0); + _burn(address(this), geldRefund); + } + + + + + emit UniswapPositionCreated(pool, tokenId, sqrtPriceX96, liquidity, amount0, amount1); + } + + + + function _ragequit(uint256 _amount) private { + address[] memory tokens = new address[](0); + baal.ragequit(address(this), _amount, 0, tokens); + } + function setDaoAddress(address _dao) external onlyOwner { + DAO = _dao; + } + receive() external payable { revert("No plain Ether accepted, use register() function to check in :)"); } diff --git a/src/lib/CustomMath.sol b/src/lib/CustomMath.sol new file mode 100644 index 0000000..7a1550d --- /dev/null +++ b/src/lib/CustomMath.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7 <0.9.0; + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +/** + * @title Math utility functions + * @author DAOHaus + * @notice Includes math functions to calculate prices on Uniswap V3 + */ +library CustomMath { + /// @dev Scale used by Uniswap for working with Q64.96 (binary fixed-point) numbers + uint256 constant Q96 = 2 ** 96; + + /** + * @notice Calculates the squeare root of provided value + * @param x value + * @return SQRT(`value`) + */ + function sqrt(uint256 x) internal pure returns (uint256) { + if (x == 0) return 0; + uint256 z = (x + 1) / 2; + uint256 y = x; + while (z < y) { + y = z; + z = (x / z + z) / 2; + } + return y; + } + + /** + * @dev Calculates the sqrtPriceX96 value for Uniswap V3 pools. + * + * This function computes the square root of the price ratio between two tokens + * and adjusts it to the Uniswap V3 format, which requires the square root price + * to be scaled by 2^96. This format is used by Uniswap V3 to facilitate high-precision + * and low-cost arithmetic operations within the protocol. + * + * @param amount0 The amount of token0, where token0 is the token with a numerically lower address. + * @param amount1 The amount of token1, where token1 is the token with a numerically higher address. + * + * The price ratio is calculated as the number of units of token1 equivalent to one unit of token0, + * scaled up by 1e18 to maintain precision during the division operation. + * + * @return The square root of the price ratio, adjusted to the Uniswap V3 fixed-point format (sqrtPriceX96). + * + * Requirements: + * - Both `amount0` and `amount1` must be greater than zero to avoid division by zero errors + * and ensure meaningful price calculations. + * + */ + function calculateSqrtPriceX96(uint256 amount0, uint256 amount1) internal pure returns (uint160) { + require(amount0 > 0 && amount1 > 0, "Token amounts cannot be zero"); + + // Calculate the price ratio as amount1 / amount0 + // Here, `amount1` is multiplied by 1e18 to retain precision after dividing by `amount0`. + uint256 priceRatio = (amount1 * 1e18) / amount0; + + // Compute the square root of the price ratio. + uint256 sqrtPrice = sqrt(priceRatio); + + // Adjust the square root price to the Uniswap V3 fixed-point format by scaling up by 2^96, + // then dividing by 1e9 to correct for the initial scaling by 1e18. + uint256 sqrtPriceX96 = (sqrtPrice * Q96) / 1e9; + + // Return the result as a uint160, conforming to the Uniswap V3 type requirement for sqrtPriceX96. + return uint160(sqrtPriceX96); + } +} \ No newline at end of file diff --git a/src/lib/INonfungiblePositionManager.sol b/src/lib/INonfungiblePositionManager.sol new file mode 100644 index 0000000..40c4de6 --- /dev/null +++ b/src/lib/INonfungiblePositionManager.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.8.7 <0.9.0; + +// @notice taken from RaidGuild s7 Yeet24ShamanModule +// @notice https://basescan.org/address/0x22952b522e72015B671B4715599F7522073E37c1#code + +interface INonfungiblePositionManager { + struct MintParams { + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; + } + + function factory() external view returns (address); + + function ownerOf(uint256 tokenId) external view returns (address); + + /// @notice Creates a new position wrapped in a NFT + /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized + /// a method does not exist, i.e. the pool is assumed to be initialized. + /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata + /// @return tokenId The ID of the token that represents the minted position + /// @return liquidity The amount of liquidity for this position + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function mint( + MintParams calldata params + ) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + + function createAndInitializePoolIfNecessary( + address token0, + address token1, + uint24 fee, + uint160 sqrtPriceX96 + ) external payable returns (address pool); +} \ No newline at end of file diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 4cb6d2b..05ca611 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -36,7 +36,7 @@ contract raid_geldTest is Test, Constants { vm.deal(owner, 10 ether); fundAccount(player1); vm.prank(owner); - raid_geld = new RaidGeld(DAO_TOKEN, POOL, BAAL); + raid_geld = new RaidGeld(DAO_TOKEN, POOL, BAAL, NFT_POSITION_MANAGER); raid_geld.weth().deposit{value: 5 ether}(); } -- 2.39.2 From 948ff4219c0dad3562bb071011dab59ed4a25ebf Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Fri, 1 Nov 2024 03:35:15 +0100 Subject: [PATCH 6/8] pool creation --- src/RaidGeld.sol | 48 +++++++++++++++++++++------------------------ test/RaidGeld.t.sol | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index ec19970..b480bb8 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -36,6 +36,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint24 internal poolFee = 10000; INonfungiblePositionManager public nonfungiblePositionManager; // RGCVII pool + address public DAOWETHpool; address public pool; // Uniswap ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER); @@ -55,7 +56,16 @@ contract RaidGeld is ERC20, Ownable, Constants { uint16 anointedLevel, uint16 championLevel ); - event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); + event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); + + /* * @notice emitted when the UniV3 pool is created and the initial liquidity position is minted + * @param pool pool address + * @param positionId NFT position Id + * @param sqrtPriceX96 initial token price + * @param liquidity final liquidity provided + * @param amount0 final amount of liquidity provided for token0 + * @param amount1 final amount of liquidity provided for token1 + * */ event UniswapPositionCreated( address indexed pool, uint256 indexed positionId, @@ -63,7 +73,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint128 liquidity, uint256 amount0, uint256 amount1 - ); + ); // Modifier for functions that should only be available to registered players modifier onlyPlayer() { require(players[msg.sender].created_at != 0, "Not an initiated player"); @@ -72,7 +82,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Modifier for dao to deploy the swap pool modifier onlyDaoOnlyOnce() { - require(msg.sender == DAO && poolDeployed == false, "Not DAO"); + require(msg.sender == DAO && poolDeployed == false, "Not DAO or pool already deployed"); _; poolDeployed = true; } @@ -92,7 +102,7 @@ contract RaidGeld is ERC20, Ownable, Constants { constructor(address _daoToken, address _pool, address _baal, address _nftPositionManager) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { daoToken = ERC20(_daoToken); - pool = _pool; + DAOWETHpool = _pool; baal = IBaal(_baal); BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); nonfungiblePositionManager = INonfungiblePositionManager(_nftPositionManager); @@ -175,7 +185,6 @@ contract RaidGeld is ERC20, Ownable, Constants { } function performSacrifice(uint256 _baseAmount) private { uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTISSA; - address[] memory tokens = new address[](0); _ragequit(amount); } @@ -327,8 +336,7 @@ contract RaidGeld is ERC20, Ownable, Constants { function deploySwapPool(uint256 _geldAmount, uint256 _daoTokenAmount) external onlyDaoOnlyOnce { - uint256 daoBalanceBefore = daoToken.balanceOf(address(this)); - uint256 geldBalanceBefore = balanceOf(address(this)); + daoToken.transferFrom(DAO, address(this), _daoTokenAmount); _mint(address(this), _geldAmount); bool isGeldFirst = address(this) < address(daoToken); @@ -362,34 +370,22 @@ contract RaidGeld is ERC20, Ownable, Constants { amount1Desired: liquidityAmount1, amount0Min: 0, amount1Min: 0, - recipient: _msgSender(), // baalVaultOnly ensures vault is the caller + recipient: DAO, deadline: block.timestamp + 15 minutes // Ensure a reasonable deadline }); // Mint the position - (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) = nonfungiblePositionManager.mint( + (uint256 tokenId, uint128 liquidity, uint256 amount0 , uint256 amount1) = nonfungiblePositionManager.mint( mintParams ); - - // Remove allowance and refund in both assets. - uint256 daoRefund = daoBalanceBefore - daoToken.balanceOf(address(this)); - if (daoRefund > 0) { - TransferHelper.safeApprove(address(daoToken), address(nonfungiblePositionManager), 0); - _ragequit(daoRefund); - } - uint256 geldRefund = geldBalanceBefore - balanceOf(address(this)); - if (geldRefund > 0) { - TransferHelper.safeApprove(address(this), address(nonfungiblePositionManager), 0); - _burn(address(this), geldRefund); - } - - - + // daoToken dust will join the reward poool + // this contract should not hold any $GELD so we burn all remaining balance + _burn(address(this), balanceOf(address(this))); + emit UniswapPositionCreated(pool, tokenId, sqrtPriceX96, liquidity, amount0, amount1); - } - + } function _ragequit(uint256 _amount) private { address[] memory tokens = new address[](0); diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 05ca611..e71a781 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -7,6 +7,8 @@ import {RaidGeld, Army, Player, Boss} from "../src/RaidGeld.sol"; import "../src/RaidGeldUtils.sol"; import {Constants} from "../src/Constants.sol"; +import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; + contract raid_geldTest is Test, Constants { using stdStorage for StdStorage; @@ -58,6 +60,7 @@ contract raid_geldTest is Test, Constants { vm.expectRevert(); // Send Ether with some data to trigger fallback (bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234"); + if (success) this; } function test_01_no_receive() public { @@ -397,4 +400,36 @@ contract raid_geldTest is Test, Constants { } require(success, "Player should eventually succeed"); } + + + function test_10_dao_seeds_uniV3_pool() public { + vm.prank(owner); + raid_geld.setDaoAddress(address(this)); + assertEq(raid_geld.DAO(), address(this)); + + uint256 DAO_DAO_BALANCE = 10000000 ether; + uint256 DAO_TOKEN_LIQUIDITY = 10000 ether; + uint256 GELD_LIQUIDITY = DAO_TOKEN_LIQUIDITY * 10; + stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(address(this)).checked_write(DAO_DAO_BALANCE); + assertEq(raid_geld.daoToken().balanceOf(address(this)), DAO_DAO_BALANCE); + + vm.expectRevert("Not DAO or pool already deployed"); + vm.prank(owner); + raid_geld.deploySwapPool(GELD_LIQUIDITY, DAO_TOKEN_LIQUIDITY); + + // Deploying pool should work, done by DAO + raid_geld.daoToken().approve(address(raid_geld), DAO_TOKEN_LIQUIDITY); + raid_geld.deploySwapPool(GELD_LIQUIDITY, DAO_TOKEN_LIQUIDITY); + + address token0 = IUniswapV3Pool(raid_geld.pool()).token0(); + address token1 = IUniswapV3Pool(raid_geld.pool()).token1(); + if (DAO_TOKEN < address(raid_geld)) { + assertEq(token0, DAO_TOKEN); + assertEq(token1, address(raid_geld)); + } else { + assertEq(token0, address(raid_geld)); + assertEq(token1, DAO_TOKEN); + } + + } } -- 2.39.2 From 030dc849c68a018a55bd134369971701113767b2 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Fri, 1 Nov 2024 11:42:50 +0100 Subject: [PATCH 7/8] Adjusted buyins, adjusted rewards --- app/src/components/BossInfo.tsx | 14 +++++++------- app/src/providers/PlayerProvider.tsx | 4 ++-- src/Constants.sol | 1 + src/RaidGeld.sol | 17 ++++++++++++----- test/RaidGeld.t.sol | 3 +++ test/RaidGeldUtils.t.sol | 22 ++++++++++++++++++++-- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/app/src/components/BossInfo.tsx b/app/src/components/BossInfo.tsx index a17e8be..9e72da8 100644 --- a/app/src/components/BossInfo.tsx +++ b/app/src/components/BossInfo.tsx @@ -25,13 +25,13 @@ export const bossToName: Record = { } export const bossToReward: Record = { - 0: BigInt("200000000000000000"), - 1: BigInt("28274420000000000000"), - 2: BigInt("174191628800000000000"), - 3: BigInt("513254698112000000000"), - 4: BigInt("963499867554252800000"), - 5: BigInt("1424610762718861153000"), - 6: BigInt("1758160308403017784500"), + 0: BigInt("129600000000000000"), + 1: BigInt("18321824160000000000"), + 2: BigInt("112876175462400000000"), + 3: BigInt("332589044376576000000"), + 4: BigInt("624347914175155814400"), + 5: BigInt("923147774241822027325"), + 6: BigInt("1139287879845155524372"), } // for boss chances (percent) [99, 89, 80, 70, 62, 51, 40] diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index 199fe9f..60072a1 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -158,7 +158,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { abi, address: contractAddress, functionName: 'register_eth', - value: parseEther("0.00005"), + value: parseEther("0.00045"), }, { onSuccess: (hash) => { setHashAndCallback([hash, resetHashAndCallback]) @@ -170,7 +170,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { abi, address: daoTokenAddress, functionName: 'approve', - args: [contractAddress, parseEther("500")], + args: [contractAddress, parseEther("400")], }, { onSuccess: (hash) => { setHashAndCallback([ diff --git a/src/Constants.sol b/src/Constants.sol index b099212..b66b8c6 100644 --- a/src/Constants.sol +++ b/src/Constants.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; contract Constants { //base addresses + address public constant DAO_TREASURY = 0xfaAf7776E05696682f6B170Bf463819219c8d3A6; address public constant DAO_TOKEN = 0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8; address public constant POOL = 0x27004f6d0c1bB7979367D32Ba9d6DF6d61A18926; address public constant BAAL = 0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD; diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index fb2a4d8..778ae62 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -16,7 +16,7 @@ import { Constants} from "../src/Constants.sol"; contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant MANTISSA = 1e4; - uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; + uint256 public constant BUY_IN_AMOUNT = 0.00045 ether; uint256 public BUY_IN_DAO_TOKEN_AMOUNT; uint256 public constant INITIAL_GELD = 50 * MANTISSA; uint256 public constant SACRIFICE_SHARE = 1e3; // 10% @@ -108,7 +108,7 @@ contract RaidGeld is ERC20, Ownable, Constants { daoToken = ERC20(_daoToken); DAOWETHpool = _pool; baal = IBaal(_baal); - BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); + BUY_IN_DAO_TOKEN_AMOUNT = 400 * 10 ** daoToken.decimals(); nonfungiblePositionManager = INonfungiblePositionManager(_nftPositionManager); IUniswapV3Factory factory = IUniswapV3Factory(nonfungiblePositionManager.factory()); @@ -116,7 +116,6 @@ contract RaidGeld is ERC20, Ownable, Constants { require(tickSpacing != 0, "InvalidPoolFee"); maxTick = (887272 / tickSpacing) * tickSpacing; minTick = -maxTick; - } function start_game(address player) private { @@ -311,7 +310,7 @@ contract RaidGeld is ERC20, Ownable, Constants { ? RaidGeldUtils.getBossPower(boss_to_attack.level) : balanceOf(msg.sender); bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao); - emit BossBattle(msg.sender, boss_to_attack.level, hasWonBattle); + emit BossBattle(msg.sender, boss_to_attack.level, hasWonBattle); lastBossResults[msg.sender] = LastBossResult({ battled_at: block.timestamp, level: boss_to_attack.level, @@ -322,8 +321,16 @@ contract RaidGeld is ERC20, Ownable, Constants { if (hasWonBattle) { // Burn geld, send some sweet DAO Token and continue _burn(msg.sender, geld_to_burn); - uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, BUY_IN_DAO_TOKEN_AMOUNT); + uint256 baseReward = (BUY_IN_DAO_TOKEN_AMOUNT - BUY_IN_DAO_TOKEN_AMOUNT * SACRIFICE_SHARE / MANTISSA); + uint256 wholeReward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, baseReward); + uint256 treasuryShare = wholeReward * SACRIFICE_SHARE / MANTISSA; + uint256 reward = wholeReward - treasuryShare; + // send a share to dao treasury + daoToken.transfer(DAO_TREASURY, treasuryShare); + players[msg.sender].total_rewards += reward; + + // send user its reward daoToken.transfer(msg.sender, reward); emit BossDefeated(msg.sender, boss_to_attack.level, reward); diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 0ecbbdb..8f30f76 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -269,6 +269,7 @@ contract raid_geldTest is Test, Constants { registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); + uint256 initialDaoTreasuryBalance = raid_geld.daoToken().balanceOf(DAO_TREASURY); uint256 initialDaoBalance = raid_geld.daoToken().balanceOf(player1); uint256 initialContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); @@ -286,6 +287,7 @@ contract raid_geldTest is Test, Constants { // First boss doesnt grant a new prestige level (ascension) assertEq(results[1], false); + uint256 daoTreasuryBalance = raid_geld.daoToken().balanceOf(DAO_TREASURY); uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1); uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); @@ -297,6 +299,7 @@ contract raid_geldTest is Test, Constants { assertEq(bossResult.prestigeGained, false); // User should receive funs, contract should lose them + assertLt(initialDaoTreasuryBalance, daoTreasuryBalance); assertLt(initialDaoBalance, afterBossDaoBalance); assertGt(initialContractBalance, afterBossContractBalance); diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 5bb42b3..760dc66 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -5,8 +5,22 @@ import {Test, console} from "forge-std/Test.sol"; import {Army, Raider} from "../src/RaidGeldStructs.sol"; import "../src/RaidGeldUtils.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; +import {RaidGeld} from "../src/RaidGeld.sol"; +import {Constants} from "../src/Constants.sol"; + +contract raid_geldTest is Test, Constants { + + RaidGeld public raid_geld; + address public owner; + + function setUp() public { + owner = address(0x126); + vm.deal(owner, 10 ether); + vm.prank(owner); + raid_geld = new RaidGeld(DAO_TOKEN, POOL, BAAL, NFT_POSITION_MANAGER); + raid_geld.weth().deposit{value: 5 ether}(); + } -contract raid_geldTest is Test { function test_0_unit_price() public pure { // buying 1 unit of moloch_denier uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1); @@ -141,8 +155,12 @@ contract raid_geldTest is Test { function test_4_print_boss_rewards() public { uint256 total = 0; + uint256 buyInAmount = 400e18; + uint256 baseReward = buyInAmount - buyInAmount * raid_geld.SACRIFICE_SHARE() / raid_geld.MANTISSA(); for (uint8 i = 0; i < 7; i++) { - uint256 reward = RaidGeldUtils.calculateBossReward(i, 500e18); + uint256 wholeReward = RaidGeldUtils.calculateBossReward(i, baseReward); + uint256 baalShare = wholeReward * raid_geld.SACRIFICE_SHARE() / raid_geld.MANTISSA(); + uint256 reward = wholeReward - baalShare; console.log("Reward", i,reward); total += reward; } -- 2.39.2 From 8d4ec9f0c57a156aedce8746d9759309cdb29bc5 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Fri, 1 Nov 2024 11:54:39 +0100 Subject: [PATCH 8/8] Boss color change + random test was crying --- app/src/styles/Info.module.css | 10 +++++----- test/RaidGeldUtils.t.sol | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/styles/Info.module.css b/app/src/styles/Info.module.css index 440b4de..b24f79f 100644 --- a/app/src/styles/Info.module.css +++ b/app/src/styles/Info.module.css @@ -29,17 +29,17 @@ color: #9b215e; } & > .boss3 { - color: #ebb638; - } - & > .boss4 { color: #c6282e; } - & > .boss5 { + & > .boss4 { color: #d06b53; } - & > .boss6 { + & > .boss5 { color: #8f968f; } + & > .boss6 { + color: #ebb638; + } } & small { opacity: 0.7; diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 760dc66..341a6f9 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -111,7 +111,7 @@ contract raid_geldTest is Test, Constants { function test_22_random_range(uint256 min, uint256 max) public { vm.assume(max > min); - vm.assume(max - min < 100); + vm.assume(max - min < 200); vm.assume(max < type(uint256).max); uint256 range = max - min + 1; bool[] memory seen = new bool[](range); -- 2.39.2