Multiple claims + usdc tracking in withdrawliabilities

This commit is contained in:
2025-09-15 14:07:43 +02:00
parent 4f8f062f35
commit 8e0b6a58bd
2 changed files with 153 additions and 126 deletions

View File

@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
@@ -41,6 +40,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 estDaysRemaining;
uint256 currentTreasuryTvl;
uint256 totalLiability; // Snapshot of totalBigStakes at epoch end
uint256 paybackPercent; // Payback percentage used for this epoch (scaled by 10000)
uint256 unlockPercentage; // Calculated unlock percentage (scaled by 10000)
uint256 timestamp; // When this epoch ended
}
@@ -90,8 +90,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 private stakeIdCounter;
uint256 private vestingStakeIdCounter;
// Track total withdraw vesting liabilities by token address
mapping(address => uint256) public withdrawVestingLiabilities;
// Track total withdraw liabilities by token address (all tokens including BSC_TOKEN)
mapping(address => uint256) public withdrawLiabilities;
// Epoch-based staking variables
mapping(uint256 => Epoch) public epochs;
@@ -107,13 +107,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 public marketplaceMin; // Minimum value for listings (in USD, e.g., 25 * 1e18 = $25)
uint256 public cancellationFee; // Fee percentage for cancelling listings (e.g., 500 = 5%)
mapping(address => uint256) public marketplace_sales; // Track total sales per user
mapping(address => uint256) public pendingSellStakes; // Track total value of pending sell stakes per user
SellStakeKey[] public sellStakeKeys; // Array for iteration over active sell stakes
mapping(address => mapping(uint256 => uint256)) private sellStakeKeyIndex; // Track position in keys array
MarketplaceHistory[] public marketplaceHistory; // Complete history of all transactions
mapping(address => uint256) public totalClaimed; // Track total amount claimed and sent to withdrawStakes per user
// Events
event VestingCreated(address indexed user, uint256 amount, uint256 bonus);
event VestingClaimed(address indexed user, uint256 amount, uint256 bonus);
event BonusClaimed(address indexed user, uint256 bonus);
event UnlockScheduleSet(address indexed token);
@@ -343,6 +343,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
estDaysRemaining: estDaysRemaining,
currentTreasuryTvl: currentTreasuryTvl,
totalLiability: totalBigStakes,
paybackPercent: _paybackPercent,
unlockPercentage: unlockPercentage,
timestamp: block.timestamp
});
@@ -366,11 +367,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
return totalUnclaimed;
}
/// @notice Get user's net stake (big stake minus unclaimed funds)
/// @notice Get user's net stake (big stake minus unclaimed funds minus pending sell stakes)
function getNetStake(address user) public view returns (uint256) {
uint256 bigStake = userBigStake[user];
uint256 unclaimed = calculateUnclaimedFunds(user);
return bigStake - unclaimed;
uint256 pending = pendingSellStakes[user];
uint256 committed = unclaimed + pending;
return bigStake > committed ? bigStake - committed : 0;
}
/// @notice Get comprehensive user stake information
@@ -411,6 +414,9 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: BSC_TOKEN
}));
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += unclaimedAmount;
emit FundsClaimed(msg.sender, unclaimedAmount);
}
@@ -433,10 +439,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
address token = stake.token;
stake.amount = 0; // Mark as withdrawn
// Decrement withdraw vesting liabilities for non-BSC tokens
if (token != BSC_TOKEN) {
withdrawVestingLiabilities[token] -= amount;
}
// Decrement withdraw liabilities for all tokens
withdrawLiabilities[token] -= amount;
// Transfer tokens to user based on the specified token
IERC20(token).safeTransfer(msg.sender, amount);
@@ -482,6 +486,9 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: BSC_TOKEN
}));
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += payoutAmount;
emit FundsClaimed(msg.sender, payoutAmount);
}
@@ -622,9 +629,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
stakeIdCounter++;
uint256 stakeId = stakeIdCounter;
// Deduct value from user's big stake immediately
userBigStake[msg.sender] -= value;
totalBigStakes -= value;
// Add to pending sell stakes tracking (don't touch actual bigStake until sale/cancel)
pendingSellStakes[msg.sender] += value;
// Create the sellStake entry
sellStakes[msg.sender][stakeId] = SellStake({
@@ -653,14 +659,16 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 value = sellStakeEntry.value;
// Calculate cancellation fee
// Calculate cancellation fee and apply directly to userBigStake
uint256 fee = (value * cancellationFee) / 10000;
uint256 valueAfterFee = value - fee;
// Restore value minus fee to user's big stake
userBigStake[msg.sender] += valueAfterFee;
totalBigStakes += valueAfterFee;
// Note: fee reduces total liability (not added back to totalBigStakes)
// Remove from pending and apply fee directly to bigStake
pendingSellStakes[msg.sender] -= value;
if (fee > 0) {
userBigStake[msg.sender] -= fee;
totalBigStakes -= fee;
}
// Note: fee reduces total liability permanently
// Emit fee event
if (fee > 0) {
@@ -720,10 +728,12 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
// Transfer payment from buyer to seller (direct transfer)
IERC20(BSC_TOKEN).safeTransferFrom(msg.sender, seller, salePrice);
// Add buyerStake to buyer's big stake (value - protocol share)
// Transfer stakes: remove from seller, add to buyer (minus protocol share)
userBigStake[seller] -= value;
userBigStake[msg.sender] += buyerStake;
totalBigStakes += buyerStake;
// Note: protocolShare reduces total liability (not added back to totalBigStakes)
pendingSellStakes[seller] -= value;
// Note: totalBigStakes decreases by protocolShare (value - buyerStake)
totalBigStakes -= (value - buyerStake);
// Track marketplace sales for seller
marketplace_sales[seller] += salePrice;
@@ -759,27 +769,27 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
/// @notice This function will end and clear a user's vestings.
/// @dev Only to be used by bots in emergencies
/// @param user The user whose vestings will be ended and 0'd
function clearVesting(address user) external onlyBot {
for (uint256 i = 0; i < vestings[user].length; ++i) {
Vesting storage vesting = vestings[user][i];
// function clearVesting(address user) external onlyBot {
// for (uint256 i = 0; i < vestings[user].length; ++i) {
// Vesting storage vesting = vestings[user][i];
// Decrement accounting variables before clearing
if (!vesting.complete) {
if (dollarsVested[user] >= vesting.usdAmount) {
dollarsVested[user] -= vesting.usdAmount;
}
if (vestedTotal[vesting.token] >= vesting.amount) {
vestedTotal[vesting.token] -= vesting.amount;
}
}
// // Decrement accounting variables before clearing
// if (!vesting.complete) {
// if (dollarsVested[user] >= vesting.usdAmount) {
// dollarsVested[user] -= vesting.usdAmount;
// }
// if (vestedTotal[vesting.token] >= vesting.amount) {
// vestedTotal[vesting.token] -= vesting.amount;
// }
// }
vesting.amount = 0;
vesting.bonus = 0;
vesting.claimedAmount = 0;
vesting.claimedBonus = 0;
vesting.complete = true;
}
}
// vesting.amount = 0;
// vesting.bonus = 0;
// vesting.claimedAmount = 0;
// vesting.claimedBonus = 0;
// vesting.complete = true;
// }
// }
/// @notice Creates a vesting for a given user
/// @dev Only to be used by bots for manual vesting creation
@@ -1025,8 +1035,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: vesting.token
}));
// Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[vesting.token] += amountToClaim;
// Increment withdraw liabilities for this token
withdrawLiabilities[vesting.token] += amountToClaim;
emit VestingClaimed(msg.sender, amountToClaim, 0);
}
@@ -1073,35 +1083,56 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: _token
}));
// Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] += totalReward;
// Increment withdraw liabilities for this token
withdrawLiabilities[_token] += totalReward;
emit VestingClaimed(msg.sender, totalReward, 0);
}
/// @notice Claim unlocked bonus tokens from a specific vesting
/// @param _vestingIndex The index of the vesting to claim bonus from
function claimBonus(uint256 _vestingIndex) external nonReentrant {
require(_vestingIndex < vestings[msg.sender].length, "Invalid vesting index");
/// @notice Claim unlocked bonus tokens from multiple vestings
/// @param _vestingIndices Array of vesting indices to claim bonus from
function claimBonus(uint256[] calldata _vestingIndices) external nonReentrant {
uint256 totalBonusClaimed = 0;
Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
for (uint256 i = 0; i < _vestingIndices.length; i++) {
uint256 _vestingIndex = _vestingIndices[i];
// Skip invalid vesting indices
if (_vestingIndex >= vestings[msg.sender].length) {
continue;
}
Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
require(maxBonus >= vesting.claimedBonus, "Invalid claim amount");
uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
require(bonusToClaim > 0, "Nothing to claim");
// Skip if invalid claim amount or nothing to claim
if (maxBonus < vesting.claimedBonus) {
continue;
}
uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
if (bonusToClaim == 0) {
continue;
}
vesting.claimedBonus += bonusToClaim;
vesting.claimedBonus += bonusToClaim;
totalBonusClaimed += bonusToClaim;
// Create withdrawable stake with unlock delay (add 1e6 to distinguish from normal stakes)
withdrawStakes[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6,
amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay,
token: BSC_TOKEN
}));
emit BonusClaimed(msg.sender, bonusToClaim);
// Create withdrawable stake with unlock delay (add 1e6 to distinguish from normal stakes)
withdrawStakes[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6,
amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay,
token: BSC_TOKEN
}));
}
// Only update liabilities and emit event if something was actually claimed
if (totalBonusClaimed > 0) {
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += totalBonusClaimed;
emit BonusClaimed(msg.sender, totalBonusClaimed);
}
}