Skip to content

Commit

Permalink
Add treasury managment
Browse files Browse the repository at this point in the history
  • Loading branch information
SigismundSchlomo committed Jun 20, 2024
1 parent f1426f0 commit a9ba8d6
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 142 deletions.
88 changes: 0 additions & 88 deletions contracts/staking/liquid/NodesManager.sol

This file was deleted.

200 changes: 147 additions & 53 deletions contracts/staking/liquid/Pool.sol
Original file line number Diff line number Diff line change
@@ -1,56 +1,110 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./StAMB.sol";
import "./IPool.sol";
import "./NodesManager.sol";
import "../../consensus/IValidatorSet.sol";
import "../../finance/Finance.sol";
import "../../finance/Treasury.sol";
import "../../funds/RewardsBank.sol";


contract Pool is Ownable, NodesManager, IPool {
//TODO: Choose proper access control base
contract Pool is AccessControl, IStakeManager {
uint constant private MILLION = 1000000;
uint constant private FIXEDPOINT = 1 ether;

PoolToken public token;
bytes32 constant public VALIDATOR_SET_ROLE = keccak256("VALIDATOR_SET_ROLE");
bytes32 constant public BACKEND_ROLE = keccak256("BACKEND_ROLE");

IValidatorSet public validatorSet;
RewardsBank public rewardsBank;
Treasury public treasury;
Finance public finance;
StAMB public token;
uint public minStakeValue;
uint public maxTotalStake;
uint public fee;
bool public active;
uint public totalStake;
uint public interest;
uint public nodeStake; // stake for 1 onboarded node
uint public maxNodesCount;
address[] public nodes;

uint private _requestStake;
uint private _requestId;
uint private _requestStake;

//TODO: Fix the order of the parameters
constructor(
uint minStakeValue_, uint fee_, uint maxTotalStake_,
IValidatorSet validatorSet_, uint nodeStake_
) Ownable() NodesManager(validatorSet_, nodeStake_) {
IValidatorSet validatorSet_, RewardsBank rewardsBank_, Treasury treasury_,
StAMB token_, uint interest_, uint nodeStake_, uint minStakeValue_, uint maxNodesCount_
) {
require(minStakeValue_ > 0, "Pool min stake value is zero");
require(fee_ >= 0 && fee_ < MILLION, "Pool fee must be from 0 to 1000000");
require(interest_ >= 0 && interest_ <= 1000000, "Invalid percent value");

finance = new Finance(address(this));

token = new PoolToken();
rewardsBank = rewardsBank_;
treasury = treasury_;
token = token_;
minStakeValue = minStakeValue_;
maxTotalStake = maxTotalStake_;
fee = fee_;
interest = interest_;
nodeStake = nodeStake_;
maxNodesCount = maxNodesCount_;
nodes = new address[](0);

_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(VALIDATOR_SET_ROLE, address(validatorSet_));
}


//EVENTS

event StakeChanged(address user, int stake, int tokens);
event Reward(uint reward, uint tokenPrice);
event AddNodeRequest(uint indexed requestId, uint indexed nodeId, uint stake);
event NodeOnboarded(address indexed node, uint indexed nodeId, uint stake);
event RequestFailed(uint indexed requestId, uint indexed nodeId, uint stake);
event NodeRetired(uint indexed nodeId, uint stake);
event Reward(address indexed addr, uint amount);

// VALIDATOR SET METHODS

function reward(address nodeAddress, uint256 amount) public onlyRole(VALIDATOR_SET_ROLE) {
uint treasuryAmount = treasury.calcFee(amount);
rewardsBank.withdrawAmb(payable(address(treasury)), treasuryAmount);
amount -= treasuryAmount;

rewardsBank.withdrawAmb(payable(nodeAddress), amount);
validatorSet.emitReward(address(rewardsBank), nodeAddress, nodeAddress, address(finance), address(0), amount);
emit Reward(nodeAddress, amount);
_requestNodeCreation();
}

// Why we need it?
function report(address nodeAddress) public {}

function setInterest(uint interest_) public onlyRole(VALIDATOR_SET_ROLE) {
require(interest_ >= 0 && interest_ <= 1000000, "Invalid percent value");
interest = interest_;
}

function transferRewards(address to, uint amount) public onlyRole(VALIDATOR_SET_ROLE) {
rewardsBank.withdrawAmb(payable(to), amount);
}


// TODO: Why we need this stuff??
// OWNER METHODS

function activate() public payable onlyOwner {
function activate() public payable onlyRole(DEFAULT_ADMIN_ROLE) {
require(!active, "Pool is already active");
require(msg.value == nodeStake, "Send value not equals node stake value");
active = true;
_requestNodeCreation();
}

function deactivate(uint maxNodes) public onlyOwner {
function deactivate(uint maxNodes) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(active, "Pool is not active");
while (nodes.length > maxNodes) {
_retireNode();
Expand All @@ -61,8 +115,42 @@ contract Pool is Ownable, NodesManager, IPool {
}
}

function setBackendRole(address backend) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(BACKEND_ROLE, backend);
}

// BAKEND METHODS

function increaseStake() public onlyRole(BACKEND_ROLE) {
require(active, "Pool is not active");
uint amount = totalStake * interest / MILLION;
finance.withdraw(payable(address(this)), amount);
emit StakeChanged(address(this), int(amount), int(_toTokens(amount)));
_requestNodeCreation();
}

function onboardNode(uint requestId, address node, uint nodeId) public onlyRole(BACKEND_ROLE) {
require(node != address(0), "Node address can't be zero");
require(_requestStake > 0, "No active request");
require(validatorSet.getNodeStake(node) == 0, "Node already onboarded");
require(requestId == _requestId, "Invalid request id");

if (nodeId == nodes.length && address(this).balance >= _requestStake) {
nodes.push(node);
// false - node must not always be in the top list
validatorSet.newStake(node, _requestStake, false);
emit NodeOnboarded(node, nodeId, _requestStake);
} else {
emit RequestFailed(requestId, nodeId, _requestStake);
}

_requestStake = 0;
_requestNodeCreation();
}

// PUBLIC METHODS

//TODO: Update
function stake() public payable {
require(active, "Pool is not active");
require(msg.value >= minStakeValue, "Pool: stake value too low");
Expand All @@ -77,6 +165,7 @@ contract Pool is Ownable, NodesManager, IPool {
_requestNodeCreation();
}

//TODO: Update
function unstake(uint tokens) public {
require(tokens <= token.balanceOf(msg.sender), "Sender has not enough tokens");
uint deposit = _fromTokens(tokens);
Expand All @@ -91,41 +180,39 @@ contract Pool is Ownable, NodesManager, IPool {
emit StakeChanged(msg.sender, - int(deposit), - int(tokens));
}

//TODO: Decide how the rewards should be distributed
function addReward() public payable {
uint reward;

// PRIVATE METHODS

if (nodes.length > 0) {
reward = msg.value;
if (msg.sender == nodes[0]) {

uint extraStake = (totalStake % nodeStake);
uint ownerStake = nodeStake - extraStake;

// todo wtf?

if (ownerStake < nodeStake) // equal to (extraStake > 0)
reward -= reward * ownerStake / nodeStake;
else // equal to (extraStake == 0)
reward = 0;
function _requestNodeCreation() private {
if (_requestStake == 0
&& address(this).balance >= nodeStake
&& nodes.length < maxNodesCount) {
_requestId++;
_requestStake = nodeStake;
emit AddNodeRequest(_requestId, nodes.length, _requestStake);
}
}

}
if (reward > 0) {
if (fee > 0)
reward -= reward * fee / MILLION;
function _retireNode() private {
uint deposit = getNodeDeposit(nodes[nodes.length - 1]);
validatorSet.unstake(nodes[nodes.length - 1], deposit);
emit NodeRetired(nodes.length - 1, deposit);
nodes.pop();
}

totalStake += reward;
emit Reward(reward, getTokenPrice());
}
}
function _fromTokens(uint amount) private view returns (uint) {
uint tokenPrice = getTokenPrice();
return amount * tokenPrice / FIXEDPOINT;
}

payable(owner()).transfer(msg.value - reward);
_requestNodeCreation();
function _toTokens(uint amount) private view returns (uint) {
uint tokenPrice = getTokenPrice();
return amount * FIXEDPOINT / tokenPrice;
}

// VIEW METHODS
// VIEW METHODS

function viewStake() public view returns (uint) {
function getStake() public view returns (uint) {
return token.balanceOf(msg.sender);
}

Expand All @@ -136,20 +223,27 @@ contract Pool is Ownable, NodesManager, IPool {
return totalStake * FIXEDPOINT / totalTokens;
}

// INTERNAL METHODS
function getInterest() public view returns (uint) {
return interest;
}

function getRewards() public view returns (uint) {
return address(finance).balance;
}

function _fromTokens(uint amount) internal view returns (uint) {
uint tokenPrice = getTokenPrice();
return amount * tokenPrice / FIXEDPOINT;
function getNodeDeposit(address node) public view returns (uint) {
return validatorSet.getNodeStake(node);
}

function _toTokens(uint amount) internal view returns (uint) {
uint tokenPrice = getTokenPrice();
return amount * FIXEDPOINT / tokenPrice;
function getNodesCount() public view returns (uint) {
return nodes.length;
}


function getNodes() public view returns (address[] memory) {
return nodes;
}

//TODO: Provide some fallback? Return funds to sender?
receive() external payable {}


}
2 changes: 1 addition & 1 deletion contracts/staking/liquid/StAMB.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract PoolToken is ERC20, Ownable {
contract StAMB is ERC20, Ownable {

constructor() ERC20("Staked Amber", "stAMB") Ownable() {
}
Expand Down

0 comments on commit a9ba8d6

Please sign in to comment.