Compare commits
6 Commits
fefa2641e4
...
5491598dfc
| Author | SHA1 | Date | |
|---|---|---|---|
| 5491598dfc | |||
| 5bb2b5f97b | |||
| 0ea4ba320c | |||
| 26b2480b52 | |||
| 54b370f41b | |||
| 84c0aeff03 |
@ -1,17 +1,27 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { UnitType, usePlayer } from '../providers/PlayerProvider';
|
import { UnitType, usePlayer } from '../providers/PlayerProvider';
|
||||||
import styles from '../styles/Army.module.css';
|
import styles from '../styles/Army.module.css';
|
||||||
import { toReadable } from './Counter';
|
import { calculateBalance, toReadable } from './Counter';
|
||||||
|
|
||||||
const PRECISION = BigInt(10000);
|
const PRECISION = BigInt(10000);
|
||||||
const BASE_PRICE = BigInt(380000);
|
|
||||||
const PRICE_FACTOR = BigInt(11500);
|
const PRICE_FACTOR = BigInt(11500);
|
||||||
const APPRENTICE_PROFIT = BigInt(61000);
|
|
||||||
const ANOINTED_PROFIT = BigInt(6 * 64000);
|
const base_cost: Record<UnitType, bigint> = {
|
||||||
const CHAMPION_PROFIT = BigInt(BigInt(67000 * 61000 * 64000) / PRECISION / PRECISION);
|
0: BigInt(380000),
|
||||||
|
1: BigInt(3420000),
|
||||||
|
2: BigInt(30096000),
|
||||||
|
3: BigInt(255816000),
|
||||||
|
}
|
||||||
|
|
||||||
|
const profits: Record<UnitType, bigint> = {
|
||||||
|
0: BigInt(2533),
|
||||||
|
1: BigInt(27863),
|
||||||
|
2: BigInt(306493),
|
||||||
|
3: BigInt(3371423),
|
||||||
|
}
|
||||||
|
|
||||||
function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number) {
|
function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number) {
|
||||||
let rollingPriceCalculation = BigInt(unit + 1) * BASE_PRICE;
|
let rollingPriceCalculation = base_cost[unit];
|
||||||
let price = BigInt(0);
|
let price = BigInt(0);
|
||||||
|
|
||||||
// Each level costs 15% more than previous
|
// Each level costs 15% more than previous
|
||||||
@ -26,12 +36,7 @@ function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number)
|
|||||||
|
|
||||||
function calculateProfitPerSecond(unit: UnitType, level: number) {
|
function calculateProfitPerSecond(unit: UnitType, level: number) {
|
||||||
// Each next unit scales progressivelly better
|
// Each next unit scales progressivelly better
|
||||||
switch (unit) {
|
return BigInt(level) * profits[unit]
|
||||||
case 0: return BigInt(level) * PRECISION
|
|
||||||
case 1: return BigInt(level) * APPRENTICE_PROFIT
|
|
||||||
case 2: return BigInt(level) * ANOINTED_PROFIT
|
|
||||||
case 3: return BigInt(level) * CHAMPION_PROFIT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unitTypeToCss: Record<UnitType, string> = {
|
const unitTypeToCss: Record<UnitType, string> = {
|
||||||
@ -57,15 +62,15 @@ const defaultAvailabilityMap: Record<UnitType, boolean> = {
|
|||||||
|
|
||||||
const unitDiscoveredAt: Record<UnitType, bigint> = {
|
const unitDiscoveredAt: Record<UnitType, bigint> = {
|
||||||
0: BigInt(0),
|
0: BigInt(0),
|
||||||
1: BigInt(2_000_0000),
|
1: BigInt(300_0000),
|
||||||
2: BigInt(2_000_000_0000),
|
2: BigInt(2800_0000),
|
||||||
3: BigInt(200_000_000_0000)
|
3: BigInt(24000_0000)
|
||||||
}
|
}
|
||||||
const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
|
const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
|
||||||
0: BigInt(0),
|
0: BigInt(0),
|
||||||
1: BigInt(1_000_0000),
|
1: BigInt(200_0000),
|
||||||
2: BigInt(1_000_000_0000),
|
2: BigInt(2000_0000),
|
||||||
3: BigInt(100_000_000_0000),
|
3: BigInt(25000_0000),
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UnitProps {
|
interface UnitProps {
|
||||||
@ -79,8 +84,8 @@ interface UnitProps {
|
|||||||
const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps) => {
|
const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps) => {
|
||||||
const [unitPrice, unitProfit] = useMemo(() => {
|
const [unitPrice, unitProfit] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
toReadable(calculateUnitPrice(unitType, n_units, 1)) + " geld",
|
toReadable(calculateUnitPrice(unitType, n_units, 1)),
|
||||||
toReadable(calculateProfitPerSecond(unitType, n_units)) + " geld / sec"
|
toReadable(calculateProfitPerSecond(unitType, n_units))
|
||||||
]
|
]
|
||||||
}, [n_units, unitType]);
|
}, [n_units, unitType]);
|
||||||
return <div onClick={() => addUnit(unitType)} className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable}`}>
|
return <div onClick={() => addUnit(unitType)} className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable}`}>
|
||||||
@ -91,9 +96,9 @@ const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps
|
|||||||
${isShrouded ? styles.isShrouded : ""}
|
${isShrouded ? styles.isShrouded : ""}
|
||||||
`} />
|
`} />
|
||||||
<span className={`${styles.unitName} ${styles.uiElement}`}>{isShrouded ? "???????" : unitTypeToName[unitType]}</span>
|
<span className={`${styles.unitName} ${styles.uiElement}`}>{isShrouded ? "???????" : unitTypeToName[unitType]}</span>
|
||||||
{isShrouded ? null : <span className={`${styles.unitPrice} ${styles.uiElement}`}>{unitPrice}</span>}
|
{isShrouded ? null : <span className={`${styles.unitPrice} ${styles.uiElement}`}>{unitPrice} <small>GELD</small></span>}
|
||||||
{n_units > 0 ? <span className={`${styles.unitSupply} ${styles.uiElement}`}>{n_units}</span> : null}
|
{n_units > 0 ? <span className={`${styles.unitSupply} ${styles.uiElement}`}>{n_units}</span> : null}
|
||||||
{n_units > 0 ? <span className={`${styles.unitProfit} ${styles.uiElement}`}>{unitProfit}</span> : null}
|
{n_units > 0 ? <span className={`${styles.unitProfit} ${styles.uiElement}`}>{unitProfit} <small>per sec</small></span> : null}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +107,11 @@ const Army = () => {
|
|||||||
const [canPurchase, setCanPurchase] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
const [canPurchase, setCanPurchase] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
||||||
const [isShrouded, setIsShrouded] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
const [isShrouded, setIsShrouded] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
||||||
const [canKnowAbout, setCanKnowAbout] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
const [canKnowAbout, setCanKnowAbout] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
|
||||||
|
const balanceCount = useRef(BigInt(balance ?? 0))
|
||||||
|
|
||||||
console.log(canPurchase, "\nshroud", isShrouded, "\ncanknowabout", canKnowAbout)
|
const setAvailabilities = useCallback(() => {
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isRegistered) {
|
if (isRegistered) {
|
||||||
const totalMinted = player?.total_minted ?? BigInt(0);
|
const totalMinted = (player?.total_minted ?? BigInt(0)) + (balanceCount.current - (balance ?? BigInt(0)));
|
||||||
const n_units: Record<UnitType, number> = {
|
const n_units: Record<UnitType, number> = {
|
||||||
0: army?.moloch_denier.level ?? 0,
|
0: army?.moloch_denier.level ?? 0,
|
||||||
1: army?.apprentice.level ?? 0,
|
1: army?.apprentice.level ?? 0,
|
||||||
@ -127,10 +131,10 @@ const Army = () => {
|
|||||||
3: totalMinted >= unitAvailableToDiscoverAt[3],
|
3: totalMinted >= unitAvailableToDiscoverAt[3],
|
||||||
}
|
}
|
||||||
const canActuallyBuy = {
|
const canActuallyBuy = {
|
||||||
0: balance >= calculateUnitPrice(0, n_units[0], 1) && isKnown[0] && !inShroud[0],
|
0: balanceCount.current >= calculateUnitPrice(0, n_units[0], 1) && isKnown[0] && !inShroud[0],
|
||||||
1: balance >= calculateUnitPrice(1, n_units[1], 1) && isKnown[1] && !inShroud[1],
|
1: balanceCount.current >= calculateUnitPrice(1, n_units[1], 1) && isKnown[1] && !inShroud[1],
|
||||||
2: balance >= calculateUnitPrice(2, n_units[2], 1) && isKnown[2] && !inShroud[2],
|
2: balanceCount.current >= calculateUnitPrice(2, n_units[2], 1) && isKnown[2] && !inShroud[2],
|
||||||
3: balance >= calculateUnitPrice(3, n_units[3], 1) && isKnown[3] && !inShroud[3],
|
3: balanceCount.current >= calculateUnitPrice(3, n_units[3], 1) && isKnown[3] && !inShroud[3],
|
||||||
};
|
};
|
||||||
setCanPurchase(canActuallyBuy)
|
setCanPurchase(canActuallyBuy)
|
||||||
setIsShrouded(inShroud)
|
setIsShrouded(inShroud)
|
||||||
@ -141,12 +145,24 @@ const Army = () => {
|
|||||||
setCanKnowAbout(defaultAvailabilityMap)
|
setCanKnowAbout(defaultAvailabilityMap)
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
balance,
|
|
||||||
army,
|
army,
|
||||||
|
balance,
|
||||||
isRegistered,
|
isRegistered,
|
||||||
player?.total_minted
|
player?.total_minted
|
||||||
])
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tickInterval = setInterval(() => {
|
||||||
|
balanceCount.current = calculateBalance(
|
||||||
|
balance ?? BigInt(0),
|
||||||
|
army?.profit_per_second ?? BigInt(0),
|
||||||
|
player?.last_raided_at ?? BigInt(0)
|
||||||
|
);
|
||||||
|
setAvailabilities()
|
||||||
|
}, 100);
|
||||||
|
return () => clearInterval(tickInterval)
|
||||||
|
}, [balance, army?.profit_per_second, player?.last_raided_at, setAvailabilities])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} />
|
<div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} />
|
||||||
<div className={styles.armyUnits}>
|
<div className={styles.armyUnits}>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useReducer, useRef } from "react";
|
import { useEffect, useReducer, useRef } from "react";
|
||||||
import { usePlayer } from "../providers/PlayerProvider"
|
import { usePlayer } from "../providers/PlayerProvider"
|
||||||
import styles from "../styles/Header.module.css"
|
import styles from "../styles/Header.module.css"
|
||||||
|
import { formatUnits } from "viem";
|
||||||
|
|
||||||
const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigint) => {
|
export const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigint) => {
|
||||||
// convert to milliseconds trick so we get a more smooth counter
|
// convert to milliseconds trick so we get a more smooth counter
|
||||||
const millisecondsSinceLastRaid =
|
const millisecondsSinceLastRaid =
|
||||||
(new Date()).getTime() - parseInt(lastRaidedAt.toString()) * 1000;
|
(new Date()).getTime() - parseInt(lastRaidedAt.toString()) * 1000;
|
||||||
@ -12,8 +13,8 @@ const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigi
|
|||||||
/ BigInt(1000) /* deduct milliseconds*/))
|
/ BigInt(1000) /* deduct milliseconds*/))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toReadable = (value: bigint) => {
|
export const toReadable = (rawValue: bigint) => {
|
||||||
value = value / BigInt(10000);
|
const value = rawValue / BigInt(10000);
|
||||||
const suffixes = [
|
const suffixes = [
|
||||||
{ value: BigInt('1000'), suffix: 'thousand' },
|
{ value: BigInt('1000'), suffix: 'thousand' },
|
||||||
{ value: BigInt('1000000'), suffix: 'million' },
|
{ value: BigInt('1000000'), suffix: 'million' },
|
||||||
@ -38,7 +39,7 @@ export const toReadable = (value: bigint) => {
|
|||||||
for (let i = 0; i < suffixes.length; i++) {
|
for (let i = 0; i < suffixes.length; i++) {
|
||||||
if (value < suffixes[i].value) {
|
if (value < suffixes[i].value) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
return value.toString();
|
return formatUnits(rawValue, 4);
|
||||||
} else {
|
} else {
|
||||||
const divided = value / suffixes[i - 1].value;
|
const divided = value / suffixes[i - 1].value;
|
||||||
const numStr = (value % suffixes[i - 1].value).toString().slice(0, 3);
|
const numStr = (value % suffixes[i - 1].value).toString().slice(0, 3);
|
||||||
|
|||||||
@ -145,6 +145,11 @@
|
|||||||
filter: sepia(1);
|
filter: sepia(1);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
&:hover {
|
||||||
|
.unitProfit {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.uiElement {
|
.uiElement {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -173,6 +178,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.static {
|
.static {
|
||||||
width: 110px;
|
width: 110px;
|
||||||
|
|||||||
@ -61,14 +61,13 @@ contract RaidGeld is ERC20, Ownable {
|
|||||||
// Helper so we can use it when buying units too
|
// Helper so we can use it when buying units too
|
||||||
function performRaid(address player) private {
|
function performRaid(address player) private {
|
||||||
uint256 time_past = block.timestamp - players[player].last_raided_at;
|
uint256 time_past = block.timestamp - players[player].last_raided_at;
|
||||||
|
|
||||||
uint256 new_geld = armies[player].profit_per_second * time_past;
|
uint256 new_geld = armies[player].profit_per_second * time_past;
|
||||||
|
|
||||||
// TODO: Pink noise, make it so sometimes its better than expected
|
// TODO: Pink noise, make it so sometimes its better than expected
|
||||||
|
|
||||||
_mint(player, new_geld);
|
_mint(player, new_geld);
|
||||||
players[player].last_raided_at = block.timestamp;
|
players[player].last_raided_at = block.timestamp;
|
||||||
players[player].total_minted = new_geld;
|
players[player].total_minted += new_geld;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get Player struct
|
// Function to get Player struct
|
||||||
@ -108,8 +107,11 @@ contract RaidGeld is ERC20, Ownable {
|
|||||||
|
|
||||||
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
|
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
|
||||||
// First trigger a raid so player receives what he is due at to this moment
|
// First trigger a raid so player receives what he is due at to this moment
|
||||||
|
|
||||||
|
uint256 time_past = block.timestamp - players[msg.sender].last_raided_at;
|
||||||
|
uint256 new_geld = armies[msg.sender].profit_per_second * time_past;
|
||||||
|
require(balanceOf(msg.sender) + new_geld > cost, "Not enough GELD to add this unit");
|
||||||
performRaid(msg.sender);
|
performRaid(msg.sender);
|
||||||
require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much");
|
|
||||||
|
|
||||||
// TODO: Since we are first minting then burning the token, this could be simplified
|
// TODO: Since we are first minting then burning the token, this could be simplified
|
||||||
// by first calculating the difference and then minting / burning in just one operation
|
// by first calculating the difference and then minting / burning in just one operation
|
||||||
|
|||||||
@ -5,18 +5,31 @@ import {Army} from "../src/RaidGeldStructs.sol";
|
|||||||
|
|
||||||
library RaidGeldUtils {
|
library RaidGeldUtils {
|
||||||
uint256 public constant PRECISION = 10000;
|
uint256 public constant PRECISION = 10000;
|
||||||
uint256 constant BASE_PRICE = 380000;
|
|
||||||
uint256 constant PRICE_FACTOR = 11500;
|
uint256 constant PRICE_FACTOR = 11500;
|
||||||
uint256 constant MOLOCH_DENIER_PROFIT = 10000;
|
|
||||||
uint256 constant APPRENTICE_PROFIT = 61000;
|
// base price * (0.00666) * 11 per each next unit
|
||||||
uint256 constant ANOINTED_PROFIT = 6 * 64000;
|
uint256 constant MOLOCH_DENIER_PROFIT = 2533;
|
||||||
uint256 constant CHAMPION_PROFIT = 36 * 67000;
|
uint256 constant APPRENTICE_PROFIT = 27863;
|
||||||
|
uint256 constant ANOINTED_PROFIT = 306493;
|
||||||
|
uint256 constant CHAMPION_PROFIT = 3371423;
|
||||||
|
|
||||||
|
// each costs 10 times plus a bit more
|
||||||
|
uint256 constant MOLOCH_DENIER_BASE_COST = 380000;
|
||||||
|
uint256 constant APPRENTICE_BASE_COST = 3420000;
|
||||||
|
uint256 constant ANOINTED_BASE_COST = 30096000;
|
||||||
|
uint256 constant CHAMPION_BASE_COST = 255816000;
|
||||||
|
|
||||||
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
|
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
|
||||||
require(unit <= 3, "No matching unit found");
|
require(unit <= 3, "No matching unit found");
|
||||||
|
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
|
||||||
uint256 rollingPriceCalculation = uint256(unit + 1) * BASE_PRICE;
|
|
||||||
uint256 price = 0;
|
uint256 price = 0;
|
||||||
|
if (unit == 1) {
|
||||||
|
rollingPriceCalculation = APPRENTICE_BASE_COST;
|
||||||
|
} else if (unit == 2) {
|
||||||
|
rollingPriceCalculation = ANOINTED_BASE_COST;
|
||||||
|
} else if (unit == 3) {
|
||||||
|
rollingPriceCalculation = CHAMPION_BASE_COST;
|
||||||
|
}
|
||||||
|
|
||||||
// Each level costs 15% more than previous
|
// Each level costs 15% more than previous
|
||||||
for (uint256 i = 0; i < currentLevel + units; i++) {
|
for (uint256 i = 0; i < currentLevel + units; i++) {
|
||||||
|
|||||||
@ -161,7 +161,7 @@ contract raid_geldTest is Test {
|
|||||||
uint256 last_raided_at = player.last_raided_at;
|
uint256 last_raided_at = player.last_raided_at;
|
||||||
assertLt(balance, newBalance);
|
assertLt(balance, newBalance);
|
||||||
|
|
||||||
// After wait time passes raid should bring in profits again
|
// After wait time passes raid should bring in profits again
|
||||||
vm.warp(block.timestamp + 15);
|
vm.warp(block.timestamp + 15);
|
||||||
raid_geld.raid();
|
raid_geld.raid();
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ 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);
|
||||||
assertEq(basePriceMolochDenier, RaidGeldUtils.BASE_PRICE);
|
assertEq(basePriceMolochDenier, RaidGeldUtils.MOLOCH_DENIER_BASE_COST);
|
||||||
|
|
||||||
// buying 3 units
|
// buying 3 units
|
||||||
// has to be a bit more than 3 * 38 = 114
|
// has to be a bit more than 3 * 38 = 114
|
||||||
@ -37,7 +37,7 @@ contract raid_geldTest is Test {
|
|||||||
profit_per_second: 0 // irrelevant for this test
|
profit_per_second: 0 // irrelevant for this test
|
||||||
});
|
});
|
||||||
uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
|
uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
|
||||||
assertEq(profits_per_second, RaidGeldUtils.PRECISION);
|
assertEq(profits_per_second, RaidGeldUtils.MOLOCH_DENIER_PROFIT);
|
||||||
|
|
||||||
army = Army({
|
army = Army({
|
||||||
moloch_denier: Raider({level: _dLvl}),
|
moloch_denier: Raider({level: _dLvl}),
|
||||||
@ -47,7 +47,7 @@ contract raid_geldTest is Test {
|
|||||||
profit_per_second: 0 // irrelevant for this test
|
profit_per_second: 0 // irrelevant for this test
|
||||||
});
|
});
|
||||||
profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
|
profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
|
||||||
uint256 expected = _dLvl * RaidGeldUtils.PRECISION + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
|
uint256 expected = _dLvl * RaidGeldUtils.MOLOCH_DENIER_PROFIT + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
|
||||||
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
|
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
|
||||||
assertEq(profits_per_second, expected);
|
assertEq(profits_per_second, expected);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user