Buyout SellStakes

This commit is contained in:
2025-10-22 02:45:03 +02:00
parent 8178311084
commit 7ce2927464
3 changed files with 206 additions and 78 deletions

File diff suppressed because one or more lines are too long

View File

@@ -115,6 +115,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
MarketplaceHistory[] public marketplaceHistory; // Complete history of all transactions MarketplaceHistory[] public marketplaceHistory; // Complete history of all transactions
mapping(address => uint256) public totalClaimed; // Track total amount claimed and sent to withdrawStakes per user mapping(address => uint256) public totalClaimed; // Track total amount claimed and sent to withdrawStakes per user
uint256 public highestRatio; // Track the highest TVL/liability ratio ever achieved (scaled by 10000) uint256 public highestRatio; // Track the highest TVL/liability ratio ever achieved (scaled by 10000)
mapping(address => mapping(uint256 => uint256)) public contractOwnedSellStakes; // Track contract-owned sellStakes linked to user withdrawStake IDs
uint256 public sellStakePremiumPercent; // Premium percentage for contract-owned sellStakes (e.g., 1000 = 10%)
// Events // Events
event VestingClaimed(address indexed user, uint256 amount, uint256 bonus); event VestingClaimed(address indexed user, uint256 amount, uint256 bonus);
@@ -163,6 +165,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
unlockDelay = 60 * 60 * 72; unlockDelay = 60 * 60 * 72;
maxUnlockPercentage = 100; // 1% maximum unlock per epoch maxUnlockPercentage = 100; // 1% maximum unlock per epoch
sellStakePremiumPercent = 1000; // 10% premium for contract-owned sellStakes
} }
// Ownership Management // Ownership Management
@@ -212,40 +215,40 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
priceOracles[_token] = _oracle; priceOracles[_token] = _oracle;
} }
/// @notice Set unlock schedule for a token using percentage-based steps // /// @notice Set unlock schedule for a token using percentage-based steps
/// @param _token The token address to set the schedule for // /// @param _token The token address to set the schedule for
/// @param _lockTime The initial lock time in seconds // /// @param _lockTime The initial lock time in seconds
/// @param _percentagePerStep The percentage to unlock at each step (scaled by 10000) // /// @param _percentagePerStep The percentage to unlock at each step (scaled by 10000)
function setUnlockScheduleByPercentage(address _token, uint256 _lockTime, uint256 _percentagePerStep) external onlyOwner { // function setUnlockScheduleByPercentage(address _token, uint256 _lockTime, uint256 _percentagePerStep) external onlyOwner {
require(_token != address(0), "Invalid token address"); // require(_token != address(0), "Invalid token address");
require(_percentagePerStep > 0 && _percentagePerStep <= 10000, "Invalid percentage"); // require(_percentagePerStep > 0 && _percentagePerStep <= 10000, "Invalid percentage");
// Clear existing schedule // // Clear existing schedule
delete unlockSchedules[_token]; // delete unlockSchedules[_token];
uint256 totalPercentage = 0; // uint256 totalPercentage = 0;
uint256 timeOffset = _lockTime; // uint256 timeOffset = _lockTime;
// Create unlock steps until we reach 100% // // Create unlock steps until we reach 100%
while (totalPercentage < 10000) { // while (totalPercentage < 10000) {
uint256 stepPercentage = _percentagePerStep; // uint256 stepPercentage = _percentagePerStep;
// Adjust last step to exactly reach 100% // // Adjust last step to exactly reach 100%
if (totalPercentage + stepPercentage > 10000) { // if (totalPercentage + stepPercentage > 10000) {
stepPercentage = 10000 - totalPercentage; // stepPercentage = 10000 - totalPercentage;
} // }
unlockSchedules[_token].push(UnlockStep({ // unlockSchedules[_token].push(UnlockStep({
timeOffset: timeOffset, // timeOffset: timeOffset,
percentage: stepPercentage // percentage: stepPercentage
})); // }));
totalPercentage += stepPercentage; // totalPercentage += stepPercentage;
timeOffset += _lockTime; // Each step adds the same time interval // timeOffset += _lockTime; // Each step adds the same time interval
} // }
emit UnlockScheduleSet(_token); // emit UnlockScheduleSet(_token);
} // }
// /// @notice Set custom unlock schedule for a token with specific steps // /// @notice Set custom unlock schedule for a token with specific steps
// /// @param _token The token address to set the schedule for // /// @param _token The token address to set the schedule for
@@ -297,6 +300,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
instantBuyoutPercent = _newPercent; instantBuyoutPercent = _newPercent;
} }
/// @notice Update contract-owned sellStake premium percentage
/// @param _newPremium The premium percentage (scaled by 10000), ex: 1000 = 10%
function updateSellStakePremiumPercent(uint256 _newPremium) external onlyOwner {
require(_newPremium <= 5000, "Premium cannot exceed 50%");
sellStakePremiumPercent = _newPremium;
}
// Epoch-based Staking Functions // Epoch-based Staking Functions
/// @notice Internal function to calculate unlock percentage based on ratio improvement vs historical high /// @notice Internal function to calculate unlock percentage based on ratio improvement vs historical high
@@ -441,6 +451,33 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
if (stake.stakeId == stakeId && stake.amount > 0) { if (stake.stakeId == stakeId && stake.amount > 0) {
require(block.timestamp >= stake.unlockTime, "Stake locked"); require(block.timestamp >= stake.unlockTime, "Stake locked");
// Check if there's a contract-owned sellStake linked to this withdrawStake
uint256 contractSellStakeId = contractOwnedSellStakes[msg.sender][stakeId];
if (contractSellStakeId > 0) {
SellStake storage contractSellStake = sellStakes[address(this)][contractSellStakeId];
if (contractSellStake.value > 0) {
uint256 sellValue = contractSellStake.value;
// Cancel the contract-owned sellStake (no fee)
pendingSellStakes[address(this)] -= sellValue;
delete sellStakes[address(this)][contractSellStakeId];
// Remove from keys array using swap-and-pop
uint256 keyIndex = sellStakeKeyIndex[address(this)][contractSellStakeId];
uint256 lastKeyIndex = sellStakeKeys.length - 1;
if (keyIndex != lastKeyIndex) {
SellStakeKey memory lastKey = sellStakeKeys[lastKeyIndex];
sellStakeKeys[keyIndex] = lastKey;
sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = keyIndex;
}
sellStakeKeys.pop();
delete sellStakeKeyIndex[address(this)][contractSellStakeId];
emit StakeSaleCancelled(address(this), contractSellStakeId);
}
delete contractOwnedSellStakes[msg.sender][stakeId];
}
uint256 amount = stake.amount; uint256 amount = stake.amount;
address token = stake.token; address token = stake.token;
stake.amount = 0; // Mark as withdrawn stake.amount = 0; // Mark as withdrawn
@@ -485,8 +522,9 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
// Create withdrawable stake with unlock delay (like claimUnlockedFunds) // Create withdrawable stake with unlock delay (like claimUnlockedFunds)
stakeIdCounter++; stakeIdCounter++;
uint256 withdrawStakeId = stakeIdCounter;
withdrawStakes[msg.sender].push(WithdrawStake({ withdrawStakes[msg.sender].push(WithdrawStake({
stakeId: stakeIdCounter, stakeId: withdrawStakeId,
amount: payoutAmount, amount: payoutAmount,
unlockTime: block.timestamp + unlockDelay, unlockTime: block.timestamp + unlockDelay,
token: BSC_TOKEN token: BSC_TOKEN
@@ -495,6 +533,29 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
// Increment withdraw liabilities for BSC_TOKEN // Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += payoutAmount; withdrawLiabilities[BSC_TOKEN] += payoutAmount;
// Create contract-owned sellStake at premium price
uint256 sellStakePrice = (payoutAmount * (10000 + sellStakePremiumPercent)) / 10000;
stakeIdCounter++;
uint256 sellStakeId = stakeIdCounter;
sellStakes[address(this)][sellStakeId] = SellStake({
value: amount, // Original amount - what buyer receives in stake
salePrice: sellStakePrice, // Premium price buyer pays
seller: address(this),
listTime: block.timestamp
});
pendingSellStakes[address(this)] += amount;
contractOwnedSellStakes[msg.sender][withdrawStakeId] = sellStakeId; // Link withdrawStake ID to sellStake ID
// Add to iteration keys
sellStakeKeys.push(SellStakeKey({
seller: address(this),
stakeId: sellStakeId
}));
sellStakeKeyIndex[address(this)][sellStakeId] = sellStakeKeys.length - 1;
emit StakeUpForSale(address(this), sellStakePrice, sellStakeId);
emit FundsClaimed(msg.sender, payoutAmount); emit FundsClaimed(msg.sender, payoutAmount);
} }
@@ -822,10 +883,17 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
userLastClaimedEpoch[msg.sender] = currentEpochId; userLastClaimedEpoch[msg.sender] = currentEpochId;
} }
// Transfer stakes: remove from seller, add to buyer (minus protocol share) // Transfer stakes: handle contract seller differently
userBigStake[seller] -= value; if (seller == address(this)) {
userBigStake[msg.sender] += buyerStake; // Contract is seller - only add to buyer, don't deduct from contract
pendingSellStakes[seller] -= value; userBigStake[msg.sender] += buyerStake;
pendingSellStakes[address(this)] -= value;
} else {
// Normal seller flow - remove from seller, add to buyer (minus protocol share)
userBigStake[seller] -= value;
userBigStake[msg.sender] += buyerStake;
pendingSellStakes[seller] -= value;
}
// Increment buyer's original stake tracking (marketplace purchases count as original stake) // Increment buyer's original stake tracking (marketplace purchases count as original stake)
userOriginalStake[msg.sender] += buyerStake; userOriginalStake[msg.sender] += buyerStake;
@@ -1383,7 +1451,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
/// @notice Test function for upgrade verification /// @notice Test function for upgrade verification
/// @return Returns a constant value to verify upgrade worked /// @return Returns a constant value to verify upgrade worked
function testUpgradeFunction() external pure returns (uint256) { function testUpgradeFunction() external pure returns (uint256) {
return 1001; // Different value from bsc_paca to distinguish contracts return 1003; // Different value from bsc_paca to distinguish contracts
} }
} }

View File

@@ -33,42 +33,75 @@ module.exports = {
url: "http://127.0.0.1:8545", url: "http://127.0.0.1:8545",
}, },
bscTestnet: { bscTestnet: {
url: "https://virtual.binance.eu.rpc.tenderly.co/77a9a30c-44ca-45e0-ae2a-9b5ddb41d20e", url: "https://virtual.binance.eu.rpc.tenderly.co/fbd4f88f-4e4c-4894-a197-2272e3b3d197",
chainId: 1337, chainId: 1337,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [], accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
}, },
anvil: { anvil: {
url: "https://rpc.tacopancake.com", url: "https://virtual.binance.eu.rpc.tenderly.co/7c672a28-191d-4bb0-9af8-5ae1e4464c5d",
chainId: 1337, chainId: 1337,
accounts: [ accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // Default Anvil account 0 // "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // Default Anvil account 0
], ],
allowUnlimitedContractSize: true,
}, },
buildbear: { buildbear: {
url: "https://rpc.buildbear.io/stale-bucky-0f42044d", url: "https://rpc.buildbear.io/interim-warlock-36006e5c",
chainId: 31337, chainId: 31337,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [], accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
}, },
basefork: {
url: "https://rpc.buildbear.io/yodelling-wong-30e77094",
chainId: 31337,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
},
mainnet: {
url: env.MAINNET_RPC_URL || "https://bsc-dataseed1.binance.org",
chainId: 56,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
},
base: {
url: env.BASE_RPC_URL || "https://base-mainnet.public.blastapi.io",
chainId: 8453,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
},
sonic: {
url: env.SONIC_RPC_URL || "https://rpc.soniclabs.com",
chainId: 146,
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
},
}, },
etherscan: { etherscan: {
apiKey: { apiKey: "JRKR5T93RQTKVERUJ2NVE4T994TD66QGFH",
bsc: env.BSCSCAN_API_KEY || "",
bscTestnet: env.BSCSCAN_API_KEY || "",
buildbear: "abc", // API key not needed for BuildBear
},
customChains: [ customChains: [
{ {
network: "buildbear", network: "buildbear",
chainId: 31337, chainId: 31337,
urls: { urls: {
apiURL: "https://rpc.buildbear.io/verify/etherscan/stale-bucky-0f42044d", apiURL: "https://rpc.buildbear.io/verify/etherscan/missing-darkphoenix-ad09c709",
browserURL: "https://explorer.buildbear.io/stale-bucky-0f42044d" browserURL: "https://explorer.buildbear.io/missing-darkphoenix-ad09c709"
}
},
{
network: "base",
chainId: 8453,
urls: {
apiURL: "https://api.basescan.org/api",
browserURL: "https://basescan.org"
}
},
{
network: "sonic",
chainId: 146,
urls: {
apiURL: "https://api.sonicscan.org/api",
browserURL: "https://sonicscan.org"
} }
} }
] ]
}, },
sourcify: { sourcify: {
enabled: true, enabled: false,
apiUrl: "https://rpc.buildbear.io/verify/sourcify/server/stale-bucky-0f42044d" apiUrl: "https://rpc.buildbear.io/verify/sourcify/server/missing-darkphoenix-ad09c709"
}, },
}; };