Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved staking (new staking - part 2 - SWIP-20) #264

Merged
merged 41 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1f31392
rename to commitedStake
0xCardinalError May 30, 2024
bd828e6
add potential stake and commited stake and update their amounts with …
0xCardinalError May 30, 2024
75280ae
update from latest staking
0xCardinalError Jun 10, 2024
c16f56c
add potential and commited stakes
0xCardinalError Jun 10, 2024
40e6d0e
change to potentialStake
0xCardinalError Jun 10, 2024
cbcac28
add effectiveStake as calculation
0xCardinalError Jun 10, 2024
63d4081
remove minimum from redis and add it to staking
0xCardinalError Jun 12, 2024
dccbe4d
use just 2 params
0xCardinalError Jun 13, 2024
658d9c0
substract commitedStake amount accordingly to currentPrice
0xCardinalError Jun 13, 2024
ae07e41
make new deployment procedure with new staking contracts
0xCardinalError Jun 24, 2024
fd1cd1a
add check if node has entered staking game
0xCardinalError Jun 24, 2024
aa1d34e
fix redistribution tests to match new staking calculations
0xCardinalError Jun 24, 2024
f90d7b7
add migration code, fix staking code
0xCardinalError Jun 24, 2024
f1e9bbc
remove logs
0xCardinalError Jun 24, 2024
03b5d86
add migration tests that checks values
0xCardinalError Jul 2, 2024
2dfab4e
fix error in value changes, make withdrawl test
0xCardinalError Jul 2, 2024
f838e56
add few more tests
0xCardinalError Jul 5, 2024
e0ce4cd
add extra case in test, for minimal first deposit
0xCardinalError Jul 5, 2024
a50e039
fix wrong spelling of committed in many places
0xCardinalError Jul 8, 2024
7209d9d
group variable
0xCardinalError Jul 8, 2024
5ed3b21
remove obsolete code in calc
0xCardinalError Jul 8, 2024
f399af6
remove obsolete variable, add comment about it
0xCardinalError Jul 8, 2024
094c116
better comments
0xCardinalError Jul 8, 2024
b8fb6bb
remove not needed struct access
0xCardinalError Jul 8, 2024
c749d83
change naming
0xCardinalError Jul 9, 2024
5998c9c
add withdrawable stake read function
0xCardinalError Jul 9, 2024
ed004d2
change usable stake to effective stake
0xCardinalError Jul 11, 2024
0b6d0a7
fix stat test with effective stake
0xCardinalError Jul 12, 2024
8aad41a
add test for multiple withdrawls
0xCardinalError Jul 12, 2024
dae4e65
remove wrong line
0xCardinalError Jul 12, 2024
01ea1f1
add check that value is the same
0xCardinalError Jul 12, 2024
1b64dba
add more tests and conditions
0xCardinalError Jul 12, 2024
9e3e1dd
optmize gas
0xCardinalError Jul 16, 2024
ded87e6
another optimization for gas with local var
0xCardinalError Jul 16, 2024
3438f2d
fix wrong var
0xCardinalError Jul 16, 2024
974b71f
Merge branch 'new_staking' of https://github.com/ethersphere/storage-…
0xCardinalError Jul 16, 2024
b06a313
deploy to tesnet light and fix deployment constructor params
0xCardinalError Jul 16, 2024
849937d
remove non exsisting call
0xCardinalError Jul 22, 2024
4fb1f33
testnet deployed
0xCardinalError Aug 22, 2024
6021b5c
fix: Remove pauser role (#263)
0xCardinalError Aug 26, 2024
228946c
fix: remove isValue as it is not needed (#260)
0xCardinalError Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deploy/local/003_deploy_staking.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { DeployFunction } from 'hardhat-deploy/types';
import { networkConfig } from '../../helper-hardhat-config';

const func: DeployFunction = async function ({ deployments, getNamedAccounts, network, ethers }) {

Check warning on line 4 in deploy/local/003_deploy_staking.ts

View workflow job for this annotation

GitHub Actions / check

'ethers' is defined but never used
const { deploy, get, log } = deployments;
const { deployer } = await getNamedAccounts();
const swarmNetworkID = networkConfig[network.name]?.swarmNetworkId;

const token = await get('TestToken');
const oracleAddress = (await get('PriceOracle')).address;

const args = [token.address, swarmNetworkID];
const args = [token.address, swarmNetworkID, oracleAddress];
await deploy('StakeRegistry', {
from: deployer,
args: args,
Expand Down
14 changes: 8 additions & 6 deletions deploy/main/003_deploy_staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const func: DeployFunction = async function ({ deployments, getNamedAccounts, ne
const { deployer } = await getNamedAccounts();
const swarmNetworkID = networkConfig[network.name]?.swarmNetworkId;
const token = await get('Token');
let staking = null;
const oracleAddress = (await get('PriceOracle')).address;

// We use legacy token that was migrated, until we deploy new one with this framework
if (!(staking = await get('StakeRegistry'))) {
} else {
log('Using already deployed Staking at', staking.address);
}
const args = [token.address, swarmNetworkID, oracleAddress];
await deploy('StakeRegistry', {
from: deployer,
args: args,
log: true,
waitConfirmations: networkConfig[network.name]?.blockConfirmations || 6,
});

log('----------------------------------------------------');
};
Expand Down
3 changes: 2 additions & 1 deletion deploy/test/003_deploy_staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const func: DeployFunction = async function ({ deployments, getNamedAccounts, ne
const { deployer } = await getNamedAccounts();
const swarmNetworkID = networkConfig[network.name]?.swarmNetworkId;
const token = await get('TestToken');
const oracleAddress = (await get('PriceOracle')).address;

const args = [token.address, swarmNetworkID];
const args = [token.address, swarmNetworkID, oracleAddress];
await deploy('StakeRegistry', {
from: deployer,
args: args,
Expand Down
17 changes: 5 additions & 12 deletions src/Redistribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface IStakeRegistry {

function overlayOfAddress(address _owner) external view returns (bytes32);

function stakeOfAddress(address _owner) external view returns (uint256);
function nodeEffectiveStake(address _owner) external view returns (uint256);

function getStakeStruct(address _owner) external view returns (Stake memory);
}
Expand Down Expand Up @@ -155,9 +155,6 @@ contract Redistribution is AccessControl, Pausable {
// The length of a round in blocks.
uint256 private constant ROUND_LENGTH = 152;

// The miniumum stake allowed to be staked using the Staking contract.
uint64 private constant MIN_STAKE = 100000000000000000;

// Maximum value of the keccack256 hash.
bytes32 private constant MAX_H = 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;

Expand Down Expand Up @@ -223,11 +220,11 @@ contract Redistribution is AccessControl, Pausable {
error NotCommitPhase(); // Game is not in commit phase
error NoCommitsReceived(); // Round didn't receive any commits
error PhaseLastBlock(); // We don't permit commits in last block of the phase
error BelowMinimumStake(); // Node participating in game has stake below minimum treshold
error CommitRoundOver(); // Commit phase in this round is over
error CommitRoundNotStarted(); // Commit phase in this round has not started yet
error NotMatchingOwner(); // Sender of commit is not matching the overlay address
error MustStake2Rounds(); // Before entering the game node must stake 2 rounds prior
error NotStaked(); // Node didn't add any staking
error WrongPhase(); // Checking in wrong phase, need to check duing claim phase of current round for next round or commit in current round
error AlreadyCommited(); // Node already commited in this round
error NotRevealPhase(); // Game is not in reveal phase
Expand Down Expand Up @@ -290,7 +287,7 @@ contract Redistribution is AccessControl, Pausable {
function commit(bytes32 _obfuscatedHash, uint64 _roundNumber) external whenNotPaused {
uint64 cr = currentRound();
bytes32 _overlay = Stakes.overlayOfAddress(msg.sender);
uint256 _stake = Stakes.stakeOfAddress(msg.sender);
uint256 _stake = Stakes.nodeEffectiveStake(msg.sender);

if (!currentPhaseCommit()) {
revert NotCommitPhase();
Expand All @@ -307,8 +304,8 @@ contract Redistribution is AccessControl, Pausable {
revert CommitRoundNotStarted();
}

if (_stake < MIN_STAKE) {
revert BelowMinimumStake();
if (Stakes.lastUpdatedBlockNumberOfAddress(msg.sender) == 0) {
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
revert NotStaked();
}

if (Stakes.lastUpdatedBlockNumberOfAddress(msg.sender) >= block.number - 2 * ROUND_LENGTH) {
Expand Down Expand Up @@ -819,10 +816,6 @@ contract Redistribution is AccessControl, Pausable {
revert MustStake2Rounds();
}

if (Stakes.stakeOfAddress(_owner) < MIN_STAKE) {
revert BelowMinimumStake();
}

return inProximity(Stakes.overlayOfAddress(_owner), currentRoundAnchor(), _depth);
}

Expand Down
121 changes: 94 additions & 27 deletions src/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

interface IPriceOracle {
function currentPrice() external view returns (uint32);
}

/**
* @title Staking contract for the Swarm storage incentives
* @author The Swarm Authors
Expand All @@ -19,7 +23,9 @@ contract StakeRegistry is AccessControl, Pausable {
// Overlay of the node that is being staked
bytes32 overlay;
// Amount of tokens staked
uint256 stakeAmount;
uint256 commitedStake;
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
// Amount of tokens staked as potential stake
uint256 potentialStake;
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
// Block height the stake was updated
uint256 lastUpdatedBlockNumber;
// Used to indicate presents in stakes struct
Expand All @@ -37,15 +43,27 @@ contract StakeRegistry is AccessControl, Pausable {
// Swarm network ID
uint64 NetworkId;

// The miniumum stake allowed to be staked using the Staking contract.
uint64 private constant MIN_STAKE = 100000000000000000;

// Address of the staked ERC20 token
address public immutable bzzToken;

// The address of the linked PriceOracle contract.
IPriceOracle public OracleContract;

// ----------------------------- Events ------------------------------

/**
* @dev Emitted when a stake is created or updated by `owner` of the `overlay` by `stakeamount`, during `lastUpdatedBlock`.
* @dev Emitted when a stake is created or updated by `owner` of the `overlay` by `commitedStake`, and `potentialStake` during `lastUpdatedBlock`.
*/
event StakeUpdated(address indexed owner, uint256 stakeAmount, bytes32 overlay, uint256 lastUpdatedBlock);
event StakeUpdated(
address indexed owner,
uint256 commitedStake,
uint256 potentialStake,
bytes32 overlay,
uint256 lastUpdatedBlock
);

/**
* @dev Emitted when a stake for address `slashed` is slashed by `amount`.
Expand All @@ -62,23 +80,30 @@ contract StakeRegistry is AccessControl, Pausable {
*/
event OverlayChanged(address owner, bytes32 overlay);

/**
* @dev Emitted when a stake for address is withdrawn
*/
event StakeWithdrawn(address node, uint256 amount);

// ----------------------------- Errors ------------------------------

error TransferFailed(); // Used when token transfers fail
error Frozen(); // Used when an action cannot proceed because the overlay is frozen
error Unauthorized(); // Used where only the owner can perform the action
error OnlyRedistributor(); // Used when only the redistributor role is allowed
error OnlyPauser(); // Used when only the pauser role is allowed
error BelowMinimumStake(); // Node participating in game has stake below minimum treshold

// ----------------------------- CONSTRUCTOR ------------------------------

/**
* @param _bzzToken Address of the staked ERC20 token
* @param _NetworkId Swarm network ID
*/
constructor(address _bzzToken, uint64 _NetworkId) {
constructor(address _bzzToken, uint64 _NetworkId, address _oracleContract) {
NetworkId = _NetworkId;
bzzToken = _bzzToken;
OracleContract = IPriceOracle(_oracleContract);
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
}
Expand All @@ -96,21 +121,35 @@ contract StakeRegistry is AccessControl, Pausable {
function manageStake(bytes32 _setNonce, uint256 _addAmount) external whenNotPaused {
bytes32 _previousOverlay = stakes[msg.sender].overlay;
bytes32 _newOverlay = keccak256(abi.encodePacked(msg.sender, reverse(NetworkId), _setNonce));
uint256 _addPotentialStake = _addAmount;
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
uint256 _addCommitedStake = _addAmount / OracleContract.currentPrice(); // losing some decimals from start 10n16 is 99999999999984000

// First time adding stake, check the minimum is added
if (_addPotentialStake < MIN_STAKE && !stakes[msg.sender].isValue) {
revert BelowMinimumStake();
}

if (stakes[msg.sender].isValue && !addressNotFrozen(msg.sender)) revert Frozen();
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
uint256 updatedAmount = stakes[msg.sender].isValue ? _addAmount + stakes[msg.sender].stakeAmount : _addAmount;
uint256 updatedPotentialStake = stakes[msg.sender].isValue
? _addPotentialStake + stakes[msg.sender].potentialStake
: _addPotentialStake;
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved

uint256 updatedCommitedStake = stakes[msg.sender].isValue
? _addCommitedStake + stakes[msg.sender].commitedStake
: _addCommitedStake;
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved

stakes[msg.sender] = Stake({
overlay: _newOverlay,
stakeAmount: updatedAmount,
commitedStake: updatedCommitedStake,
potentialStake: updatedPotentialStake,
lastUpdatedBlockNumber: block.number,
isValue: true
});

// Transfer tokens and emit event that stake has been updated
if (_addAmount > 0) {
if (!ERC20(bzzToken).transferFrom(msg.sender, address(this), _addAmount)) revert TransferFailed();
emit StakeUpdated(msg.sender, updatedAmount, _newOverlay, block.number);
if (_addPotentialStake > 0) {
if (!ERC20(bzzToken).transferFrom(msg.sender, address(this), _addPotentialStake)) revert TransferFailed();
emit StakeUpdated(msg.sender, updatedCommitedStake, updatedPotentialStake, _newOverlay, block.number);
}

// Emit overlay change event
Expand All @@ -120,23 +159,34 @@ contract StakeRegistry is AccessControl, Pausable {
}

/**
* @dev Withdraw stake only when the staking contract is paused,
* @param _amount The amount of ERC20 tokens to be withdrawn
* @dev Withdraw node stake surplus
*/
function withdrawFromStake(uint256 _amount) external whenPaused {
function withdrawFromStake() external {
Stake memory stake = stakes[msg.sender];

// We cap the limit to not be over what is possible
uint256 withDrawLimit = (_amount > stake.stakeAmount) ? stake.stakeAmount : _amount;
stake.stakeAmount -= withDrawLimit;
uint256 _surplusStake = stake.potentialStake -
calculateEffectiveStake(stake.commitedStake, stake.potentialStake);
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved

if (stake.stakeAmount == 0) {
delete stakes[msg.sender];
} else {
stakes[msg.sender].lastUpdatedBlockNumber = block.number;
if (_surplusStake > 0) {
stakes[msg.sender].potentialStake -= _surplusStake;
stakes[msg.sender].commitedStake -= _surplusStake / OracleContract.currentPrice();
if (!ERC20(bzzToken).transfer(msg.sender, _surplusStake)) revert TransferFailed();
emit StakeWithdrawn(msg.sender, _surplusStake);
}
}

/**
* @dev Migrate stake only when the staking contract is paused,
* can only be called by the owner of the stake
*/
function migrateStake() external whenPaused {
Stake memory stake = stakes[msg.sender];
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved

if (!ERC20(bzzToken).transfer(msg.sender, withDrawLimit)) revert TransferFailed();
// We take out all the stake so user can migrate stake to other contract
if (stake.lastUpdatedBlockNumber != 0) {
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
if (!ERC20(bzzToken).transfer(msg.sender, stake.potentialStake)) revert TransferFailed();
delete stakes[msg.sender];
}
}

/**
Expand All @@ -162,8 +212,8 @@ contract StakeRegistry is AccessControl, Pausable {
if (!hasRole(REDISTRIBUTOR_ROLE, msg.sender)) revert OnlyRedistributor();

if (stakes[_owner].isValue) {
if (stakes[_owner].stakeAmount > _amount) {
stakes[_owner].stakeAmount -= _amount;
if (stakes[_owner].potentialStake > _amount) {
stakes[_owner].potentialStake -= _amount;
stakes[_owner].lastUpdatedBlockNumber = block.number;
} else {
delete stakes[_owner];
Expand Down Expand Up @@ -209,20 +259,22 @@ contract StakeRegistry is AccessControl, Pausable {
}

/**
* @dev Returns the current `stakeAmount` of `address`.
* @dev Returns the current `effectiveStake` of `address`.
* @param _owner _owner of node
*/
function stakeOfAddress(address _owner) public view returns (uint256) {
return stakes[_owner].stakeAmount;
function nodeEffectiveStake(address _owner) public view returns (uint256) {
Stake memory stake = stakes[_owner];
return calculateEffectiveStake(stake.commitedStake, stake.potentialStake);
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO should we change this to effective stake?
0xCardinalError marked this conversation as resolved.
Show resolved Hide resolved
/**
* @dev Returns the current usable `stakeAmount` of `address`.
* @dev Returns the current usable `potentialStake` of `address`.
* Checks whether the stake is currently frozen.
* @param _owner owner of node
*/
function usableStakeOfAddress(address _owner) public view returns (uint256) {
return addressNotFrozen(_owner) ? stakes[_owner].stakeAmount : 0;
return addressNotFrozen(_owner) ? stakes[_owner].potentialStake : 0;
}

/**
Expand All @@ -240,6 +292,21 @@ contract StakeRegistry is AccessControl, Pausable {
return stakes[_owner].overlay;
}

function calculateEffectiveStake(
uint256 committedStake,
uint256 potentialStakeBalance
) internal view returns (uint256) {
// Calculate the product of committedStake and unitPrice to get price in BZZ
uint256 committedStakeBzz = committedStake * OracleContract.currentPrice();

// Return the minimum value between committedStakeBzz and potentialStakeBalance
if (committedStakeBzz < potentialStakeBalance) {
return committedStakeBzz;
} else {
return potentialStakeBalance;
}
}

/**
* @dev Please both Endians 🥚.
* @param input Eth address used for overlay calculation.
Expand Down
Loading
Loading