Compare commits
10 Commits
fc749203b8
...
2c18fa35db
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c18fa35db | |||
| 8d4ec9f0c5 | |||
| 030dc849c6 | |||
|
|
1f24f68862 | ||
|
|
948ff4219c | ||
|
|
2b6beb4b68 | ||
|
|
f6f3e5cadf | ||
|
|
06434f5281 | ||
|
|
e028e84a9d | ||
|
|
98913323f8 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
|||||||
[submodule "lib/openzeppelin-contracts"]
|
[submodule "lib/openzeppelin-contracts"]
|
||||||
path = lib/openzeppelin-contracts
|
path = lib/openzeppelin-contracts
|
||||||
url = https://github.com/OpenZeppelin/openzeppelin-contracts
|
url = https://github.com/OpenZeppelin/openzeppelin-contracts
|
||||||
|
[submodule "lib/Baal"]
|
||||||
|
path = lib/Baal
|
||||||
|
url = https://github.com/HausDAO/Baal
|
||||||
|
|||||||
@ -25,13 +25,13 @@ export const bossToName: Record<BossLevel, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const bossToReward: Record<BossLevel, bigint> = {
|
export const bossToReward: Record<BossLevel, bigint> = {
|
||||||
0: BigInt("200000000000000000"),
|
0: BigInt("129600000000000000"),
|
||||||
1: BigInt("28274420000000000000"),
|
1: BigInt("18321824160000000000"),
|
||||||
2: BigInt("174191628800000000000"),
|
2: BigInt("112876175462400000000"),
|
||||||
3: BigInt("513254698112000000000"),
|
3: BigInt("332589044376576000000"),
|
||||||
4: BigInt("963499867554252800000"),
|
4: BigInt("624347914175155814400"),
|
||||||
5: BigInt("1424610762718861153000"),
|
5: BigInt("923147774241822027325"),
|
||||||
6: BigInt("1758160308403017784500"),
|
6: BigInt("1139287879845155524372"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
|
// for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
|
||||||
|
|||||||
@ -158,7 +158,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: 'register_eth',
|
functionName: 'register_eth',
|
||||||
value: parseEther("0.00005"),
|
value: parseEther("0.00045"),
|
||||||
}, {
|
}, {
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, resetHashAndCallback])
|
setHashAndCallback([hash, resetHashAndCallback])
|
||||||
@ -170,7 +170,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
abi,
|
abi,
|
||||||
address: daoTokenAddress,
|
address: daoTokenAddress,
|
||||||
functionName: 'approve',
|
functionName: 'approve',
|
||||||
args: [contractAddress, parseEther("500")],
|
args: [contractAddress, parseEther("400")],
|
||||||
}, {
|
}, {
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([
|
setHashAndCallback([
|
||||||
|
|||||||
@ -29,17 +29,17 @@
|
|||||||
color: #9b215e;
|
color: #9b215e;
|
||||||
}
|
}
|
||||||
& > .boss3 {
|
& > .boss3 {
|
||||||
color: #ebb638;
|
|
||||||
}
|
|
||||||
& > .boss4 {
|
|
||||||
color: #c6282e;
|
color: #c6282e;
|
||||||
}
|
}
|
||||||
& > .boss5 {
|
& > .boss4 {
|
||||||
color: #d06b53;
|
color: #d06b53;
|
||||||
}
|
}
|
||||||
& > .boss6 {
|
& > .boss5 {
|
||||||
color: #8f968f;
|
color: #8f968f;
|
||||||
}
|
}
|
||||||
|
& > .boss6 {
|
||||||
|
color: #ebb638;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
& small {
|
& small {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|||||||
@ -4,3 +4,8 @@ out = "out"
|
|||||||
libs = ["lib"]
|
libs = ["lib"]
|
||||||
|
|
||||||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
|
# 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/",
|
||||||
|
]
|
||||||
1
lib/Baal
Submodule
1
lib/Baal
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit ee3d5ab910399c2b70e70aee2eb3c874047313ed
|
||||||
1
lib/v3-core
Submodule
1
lib/v3-core
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e3589b192d0be27e100cd0daaf6c97204fdb1899
|
||||||
1
lib/v3-periphery
Submodule
1
lib/v3-periphery
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 80f26c86c57b8a5e4b913f42844d4c8bd274d058
|
||||||
@ -12,7 +12,7 @@ contract RaidGeldScript is Script, Constants {
|
|||||||
|
|
||||||
function run() public {
|
function run() public {
|
||||||
vm.startBroadcast();
|
vm.startBroadcast();
|
||||||
raidgeld = new RaidGeld(DAO_TOKEN, POOL);
|
raidgeld = new RaidGeld(DAO_TOKEN, POOL, BAAL, NFT_POSITION_MANAGER);
|
||||||
vm.stopBroadcast();
|
vm.stopBroadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,11 @@ pragma solidity ^0.8.13;
|
|||||||
|
|
||||||
contract Constants {
|
contract Constants {
|
||||||
//base addresses
|
//base addresses
|
||||||
|
address public constant DAO_TREASURY = 0xfaAf7776E05696682f6B170Bf463819219c8d3A6;
|
||||||
address public constant DAO_TOKEN = 0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8;
|
address public constant DAO_TOKEN = 0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8;
|
||||||
address public constant POOL = 0x27004f6d0c1bB7979367D32Ba9d6DF6d61A18926;
|
address public constant POOL = 0x27004f6d0c1bB7979367D32Ba9d6DF6d61A18926;
|
||||||
|
address public constant BAAL = 0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD;
|
||||||
address public constant WETH = 0x4200000000000000000000000000000000000006;
|
address public constant WETH = 0x4200000000000000000000000000000000000006;
|
||||||
address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
||||||
|
address public constant NFT_POSITION_MANAGER = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1;
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/RaidGeld.sol
145
src/RaidGeld.sol
@ -1,18 +1,25 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.13;
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
import "@openzeppelin/contracts/access/Ownable.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 { 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, LastBossResult} from "../src/RaidGeldStructs.sol";
|
import {Army, Player, Raider, Boss, LastBossResult} from "../src/RaidGeldStructs.sol";
|
||||||
import "../src/Constants.sol";
|
import { Constants} from "../src/Constants.sol";
|
||||||
|
|
||||||
contract RaidGeld is ERC20, Ownable, Constants {
|
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.00045 ether;
|
||||||
uint256 public BUY_IN_DAO_TOKEN_AMOUNT;
|
uint256 public BUY_IN_DAO_TOKEN_AMOUNT;
|
||||||
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
||||||
|
uint256 public constant SACRIFICE_SHARE = 1e3; // 10%
|
||||||
mapping(address => Player) private players;
|
mapping(address => Player) private players;
|
||||||
mapping(address => Army) private armies;
|
mapping(address => Army) private armies;
|
||||||
mapping(address => Boss) private bosses;
|
mapping(address => Boss) private bosses;
|
||||||
@ -22,7 +29,15 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
IWETH public immutable weth = IWETH(WETH);
|
IWETH public immutable weth = IWETH(WETH);
|
||||||
// RGCVII token
|
// RGCVII token
|
||||||
ERC20 public daoToken;
|
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
|
// RGCVII pool
|
||||||
|
address public DAOWETHpool;
|
||||||
address public pool;
|
address public pool;
|
||||||
// Uniswap
|
// Uniswap
|
||||||
ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER);
|
ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER);
|
||||||
@ -42,17 +57,40 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
uint16 anointedLevel,
|
uint16 anointedLevel,
|
||||||
uint16 championLevel
|
uint16 championLevel
|
||||||
);
|
);
|
||||||
event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount);
|
event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount);
|
||||||
event PrestigeGained(address indexed player, uint32 prestigeLevel);
|
event PrestigeGained(address indexed player, uint32 prestigeLevel);
|
||||||
event BossDefeated(address indexed player, uint8 bossLevel, uint256 earnings);
|
event BossDefeated(address indexed player, uint8 bossLevel, uint256 earnings);
|
||||||
event BossBattle(address indexed player, uint8 bossLevel, bool hasWon);
|
event BossBattle(address indexed player, uint8 bossLevel, bool hasWon);
|
||||||
|
|
||||||
|
/* * @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,
|
||||||
|
uint160 sqrtPriceX96,
|
||||||
|
uint128 liquidity,
|
||||||
|
uint256 amount0,
|
||||||
|
uint256 amount1
|
||||||
|
);
|
||||||
// Modifier for functions that should only be available to registered players
|
// Modifier for functions that should only be available to registered players
|
||||||
modifier onlyPlayer() {
|
modifier onlyPlayer() {
|
||||||
require(players[msg.sender].created_at != 0, "Not an initiated player");
|
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 or pool already deployed");
|
||||||
|
_;
|
||||||
|
poolDeployed = true;
|
||||||
|
}
|
||||||
|
|
||||||
modifier onlyActiveSession() {
|
modifier onlyActiveSession() {
|
||||||
require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first");
|
require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first");
|
||||||
_;
|
_;
|
||||||
@ -66,10 +104,18 @@ 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, address _nftPositionManager) ERC20("Raid Geld", "GELD") Ownable(msg.sender) {
|
||||||
daoToken = ERC20(_daoToken);
|
daoToken = ERC20(_daoToken);
|
||||||
pool = _pool;
|
DAOWETHpool = _pool;
|
||||||
BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals();
|
baal = IBaal(_baal);
|
||||||
|
BUY_IN_DAO_TOKEN_AMOUNT = 400 * 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 {
|
function start_game(address player) private {
|
||||||
@ -127,7 +173,8 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
amountOutMinimum: 0,
|
amountOutMinimum: 0,
|
||||||
sqrtPriceLimitX96: 0
|
sqrtPriceLimitX96: 0
|
||||||
});
|
});
|
||||||
router.exactInputSingle(params);
|
uint256 daoTokenAmount = router.exactInputSingle(params);
|
||||||
|
performSacrifice(daoTokenAmount);
|
||||||
start_game(msg.sender);
|
start_game(msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,8 +185,13 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
require(
|
require(
|
||||||
daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens"
|
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);
|
start_game(msg.sender);
|
||||||
}
|
}
|
||||||
|
function performSacrifice(uint256 _baseAmount) private {
|
||||||
|
uint256 amount = _baseAmount * SACRIFICE_SHARE / MANTISSA;
|
||||||
|
_ragequit(amount);
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
@ -258,7 +310,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
? RaidGeldUtils.getBossPower(boss_to_attack.level)
|
? RaidGeldUtils.getBossPower(boss_to_attack.level)
|
||||||
: balanceOf(msg.sender);
|
: balanceOf(msg.sender);
|
||||||
bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao);
|
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({
|
lastBossResults[msg.sender] = LastBossResult({
|
||||||
battled_at: block.timestamp,
|
battled_at: block.timestamp,
|
||||||
level: boss_to_attack.level,
|
level: boss_to_attack.level,
|
||||||
@ -269,8 +321,16 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
if (hasWonBattle) {
|
if (hasWonBattle) {
|
||||||
// Burn geld, send some sweet DAO Token and continue
|
// Burn geld, send some sweet DAO Token and continue
|
||||||
_burn(msg.sender, geld_to_burn);
|
_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;
|
players[msg.sender].total_rewards += reward;
|
||||||
|
|
||||||
|
// send user its reward
|
||||||
daoToken.transfer(msg.sender, reward);
|
daoToken.transfer(msg.sender, reward);
|
||||||
emit BossDefeated(msg.sender, boss_to_attack.level, reward);
|
emit BossDefeated(msg.sender, boss_to_attack.level, reward);
|
||||||
|
|
||||||
@ -307,6 +367,67 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
BUY_IN_DAO_TOKEN_AMOUNT = newAmount;
|
BUY_IN_DAO_TOKEN_AMOUNT = newAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deploySwapPool(uint256 _geldAmount, uint256 _daoTokenAmount) external onlyDaoOnlyOnce {
|
||||||
|
|
||||||
|
daoToken.transferFrom(DAO, address(this), _daoTokenAmount);
|
||||||
|
_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: DAO,
|
||||||
|
deadline: block.timestamp + 15 minutes // Ensure a reasonable deadline
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mint the position
|
||||||
|
(uint256 tokenId, uint128 liquidity, uint256 amount0 , uint256 amount1) = nonfungiblePositionManager.mint(
|
||||||
|
mintParams
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
baal.ragequit(address(this), _amount, 0, tokens);
|
||||||
|
}
|
||||||
|
function setDaoAddress(address _dao) external onlyOwner {
|
||||||
|
DAO = _dao;
|
||||||
|
}
|
||||||
|
|
||||||
receive() external payable {
|
receive() external payable {
|
||||||
revert("No plain Ether accepted, use register() function to check in :)");
|
revert("No plain Ether accepted, use register() function to check in :)");
|
||||||
}
|
}
|
||||||
|
|||||||
69
src/lib/CustomMath.sol
Normal file
69
src/lib/CustomMath.sol
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/lib/INonfungiblePositionManager.sol
Normal file
44
src/lib/INonfungiblePositionManager.sol
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ import {RaidGeld, Army, Player, Boss, LastBossResult} from "../src/RaidGeld.sol"
|
|||||||
import "../src/RaidGeldUtils.sol";
|
import "../src/RaidGeldUtils.sol";
|
||||||
import {Constants} from "../src/Constants.sol";
|
import {Constants} from "../src/Constants.sol";
|
||||||
|
|
||||||
|
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||||
|
|
||||||
contract raid_geldTest is Test, Constants {
|
contract raid_geldTest is Test, Constants {
|
||||||
using stdStorage for StdStorage;
|
using stdStorage for StdStorage;
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
vm.deal(owner, 10 ether);
|
vm.deal(owner, 10 ether);
|
||||||
fundAccount(player1);
|
fundAccount(player1);
|
||||||
vm.prank(owner);
|
vm.prank(owner);
|
||||||
raid_geld = new RaidGeld(DAO_TOKEN, POOL);
|
raid_geld = new RaidGeld(DAO_TOKEN, POOL, BAAL, NFT_POSITION_MANAGER);
|
||||||
raid_geld.weth().deposit{value: 5 ether}();
|
raid_geld.weth().deposit{value: 5 ether}();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +60,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
vm.expectRevert();
|
vm.expectRevert();
|
||||||
// Send Ether with some data to trigger fallback
|
// Send Ether with some data to trigger fallback
|
||||||
(bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234");
|
(bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234");
|
||||||
|
if (success) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_01_no_receive() public {
|
function test_01_no_receive() public {
|
||||||
@ -116,8 +119,10 @@ contract raid_geldTest is Test, Constants {
|
|||||||
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());
|
||||||
|
|
||||||
// Verify the contract dao token balance is updated
|
// 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(
|
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
|
// Verify player is set initially
|
||||||
@ -264,6 +269,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
registerPlayerWithDaoToken();
|
registerPlayerWithDaoToken();
|
||||||
raid_geld.addUnit(0, 1);
|
raid_geld.addUnit(0, 1);
|
||||||
|
|
||||||
|
uint256 initialDaoTreasuryBalance = raid_geld.daoToken().balanceOf(DAO_TREASURY);
|
||||||
uint256 initialDaoBalance = raid_geld.daoToken().balanceOf(player1);
|
uint256 initialDaoBalance = raid_geld.daoToken().balanceOf(player1);
|
||||||
uint256 initialContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
uint256 initialContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||||
|
|
||||||
@ -281,6 +287,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
// First boss doesnt grant a new prestige level (ascension)
|
// First boss doesnt grant a new prestige level (ascension)
|
||||||
assertEq(results[1], false);
|
assertEq(results[1], false);
|
||||||
|
|
||||||
|
uint256 daoTreasuryBalance = raid_geld.daoToken().balanceOf(DAO_TREASURY);
|
||||||
uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1);
|
uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1);
|
||||||
uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||||
LastBossResult memory bossResult = raid_geld.getLastBossResult(player1);
|
LastBossResult memory bossResult = raid_geld.getLastBossResult(player1);
|
||||||
@ -292,6 +299,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
assertEq(bossResult.prestigeGained, false);
|
assertEq(bossResult.prestigeGained, false);
|
||||||
|
|
||||||
// User should receive funs, contract should lose them
|
// User should receive funs, contract should lose them
|
||||||
|
assertLt(initialDaoTreasuryBalance, daoTreasuryBalance);
|
||||||
assertLt(initialDaoBalance, afterBossDaoBalance);
|
assertLt(initialDaoBalance, afterBossDaoBalance);
|
||||||
assertGt(initialContractBalance, afterBossContractBalance);
|
assertGt(initialContractBalance, afterBossContractBalance);
|
||||||
|
|
||||||
@ -412,4 +420,36 @@ contract raid_geldTest is Test, Constants {
|
|||||||
}
|
}
|
||||||
require(success, "Player should eventually succeed");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,22 @@ import {Test, console} from "forge-std/Test.sol";
|
|||||||
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
||||||
import "../src/RaidGeldUtils.sol";
|
import "../src/RaidGeldUtils.sol";
|
||||||
import "@openzeppelin/contracts/utils/math/Math.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 {
|
function test_0_unit_price() public pure {
|
||||||
// buying 1 unit of moloch_denier
|
// buying 1 unit of moloch_denier
|
||||||
uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1);
|
uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1);
|
||||||
@ -97,7 +111,7 @@ contract raid_geldTest is Test {
|
|||||||
|
|
||||||
function test_22_random_range(uint256 min, uint256 max) public {
|
function test_22_random_range(uint256 min, uint256 max) public {
|
||||||
vm.assume(max > min);
|
vm.assume(max > min);
|
||||||
vm.assume(max - min < 100);
|
vm.assume(max - min < 200);
|
||||||
vm.assume(max < type(uint256).max);
|
vm.assume(max < type(uint256).max);
|
||||||
uint256 range = max - min + 1;
|
uint256 range = max - min + 1;
|
||||||
bool[] memory seen = new bool[](range);
|
bool[] memory seen = new bool[](range);
|
||||||
@ -141,8 +155,12 @@ contract raid_geldTest is Test {
|
|||||||
|
|
||||||
function test_4_print_boss_rewards() public {
|
function test_4_print_boss_rewards() public {
|
||||||
uint256 total = 0;
|
uint256 total = 0;
|
||||||
|
uint256 buyInAmount = 400e18;
|
||||||
|
uint256 baseReward = buyInAmount - buyInAmount * raid_geld.SACRIFICE_SHARE() / raid_geld.MANTISSA();
|
||||||
for (uint8 i = 0; i < 7; i++) {
|
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);
|
console.log("Reward", i,reward);
|
||||||
total += reward;
|
total += reward;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user