From c37b5c20548ff070b06316662e923f9c998013eb Mon Sep 17 00:00:00 2001 From: Kant <148298869+Kanthub@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:38:23 +0800 Subject: [PATCH] fix security problems (#18) * new fix * fix security problems * fix tiny logic and script * fix deploy-script * update stakeManager --- foundry-upgrades.json | 3 + foundry.toml | 3 + script/DeployerStakingManger.s.sol | 52 +++- script/UpgradeDirectSalePoolV1.s.sol | 63 ++++- script/UpgradeFishcakeEventManagerV2.s.sol | 66 ++++- script/UpgradeInvestSalePoolV1.s.sol | 76 ++++++ .../UpgradeNftManagerV5DeployerScript.s.sol | 84 +++++-- .../core/FishcakeEventManagerStorage.sol | 42 ++-- src/contracts/core/FishcakeEventManagerV2.sol | 170 ++++++++++--- src/contracts/core/StakingManager.sol | 226 +++++++++++++----- src/contracts/core/StakingManagerStorage.sol | 9 +- .../core/sale/DirectSalePoolStorage.sol | 7 +- src/contracts/core/sale/DirectSalePoolV1.sol | 75 ++++-- src/contracts/core/sale/InvestorSalePool.sol | 143 +++++++++-- .../core/sale/InvestorSalePoolStorage.sol | 7 +- src/contracts/core/token/FishCakeCoin.sol | 73 ++++-- .../core/token/FishCakeCoinStorage.sol | 2 +- .../core/token/NftManagerStorage.sol | 14 +- src/contracts/core/token/NftManagerV5.sol | 211 +++++++++++----- .../interfaces/IFishcakeEventManager.sol | 8 +- src/contracts/interfaces/IStakingManager.sol | 24 +- 21 files changed, 1066 insertions(+), 292 deletions(-) create mode 100644 foundry-upgrades.json create mode 100644 script/UpgradeInvestSalePoolV1.s.sol diff --git a/foundry-upgrades.json b/foundry-upgrades.json new file mode 100644 index 0000000..83cfefe --- /dev/null +++ b/foundry-upgrades.json @@ -0,0 +1,3 @@ +{ + "build-info-directory": "out/build-info-full" +} diff --git a/foundry.toml b/foundry.toml index e18a79e..32b27bf 100644 --- a/foundry.toml +++ b/foundry.toml @@ -36,3 +36,6 @@ solc_version = "0.8.26" #sepolia = "${SEPOLIA_RPC_URL}" #local = "${LOCAL_RPC_URL}" #polygon = "${POLYGON_RPC_URL}" + + + diff --git a/script/DeployerStakingManger.s.sol b/script/DeployerStakingManger.s.sol index 46e8824..6771d23 100644 --- a/script/DeployerStakingManger.s.sol +++ b/script/DeployerStakingManger.s.sol @@ -4,16 +4,20 @@ pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { StakingManager } from "../src/contracts/core/StakingManager.sol"; -import { IFishcakeEventManager } from "../src/contracts/interfaces/IFishcakeEventManager.sol"; -import { INftManager } from "../src/contracts/interfaces/INftManager.sol"; - +import {StakingManager} from "../src/contracts/core/StakingManager.sol"; +import {IFishcakeEventManager} from "../src/contracts/interfaces/IFishcakeEventManager.sol"; +import {INftManager} from "../src/contracts/interfaces/INftManager.sol"; contract DeployerStakingMangerScript is Script { + IFishcakeEventManager public constant PROXY_FISH_CAKE_EVENT_MANAGER = + IFishcakeEventManager( + address(0x2CAf752814f244b3778e30c27051cc6B45CB1fc9) + ); + INftManager public constant PROXY_NFT_MANAGER = + INftManager(address(0x2F2Cb24BaB1b6E2353EF6246a2Ea4ce50487008B)); - IFishcakeEventManager public constant PROXY_FISH_CAKE_EVENT_MANAGER = IFishcakeEventManager(address(0x2CAf752814f244b3778e30c27051cc6B45CB1fc9)); - INftManager public constant PROXY_NFT_MANAGER = INftManager(address(0x2F2Cb24BaB1b6E2353EF6246a2Ea4ce50487008B)); - + address public constant fccAddress = + address(0x84eBc138F4Ab844A3050a6059763D269dC9951c6); function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); @@ -24,14 +28,36 @@ contract DeployerStakingMangerScript is Script { StakingManager stakingManagerImplementation = new StakingManager(); - console.log("StakingManager address:", address(stakingManagerImplementation)); - - bytes memory data = abi.encodeCall(stakingManagerImplementation.initialize, (deployerAddress, PROXY_FISH_CAKE_EVENT_MANAGER, PROXY_NFT_MANAGER)); - - ERC1967Proxy proxyStakingManager = new ERC1967Proxy(address(stakingManagerImplementation), data); + console.log( + "=========== StakingManager Logic address: =============", + address(stakingManagerImplementation) + ); + + bytes memory data = abi.encodeCall( + stakingManagerImplementation.initialize, + ( + deployerAddress, // 升级权限和逻辑权限都在deployerAddress + fccAddress, + PROXY_FISH_CAKE_EVENT_MANAGER, + PROXY_NFT_MANAGER + ) + ); + + ERC1967Proxy proxyStakingManager = new ERC1967Proxy( + address(stakingManagerImplementation), + data + ); vm.stopBroadcast(); - console.log("UUPS Proxy Address:", address(proxyStakingManager)); + console.log( + "========UUPS Proxy StakingManager Address: =========", + address(proxyStakingManager) + ); + + console.log( + "========StakingManager Owner: =========", + StakingManager(payable(address(proxyStakingManager))).owner() + ); } } diff --git a/script/UpgradeDirectSalePoolV1.s.sol b/script/UpgradeDirectSalePoolV1.s.sol index d84b2f2..9495fb6 100644 --- a/script/UpgradeDirectSalePoolV1.s.sol +++ b/script/UpgradeDirectSalePoolV1.s.sol @@ -2,32 +2,75 @@ pragma solidity ^0.8.13; import "@openzeppelin-foundry-upgrades/Upgrades.sol"; -import "forge-std/Script.sol"; -import { DirectSalePoolV1 } from "../src/contracts/core/sale/DirectSalePoolV1.sol"; +import "forge-std/Script.sol"; +import {DirectSalePoolV1} from "../src/contracts/core/sale/DirectSalePoolV1.sol"; contract UpgradeDirectSalePoolV1Script is Script { - address public constant PROXY_DIRECT_SALE_POOL = address(0xF71C97C9C6B2133A0Cb5c3ED4CC6eFe5e1BC534C); + address public constant INITIAL_OWNER = + 0x7a129d41bb517aD9A6FA49afFAa92eBeea2DFe07; + address public constant FCC_ADDRESS = + 0x84eBc138F4Ab844A3050a6059763D269dC9951c6; + address public constant USDT_ADDRESS = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address public constant REDEMPT_POOL = + 0x036423643CEB603B7aff40A05627F09C04b9897E; + + address public constant PROXY_DIRECT_SALE_POOL = + address(0xF71C97C9C6B2133A0Cb5c3ED4CC6eFe5e1BC534C); function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); console.log("deploy deployerAddress:", address(deployerAddress)); - console.log("address(this):", address(this)); + // console.log("address(this):", address(this)); vm.startBroadcast(deployerPrivateKey); DirectSalePoolV1 newImplementation = new DirectSalePoolV1(); - console.log("New DirectSalePoolV1 implementation deployed at:", address(newImplementation)); + console.log( + "New DirectSalePoolV1 implementation deployed at:", + address(newImplementation) + ); + + console.log( + "DirectSalePool Proxy Admin:", + Upgrades.getAdminAddress(PROXY_DIRECT_SALE_POOL) + ); + console.log( + "DirectSalePool upgraded before:", + Upgrades.getImplementationAddress(PROXY_DIRECT_SALE_POOL) + ); - console.log("DirectSalePool Proxy Admin:", Upgrades.getAdminAddress(PROXY_DIRECT_SALE_POOL)); - console.log("DirectSalePool upgraded before:", Upgrades.getImplementationAddress(PROXY_DIRECT_SALE_POOL)); + // // 加入初始化 data + // bytes memory data = abi.encodeCall( + // DirectSalePoolV1.initialize, + // (INITIAL_OWNER, FCC_ADDRESS, REDEMPT_POOL, USDT_ADDRESS) + // ); // 升级权限在 deployerAddress,逻辑权限在 INITIAL_OWNER - Upgrades.upgradeProxy(PROXY_DIRECT_SALE_POOL, "DirectSalePoolV1.sol:DirectSalePoolV1", "", deployerAddress); + Upgrades.upgradeProxy( + PROXY_DIRECT_SALE_POOL, + "DirectSalePoolV1.sol:DirectSalePoolV1", + "", + deployerAddress + ); console.log("DirectSalePoolV1 proxy upgraded successfully"); vm.stopBroadcast(); + console.log( + "=========DirectSalePool upgraded logic address after: ===========", + Upgrades.getImplementationAddress(PROXY_DIRECT_SALE_POOL) + ); + console.log( + "DirectSalePool Proxy Admin:", + Upgrades.getAdminAddress(PROXY_DIRECT_SALE_POOL) + ); + + DirectSalePoolV1 directSalePoolV1 = DirectSalePoolV1( + payable(PROXY_DIRECT_SALE_POOL) + ); + + console.log("========Proxy:==========", PROXY_DIRECT_SALE_POOL); - console.log("DirectSalePool upgraded after:", Upgrades.getImplementationAddress(PROXY_DIRECT_SALE_POOL)); - console.log("DirectSalePool Proxy Admin:", Upgrades.getAdminAddress(PROXY_DIRECT_SALE_POOL)); + console.log("========Owner:==========", directSalePoolV1.owner()); } } diff --git a/script/UpgradeFishcakeEventManagerV2.s.sol b/script/UpgradeFishcakeEventManagerV2.s.sol index 62c1abe..91cb328 100644 --- a/script/UpgradeFishcakeEventManagerV2.s.sol +++ b/script/UpgradeFishcakeEventManagerV2.s.sol @@ -2,31 +2,77 @@ pragma solidity ^0.8.13; import "@openzeppelin-foundry-upgrades/Upgrades.sol"; -import "forge-std/Script.sol"; +import "forge-std/Script.sol"; import {FishcakeEventManagerV2} from "../src/contracts/core/FishcakeEventManagerV2.sol"; contract FishcakeEventManagerV2Script is Script { - address public constant PROXY_FISH_CAKE_EVENT_MANAGER = address(0x2CAf752814f244b3778e30c27051cc6B45CB1fc9); + address public constant PROXY_FISH_CAKE_EVENT_MANAGER = + address(0x2CAf752814f244b3778e30c27051cc6B45CB1fc9); + + address public constant INITIAL_OWNER = + 0x7a129d41bb517aD9A6FA49afFAa92eBeea2DFe07; + address public constant FCC_ADDRESS = + 0x84eBc138F4Ab844A3050a6059763D269dC9951c6; + address public constant USDT_ADDRESS = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address public constant NFT_MANAGER = + 0x2F2Cb24BaB1b6E2353EF6246a2Ea4ce50487008B; function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); console.log("deploy deployerAddress:", address(deployerAddress)); - console.log("address(this):", address(this)); + // console.log("address(this):", address(this)); vm.startBroadcast(deployerPrivateKey); FishcakeEventManagerV2 newImplementation = new FishcakeEventManagerV2(); - console.log("New FishcakeEventManagerV2 implementation deployed at:", address(newImplementation)); + console.log( + "New FishcakeEventManagerV2 implementation deployed at:", + address(newImplementation) + ); + + console.log( + "Proxy Admin:", + Upgrades.getAdminAddress(PROXY_FISH_CAKE_EVENT_MANAGER) + ); + console.log( + "upgraded before:", + Upgrades.getImplementationAddress(PROXY_FISH_CAKE_EVENT_MANAGER) + ); - console.log("Proxy Admin:", Upgrades.getAdminAddress(PROXY_FISH_CAKE_EVENT_MANAGER)); - console.log("upgraded before:", Upgrades.getImplementationAddress(PROXY_FISH_CAKE_EVENT_MANAGER)); + // // 加入初始化 data + // bytes memory data = abi.encodeCall( + // FishcakeEventManagerV2.initialize, + // (INITIAL_OWNER, FCC_ADDRESS, USDT_ADDRESS, NFT_MANAGER) + // ); // 升级权限在 deployerAddress,逻辑权限在 INITIAL_OWNER - Upgrades.upgradeProxy(PROXY_FISH_CAKE_EVENT_MANAGER, "FishcakeEventManagerV2.sol:FishcakeEventManagerV2", "", deployerAddress); + Upgrades.upgradeProxy( + PROXY_FISH_CAKE_EVENT_MANAGER, + "FishcakeEventManagerV2.sol:FishcakeEventManagerV2", + "", + deployerAddress + ); console.log("FishcakeEventManagerV2 proxy upgraded successfully"); - console.log("======================================================================="); + console.log( + "=======================================================================" + ); vm.stopBroadcast(); - console.log("upgraded after:", Upgrades.getImplementationAddress(PROXY_FISH_CAKE_EVENT_MANAGER)); - console.log("Proxy Admin:", Upgrades.getAdminAddress(PROXY_FISH_CAKE_EVENT_MANAGER)); + console.log( + "==========upgraded logic address after: ============", + Upgrades.getImplementationAddress(PROXY_FISH_CAKE_EVENT_MANAGER) + ); + console.log( + "Proxy Admin:", + Upgrades.getAdminAddress(PROXY_FISH_CAKE_EVENT_MANAGER) + ); + + FishcakeEventManagerV2 fishcakeEventManagerV2 = FishcakeEventManagerV2( + payable(PROXY_FISH_CAKE_EVENT_MANAGER) + ); + + console.log("========Proxy:==========", PROXY_FISH_CAKE_EVENT_MANAGER); + + console.log("========Owner:==========", fishcakeEventManagerV2.owner()); } } diff --git a/script/UpgradeInvestSalePoolV1.s.sol b/script/UpgradeInvestSalePoolV1.s.sol new file mode 100644 index 0000000..3e7761d --- /dev/null +++ b/script/UpgradeInvestSalePoolV1.s.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin-foundry-upgrades/Upgrades.sol"; +import "forge-std/Script.sol"; +import {InvestorSalePool} from "../src/contracts/core/sale/InvestorSalePool.sol"; + +contract UpgradeInvestorSalePoolScript is Script { + address public constant INITIAL_OWNER = + 0x7a129d41bb517aD9A6FA49afFAa92eBeea2DFe07; + address public constant FCC_ADDRESS = + 0x84eBc138F4Ab844A3050a6059763D269dC9951c6; + address public constant USDT_ADDRESS = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address public constant REDEMPT_POOL = + 0x036423643CEB603B7aff40A05627F09C04b9897E; + + address public constant PROXY_INVESTOR_SALE_POOL = + address(0x9dA9d48c3b1CB9B8c4AE3c195a6Bee5BAaa5314A); + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployerAddress = vm.addr(deployerPrivateKey); + console.log("deploy deployerAddress:", address(deployerAddress)); + + // console.log("address(this):", address(this)); + + vm.startBroadcast(deployerPrivateKey); + InvestorSalePool newImplementation = new InvestorSalePool(); + console.log( + "New InvestorSalePool implementation deployed at:", + address(newImplementation) + ); + + console.log( + "DirectSalePool Proxy Admin:", + Upgrades.getAdminAddress(PROXY_INVESTOR_SALE_POOL) + ); + console.log( + "DirectSalePool upgraded before:", + Upgrades.getImplementationAddress(PROXY_INVESTOR_SALE_POOL) + ); + + // // 加入初始化 data + // bytes memory data = abi.encodeCall( + // InvestorSalePool.initialize, + // (INITIAL_OWNER, FCC_ADDRESS, REDEMPT_POOL, USDT_ADDRESS) + // ); // 升级权限在 deployerAddress,逻辑权限在 INITIAL_OWNER + + Upgrades.upgradeProxy( + PROXY_INVESTOR_SALE_POOL, + "InvestorSalePool.sol:InvestorSalePool", + "", + deployerAddress + ); + console.log("InvestorSalePool proxy upgraded successfully"); + vm.stopBroadcast(); + + console.log( + "======= InvestorSalePool logic address upgraded after:==========", + Upgrades.getImplementationAddress(PROXY_INVESTOR_SALE_POOL) + ); + console.log( + "InvestorSalePool Proxy Admin:", + Upgrades.getAdminAddress(PROXY_INVESTOR_SALE_POOL) + ); + + InvestorSalePool investorSalePool = InvestorSalePool( + payable(PROXY_INVESTOR_SALE_POOL) + ); + + console.log("========Proxy:==========", PROXY_INVESTOR_SALE_POOL); + + console.log("========Owner:==========", investorSalePool.owner()); + } +} diff --git a/script/UpgradeNftManagerV5DeployerScript.s.sol b/script/UpgradeNftManagerV5DeployerScript.s.sol index 94c9fcf..dda20d5 100644 --- a/script/UpgradeNftManagerV5DeployerScript.s.sol +++ b/script/UpgradeNftManagerV5DeployerScript.s.sol @@ -11,35 +11,91 @@ import "@openzeppelin-foundry-upgrades/Upgrades.sol"; import {NftManagerV5} from "../src/contracts/core/token/NftManagerV5.sol"; contract UpgradeNftManagerV5DeployerScript is Script { + address public constant INITIAL_OWNER = + 0x7a129d41bb517aD9A6FA49afFAa92eBeea2DFe07; + address public constant FCC_ADDRESS = + 0x84eBc138F4Ab844A3050a6059763D269dC9951c6; + address public constant USDT_ADDRESS = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address public constant REDEMPT_POOL = + 0x036423643CEB603B7aff40A05627F09C04b9897E; + address public constant STAKING_MANAGER = + 0x19C6bf3Ae8DFf14967C1639b96887E8778738417; + // main network - address public constant PROXY_NFT_MANAGER = address(0x2F2Cb24BaB1b6E2353EF6246a2Ea4ce50487008B); + address public constant PROXY_NFT_MANAGER = + address(0x2F2Cb24BaB1b6E2353EF6246a2Ea4ce50487008B); function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); NftManagerV5 newImplementation = new NftManagerV5(); - console.log("New newNftManager implementation deployed at:", address(newImplementation)); + console.log( + "New newNftManager implementation deployed at:", + address(newImplementation) + ); vm.startBroadcast(deployerPrivateKey); - console.log("upgraded before:", Upgrades.getImplementationAddress(PROXY_NFT_MANAGER)); - Upgrades.upgradeProxy(PROXY_NFT_MANAGER, "NftManagerV5.sol:NftManagerV5", ""); + console.log( + "upgraded before:", + Upgrades.getImplementationAddress(PROXY_NFT_MANAGER) + ); + Upgrades.upgradeProxy( + PROXY_NFT_MANAGER, + "NftManagerV5.sol:NftManagerV5", + "" + ); + NftManagerV5 upgradedNftManager = NftManagerV5( + payable(PROXY_NFT_MANAGER) + ); + upgradedNftManager.initializeV5(STAKING_MANAGER); + vm.stopBroadcast(); - console.log("upgraded after:", Upgrades.getImplementationAddress(PROXY_NFT_MANAGER)); - console.log("Proxy Admin:", Upgrades.getAdminAddress(PROXY_NFT_MANAGER)); + + console.log( + "=========upgraded logic address after:=========", + Upgrades.getImplementationAddress(PROXY_NFT_MANAGER) + ); + console.log( + "New NftManagerV5 implementation:", + address(newImplementation) + ); + + console.log( + "Proxy Admin:", + Upgrades.getAdminAddress(PROXY_NFT_MANAGER) + ); console.log("NftManager proxy upgraded successfully"); - console.log("======================================================================="); - console.log("Owner:", deployerAddress); - console.log("New NftManagerV5 implementation:", address(newImplementation)); - console.log("NftManager proxy:", PROXY_NFT_MANAGER); + console.log( + "=======================================================================" + ); + + console.log("========Owner:==========", upgradedNftManager.owner()); + + console.log("=========NftManager proxy:==========", PROXY_NFT_MANAGER); + + console.log( + "========StakingManager:==========", + address(upgradedNftManager.stakingManagerAddress()) + ); // Verify upgraded state - NftManagerV5 upgradedNftManager = NftManagerV5(payable(PROXY_NFT_MANAGER)); + console.log("Verifying upgraded state..."); - console.log("fccTokenAddr:", address(upgradedNftManager.fccTokenAddr())); - console.log("tokenUsdtAddr:", address(upgradedNftManager.tokenUsdtAddr())); - console.log("redemptionPoolAddress:", address(upgradedNftManager.redemptionPoolAddress())); + console.log( + "fccTokenAddr:", + address(upgradedNftManager.fccTokenAddr()) + ); + console.log( + "tokenUsdtAddr:", + address(upgradedNftManager.tokenUsdtAddr()) + ); + console.log( + "redemptionPoolAddress:", + address(upgradedNftManager.redemptionPoolAddress()) + ); console.log("New name:", upgradedNftManager.name()); console.log("New symbol:", upgradedNftManager.symbol()); diff --git a/src/contracts/core/FishcakeEventManagerStorage.sol b/src/contracts/core/FishcakeEventManagerStorage.sol index 1f5672f..fb52698 100644 --- a/src/contracts/core/FishcakeEventManagerStorage.sol +++ b/src/contracts/core/FishcakeEventManagerStorage.sol @@ -7,28 +7,31 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "../interfaces/IFishcakeEventManager.sol"; import "../interfaces/INftManager.sol"; -abstract contract FishcakeEventManagerStorage is Initializable, IFishcakeEventManager { +abstract contract FishcakeEventManagerStorage is + Initializable, + IFishcakeEventManager +{ using SafeERC20 for IERC20; - uint256 public constant totalMineAmt = 300_000_000 * 10 ** 6; // Total mining quantity - uint256 public constant maxDeadLine = 2592000; // 30 days = 2592000 s - uint256 public constant oneDay = 86400; // one day 86400 s + uint256 public constant totalMineAmt = 300_000_000 * 10 ** 6; // Total mining quantity + uint256 public constant maxDeadLine = 2592000; // 30 days = 2592000 s + uint256 public constant oneDay = 86400; // one day 86400 s uint256 public constant merchantOnceMaxMineAmt = 240 * 10 ** 6; // pro nft once max mining quantity - uint256 public constant userOnceMaxMineAmt = 24 * 10 ** 6; // basic nft once max mining quantity + uint256 public constant userOnceMaxMineAmt = 24 * 10 ** 6; // basic nft once max mining quantity - uint256 public minedAmt; // Mined quantity + uint256 public minedAmt; // Mined quantity uint8 public minePercent; // Mining percentage - bool public isMint; // Whether to mint + bool public isMint; // Whether to mint IERC20 public FccTokenAddr; IERC20 public UsdtTokenAddr; INftManager public iNFTManager; struct ActivityInfo { - uint256 activityId; // Activity ID + uint256 activityId; // Activity ID address businessAccount; // Initiator's account(0x...) - string businessName; // Merchant name - string activityContent; // Activity content + string businessName; // Merchant name + string activityContent; // Activity content string latitudeLongitude; // Latitude and longitude uint256 activityCreateTime; // Activity creation time uint256 activityDeadLine; // Activity end time @@ -49,25 +52,30 @@ abstract contract FishcakeEventManagerStorage is Initializable, IFishcakeEventMa } struct DropInfo { - uint256 activityId; // Activity ID + uint256 activityId; // Activity ID address userAccount; // Initiator's account(0x...) - uint256 dropTime; // drop Time - uint256 dropAmt; // drop amount + uint256 dropTime; // drop Time + uint256 dropAmt; // drop amount } - uint256[] public activityInfoChangedIdx; // Translation: Indices of changed statuses - ActivityInfo[] public activityInfoArrs; // all + uint256[] public activityInfoChangedIdx; // Translation: Indices of changed statuses + ActivityInfo[] public activityInfoArrs; // all ActivityInfoExt[] public activityInfoExtArrs; // Translation: Array of all activities DropInfo[] public dropInfoArrs; // drop InfoA rrs mapping(address => uint256) public NTFLastMineTime; // nft last mining time - mapping(uint256 => mapping(address => bool)) public activityDroppedToAccount; + mapping(uint256 => mapping(address => bool)) + public activityDroppedToAccount; mapping(address => uint256) public minerMineAmount; - function __FishcakeEventManagerStorage_init(address _fccAddress, address _usdtTokenAddr, address _NFTManagerAddr) internal initializer { + function __FishcakeEventManagerStorage_init( + address _fccAddress, + address _usdtTokenAddr, + address _NFTManagerAddr + ) internal onlyInitializing { FccTokenAddr = IERC20(_fccAddress); UsdtTokenAddr = IERC20(_usdtTokenAddr); iNFTManager = INftManager(_NFTManagerAddr); diff --git a/src/contracts/core/FishcakeEventManagerV2.sol b/src/contracts/core/FishcakeEventManagerV2.sol index 8ec3286..a0b4018 100644 --- a/src/contracts/core/FishcakeEventManagerV2.sol +++ b/src/contracts/core/FishcakeEventManagerV2.sol @@ -32,15 +32,24 @@ contract FishcakeEventManagerV2 is _; } - function initialize(address _initialOwner, address _fccAddress, address _usdtTokenAddr, address _NFTManagerAddr) - public - initializer - { - require(_initialOwner != address(0), "FishcakeEventManager initialize: _initialOwner can't be zero address"); + function initialize( + address _initialOwner, + address _fccAddress, + address _usdtTokenAddr, + address _NFTManagerAddr + ) public initializer { + require( + _initialOwner != address(0), + "FishcakeEventManager initialize: _initialOwner can't be zero address" + ); __Ownable_init(_initialOwner); __ERC20_init("FishCake", "FCC"); __ReentrancyGuard_init(); - __FishcakeEventManagerStorage_init(_fccAddress, _usdtTokenAddr, _NFTManagerAddr); + __FishcakeEventManagerStorage_init( + _fccAddress, + _usdtTokenAddr, + _NFTManagerAddr + ); _transferOwnership(_initialOwner); } @@ -56,11 +65,21 @@ contract FishcakeEventManagerV2 is uint256 _maxDropAmt, address _tokenContractAddr ) public nonReentrant returns (bool, uint256) { - require(_dropType == 2 || _dropType == 1, "FishcakeEventManager activityAdd: Drop Type Error."); - require(_maxDropAmt >= _minDropAmt, "FishcakeEventManager activityAdd: MaxDropAmt Setup Error."); - require(_totalDropAmts > 0, "FishcakeEventManager activityAdd: Drop Amount Error."); require( - block.timestamp < _activityDeadLine && _activityDeadLine < block.timestamp + maxDeadLine, + _dropType == 2 || _dropType == 1, + "FishcakeEventManager activityAdd: Drop Type Error." + ); + require( + _maxDropAmt >= _minDropAmt, + "FishcakeEventManager activityAdd: MaxDropAmt Setup Error." + ); + require( + _totalDropAmts > 0, + "FishcakeEventManager activityAdd: Drop Amount Error." + ); + require( + block.timestamp < _activityDeadLine && + _activityDeadLine < block.timestamp + maxDeadLine, "FishcakeEventManager activityAdd: Activity DeadLine Error." ); @@ -69,15 +88,17 @@ contract FishcakeEventManagerV2 is "FishcakeEventManager activityAdd: Drop Number Not Meet Total Drop Amounts." ); require( - _totalDropAmts >= 10e5, "FishcakeEventManager activityAdd: Total Drop Amounts Too Little , Minimum of 1." + _totalDropAmts >= 10e5, + "FishcakeEventManager activityAdd: Total Drop Amounts Too Little , Minimum of 1." ); require( - _dropNumber <= 101 || _dropNumber <= _totalDropAmts / 10e6, + _dropNumber < 101 || _dropNumber <= _totalDropAmts / 10e6, "FishcakeEventManager activityAdd: Drop Number Too Large ,Limit 100 or TotalDropAmts/10." ); require( - _tokenContractAddr == address(UsdtTokenAddr) || _tokenContractAddr == address(FccTokenAddr), + _tokenContractAddr == address(UsdtTokenAddr) || + _tokenContractAddr == address(FccTokenAddr), "FishcakeEventManager activityAdd: Token contract address error" ); @@ -86,7 +107,11 @@ contract FishcakeEventManagerV2 is } // Transfer token to this contract for locking. - IERC20(_tokenContractAddr).transferFrom(msg.sender, address(this), _totalDropAmts); + IERC20(_tokenContractAddr).transferFrom( + msg.sender, + address(this), + _totalDropAmts + ); ActivityInfo memory ai = ActivityInfo({ activityId: activityInfoArrs.length + 1, @@ -132,7 +157,9 @@ contract FishcakeEventManagerV2 is return (true, ai.activityId); } - function activityFinish(uint256 _activityId) public nonReentrant returns (bool) { + function activityFinish( + uint256 _activityId + ) public nonReentrant returns (bool) { require( _activityId > 0 && _activityId <= activityInfoArrs.length, "FishcakeEventManager activityFinish: Activity Id Error." @@ -140,11 +167,19 @@ contract FishcakeEventManagerV2 is ActivityInfo storage ai = activityInfoArrs[_activityId - 1]; ActivityInfoExt storage aie = activityInfoExtArrs[_activityId - 1]; - require(ai.businessAccount == msg.sender, "FishcakeEventManager activityFinish: Not The Owner."); - require(aie.activityStatus == 1, "FishcakeEventManager activityFinish: Activity Status Error."); + require( + ai.businessAccount == msg.sender, + "FishcakeEventManager activityFinish: Not The Owner." + ); + require( + aie.activityStatus == 1, + "FishcakeEventManager activityFinish: Activity Status Error." + ); aie.activityStatus = 2; - uint256 returnAmount = ai.maxDropAmt * ai.dropNumber - aie.alreadyDropAmts; + uint256 returnAmount = ai.maxDropAmt * + ai.dropNumber - + aie.alreadyDropAmts; uint256 minedAmount = 0; if (returnAmount > 0) { @@ -152,27 +187,47 @@ contract FishcakeEventManagerV2 is } // ifReward There is only one reward in 24 hours - if (isMint && ifReward() && (iNFTManager.getMerchantNTFDeadline(_msgSender()) > block.timestamp || iNFTManager.getUserNTFDeadline(_msgSender()) > block.timestamp)) { + if ( + isMint && + ifReward() && + (iNFTManager.getMerchantNTFDeadline(_msgSender()) > + block.timestamp || + iNFTManager.getUserNTFDeadline(_msgSender()) > block.timestamp) + ) { // Get the current percentage of mined tokens uint8 currentMinePercent = 0; uint256 merchantOnceMaxMineTmpAmt = 0; uint256 userOnceMaxMineTmpAmt = 0; - (currentMinePercent, merchantOnceMaxMineTmpAmt, userOnceMaxMineTmpAmt) = getCurrentMinePercent(); + ( + currentMinePercent, + merchantOnceMaxMineTmpAmt, + userOnceMaxMineTmpAmt + ) = getCurrentMinePercent(); if (minePercent != currentMinePercent) { minePercent = currentMinePercent; } - if (minePercent > 0 && address(FccTokenAddr) == ai.tokenContractAddr) { - uint8 percent = - (iNFTManager.getMerchantNTFDeadline(_msgSender()) > block.timestamp ? minePercent : minePercent / 2); + if ( + minePercent > 0 && address(FccTokenAddr) == ai.tokenContractAddr + ) { + uint8 percent = ( + iNFTManager.getMerchantNTFDeadline(_msgSender()) > + block.timestamp + ? minePercent + : minePercent / 2 + ); uint256 maxMineAmtLimit = ( - iNFTManager.getMerchantNTFDeadline(_msgSender()) > block.timestamp + iNFTManager.getMerchantNTFDeadline(_msgSender()) > + block.timestamp ? merchantOnceMaxMineTmpAmt : userOnceMaxMineTmpAmt ); // For each FCC release activity hosted on the platform, the activity initiator can mine tokens based on either 50% of the total token quantity consumed by the activity or 50% of the total number of participants multiplied by 20, whichever is lower. uint256 tmpDroppedVal = aie.alreadyDropNumber * 20 * 1e6; - uint256 tmpBusinessMinedAmt = - ((aie.alreadyDropAmts > tmpDroppedVal ? tmpDroppedVal : aie.alreadyDropAmts) * percent) / 100; + uint256 tmpBusinessMinedAmt = (( + aie.alreadyDropAmts > tmpDroppedVal + ? tmpDroppedVal + : aie.alreadyDropAmts + ) * percent) / 100; if (tmpBusinessMinedAmt > maxMineAmtLimit) { tmpBusinessMinedAmt = maxMineAmtLimit; } @@ -198,32 +253,55 @@ contract FishcakeEventManagerV2 is activityInfoChangedIdx.push(_activityId - 1); - emit ActivityFinish(_activityId, ai.tokenContractAddr, returnAmount, minedAmount); + emit ActivityFinish( + _activityId, + ai.tokenContractAddr, + returnAmount, + minedAmount + ); return true; } - function drop(uint256 _activityId, address _userAccount, uint256 _dropAmt) external nonReentrant returns (bool) { + function drop( + uint256 _activityId, + address _userAccount, + uint256 _dropAmt + ) external nonReentrant returns (bool) { require( - activityDroppedToAccount[_activityId][_userAccount] == false, "FishcakeEventManager drop: User Has Dropped." + activityDroppedToAccount[_activityId][_userAccount] == false, + "FishcakeEventManager drop: User Has Dropped." ); ActivityInfo storage ai = activityInfoArrs[_activityId - 1]; ActivityInfoExt storage aie = activityInfoExtArrs[_activityId - 1]; - require(aie.activityStatus == 1, "FishcakeEventManager drop: Activity Status Error."); - require(ai.businessAccount == msg.sender, "FishcakeEventManager drop: Not The Owner."); - require(ai.activityDeadLine >= block.timestamp, "FishcakeEventManager drop: Activity Has Expired."); + require( + aie.activityStatus == 1, + "FishcakeEventManager drop: Activity Status Error." + ); + require( + ai.businessAccount == msg.sender, + "FishcakeEventManager drop: Not The Owner." + ); + require( + ai.activityDeadLine >= block.timestamp, + "FishcakeEventManager drop: Activity Has Expired." + ); if (ai.dropType == 2) { require( - _dropAmt <= ai.maxDropAmt && _dropAmt >= ai.minDropAmt, "FishcakeEventManager drop: Drop Amount Error." + _dropAmt <= ai.maxDropAmt && _dropAmt >= ai.minDropAmt, + "FishcakeEventManager drop: Drop Amount Error." ); } else { _dropAmt = ai.maxDropAmt; } - require(ai.dropNumber > aie.alreadyDropNumber, "FishcakeEventManager drop: Exceeded the number of rewards."); + require( + ai.dropNumber > aie.alreadyDropNumber, + "FishcakeEventManager drop: Exceeded the number of rewards." + ); require( ai.maxDropAmt * ai.dropNumber >= _dropAmt + aie.alreadyDropAmts, "FishcakeEventManager drop: The reward amount has been exceeded." @@ -233,8 +311,12 @@ contract FishcakeEventManagerV2 is activityDroppedToAccount[_activityId][_userAccount] = true; - DropInfo memory di = - DropInfo({activityId: _activityId, userAccount: _userAccount, dropTime: block.timestamp, dropAmt: _dropAmt}); + DropInfo memory di = DropInfo({ + activityId: _activityId, + userAccount: _userAccount, + dropTime: block.timestamp, + dropAmt: _dropAmt + }); dropInfoArrs.push(di); aie.alreadyDropAmts += _dropAmt; @@ -244,7 +326,9 @@ contract FishcakeEventManagerV2 is return true; } - function getMinerMineAmount(address _miner) external view returns (uint256) { + function getMinerMineAmount( + address _miner + ) external view returns (uint256) { return minerMineAmount[_miner]; } @@ -253,7 +337,11 @@ contract FishcakeEventManagerV2 is } // ======================= internal ======================= - function getCurrentMinePercent() internal view returns (uint8, uint256, uint256) { + function getCurrentMinePercent() + internal + view + returns (uint8, uint256, uint256) + { uint8 currentMinePercent = 0; uint256 merchantOnceMaxMineTmpAmt = 0; uint256 userOnceMaxMineTmpAmt = 0; @@ -278,7 +366,11 @@ contract FishcakeEventManagerV2 is merchantOnceMaxMineTmpAmt = 0; userOnceMaxMineTmpAmt = 0; } - return (currentMinePercent, merchantOnceMaxMineTmpAmt, userOnceMaxMineTmpAmt); + return ( + currentMinePercent, + merchantOnceMaxMineTmpAmt, + userOnceMaxMineTmpAmt + ); } function ifReward() internal view returns (bool _ret) { diff --git a/src/contracts/core/StakingManager.sol b/src/contracts/core/StakingManager.sol index 5483084..e4f4498 100644 --- a/src/contracts/core/StakingManager.sol +++ b/src/contracts/core/StakingManager.sol @@ -6,16 +6,23 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; import "@openzeppelin-upgrades/contracts/utils/ReentrancyGuardUpgradeable.sol"; -import { StakingManagerStorage } from "./StakingManagerStorage.sol"; +import {StakingManagerStorage} from "./StakingManagerStorage.sol"; import "../interfaces/IFishcakeEventManager.sol"; import "../interfaces/INftManager.sol"; +import "@openzeppelin-upgrades/contracts/proxy/utils/UUPSUpgradeable.sol"; -contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, StakingManagerStorage{ +contract StakingManager is + Initializable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable, + StakingManagerStorage, + UUPSUpgradeable +{ using SafeERC20 for IERC20; /// @custom:oz-upgrades-unsafe-allow constructor - constructor(){ + constructor() { _disableInitializers(); } @@ -23,38 +30,51 @@ contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpg emit Received(msg.sender, msg.value); } - function initialize(address _initialOwner, IFishcakeEventManager _feManagerAddress, INftManager _nftManagerAddress) - public - initializer - { - require(_initialOwner != address(0), "StakingManager initialize: _initialOwner can't be zero address"); + function initialize( + address _initialOwner, + address _fccAddress, + IFishcakeEventManager _feManagerAddress, + INftManager _nftManagerAddress + ) public initializer { + require( + _initialOwner != address(0), + "StakingManager initialize: _initialOwner can't be zero address" + ); feManagerAddress = _feManagerAddress; nftManagerAddress = _nftManagerAddress; + fccAddress = _fccAddress; __Ownable_init(_initialOwner); _transferOwnership(_initialOwner); __ReentrancyGuard_init(); + __UUPSUpgradeable_init(); messageNonce = 0; } - function DepositIntoStaking(uint256 amount, uint8 stakingType) external nonReentrant { - require(amount > minStakeAmount, "StakingManager DepositIntoStaking: staking amount must be more than minStakeAmount"); + function depositIntoStaking( + uint256 amount, + uint8 stakingType, + bool isAutoRenew + ) external nonReentrant { + require( + amount >= minStakeAmount, + "StakingManager DepositIntoStaking: staking amount must be at least minStakeAmount" + ); - IERC20(fccAddress).safeTransfer(address(this), amount); + IERC20(fccAddress).safeTransferFrom(msg.sender, address(this), amount); bytes32 txMessageHash = keccak256( - abi.encode( - msg.sender, - fccAddress, - amount, - messageNonce - ) + abi.encode(msg.sender, fccAddress, amount, messageNonce) ); + uint256 stakingTimestamp = 0; uint256 apr = 0; (stakingTimestamp, apr) = getStakingPeriodAndApr(stakingType); uint endTime = block.timestamp + stakingTimestamp; - uint256 tokenId = nftManagerAddress.getActiveMinerBoosterNft(msg.sender); + uint256 tokenId = nftManagerAddress.getActiveMinerBoosterNft( + msg.sender + ); + uint256 nftApr = getNftApr(msg.sender, tokenId); stakeHolderStakingInfo memory ssInfo = stakeHolderStakingInfo({ startStakingTime: block.timestamp, @@ -63,7 +83,8 @@ contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpg endStakingTime: endTime, stakingStatus: 0, // under staking now stakingType: stakingType, - bindingNft: tokenId + bindingNft: tokenId, + isAutoRenew: isAutoRenew }); nftManagerAddress.inActiveMinerBoosterNft(msg.sender); @@ -72,85 +93,155 @@ contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpg totalStakingAmount += amount; - emit StakeHolderDepositStaking(msg.sender, amount, messageNonce); + emit StakeHolderDepositStaking( + msg.sender, + amount, + stakingType, + block.timestamp, + endTime, + tokenId, + nftApr, + isAutoRenew, + messageNonce + ); messageNonce++; } - function withdrawFromStakingWithAprIncome(uint256 amount, uint256 messageNonce) external nonReentrant { + function withdrawFromStakingWithAprIncome( + uint256 amount, + uint256 messageNonce + ) external nonReentrant { bytes32 txMessageHash = keccak256( - abi.encode( - msg.sender, - fccAddress, - amount, - messageNonce - ) + abi.encode(msg.sender, fccAddress, amount, messageNonce) ); - uint256 txLockEndTime = stakingQueued[msg.sender][txMessageHash].endStakingTime; + uint256 txLockEndTime = stakingQueued[msg.sender][txMessageHash] + .endStakingTime; if (block.timestamp < txLockEndTime) { - revert FundingUnderStaking( - amount, - txLockEndTime - ); - } - uint256 amountOut = stakingQueued[msg.sender][txMessageHash].amount; - if (amountOut < minStakeAmount) { - revert NoFundingForStaking(); + revert FundingUnderStaking(amount, txLockEndTime); } + require( + stakingQueued[msg.sender][txMessageHash].stakingStatus == 0, + "already withdrawn" + ); + // uint256 amountOut = stakingQueued[msg.sender][txMessageHash].amount; + // // if (amountOut < minStakeAmount) { + // // revert NoFundingForStaking(); + // // } totalStakingAmount -= amount; stakingQueued[msg.sender][txMessageHash].stakingStatus = 1; //staking end - uint256 rewardAprFunding = calculateArpFunding( + uint256 rewardAprFunding = calculateAprFunding( msg.sender, stakingQueued[msg.sender][txMessageHash].amount, stakingQueued[msg.sender][txMessageHash].stakingType, stakingQueued[msg.sender][txMessageHash].startStakingTime, - stakingQueued[msg.sender][txMessageHash].bindingNft + stakingQueued[msg.sender][txMessageHash].bindingNft, + stakingQueued[msg.sender][txMessageHash].isAutoRenew ); IERC20(fccAddress).safeTransfer(msg.sender, amount); IERC20(fccAddress).safeTransfer(msg.sender, rewardAprFunding); - emit StakeHolderWithdrawStaking(msg.sender, amount, messageNonce, txMessageHash); + emit StakeHolderWithdrawStaking( + msg.sender, + amount, + messageNonce, + txMessageHash, + rewardAprFunding + ); } - function getStakingAprFunding(uint256 amount, uint256 messageNonce) external view returns(uint256) { + function getStakingAprFunding( + uint256 amount, + uint256 messageNonce + ) external view returns (uint256) { bytes32 txMessageHash = keccak256( - abi.encode( - msg.sender, - fccAddress, - amount, - messageNonce - ) + abi.encode(msg.sender, fccAddress, amount, messageNonce) ); - uint256 rewardAprFunding = calculateArpFunding( + uint256 rewardAprFunding = calculateAprFunding( msg.sender, stakingQueued[msg.sender][txMessageHash].amount, stakingQueued[msg.sender][txMessageHash].stakingType, stakingQueued[msg.sender][txMessageHash].startStakingTime, - stakingQueued[msg.sender][txMessageHash].bindingNft + stakingQueued[msg.sender][txMessageHash].bindingNft, + stakingQueued[msg.sender][txMessageHash].isAutoRenew ); return rewardAprFunding; } + function withdrawETHFromContract( + address to, + uint256 amount + ) external onlyOwner { + require( + to != address(0), + "StakingManager withdrawETHFromContract: to can't be zero address" + ); + require( + amount <= address(this).balance, + "StakingManager withdrawETHFromContract: amount must be less than balance" + ); + (bool success, ) = to.call{value: amount}(""); + require( + success, + "StakingManager withdrawETHFromContract: Withdraw failed" + ); + emit WithdrawETHFromContract(to, amount); + } + //==========================internal function=============================== - function calculateArpFunding(address miner, uint256 stakingAmount, uint8 stakingType, uint256 stakingTime, uint256 tokenId) internal view returns(uint256) { - uint256 stakingArp = 0; - uint256 lockType = 0; + function calculateAprFunding( + address miner, + uint256 stakingAmount, + uint8 stakingType, + uint256 stakingTime, + uint256 tokenId, + bool isAutoRenew + ) internal view returns (uint256) { + uint256 stakingApr = 0; + uint256 lockTime = 0; uint256 nftApr = getNftApr(miner, tokenId); - (lockType, stakingArp) = getStakingPeriodAndApr(stakingType); - uint256 totalRewardApr = nftApr + stakingArp; + (lockTime, stakingApr) = getStakingPeriodAndApr(stakingType); + uint256 totalRewardApr = nftApr + stakingApr; uint256 actualStakingDuration = block.timestamp - stakingTime; + + // First calculate effective staking duration + if (actualStakingDuration <= lockTime || isAutoRenew) { + actualStakingDuration = actualStakingDuration; + } else { + actualStakingDuration = lockTime; + } + + // Then calculate reward: booster APR only counts for lock time, staking APR counts for all time + uint256 reward = 0; + if (actualStakingDuration < lockTime) { + reward = + (stakingAmount * totalRewardApr * actualStakingDuration) / + (100 * 365 days); + } else { + uint256 baseReward = (stakingAmount * totalRewardApr * lockTime) / + (100 * 365 days); + uint256 extraReward = (stakingAmount * + stakingApr * + (actualStakingDuration - lockTime)) / (100 * 365 days); + reward = baseReward + extraReward; + } + + // Halve the reward if past halfAprTimeStamp(2026/01/01) if (block.timestamp >= halfAprTimeStamp) { - uint256 reward = stakingAmount * totalRewardApr * actualStakingDuration / (100 * 365 days); return reward / 2; + } else { + return reward; } - return stakingAmount * totalRewardApr * actualStakingDuration / (100 * 365 days); } - function getNftApr(address miner, uint256 tokenId) internal view returns(uint256) { - uint256 decimal = 10e6; + function getNftApr( + address miner, + uint256 tokenId + ) internal view returns (uint256) { + // uint256 decimal = 10e6; uint8 nftType = nftManagerAddress.getMinerBoosterNftType(tokenId); if (nftType == 6) { return 20; @@ -160,13 +251,20 @@ contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpg return 9; } else if (nftType == 3) { return 5; - } else { - return 1; + } else if (nftType == 0) { + return 0; + } else { + return 0; } } - function getStakingPeriodAndApr(uint8 stakingType) internal pure returns(uint256, uint256) { - require(stakingType > 0 && stakingType < 5, "StakingManager getStakingPeriod: stakingType amount must be more than 0 and less than 4"); + function getStakingPeriodAndApr( + uint8 stakingType + ) internal pure returns (uint256, uint256) { + require( + stakingType > 0 && stakingType < 5, + "StakingManager getStakingPeriod: stakingType amount must be more than 0 and less than 5" + ); if (stakingType == 1) { return (lockThirtyDays, 3); } else if (stakingType == 2) { @@ -177,4 +275,10 @@ contract StakingManager is Initializable, OwnableUpgradeable, ReentrancyGuardUpg return (lockHalfYears, 15); } } + + /// @notice 授权升级逻辑合约的函数 + /// @dev 只允许合约owner执行 + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} } diff --git a/src/contracts/core/StakingManagerStorage.sol b/src/contracts/core/StakingManagerStorage.sol index 158baf8..1b6fa6b 100644 --- a/src/contracts/core/StakingManagerStorage.sol +++ b/src/contracts/core/StakingManagerStorage.sol @@ -8,8 +8,9 @@ import "../interfaces/IFishcakeEventManager.sol"; import "../interfaces/INftManager.sol"; import "../interfaces/IStakingManager.sol"; - abstract contract StakingManagerStorage is IStakingManager { + event WithdrawETHFromContract(address indexed to, uint256 amount); + uint256 public constant minStakeAmount = 10 * 10 ** 6; uint256 public constant lockThirtyDays = 30 days; @@ -34,7 +35,6 @@ abstract contract StakingManagerStorage is IStakingManager { uint256 public messageNonce; - IFishcakeEventManager public feManagerAddress; INftManager public nftManagerAddress; @@ -44,10 +44,13 @@ abstract contract StakingManagerStorage is IStakingManager { uint256 amount; uint256 messageNonce; uint256 endStakingTime; - uint8 stakingStatus; + uint8 stakingStatus; uint8 stakingType; uint256 bindingNft; + bool isAutoRenew; } mapping(address => mapping(bytes32 => stakeHolderStakingInfo)) stakingQueued; + + uint256[100] private __gap; } diff --git a/src/contracts/core/sale/DirectSalePoolStorage.sol b/src/contracts/core/sale/DirectSalePoolStorage.sol index 3c1baf5..7722679 100644 --- a/src/contracts/core/sale/DirectSalePoolStorage.sol +++ b/src/contracts/core/sale/DirectSalePoolStorage.sol @@ -17,7 +17,11 @@ abstract contract DirectSalePoolStorage is IDirectSalePool, Initializable { uint256 public totalSellFccAmount; uint256 public totalReceiveUsdtAmount; - function __DirectSalePoolStorage_init(address _fishCakeCoin, address _redemptionPool, address _tokenUsdtAddress) internal initializer { + function __DirectSalePoolStorage_init( + address _fishCakeCoin, + address _redemptionPool, + address _tokenUsdtAddress + ) internal onlyInitializing { fishCakeCoin = IERC20(_fishCakeCoin); redemptionPool = IRedemptionPool(_redemptionPool); tokenUsdtAddress = IERC20(_tokenUsdtAddress); @@ -26,5 +30,4 @@ abstract contract DirectSalePoolStorage is IDirectSalePool, Initializable { } uint256[100] private __gap; - } diff --git a/src/contracts/core/sale/DirectSalePoolV1.sol b/src/contracts/core/sale/DirectSalePoolV1.sol index bff8b64..354fc4b 100644 --- a/src/contracts/core/sale/DirectSalePoolV1.sol +++ b/src/contracts/core/sale/DirectSalePoolV1.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin-upgrades/contracts/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin-upgrades/contracts/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; import "@openzeppelin-upgrades/contracts/utils/ReentrancyGuardUpgradeable.sol"; @@ -10,9 +11,16 @@ import "@openzeppelin-upgrades/contracts/utils/ReentrancyGuardUpgradeable.sol"; import "./DirectSalePoolStorage.sol"; import "../../history/DirectSalePool.sol"; - /// @custom:oz-upgrades-from DirectSalePool -contract DirectSalePoolV1 is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, DirectSalePoolStorage { +contract DirectSalePoolV1 is + Initializable, + ERC20Upgradeable, + ERC20BurnableUpgradeable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable, + DirectSalePoolStorage +{ + using SafeERC20 for IERC20; error TokenUsdtBalanceNotEnough(); error FishcakeTokenNotEnough(); @@ -27,18 +35,37 @@ contract DirectSalePoolV1 is Initializable, ERC20Upgradeable, ERC20BurnableUpgra _disableInitializers(); } - function initialize(address _initialOwner, address _fishCakeCoin, address _redemptionPool, address _tokenUsdtAddress) public initializer { - require(_initialOwner != address(0), "DirectSalePool initialize: _initialOwner can't be zero address"); + function initialize( + address _initialOwner, + address _fishCakeCoin, + address _redemptionPool, + address _tokenUsdtAddress + ) public initializer { + require( + _initialOwner != address(0), + "DirectSalePool initialize: _initialOwner can't be zero address" + ); __ERC20_init("FishCake", "FCC"); __Ownable_init(_initialOwner); _transferOwnership(_initialOwner); __ReentrancyGuard_init(); - __DirectSalePoolStorage_init(_fishCakeCoin, _redemptionPool, _tokenUsdtAddress); + __DirectSalePoolStorage_init( + _fishCakeCoin, + _redemptionPool, + _tokenUsdtAddress + ); } function buyFccAmount(uint256 fccAmount) external { - require(fishCakeCoin.balanceOf(address(this)) >= fccAmount, "DirectSalePool buyFccAmount: fcc token is not enough"); + require( + fishCakeCoin.balanceOf(address(this)) >= fccAmount, + "DirectSalePool buyFccAmount: fcc token is not enough" + ); uint256 payUsdtAmount = fccAmount / 10; + require( + payUsdtAmount > 0, + "DirectSalePool buyFccAmount: payUsdtAmount is zero" + ); if (tokenUsdtAddress.balanceOf(msg.sender) < payUsdtAmount) { revert TokenUsdtBalanceNotEnough(); } @@ -46,17 +73,23 @@ contract DirectSalePoolV1 is Initializable, ERC20Upgradeable, ERC20BurnableUpgra totalSellFccAmount += fccAmount; totalReceiveUsdtAmount += payUsdtAmount; - tokenUsdtAddress.transferFrom(msg.sender, address(redemptionPool), payUsdtAmount); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(redemptionPool), + payUsdtAmount + ); fishCakeCoin.transfer(msg.sender, fccAmount); emit BuyFishcakeCoin(msg.sender, payUsdtAmount, fccAmount); - } function buyFccByUsdtAmount(uint256 tokenUsdtAmount) external { - require(tokenUsdtAddress.balanceOf(msg.sender) >= tokenUsdtAmount, "DirectSalePool buyFccAmount: usdt token is not enough"); - uint256 sellFccAmount = tokenUsdtAmount * 10; // 1 USDT = 10 FCC + require( + tokenUsdtAddress.balanceOf(msg.sender) >= tokenUsdtAmount, + "DirectSalePool buyFccAmount: usdt token is not enough" + ); + uint256 sellFccAmount = tokenUsdtAmount * 10; // 1 USDT = 10 FCC if (sellFccAmount > fishCakeCoin.balanceOf(address(this))) { revert FishcakeTokenNotEnough(); } @@ -64,15 +97,29 @@ contract DirectSalePoolV1 is Initializable, ERC20Upgradeable, ERC20BurnableUpgra totalSellFccAmount += sellFccAmount; totalReceiveUsdtAmount += tokenUsdtAmount; - tokenUsdtAddress.transferFrom(msg.sender, address(redemptionPool), tokenUsdtAmount); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(redemptionPool), + tokenUsdtAmount + ); fishCakeCoin.transfer(msg.sender, sellFccAmount); emit BuyFishcakeCoin(msg.sender, tokenUsdtAmount, sellFccAmount); } - function withdrawToken(address _tokenAddr, address _account, uint256 _value) external onlyOwner nonReentrant returns (bool) { - require(_tokenAddr != address(0x0), "NftManager withdrawToken:token address error."); - require(IERC20(_tokenAddr).balanceOf(address(this)) >= _value, "NftManager withdrawToken: Balance not enough."); + function withdrawToken( + address _tokenAddr, + address _account, + uint256 _value + ) external onlyOwner nonReentrant returns (bool) { + require( + _tokenAddr != address(0x0), + "NftManager withdrawToken:token address error." + ); + require( + IERC20(_tokenAddr).balanceOf(address(this)) >= _value, + "NftManager withdrawToken: Balance not enough." + ); IERC20(_tokenAddr).transfer(_account, _value); diff --git a/src/contracts/core/sale/InvestorSalePool.sol b/src/contracts/core/sale/InvestorSalePool.sol index be23c6a..4b18238 100644 --- a/src/contracts/core/sale/InvestorSalePool.sol +++ b/src/contracts/core/sale/InvestorSalePool.sol @@ -10,8 +10,16 @@ import "@openzeppelin-upgrades/contracts/utils/ReentrancyGuardUpgradeable.sol"; import "../../interfaces/IInvestorSalePool.sol"; import "./InvestorSalePoolStorage.sol"; +contract InvestorSalePool is + Initializable, + ERC20Upgradeable, + ERC20BurnableUpgradeable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable, + InvestorSalePoolStorage +{ + using SafeERC20 for IERC20; -contract InvestorSalePool is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, InvestorSalePoolStorage { error NotSupportFccAmount(); error NotSupportUsdtAmount(); @@ -20,19 +28,35 @@ contract InvestorSalePool is Initializable, ERC20Upgradeable, ERC20BurnableUpgra event SetVaultAddress(address _vaultAddress); event WithdrawUsdt(address indexed withdrawAddress, uint256 _amount); - event BuyFishcakeCoin(address indexed buyer, uint256 USDTAmount, uint256 fishcakeCoinAmount); + event BuyFishcakeCoin( + address indexed buyer, + uint256 USDTAmount, + uint256 fishcakeCoinAmount + ); + event WithdrawFcc(address indexed withdrawAddress, uint256 _amount); constructor() { _disableInitializers(); } - function initialize(address _initialOwner, address _fishCakeCoin, address _redemptionPool, address _tokenUsdtAddress) public initializer { - - require(_initialOwner != address(0), "InvestorSalePool initialize: _initialOwner can't be zero address"); + function initialize( + address _initialOwner, + address _fishCakeCoin, + address _redemptionPool, + address _tokenUsdtAddress + ) public initializer { + require( + _initialOwner != address(0), + "InvestorSalePool initialize: _initialOwner can't be zero address" + ); __Ownable_init(_initialOwner); _transferOwnership(_initialOwner); __ReentrancyGuard_init(); - __InvestorSalePoolStorage_init(_fishCakeCoin, _redemptionPool, _tokenUsdtAddress); + __InvestorSalePoolStorage_init( + _fishCakeCoin, + _redemptionPool, + _tokenUsdtAddress + ); } function buyFccAmount(uint256 fccAmount) external { @@ -46,16 +70,28 @@ contract InvestorSalePool is Initializable, ERC20Upgradeable, ERC20BurnableUpgra totalSellFccAmount += fccAmount; totalReceiveUsdtAmount += tokenUsdtAmount; - tokenUsdtAddress.transferFrom(msg.sender, address(this), tokenUsdtAmount / 2); - tokenUsdtAddress.transferFrom(msg.sender, address(redemptionPool), tokenUsdtAmount / 2); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(this), + tokenUsdtAmount / 2 + ); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(redemptionPool), + tokenUsdtAmount / 2 + ); fishCakeCoin.transfer(msg.sender, fccAmount); emit BuyFishcakeCoin(msg.sender, tokenUsdtAmount, fccAmount); - } function buyFccByUsdtAmount(uint256 tokenUsdtAmount) external { + require( + tx.origin == msg.sender && msg.sender.code.length == 0, + "only EOA" + ); + if (tokenUsdtAddress.balanceOf(msg.sender) < tokenUsdtAmount) { revert TokenUsdtAmountNotEnough(); } @@ -67,8 +103,16 @@ contract InvestorSalePool is Initializable, ERC20Upgradeable, ERC20BurnableUpgra totalSellFccAmount += fccAmount; totalReceiveUsdtAmount += tokenUsdtAmount; - tokenUsdtAddress.transferFrom(msg.sender, address(this), tokenUsdtAmount / 2); - tokenUsdtAddress.transferFrom(msg.sender, address(redemptionPool), tokenUsdtAmount / 2); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(this), + tokenUsdtAmount / 2 + ); + tokenUsdtAddress.safeTransferFrom( + msg.sender, + address(redemptionPool), + tokenUsdtAmount / 2 + ); fishCakeCoin.transfer(msg.sender, fccAmount); @@ -85,40 +129,89 @@ contract InvestorSalePool is Initializable, ERC20Upgradeable, ERC20BurnableUpgra emit WithdrawUsdt(vaultAddress, _amount); } - function calculateFccByUsdtExternal(uint256 _amount) external pure returns (uint256) { + function withdrawFcc(uint256 _amount) external onlyOwner nonReentrant { + require( + fishCakeCoin.balanceOf(address(this)) >= _amount, + "Insufficient FCC balance" + ); + fishCakeCoin.transfer(vaultAddress, _amount); + emit WithdrawFcc(vaultAddress, _amount); + } + + function calculateFccByUsdtExternal( + uint256 _amount + ) external pure returns (uint256) { return calculateFccByUsdt(_amount); } - function calculateFccByUsdt(uint256 _usdtAmount) internal pure returns (uint256) { - if (_usdtAmount >= 100_000 * usdtDecimal) { // Tier 1: 1 FCC = 0.06 USDT + function calculateFccByUsdt( + uint256 _usdtAmount + ) internal pure returns (uint256) { + if (_usdtAmount >= 100_000 * usdtDecimal) { + // Tier 1: 1 FCC = 0.06 USDT return (_usdtAmount * 100 * fccDecimal) / (6 * usdtDecimal); - } else if (_usdtAmount < 100_000 * usdtDecimal && _usdtAmount >= 10_000 * usdtDecimal) { // Tier 2: 1 FCC = 0.07 USDT + } else if ( + _usdtAmount < 100_000 * usdtDecimal && + _usdtAmount >= 10_000 * usdtDecimal + ) { + // Tier 2: 1 FCC = 0.07 USDT return (_usdtAmount * 100 * fccDecimal) / (7 * usdtDecimal); - } else if (_usdtAmount < 10_000 * usdtDecimal && _usdtAmount >= 5_000 * usdtDecimal) { // Tier 3: 1 FCC = 0.08 USDT + } else if ( + _usdtAmount < 10_000 * usdtDecimal && + _usdtAmount >= 5_000 * usdtDecimal + ) { + // Tier 3: 1 FCC = 0.08 USDT return (_usdtAmount * 100 * fccDecimal) / (8 * usdtDecimal); - } else if (_usdtAmount < 5_000 * usdtDecimal && _usdtAmount >= 1_000 * usdtDecimal) { // Tier 4: 1 FCC = 0.09 USDT + } else if ( + _usdtAmount < 5_000 * usdtDecimal && + _usdtAmount >= 1_000 * usdtDecimal + ) { + // Tier 4: 1 FCC = 0.09 USDT return (_usdtAmount * 100 * fccDecimal) / (9 * usdtDecimal); - } else if (_usdtAmount < 1_000 * usdtDecimal && _usdtAmount > 0 * usdtDecimal) { // Tier 5: 1 FCC = 0.1 USDT + } else if ( + _usdtAmount < 1_000 * usdtDecimal && _usdtAmount > 0 * usdtDecimal + ) { + // Tier 5: 1 FCC = 0.1 USDT return (_usdtAmount * 10 * fccDecimal) / usdtDecimal; } else { revert NotSupportUsdtAmount(); } } - function calculateUsdtByFccExternal(uint256 _amount) external pure returns (uint256) { + function calculateUsdtByFccExternal( + uint256 _amount + ) external pure returns (uint256) { return calculateUsdtByFcc(_amount); } - function calculateUsdtByFcc(uint256 _fccAmount) internal pure returns (uint256) { - if (_fccAmount >= 5_000_000 * fccDecimal) { // tier1: 1 FCC = 0.06 USDT + function calculateUsdtByFcc( + uint256 _fccAmount + ) internal pure returns (uint256) { + if (_fccAmount >= 5_000_000 * fccDecimal) { + // tier1: 1 FCC = 0.06 USDT return (_fccAmount * 6 * usdtDecimal) / (100 * fccDecimal); - } else if (_fccAmount < 5_000_000 * fccDecimal && _fccAmount >= 250_000 * fccDecimal) { // tier2: 1 FCC = 0.07 USDT + } else if ( + _fccAmount < 5_000_000 * fccDecimal && + _fccAmount >= 250_000 * fccDecimal + ) { + // tier2: 1 FCC = 0.07 USDT return (_fccAmount * 7 * usdtDecimal) / (100 * fccDecimal); - } else if (_fccAmount < 250_000 * fccDecimal && _fccAmount >= 100_000 * fccDecimal) { // tier3: 1 FCC = 0.08 USDT + } else if ( + _fccAmount < 250_000 * fccDecimal && + _fccAmount >= 100_000 * fccDecimal + ) { + // tier3: 1 FCC = 0.08 USDT return (_fccAmount * 8 * usdtDecimal) / (100 * fccDecimal); - } else if (_fccAmount < 100_000 * fccDecimal && _fccAmount >= 16_666 * fccDecimal) { // tier4: 1 FCC = 0.09 USDT + } else if ( + _fccAmount < 100_000 * fccDecimal && + _fccAmount >= 16_666 * fccDecimal + ) { + // tier4: 1 FCC = 0.09 USDT return (_fccAmount * 9 * usdtDecimal) / (100 * fccDecimal); - } else if (_fccAmount < 16_666 * fccDecimal && _fccAmount > 0 * fccDecimal) { // tier5: 1 FCC = 0.1 USDT + } else if ( + _fccAmount < 16_666 * fccDecimal && _fccAmount > 0 * fccDecimal + ) { + // tier5: 1 FCC = 0.1 USDT return (_fccAmount * 10 * usdtDecimal) / (100 * fccDecimal); } else { revert NotSupportFccAmount(); diff --git a/src/contracts/core/sale/InvestorSalePoolStorage.sol b/src/contracts/core/sale/InvestorSalePoolStorage.sol index c2446ae..7ab6f9a 100644 --- a/src/contracts/core/sale/InvestorSalePoolStorage.sol +++ b/src/contracts/core/sale/InvestorSalePoolStorage.sol @@ -14,7 +14,6 @@ abstract contract InvestorSalePoolStorage is Initializable, IInvestorSalePool { uint256 public constant usdtDecimal = 10 ** 6; uint256 public constant fccDecimal = 10 ** 6; - IERC20 public fishCakeCoin; IRedemptionPool public redemptionPool; IERC20 public tokenUsdtAddress; @@ -24,7 +23,11 @@ abstract contract InvestorSalePoolStorage is Initializable, IInvestorSalePool { uint256 public totalSellFccAmount; uint256 public totalReceiveUsdtAmount; - function __InvestorSalePoolStorage_init(address _fishCakeCoin, address _redemptionPool, address _tokenUsdtAddress) internal initializer { + function __InvestorSalePoolStorage_init( + address _fishCakeCoin, + address _redemptionPool, + address _tokenUsdtAddress + ) internal onlyInitializing { fishCakeCoin = IERC20(_fishCakeCoin); redemptionPool = IRedemptionPool(_redemptionPool); tokenUsdtAddress = IERC20(_tokenUsdtAddress); diff --git a/src/contracts/core/token/FishCakeCoin.sol b/src/contracts/core/token/FishCakeCoin.sol index b53ddfc..5ffd55f 100644 --- a/src/contracts/core/token/FishCakeCoin.sol +++ b/src/contracts/core/token/FishCakeCoin.sol @@ -9,8 +9,16 @@ import "@openzeppelin-upgrades/contracts/utils/ReentrancyGuardUpgradeable.sol"; import "./FishCakeCoinStorage.sol"; - -contract FishCakeCoin is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, FishCakeCoinStorage { +contract FishCakeCoin is + Initializable, + ERC20Upgradeable, + ERC20BurnableUpgradeable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable, + FishCakeCoinStorage +{ + event SetRedemptionPool(address indexed RedemptionPool); + event SetPoolAddress(fishCakePool indexed pool); string private constant NAME = "Fishcake Coin"; string private constant SYMBOL = "FCC"; @@ -20,17 +28,27 @@ contract FishCakeCoin is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeab } modifier onlyRedemptionPool() { - require(msg.sender == RedemptionPool, "FishCakeCoin onlyRedemptionPool: Only RedemptionPool can call this function"); + require( + msg.sender == RedemptionPool, + "FishCakeCoin onlyRedemptionPool: Only RedemptionPool can call this function" + ); _; } - function initialize(address _owner, address _RedemptionPool) public initializer { - require(_owner != address(0), "FishCakeCoin initialize: _owner can't be zero address"); + function initialize( + address _owner, + address _RedemptionPool + ) public initializer { + require( + _owner != address(0), + "FishCakeCoin initialize: _owner can't be zero address" + ); __ERC20_init(NAME, SYMBOL); __ERC20Burnable_init(); __Ownable_init(_owner); RedemptionPool = _RedemptionPool; _transferOwnership(_owner); + __ReentrancyGuard_init(); isAllocation = false; } @@ -38,18 +56,20 @@ contract FishCakeCoin is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeab return 6; } - function FccBalance(address _address) external view returns(uint256) { + function FccBalance(address _address) external view returns (uint256) { return balanceOf(_address); } function setRedemptionPool(address _RedemptionPool) external onlyOwner { RedemptionPool = _RedemptionPool; + emit SetRedemptionPool(_RedemptionPool); } function setPoolAddress(fishCakePool memory _pool) external onlyOwner { _beforeAllocation(); _beforePoolAddress(_pool); fcPool = _pool; + emit SetPoolAddress(_pool); } function poolAllocate() external onlyOwner { @@ -58,32 +78,53 @@ contract FishCakeCoin is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeab _mint(fcPool.directSalePool, (MaxTotalSupply * 2) / 10); // 20% of total supply _mint(fcPool.investorSalePool, MaxTotalSupply / 10); // 10% of total supply _mint(fcPool.nftSalesRewardsPool, (MaxTotalSupply * 2) / 10); // 20% of total supply - _mint(fcPool.ecosystemPool, MaxTotalSupply / 10); // 10% of total supply + _mint(fcPool.ecosystemPool, MaxTotalSupply / 10); // 10% of total supply _mint(fcPool.foundationPool, MaxTotalSupply / 10); // 10% of total supply isAllocation = true; } function burn(address user, uint256 _amount) external onlyRedemptionPool { _burn(user, _amount); - _burnedTokens +=_amount; + _redemptionPoolBurnedTokens += _amount; emit Burn(_amount, totalSupply()); } - function FccTotalSupply() external view returns(uint256) { + function FccTotalSupply() external view returns (uint256) { return totalSupply(); } // ==================== internal function ============================= function _beforeAllocation() internal virtual { - require(!isAllocation, "FishCakeCoin _beforeAllocation:Fishcake is already allocate"); + require( + !isAllocation, + "FishCakeCoin _beforeAllocation:Fishcake is already allocate" + ); } function _beforePoolAddress(fishCakePool memory _pool) internal virtual { - require(_pool.miningPool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate MiningPool address"); - require(_pool.directSalePool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate DirectSalePool address"); - require(_pool.investorSalePool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate InvestorSalePool address"); - require(_pool.nftSalesRewardsPool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate NFTSalesRewardsPool address"); - require(_pool.ecosystemPool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate EcosystemPool address"); - require(_pool.foundationPool != address(0), "FishCakeCoin _beforeAllocation:Missing allocate FoundationPool address"); + require( + _pool.miningPool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate MiningPool address" + ); + require( + _pool.directSalePool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate DirectSalePool address" + ); + require( + _pool.investorSalePool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate InvestorSalePool address" + ); + require( + _pool.nftSalesRewardsPool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate NFTSalesRewardsPool address" + ); + require( + _pool.ecosystemPool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate EcosystemPool address" + ); + require( + _pool.foundationPool != address(0), + "FishCakeCoin _beforeAllocation:Missing allocate FoundationPool address" + ); } } diff --git a/src/contracts/core/token/FishCakeCoinStorage.sol b/src/contracts/core/token/FishCakeCoinStorage.sol index f5793d2..0b34731 100644 --- a/src/contracts/core/token/FishCakeCoinStorage.sol +++ b/src/contracts/core/token/FishCakeCoinStorage.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; abstract contract FishCakeCoinStorage { uint256 public constant MaxTotalSupply = 1_000_000_000 * 10 ** 6; - uint256 public _burnedTokens; + uint256 public _redemptionPoolBurnedTokens; address public RedemptionPool; diff --git a/src/contracts/core/token/NftManagerStorage.sol b/src/contracts/core/token/NftManagerStorage.sol index 1cdfac3..12d7146 100644 --- a/src/contracts/core/token/NftManagerStorage.sol +++ b/src/contracts/core/token/NftManagerStorage.sol @@ -10,8 +10,6 @@ import "../../interfaces/IFishcakeEventManager.sol"; import "../../interfaces/INftManager.sol"; import "../../interfaces/IStakingManager.sol"; - - abstract contract NftManagerStorage is Initializable, INftManager { using Strings for uint256; using Strings for uint8; @@ -50,6 +48,11 @@ abstract contract NftManagerStorage is Initializable, INftManager { string public epicSalmonNftJson; string public legendaryTunaNftJson; + // string public uncommonFishcakeNftJson_Used; + // string public rareShrimpNftJson_Used; + // string public epicSalmonNftJson_Used; + // string public legendaryTunaNftJson_Used; + IFishcakeEventManager public feManagerAddress; mapping(address => uint256) public minerActiveNft; @@ -60,7 +63,11 @@ abstract contract NftManagerStorage is Initializable, INftManager { IStakingManager public stakingManagerAddress; - function __NftManagerStorage_init(address _fccTokenAddr, address _tokenUsdtAddr, address _redemptionPoolAddress) internal initializer { + function __NftManagerStorage_init( + address _fccTokenAddr, + address _tokenUsdtAddr, + address _redemptionPoolAddress + ) internal initializer { fccTokenAddr = IERC20(_fccTokenAddr); tokenUsdtAddr = IERC20(_tokenUsdtAddr); redemptionPoolAddress = IRedemptionPool(_redemptionPoolAddress); @@ -72,6 +79,5 @@ abstract contract NftManagerStorage is Initializable, INftManager { proNftJson = "https://www.fishcake.org/image/1.json"; basicNftJson = "https://www.fishcake.org/image/2.json"; - } } diff --git a/src/contracts/core/token/NftManagerV5.sol b/src/contracts/core/token/NftManagerV5.sol index ca37bce..720201d 100644 --- a/src/contracts/core/token/NftManagerV5.sol +++ b/src/contracts/core/token/NftManagerV5.sol @@ -26,7 +26,11 @@ contract NftManagerV5 is event UriPrefixSet(address indexed setterAddress, string urlPrefix); - event SetValues(address indexed _setterAddress, uint256 _merchantValue, uint256 _userValue); + event SetValues( + address indexed _setterAddress, + uint256 _merchantValue, + uint256 _userValue + ); event CreateNFT( address indexed creator, @@ -43,7 +47,10 @@ contract NftManagerV5 is ); event WithdrawUToken( - address indexed withdrawer, address indexed _tokenAddr, address indexed _account, uint256 _value + address indexed withdrawer, + address indexed _tokenAddr, + address indexed _account, + uint256 _value ); event SetValidTime(address indexed setter, uint256 _time); @@ -51,7 +58,11 @@ contract NftManagerV5 is event Withdraw(address indexed withdrawer, uint256 _amount); event Received(address indexed receiver, uint256 _value); - event UpdatedNftJson(address indexed creator, uint8 nftType, string newJsonUrl); + event UpdatedNftJson( + address indexed creator, + uint8 nftType, + string newJsonUrl + ); event NameSymbolUpdated(string newName, string newSymbol); modifier onlyBooster() { @@ -75,49 +86,72 @@ contract NftManagerV5 is _disableInitializers(); } - function initialize( address _initialOwner, address _fccTokenAddr, address _tokenUsdtAddr, address _redemptionPoolAddress ) public initializer { - require(_initialOwner != address(0), "NftManager initialize: _initialOwner can't be zero address"); + require( + _initialOwner != address(0), + "NftManager initialize: _initialOwner can't be zero address" + ); __ERC721_init("Fishcake Pass NFT", "FNFT"); __ReentrancyGuard_init(); __Ownable_init(_initialOwner); _transferOwnership(_initialOwner); - __NftManagerStorage_init(_fccTokenAddr, _tokenUsdtAddr, _redemptionPoolAddress); + __NftManagerStorage_init( + _fccTokenAddr, + _tokenUsdtAddr, + _redemptionPoolAddress + ); + } + + function initializeV5( + address _stakingManagerAddress + ) public reinitializer(5) { + stakingManagerAddress = IStakingManager(_stakingManagerAddress); } receive() external payable { emit Received(msg.sender, msg.value); } - function nftUpgradeInit(address _feManagerAddress, address _boosterAddress, address _stakingManagerAddress) external onlyOwner { + function nftUpgradeInit( + address _feManagerAddress, + address _boosterAddress, + address _stakingManagerAddress + ) external onlyOwner { uncommonFishcakeNftJson = "https://www.fishcake.org/image/3.json"; rareShrimpNftJson = "https://www.fishcake.org/image/4.json"; epicSalmonNftJson = "https://www.fishcake.org/image/5.json"; legendaryTunaNftJson = "https://www.fishcake.org/image/6.json"; + // uncommonFishcakeNftJson_Used = "https://www.fishcake.org/image/3.json"; + // rareShrimpNftJson_Used = "https://www.fishcake.org/image/4.json"; + // epicSalmonNftJson_Used = "https://www.fishcake.org/image/5.json"; + // legendaryTunaNftJson_Used = "https://www.fishcake.org/image/6.json"; + feManagerAddress = IFishcakeEventManager(_feManagerAddress); stakingManagerAddress = IStakingManager(_stakingManagerAddress); boosterAddress = _boosterAddress; } - function mintBoosterNFT(address miner) external onlyBooster nonReentrant returns (bool, uint256) { + function mintBoosterNFT( + address miner + ) external onlyBooster nonReentrant returns (bool, uint256) { uint256 mineAmount = feManagerAddress.getMinerMineAmount(miner); - if (mineAmount < 30) { + if (mineAmount < 30 * 1e6) { revert MineAmountNotEnough(mineAmount); } uint256 boosterTokenId = _nextTokenId++; - _safeMint(msg.sender, boosterTokenId); - uint256 decimal = 10e6; - if(mineAmount >= 30 * decimal && mineAmount < 90 * decimal) { + _safeMint(miner, boosterTokenId); + uint256 decimal = 1e6; + if (mineAmount >= 30 * decimal && mineAmount < 90 * decimal) { nftMintType[boosterTokenId] = 3; - } else if(mineAmount >= 90 * decimal && mineAmount < 300 * decimal) { + } else if (mineAmount >= 90 * decimal && mineAmount < 300 * decimal) { nftMintType[boosterTokenId] = 4; - } else if(mineAmount >= 300 * decimal && mineAmount < 900 * decimal) { + } else if (mineAmount >= 300 * decimal && mineAmount < 900 * decimal) { nftMintType[boosterTokenId] = 5; } else { nftMintType[boosterTokenId] = 6; @@ -140,11 +174,20 @@ contract NftManagerV5 is _type == 1 || _type == 2, "NftManager createNFT: type can only equal 1 and 2, 1 stand for merchant, 2 stand for personal user" ); + require( + merchantValue > 0 && userValue > 0, + "NftManager createNFT: MerchantValue and UserValue must be set first" + ); + require( + validTime > 0, + "NftManager createNFT: validTime must be set first" + ); uint256 payUsdtAmount = _type == 1 ? merchantValue : userValue; uint256 nftDeadline = block.timestamp + validTime; if (_type == 1) { require( - tokenUsdtAddr.allowance(msg.sender, address(this)) >= merchantValue, + tokenUsdtAddr.allowance(msg.sender, address(this)) >= + merchantValue, "NftManager createNFT: Merchant allowance must more than 80 U" ); merchantNftDeadline[msg.sender] = nftDeadline; @@ -152,14 +195,21 @@ contract NftManagerV5 is } else { require( tokenUsdtAddr.allowance(msg.sender, address(this)) >= userValue, - "NftManager createNFT: Merchant allowance must more than 8 U" + "NftManager createNFT: User allowance must more than 8 U" ); userNftDeadline[msg.sender] = nftDeadline; fccTokenAddr.transfer(msg.sender, basicMineAmt); } - tokenUsdtAddr.transferFrom(msg.sender, address(this), payUsdtAmount); - tokenUsdtAddr.transfer(address(redemptionPoolAddress), (payUsdtAmount * 75) / 100); + tokenUsdtAddr.safeTransferFrom( + msg.sender, + address(this), + payUsdtAmount + ); + tokenUsdtAddr.safeTransfer( + address(redemptionPoolAddress), + (payUsdtAmount * 25) / 100 + ); uint256 tokenId = _nextTokenId++; _safeMint(msg.sender, tokenId); @@ -181,30 +231,48 @@ contract NftManagerV5 is return (true, tokenId); } - function tokenURI(uint256 tokenId) + function tokenURI( + uint256 tokenId + ) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable) returns (string memory) { - require(_ownerOf(tokenId) != address(0), "ERC721Metadata: URI query for nonexistent token"); + require( + _ownerOf(tokenId) != address(0), + "ERC721Metadata: URI query for nonexistent token" + ); uint8 nftType = nftMintType[tokenId]; - if(nftType == 1) { + if (nftType == 1) { return proNftJson; - } else if(nftType == 2) { + } else if (nftType == 2) { return basicNftJson; - } else if(nftType == 3) { + } else if (nftType == 3) { return uncommonFishcakeNftJson; - } else if(nftType == 4) { + } else if (nftType == 4) { return rareShrimpNftJson; - } else if(nftType == 5) { + } else if (nftType == 5) { return epicSalmonNftJson; - } else { + } else if (nftType == 6) { return legendaryTunaNftJson; + } else { + return ""; } + // } else if (nftType == 3 + 10) { + // return uncommonFishcakeNftJson_Used; + // } else if (nftType == 4 + 10) { + // return rareShrimpNftJson_Used; + // } else if (nftType == 5 + 10) { + // return epicSalmonNftJson_Used; + // } else if (nftType == 6 + 10) { + // return legendaryTunaNftJson_Used; + // } } - function uri(uint256 inputTokenId) public view virtual returns (string memory) { + function uri( + uint256 inputTokenId + ) public view virtual returns (string memory) { return tokenURI(inputTokenId); } @@ -219,20 +287,28 @@ contract NftManagerV5 is emit UriPrefixSet(msg.sender, _uriPrefix); } - function setValues(uint256 _merchantValue, uint256 _userValue) external onlyOwner { + function setValues( + uint256 _merchantValue, + uint256 _userValue + ) external onlyOwner { merchantValue = _merchantValue; userValue = _userValue; emit SetValues(msg.sender, _merchantValue, _userValue); } - function withdrawToken(address _tokenAddr, address _account, uint256 _value) - external - onlyOwner - nonReentrant - returns (bool) - { - require(_tokenAddr != address(0x0), "NftManager withdrawToken:token address error."); - require(IERC20(_tokenAddr).balanceOf(address(this)) >= _value, "NftManager withdrawToken: Balance not enough."); + function withdrawToken( + address _tokenAddr, + address _account, + uint256 _value + ) external onlyOwner nonReentrant returns (bool) { + require( + _tokenAddr != address(0x0), + "NftManager withdrawToken:token address error." + ); + require( + IERC20(_tokenAddr).balanceOf(address(this)) >= _value, + "NftManager withdrawToken: Balance not enough." + ); IERC20(_tokenAddr).transfer(_account, _value); @@ -241,44 +317,63 @@ contract NftManagerV5 is return true; } - function withdrawNativeToken(address payable _recipient, uint256 _amount) - public - onlyOwner - nonReentrant - returns (bool) - { - require(_recipient != address(0x0), "NftManager withdrawNativeToken: recipient address error."); - require(_amount <= address(this).balance, "NftManager withdrawNativeToken: Balance not enough."); - (bool _ret,) = _recipient.call{value: _amount}(""); + function withdrawNativeToken( + address payable _recipient, + uint256 _amount + ) public onlyOwner nonReentrant returns (bool) { + require( + _recipient != address(0x0), + "NftManager withdrawNativeToken: recipient address error." + ); + require( + _amount <= address(this).balance, + "NftManager withdrawNativeToken: Balance not enough." + ); + (bool _ret, ) = _recipient.call{value: _amount}(""); emit Withdraw(_recipient, _amount); return _ret; } - function getMerchantNTFDeadline(address _account) public view returns (uint256) { + function getMerchantNTFDeadline( + address _account + ) public view returns (uint256) { return merchantNftDeadline[_account]; } - function getUserNTFDeadline(address _account) public view returns (uint256) { + function getUserNTFDeadline( + address _account + ) public view returns (uint256) { return userNftDeadline[_account]; } - function inActiveMinerBoosterNft(address _miner) external onlyStakingManager { - minerActiveNft[_miner] = 0; + function inActiveMinerBoosterNft( + address _miner + ) external onlyStakingManager { + uint256 activeNftId = minerActiveNft[_miner]; + minerActiveNft[_miner] = activeNftId + 10; } - function getActiveMinerBoosterNft(address _miner) external view returns (uint256) { + function getActiveMinerBoosterNft( + address _miner + ) external view returns (uint256) { return minerActiveNft[_miner]; } - function getMinerBoosterNftType(uint256 tokenId) external view returns (uint8) { + function getMinerBoosterNftType( + uint256 tokenId + ) external view returns (uint8) { return nftMintType[tokenId]; } - function getTokenBalance(address tokenAddress) public view returns (uint256) { + function getTokenBalance( + address tokenAddress + ) public view returns (uint256) { return IERC20(tokenAddress).balanceOf(address(this)); } - function supportsInterface(bytes4 interfaceId) + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable) @@ -287,7 +382,10 @@ contract NftManagerV5 is return super.supportsInterface(interfaceId); } - function updateNftJson(uint8 _type, string memory _newJsonUrl) external onlyOwner { + function updateNftJson( + uint8 _type, + string memory _newJsonUrl + ) external onlyOwner { require(_type == 1 || _type == 2, "Invalid NFT type"); if (_type == 1) { proNftJson = _newJsonUrl; @@ -297,7 +395,10 @@ contract NftManagerV5 is emit UpdatedNftJson(msg.sender, _type, _newJsonUrl); } - function updateNameAndSymbol(string memory newName, string memory newSymbol) external onlyOwner { + function updateNameAndSymbol( + string memory newName, + string memory newSymbol + ) external onlyOwner { _customName = newName; _customSymbol = newSymbol; emit NameSymbolUpdated(newName, newSymbol); diff --git a/src/contracts/interfaces/IFishcakeEventManager.sol b/src/contracts/interfaces/IFishcakeEventManager.sol index 50c66f9..7ada8be 100644 --- a/src/contracts/interfaces/IFishcakeEventManager.sol +++ b/src/contracts/interfaces/IFishcakeEventManager.sol @@ -52,8 +52,14 @@ interface IFishcakeEventManager { ) external returns (bool, uint256); function activityFinish(uint256 _activityId) external returns (bool); - function drop(uint256 _activityId, address _userAccount, uint256 _dropAmt) external returns (bool); + + function drop( + uint256 _activityId, + address _userAccount, + uint256 _dropAmt + ) external returns (bool); function getMinerMineAmount(address _miner) external view returns (uint256); + function deleteMinerMineAmount(address _miner) external; } diff --git a/src/contracts/interfaces/IStakingManager.sol b/src/contracts/interfaces/IStakingManager.sol index 357b873..bef2946 100644 --- a/src/contracts/interfaces/IStakingManager.sol +++ b/src/contracts/interfaces/IStakingManager.sol @@ -4,7 +4,13 @@ pragma solidity ^0.8.0; interface IStakingManager { event StakeHolderDepositStaking( address indexed staker, - uint256 stakingAmount, + uint256 amount, + uint8 stakingType, + uint256 startStakingTime, + uint256 endStakingTime, + uint256 bindingNft, + uint256 nftApr, + bool isAutoRenew, uint256 messageNonce ); @@ -12,15 +18,23 @@ interface IStakingManager { address indexed recipant, uint256 withdrawAmount, uint256 messageNonce, - bytes32 messageHash + bytes32 messageHash, + uint256 rewardAprFunding ); event Received(address indexed receiver, uint256 _value); - error FundingUnderStaking(uint256 amount, uint256 endTime); error NoFundingForStaking(); - function DepositIntoStaking(uint256 amount, uint8 stakingType) external; - function withdrawFromStakingWithAprIncome(uint256 amount, uint256 messageNonce) external; + function depositIntoStaking( + uint256 amount, + uint8 stakingType, + bool isAutonew + ) external; + + function withdrawFromStakingWithAprIncome( + uint256 amount, + uint256 messageNonce + ) external; }