2024-02-21 22:20:04 +01:00
// SPDX-License-Identifier: GPL-3.0-or-later
2024-03-28 19:55:01 +01:00
pragma solidity ^ 0 . 8 . 19 ;
2024-02-21 22:20:04 +01:00
2025-10-04 15:17:09 +02:00
import { Kraiken } from " ./Kraiken.sol " ;
import { IERC20 } from " @openzeppelin/token/ERC20/ERC20.sol " ;
import { ERC20Permit } from " @openzeppelin/token/ERC20/extensions/ERC20Permit.sol " ;
import { IERC20Metadata } from " @openzeppelin/token/ERC20/extensions/IERC20Metadata.sol " ;
import { SafeERC20 } from " @openzeppelin/token/ERC20/utils/SafeERC20.sol " ;
import { Math } from " @openzeppelin/utils/math/Math.sol " ;
2024-02-21 22:20:04 +01:00
2024-03-14 17:31:16 +01:00
error ExceededAvailableStake ( address receiver , uint256 stakeWanted , uint256 availableStake ) ;
2024-06-07 11:22:22 +02:00
error TooMuchSnatch ( address receiver , uint256 stakeWanted , uint256 availableStake , uint256 smallestShare ) ;
2024-03-14 17:31:16 +01:00
2024-07-13 18:33:47 +02:00
/**
2025-07-11 13:47:42 +02:00
* @ title Stake Contract for Kraiken Token
* @ notice This contract manages the staking positions for the Kraiken token , allowing users to stake tokens
2024-07-13 18:33:47 +02:00
* in exchange for a share of the total supply . Stakers can set and adjust tax rates on their stakes ,
* which affect the Universal Basic Income ( UBI ) paid from the tax pool .
2025-07-08 10:33:10 +02:00
*
2024-07-13 18:33:47 +02:00
* The contract handles :
* - Creation of staking positions with specific tax rates .
* - Snatching of existing positions under certain conditions to consolidate stakes .
* - Calculation and payment of taxes based on stake duration and tax rate .
* - Adjustment of tax rates with protections against griefing through rapid changes .
* - Exiting of positions , either partially or fully , returning the staked assets to the owner .
2025-07-08 10:33:10 +02:00
*
2024-07-13 18:33:47 +02:00
* Tax rates and staking positions are adjustable , with a mechanism to prevent snatch - grieving by
* enforcing a minimum tax payment duration .
2025-10-04 15:17:09 +02:00
*
2025-08-19 11:05:08 +02:00
* @ dev Self - assessed tax implementation :
2025-08-18 00:16:09 +02:00
* - Continuous auction mechanism
* - Self - assessed valuations create prediction market
* - Tax collection and redistribution through UBI
2024-07-13 18:33:47 +02:00
* /
2024-06-19 10:33:28 +02:00
contract Stake {
2024-03-12 15:29:59 +01:00
using Math for uint256 ;
2024-02-21 22:20:04 +01:00
2024-07-16 19:47:39 +02:00
// the offset between the "precision" of the representation of shares and assets
// see https://docs.openzeppelin.com/contracts/4.x/erc4626 for reason and details
2024-03-12 15:29:59 +01:00
uint256 internal DECIMAL_OFFSET = 5 + 2 ;
2025-07-11 13:47:42 +02:00
// only 20% of the total KRAIKEN supply can be staked.
uint256 internal constant MAX_STAKE = 20 ; // 20% of KRAIKEN supply
2024-02-23 22:01:23 +01:00
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3 ; //this duration is the minimum basis for fee calculation, regardless of actual holding time.
2024-07-16 19:47:39 +02:00
// the tax rates are discrete to prevent users from snatching by micro incroments of tax
2025-10-04 15:17:09 +02:00
uint256 [ ] public TAX_RATES =
[ 1 , 3 , 5 , 8 , 12 , 18 , 24 , 30 , 40 , 50 , 60 , 80 , 100 , 130 , 180 , 250 , 320 , 420 , 540 , 700 , 920 , 1200 , 1600 , 2000 , 2600 , 3400 , 4400 , 5700 , 7500 , 9700 ] ;
2024-07-16 19:47:39 +02:00
// this is the base for the values in the array above: e.g. 1/100 = 1%
uint256 internal constant TAX_RATE_BASE = 100 ;
2024-02-21 22:20:04 +01:00
/**
* @ dev Attempted to deposit more assets than the max amount for ` receiver ` .
* /
2024-03-12 20:22:10 +01:00
2024-02-21 22:20:04 +01:00
error TaxTooLow ( address receiver , uint64 taxRateWanted , uint64 taxRateMet , uint256 positionId ) ;
2024-06-21 15:57:23 +02:00
error StakeTooLow ( address receiver , uint256 assets , uint256 minStake ) ;
2024-02-21 22:20:04 +01:00
error NoPermission ( address requester , address owner ) ;
2024-06-19 10:33:28 +02:00
error PositionNotFound ( uint256 positionId , address requester ) ;
2024-02-21 22:20:04 +01:00
2025-10-04 15:17:09 +02:00
event PositionCreated ( uint256 indexed positionId , address indexed owner , uint256 kraikenDeposit , uint256 share , uint32 taxRate ) ;
event PositionTaxPaid ( uint256 indexed positionId , address indexed owner , uint256 taxPaid , uint256 newShares , uint256 taxRate ) ;
2024-06-23 08:44:54 +02:00
event PositionRateHiked ( uint256 indexed positionId , address indexed owner , uint256 newTaxRate ) ;
2025-07-11 13:47:42 +02:00
event PositionShrunk ( uint256 indexed positionId , address indexed owner , uint256 newShares , uint256 kraikenPayout ) ;
event PositionRemoved ( uint256 indexed positionId , address indexed owner , uint256 kraikenPayout ) ;
2024-03-18 12:42:30 +01:00
2024-02-21 22:20:04 +01:00
struct StakingPosition {
2024-02-23 22:01:23 +01:00
uint256 share ;
2024-02-21 22:20:04 +01:00
address owner ;
2024-02-23 22:01:23 +01:00
uint32 creationTime ;
uint32 lastTaxTime ;
2024-11-07 15:33:40 +00:00
uint32 taxRate ; // index of TAX_RATES array
2024-02-21 22:20:04 +01:00
}
2025-07-11 13:47:42 +02:00
Kraiken private immutable kraiken ;
2025-01-23 13:21:49 +01:00
address private immutable taxReceiver ;
2024-07-17 14:08:53 +02:00
uint256 public immutable totalSupply ;
2024-02-21 22:20:04 +01:00
uint256 public outstandingStake ;
2024-03-18 12:42:30 +01:00
uint256 public nextPositionId ;
2024-07-17 14:08:53 +02:00
2024-03-14 12:40:57 +01:00
mapping ( uint256 => StakingPosition ) public positions ;
2024-02-21 22:20:04 +01:00
2024-11-07 15:33:40 +00:00
// Array to keep track of total shares at each tax rate
uint256 [ ] public totalSharesAtTaxRate ;
2025-07-11 13:47:42 +02:00
/// @notice Initializes the stake contract with references to the Kraiken contract and sets the initial position ID.
/// @param _kraiken Address of the Kraiken contract which this Stake contract interacts with.
/// @dev Sets up the total supply based on the decimals of the Kraiken token plus a fixed offset.
constructor ( address _kraiken , address _taxReceiver ) {
kraiken = Kraiken ( _kraiken ) ;
2025-01-23 13:21:49 +01:00
taxReceiver = _taxReceiver ;
2025-07-11 13:47:42 +02:00
totalSupply = 10 ** ( kraiken . decimals ( ) + DECIMAL_OFFSET ) ;
2024-07-17 14:08:53 +02:00
// start counting somewhere
2025-10-04 15:17:09 +02:00
nextPositionId = 654 _321 ;
2024-11-07 15:33:40 +00:00
// Initialize totalSharesAtTaxRate array
totalSharesAtTaxRate = new uint256 [ ] ( TAX_RATES . length ) ;
2024-02-21 22:20:04 +01:00
}
2024-03-12 20:22:10 +01:00
function authorizedStake ( ) private view returns ( uint256 ) {
2024-03-12 11:38:16 +01:00
return totalSupply * MAX_STAKE / 100 ;
}
2024-07-16 20:59:42 +02:00
/// @dev Internal function to calculate and pay taxes for a position, adjusting shares and handling position liquidation if necessary.
function _payTax ( uint256 positionId , StakingPosition storage pos , uint256 taxFloorDuration ) private {
2024-09-17 16:16:41 +02:00
// existance of position should be checked before
2024-07-16 20:59:42 +02:00
// ihet = Implied Holding Expiry Timestamp
2025-10-04 15:17:09 +02:00
uint256 ihet = ( block . timestamp - pos . creationTime < taxFloorDuration ) ? pos . creationTime + taxFloorDuration : block . timestamp ;
2024-07-16 20:59:42 +02:00
uint256 elapsedTime = ihet - pos . lastTaxTime ;
uint256 assetsBefore = sharesToAssets ( pos . share ) ;
2025-10-04 15:17:09 +02:00
uint256 taxAmountDue = assetsBefore * TAX_RATES [ pos . taxRate ] * elapsedTime / ( 365 * 24 * 60 * 60 ) / TAX_RATE_BASE ;
2024-07-16 20:59:42 +02:00
if ( taxAmountDue >= assetsBefore ) {
// can not pay more tax than value of position
taxAmountDue = assetsBefore ;
}
if ( assetsBefore - taxAmountDue > 0 ) {
// if something left over, update storage
uint256 shareAfterTax = assetsToShares ( assetsBefore - taxAmountDue ) ;
2024-11-07 15:33:40 +00:00
uint256 deltaShare = pos . share - shareAfterTax ;
totalSharesAtTaxRate [ pos . taxRate ] -= deltaShare ;
outstandingStake -= deltaShare ;
2024-07-16 20:59:42 +02:00
pos . share = shareAfterTax ;
pos . lastTaxTime = uint32 ( block . timestamp ) ;
emit PositionTaxPaid ( positionId , pos . owner , taxAmountDue , shareAfterTax , pos . taxRate ) ;
} else {
// if nothing left over, liquidate position
2024-11-07 15:33:40 +00:00
totalSharesAtTaxRate [ pos . taxRate ] -= pos . share ;
2024-07-16 20:59:42 +02:00
outstandingStake -= pos . share ;
emit PositionTaxPaid ( positionId , pos . owner , taxAmountDue , 0 , pos . taxRate ) ;
emit PositionRemoved ( positionId , pos . owner , 0 ) ;
delete pos . owner ;
delete pos . creationTime ;
delete pos . share ;
}
2025-07-11 13:47:42 +02:00
SafeERC20 . safeTransfer ( kraiken , taxReceiver , taxAmountDue ) ;
2024-07-16 20:59:42 +02:00
}
2025-08-19 11:05:08 +02:00
/// @dev Internal function to close a staking position, transferring the remaining Kraiken tokens back to the owner after tax payment.
2024-07-16 20:59:42 +02:00
function _exitPosition ( uint256 positionId , StakingPosition storage pos ) private {
2024-11-07 15:33:40 +00:00
totalSharesAtTaxRate [ pos . taxRate ] -= pos . share ;
2024-07-16 20:59:42 +02:00
outstandingStake -= pos . share ;
address owner = pos . owner ;
uint256 assets = sharesToAssets ( pos . share ) ;
emit PositionRemoved ( positionId , owner , assets ) ;
delete pos . owner ;
delete pos . creationTime ;
delete pos . share ;
2025-07-11 13:47:42 +02:00
SafeERC20 . safeTransfer ( kraiken , owner , assets ) ;
2024-07-16 20:59:42 +02:00
}
2025-08-19 11:05:08 +02:00
/// @dev Internal function to reduce the size of a staking position by a specified number of shares, transferring the corresponding Kraiken tokens to the owner.
2024-07-16 20:59:42 +02:00
function _shrinkPosition ( uint256 positionId , StakingPosition storage pos , uint256 sharesToTake ) private {
2025-07-08 10:33:10 +02:00
require ( sharesToTake < pos . share , " position too small " ) ;
2024-07-16 20:59:42 +02:00
uint256 assets = sharesToAssets ( sharesToTake ) ;
pos . share -= sharesToTake ;
2024-11-07 15:33:40 +00:00
totalSharesAtTaxRate [ pos . taxRate ] -= sharesToTake ;
2024-07-16 20:59:42 +02:00
outstandingStake -= sharesToTake ;
emit PositionShrunk ( positionId , pos . owner , pos . share , assets ) ;
2025-07-11 13:47:42 +02:00
SafeERC20 . safeTransfer ( kraiken , pos . owner , assets ) ;
2024-07-16 20:59:42 +02:00
}
2025-08-19 11:05:08 +02:00
/// @notice Converts Kraiken token assets to shares of the total staking pool.
/// @param assets Number of Kraiken tokens to convert.
/// @return Number of shares corresponding to the input assets based on the current total supply of Kraiken tokens.
2024-06-19 10:33:28 +02:00
function assetsToShares ( uint256 assets ) public view returns ( uint256 ) {
2025-07-11 13:47:42 +02:00
return assets . mulDiv ( totalSupply , kraiken . totalSupply ( ) , Math . Rounding . Down ) ;
2024-02-21 22:20:04 +01:00
}
2025-08-19 11:05:08 +02:00
/// @notice Converts shares of the total staking pool back to Kraiken token assets.
2024-07-13 18:33:47 +02:00
/// @param shares Number of shares to convert.
2025-08-19 11:05:08 +02:00
/// @return The equivalent number of Kraiken tokens for the given shares.
2024-06-19 10:33:28 +02:00
function sharesToAssets ( uint256 shares ) public view returns ( uint256 ) {
2025-07-11 13:47:42 +02:00
return shares . mulDiv ( kraiken . totalSupply ( ) , totalSupply , Math . Rounding . Down ) ;
2024-02-21 22:20:04 +01:00
}
2024-07-13 18:33:47 +02:00
/// @notice Creates a new staking position by potentially snatching shares from existing positions.
2025-08-19 11:05:08 +02:00
/// @param assets Amount of Kraiken tokens to convert into a staking position.
2024-07-13 18:33:47 +02:00
/// @param receiver Address that will own the new staking position.
/// @param taxRate The initial tax rate for the new staking position.
/// @param positionsToSnatch Array of position IDs that the new position will replace by snatching.
/// @return positionId The ID of the newly created staking position.
/// @dev Handles staking logic, including tax rate validation and position merging or dissolving.
2025-10-04 15:17:09 +02:00
function snatch ( uint256 assets , address receiver , uint32 taxRate , uint256 [ ] calldata positionsToSnatch ) public returns ( uint256 positionId ) {
2024-02-21 22:20:04 +01:00
// check lower boundary
2024-06-19 10:33:28 +02:00
uint256 sharesWanted = assetsToShares ( assets ) ;
2024-06-13 10:50:09 +02:00
{
2024-06-19 10:33:28 +02:00
// check that position size is at least minStake
2024-06-13 10:50:09 +02:00
// to prevent excessive fragmentation, increasing snatch cost
2025-07-11 13:47:42 +02:00
uint256 minStake = kraiken . minStake ( ) ;
2024-06-21 15:57:23 +02:00
if ( assets < minStake ) {
revert StakeTooLow ( receiver , assets , minStake ) ;
2024-06-13 10:50:09 +02:00
}
2024-02-21 22:20:04 +01:00
}
2024-06-13 10:50:09 +02:00
require ( taxRate < TAX_RATES . length , " tax rate out of bounds " ) ;
2024-02-21 22:20:04 +01:00
2024-06-07 11:22:22 +02:00
uint256 smallestPositionShare = totalSupply ;
2024-06-07 12:33:20 +02:00
uint256 availableStake = authorizedStake ( ) - outstandingStake ;
2024-06-07 11:22:22 +02:00
2024-06-13 08:28:42 +02:00
if ( positionsToSnatch . length >= 2 ) {
// run through all but last positions to snatch
2024-06-07 12:33:20 +02:00
for ( uint256 i = 0 ; i < positionsToSnatch . length - 1 ; i ++ ) {
StakingPosition storage pos = positions [ positionsToSnatch [ i ] ] ;
if ( pos . creationTime == 0 ) {
2024-06-19 10:33:28 +02:00
revert PositionNotFound ( positionsToSnatch [ i ] , receiver ) ;
2024-06-07 12:33:20 +02:00
}
// check that tax lower
if ( taxRate <= pos . taxRate ) {
2024-06-21 15:57:23 +02:00
revert TaxTooLow ( receiver , taxRate , pos . taxRate , positionsToSnatch [ i ] ) ;
2024-06-07 12:33:20 +02:00
}
if ( pos . share < smallestPositionShare ) {
smallestPositionShare = pos . share ;
}
// dissolve position
_payTax ( positionsToSnatch [ i ] , pos , 0 ) ;
_exitPosition ( positionsToSnatch [ i ] , pos ) ;
2024-06-07 11:22:22 +02:00
}
2024-06-13 08:28:42 +02:00
}
availableStake = authorizedStake ( ) - outstandingStake ;
2024-03-12 20:22:10 +01:00
2024-06-13 08:28:42 +02:00
if ( positionsToSnatch . length > 0 ) {
// handle last position, either shrink or snatch
2024-06-07 11:22:22 +02:00
uint256 index = positionsToSnatch . length - 1 ;
2024-06-09 16:06:41 +02:00
StakingPosition storage lastPos = positions [ positionsToSnatch [ index ] ] ;
if ( lastPos . creationTime == 0 ) {
2024-06-19 10:33:28 +02:00
revert PositionNotFound ( positionsToSnatch [ index ] , receiver ) ;
2024-06-07 11:22:22 +02:00
}
// check that tax lower
2024-06-09 16:06:41 +02:00
if ( taxRate <= lastPos . taxRate ) {
2024-06-21 15:57:23 +02:00
revert TaxTooLow ( receiver , taxRate , lastPos . taxRate , positionsToSnatch [ index ] ) ;
2024-06-07 11:22:22 +02:00
}
2024-06-09 16:06:41 +02:00
if ( lastPos . share < smallestPositionShare ) {
smallestPositionShare = lastPos . share ;
2024-06-07 11:22:22 +02:00
}
// dissolve position
2024-09-17 16:16:41 +02:00
_payTax ( positionsToSnatch [ index ] , lastPos , TAX_FLOOR_DURATION ) ;
2024-06-13 08:28:42 +02:00
if ( availableStake > sharesWanted ) {
revert TooMuchSnatch ( receiver , sharesWanted , availableStake , smallestPositionShare ) ;
}
uint256 lastSharesNeeded = sharesWanted - availableStake ;
if ( lastSharesNeeded > lastPos . share * 80 / 100 ) {
_exitPosition ( positionsToSnatch [ index ] , lastPos ) ;
} else {
_shrinkPosition ( positionsToSnatch [ index ] , lastPos , lastSharesNeeded ) ;
}
2024-06-07 11:22:22 +02:00
}
2024-06-13 08:28:42 +02:00
availableStake = authorizedStake ( ) - outstandingStake ;
2024-06-07 11:22:22 +02:00
2024-02-21 22:20:04 +01:00
if ( sharesWanted > availableStake ) {
revert ExceededAvailableStake ( receiver , sharesWanted , availableStake ) ;
}
2024-06-07 11:22:22 +02:00
// avoid greeving where more positions are freed than needed.
if ( availableStake - sharesWanted > smallestPositionShare ) {
revert TooMuchSnatch ( receiver , sharesWanted , availableStake , smallestPositionShare ) ;
}
2024-02-21 22:20:04 +01:00
// transfer
2025-07-11 13:47:42 +02:00
SafeERC20 . safeTransferFrom ( kraiken , msg . sender , address ( this ) , assets ) ;
2024-02-21 22:20:04 +01:00
// mint
2024-03-18 12:42:30 +01:00
positionId = nextPositionId ++ ;
StakingPosition storage sp = positions [ positionId ] ;
2024-03-12 11:38:16 +01:00
sp . share = sharesWanted ;
2024-02-21 22:20:04 +01:00
sp . owner = receiver ;
2024-03-12 12:27:47 +01:00
sp . lastTaxTime = uint32 ( block . timestamp ) ;
sp . creationTime = uint32 ( block . timestamp ) ;
sp . taxRate = taxRate ;
2024-02-21 22:20:04 +01:00
2024-11-07 15:33:40 +00:00
totalSharesAtTaxRate [ taxRate ] += sharesWanted ;
2024-02-21 22:20:04 +01:00
outstandingStake += sharesWanted ;
2024-06-23 08:44:54 +02:00
emit PositionCreated ( positionId , sp . owner , assets , sp . share , sp . taxRate ) ;
2024-02-21 22:20:04 +01:00
}
2024-07-16 20:59:42 +02:00
/// @notice Combines an ERC20 permit operation with the snatch function, allowing a staking position creation in one transaction.
2025-08-19 11:05:08 +02:00
/// @param assets Number of Kraiken tokens to stake.
2024-07-16 20:59:42 +02:00
/// @param receiver Address that will own the new staking position.
/// @param taxRate The initial tax rate for the new staking position.
/// @param positionsToSnatch Array of position IDs that the new position will replace by snatching.
/// @param deadline Time until which the permit is valid.
/// @param v, r, s Components of the signature for the permit.
/// @return positionId The ID of the newly created staking position.
function permitAndSnatch (
uint256 assets ,
address receiver ,
uint32 taxRate ,
uint256 [ ] calldata positionsToSnatch ,
// address owner,
// address spender,
// uint256 value,
uint256 deadline ,
uint8 v ,
bytes32 r ,
bytes32 s
2025-10-04 15:17:09 +02:00
)
external
returns ( uint256 positionId )
{
2025-07-11 13:47:42 +02:00
ERC20Permit ( address ( kraiken ) ) . permit ( receiver , address ( this ) , assets , deadline , v , r , s ) ;
2024-07-16 20:59:42 +02:00
return snatch ( assets , receiver , taxRate , positionsToSnatch ) ;
}
2024-07-13 18:33:47 +02:00
/// @notice Changes the tax rate of an existing staking position.
/// @param positionId The ID of the staking position to update.
/// @param taxRate The new tax rate to apply to the position.
/// @dev Ensures that the tax rate change is valid and applies the minimum tax based on the TAX_FLOOR_DURATION.
2024-07-16 20:59:42 +02:00
function changeTax ( uint256 positionId , uint32 taxRate ) external {
2024-06-13 10:50:09 +02:00
require ( taxRate < TAX_RATES . length , " tax rate out of bounds " ) ;
2024-06-19 10:33:28 +02:00
StakingPosition storage pos = positions [ positionId ] ;
if ( pos . creationTime == 0 ) {
revert PositionNotFound ( positionId , msg . sender ) ;
}
2024-04-15 07:08:13 +02:00
if ( pos . owner != msg . sender ) {
revert NoPermission ( msg . sender , pos . owner ) ;
}
// to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION
2024-06-13 10:50:09 +02:00
require ( taxRate > pos . taxRate , " tax too low to snatch " ) ;
2024-09-17 16:16:41 +02:00
_payTax ( positionId , pos , 0 ) ;
2024-11-07 15:33:40 +00:00
totalSharesAtTaxRate [ pos . taxRate ] -= pos . share ;
totalSharesAtTaxRate [ taxRate ] += pos . share ;
2024-04-15 07:08:13 +02:00
pos . taxRate = taxRate ;
2024-07-13 14:56:13 +02:00
emit PositionRateHiked ( positionId , pos . owner , taxRate ) ;
2024-04-15 07:08:13 +02:00
}
2024-07-13 18:33:47 +02:00
/// @notice Allows the owner of a staking position to exit, returning the staked assets.
/// @param positionId The ID of the staking position to exit.
/// @dev Pays the due taxes based on the TAX_FLOOR_DURATION and returns the remaining assets to the position owner.
2024-07-16 20:59:42 +02:00
function exitPosition ( uint256 positionId ) external {
2024-03-18 12:42:30 +01:00
StakingPosition storage pos = positions [ positionId ] ;
2024-09-17 16:16:41 +02:00
if ( pos . creationTime == 0 ) {
revert PositionNotFound ( positionId , msg . sender ) ;
}
2026-02-27 06:33:32 +00:00
if ( pos . owner != msg . sender ) {
revert NoPermission ( msg . sender , pos . owner ) ;
}
2024-03-12 11:38:16 +01:00
// to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION
2024-04-15 07:08:13 +02:00
_payTax ( positionId , pos , TAX_FLOOR_DURATION ) ;
2024-03-18 12:42:30 +01:00
_exitPosition ( positionId , pos ) ;
2024-02-21 22:20:04 +01:00
}
2024-03-12 20:22:10 +01:00
2024-07-13 18:33:47 +02:00
/// @notice Manually triggers the tax payment for a specified staking position.
/// @param positionId The ID of the staking position for which to pay taxes.
/// @dev Calculates and pays the tax due, possibly adjusting the position's share count.
2024-07-16 20:59:42 +02:00
function payTax ( uint256 positionId ) external {
2024-06-19 10:33:28 +02:00
StakingPosition storage pos = positions [ positionId ] ;
2024-09-17 16:16:41 +02:00
if ( pos . creationTime == 0 ) {
revert PositionNotFound ( positionId , msg . sender ) ;
}
2024-06-19 10:33:28 +02:00
_payTax ( positionId , pos , 0 ) ;
2024-02-21 22:20:04 +01:00
}
2024-07-13 18:33:47 +02:00
/// @notice Calculates the Tax that is due to be paid on specific positoin
/// @param positionId The ID of the staking position for which to pay taxes.
/// @param taxFloorDuration if a minimum holding duration is applied to the position this value is > 0 in seconds.
/// @dev Calculates the tax due.
2024-06-19 10:33:28 +02:00
function taxDue ( uint256 positionId , uint256 taxFloorDuration ) public view returns ( uint256 amountDue ) {
StakingPosition storage pos = positions [ positionId ] ;
2024-03-12 15:29:59 +01:00
// ihet = Implied Holding Expiry Timestamp
2025-10-04 15:17:09 +02:00
uint256 ihet = ( block . timestamp - pos . creationTime < taxFloorDuration ) ? pos . creationTime + taxFloorDuration : block . timestamp ;
2024-03-12 15:29:59 +01:00
uint256 elapsedTime = ihet - pos . lastTaxTime ;
2024-06-19 10:33:28 +02:00
uint256 assetsBefore = sharesToAssets ( pos . share ) ;
2024-06-09 16:06:41 +02:00
amountDue = assetsBefore * TAX_RATES [ pos . taxRate ] * elapsedTime / ( 365 * 24 * 60 * 60 ) / TAX_RATE_BASE ;
2024-03-12 15:29:59 +01:00
}
2024-07-17 14:08:53 +02:00
2024-11-07 15:33:40 +00:00
/// @return averageTaxRate A number between 0 and 1e18 indicating the average tax rate.
function getAverageTaxRate ( ) external view returns ( uint256 averageTaxRate ) {
// Compute average tax rate weighted by shares
averageTaxRate = 0 ;
2025-07-08 10:33:10 +02:00
if ( outstandingStake > 0 ) {
for ( uint256 i = 0 ; i < TAX_RATES . length ; i ++ ) {
averageTaxRate += TAX_RATES [ i ] * totalSharesAtTaxRate [ i ] ;
}
averageTaxRate = averageTaxRate / outstandingStake ;
// normalize tax rate
averageTaxRate = averageTaxRate * 1 e18 / TAX_RATES [ TAX_RATES . length - 1 ] ;
}
2024-11-07 15:33:40 +00:00
}
2025-08-19 11:05:08 +02:00
/// @notice Computes the percentage of Kraiken staked from outstanding Stake and authorized Stake.
/// @return percentageStaked A number between 0 and 1e18 indicating the percentage of Kraiken supply staked.
2024-11-07 15:33:40 +00:00
function getPercentageStaked ( ) external view returns ( uint256 percentageStaked ) {
percentageStaked = ( outstandingStake * 1 e18 ) / authorizedStake ( ) ;
}
2024-02-21 22:20:04 +01:00
}