Skip to content

Commit

Permalink
Added custom errors for Staking contract, exposed addStake function f…
Browse files Browse the repository at this point in the history
…or delegators, added usage of ShardingTableV2 for StakingV2
  • Loading branch information
0xbraindevd committed Jan 24, 2024
1 parent c2d0435 commit 122b318
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 15 deletions.
9 changes: 5 additions & 4 deletions contracts/v1/ServiceAgreementV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {Named} from "./interface/Named.sol";
import {Versioned} from "./interface/Versioned.sol";
import {ServiceAgreementStructsV1} from "./structs/ServiceAgreementStructsV1.sol";
import {GeneralErrors} from "./errors/GeneralErrors.sol";
import {TokenErrors} from "./errors/TokenErrors.sol";
import {ServiceAgreementErrorsV1U1} from "./errors/ServiceAgreementErrorsV1U1.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

Expand Down Expand Up @@ -106,9 +107,9 @@ contract ServiceAgreementV1 is Named, Versioned, ContractStatus, Initializable {

IERC20 tknc = tokenContract;
if (tknc.allowance(args.assetCreator, address(this)) < args.tokenAmount)
revert ServiceAgreementErrorsV1U1.TooLowAllowance(tknc.allowance(args.assetCreator, address(this)));
revert TokenErrors.TooLowAllowance(address(tknc), tknc.allowance(args.assetCreator, address(this)));
if (tknc.balanceOf(args.assetCreator) < args.tokenAmount)
revert ServiceAgreementErrorsV1U1.TooLowBalance(tknc.balanceOf(args.assetCreator));
revert TokenErrors.TooLowBalance(address(tknc), tknc.balanceOf(args.assetCreator));

tknc.transferFrom(args.assetCreator, sasProxy.agreementV1StorageAddress(), args.tokenAmount);

Expand Down Expand Up @@ -253,9 +254,9 @@ contract ServiceAgreementV1 is Named, Versioned, ContractStatus, Initializable {
IERC20 tknc = tokenContract;

if (tknc.allowance(assetOwner, address(this)) < tokenAmount)
revert ServiceAgreementErrorsV1U1.TooLowAllowance(tknc.allowance(assetOwner, address(this)));
revert TokenErrors.TooLowAllowance(address(tknc), tknc.allowance(assetOwner, address(this)));
if (tknc.balanceOf(assetOwner) < tokenAmount)
revert ServiceAgreementErrorsV1U1.TooLowBalance(tknc.balanceOf(assetOwner));
revert TokenErrors.TooLowBalance(address(tknc), tknc.balanceOf(assetOwner));

tknc.transferFrom(assetOwner, sasAddress, tokenAmount);
}
Expand Down
1 change: 1 addition & 0 deletions contracts/v1/errors/GeneralErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pragma solidity ^0.8.16;
library GeneralErrors {
error OnlyHubOwnerFunction(address caller);
error OnlyHubContractsFunction(address caller);
error OnlyProfileAdminFunction(address caller);
}
2 changes: 0 additions & 2 deletions contracts/v1/errors/ServiceAgreementErrorsV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ library ServiceAgreementErrorsV1 {
error ZeroEpochsNumber();
error ZeroTokenAmount();
error ScoreFunctionDoesntExist(uint8 scoreFunctionId);
error TooLowAllowance(uint256 amount);
error TooLowBalance(uint256 amount);
error ServiceAgreementHasBeenExpired(
bytes32 agreementId,
uint256 startTime,
Expand Down
2 changes: 0 additions & 2 deletions contracts/v1/errors/ServiceAgreementErrorsV1U1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ library ServiceAgreementErrorsV1U1 {
error ZeroTokenAmount();
error ScoreFunctionDoesntExist(uint8 scoreFunctionId);
error HashFunctionDoesntExist(uint8 hashFunctionId);
error TooLowAllowance(uint256 amount);
error TooLowBalance(uint256 amount);
error ServiceAgreementHasBeenExpired(
bytes32 agreementId,
uint256 startTime,
Expand Down
8 changes: 8 additions & 0 deletions contracts/v1/errors/TokenErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.16;

library TokenErrors {
error TooLowAllowance(address tokenAddress, uint256 amount);
error TooLowBalance(address tokenAddress, uint256 amount);
}
14 changes: 7 additions & 7 deletions contracts/v1/storage/ShardingTableStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.16;
import {HubDependent} from "../abstract/HubDependent.sol";
import {Named} from "../interface/Named.sol";
import {Versioned} from "../interface/Versioned.sol";
import {ShardingTableStructs} from "../structs/ShardingTableStructs.sol";
import {ShardingTableStructsV1} from "../structs/ShardingTableStructsV1.sol";
import {NULL} from "../constants/ShardingTableConstants.sol";

contract ShardingTableStorage is Named, Versioned, HubDependent {
Expand All @@ -17,7 +17,7 @@ contract ShardingTableStorage is Named, Versioned, HubDependent {
uint72 public nodesCount;

// identityId => Node
mapping(uint72 => ShardingTableStructs.Node) internal nodes;
mapping(uint72 => ShardingTableStructsV1.Node) internal nodes;

constructor(address hubAddress) HubDependent(hubAddress) {
head = NULL;
Expand Down Expand Up @@ -49,14 +49,14 @@ contract ShardingTableStorage is Named, Versioned, HubDependent {
}

function createNodeObject(uint72 identityId, uint72 prevIdentityId, uint72 nextIdentityId) external onlyContracts {
nodes[identityId] = ShardingTableStructs.Node({
nodes[identityId] = ShardingTableStructsV1.Node({
identityId: identityId,
prevIdentityId: prevIdentityId,
nextIdentityId: nextIdentityId
});
}

function getNode(uint72 identityId) external view returns (ShardingTableStructs.Node memory) {
function getNode(uint72 identityId) external view returns (ShardingTableStructsV1.Node memory) {
return nodes[identityId];
}

Expand All @@ -79,10 +79,10 @@ contract ShardingTableStorage is Named, Versioned, HubDependent {
function getMultipleNodes(
uint72 firstIdentityId,
uint16 nodesNumber
) external view returns (ShardingTableStructs.Node[] memory) {
ShardingTableStructs.Node[] memory nodesPage = new ShardingTableStructs.Node[](nodesNumber);
) external view returns (ShardingTableStructsV1.Node[] memory) {
ShardingTableStructsV1.Node[] memory nodesPage = new ShardingTableStructsV1.Node[](nodesNumber);

ShardingTableStructs.Node memory currentNode = nodes[firstIdentityId];
ShardingTableStructsV1.Node memory currentNode = nodes[firstIdentityId];
for (uint256 i; i < nodesNumber; ) {
nodesPage[i] = currentNode;
currentNode = nodes[currentNode.nextIdentityId];
Expand Down
269 changes: 269 additions & 0 deletions contracts/v2/Staking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.16;

import {ShardingTableV2} from "./ShardingTable.sol";
import {Shares} from "../v1/Shares.sol";
import {IdentityStorageV2} from "./storage/IdentityStorage.sol";
import {ParametersStorage} from "../v1/storage/ParametersStorage.sol";
import {ProfileStorage} from "../v1/storage/ProfileStorage.sol";
import {ServiceAgreementStorageProxy} from "../v1/storage/ServiceAgreementStorageProxy.sol";
import {ShardingTableStorageV2} from "./storage/ShardingTableStorage.sol";
import {StakingStorage} from "../v1/storage/StakingStorage.sol";
import {ContractStatus} from "../v1/abstract/ContractStatus.sol";
import {Initializable} from "../v1/interface/Initializable.sol";
import {Named} from "../v1/interface/Named.sol";
import {Versioned} from "../v1/interface/Versioned.sol";
import {GeneralErrors} from "../v1/errors/GeneralErrors.sol";
import {TokenErrors} from "../v1/errors/TokenErrors.sol";
import {ProfileErrors} from "./errors/ProfileErrors.sol";
import {StakingErrors} from "./errors/StakingErrors.sol";
import {ADMIN_KEY} from "../v1/constants/IdentityConstants.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract StakingV2 is Named, Versioned, ContractStatus, Initializable {
event StakeIncreased(
uint72 indexed identityId,
bytes nodeId,
address indexed staker,
uint96 oldStake,
uint96 newStake
);
event StakeWithdrawalStarted(
uint72 indexed identityId,
bytes nodeId,
address indexed staker,
uint96 oldStake,
uint96 newStake,
uint256 withdrawalPeriodEnd
);
event StakeWithdrawn(uint72 indexed identityId, bytes nodeId, address indexed staker, uint96 withdrawnStakeAmount);
event AccumulatedOperatorFeeIncreased(
uint72 indexed identityId,
bytes nodeId,
uint96 oldAccumulatedOperatorFee,
uint96 newAccumulatedOperatorFee
);
event OperatorFeeUpdated(uint72 indexed identityId, bytes nodeId, uint8 operatorFee);

string private constant _NAME = "Staking";
string private constant _VERSION = "1.0.2";

ShardingTableV2 public shardingTableContract;
IdentityStorageV2 public identityStorage;
ParametersStorage public parametersStorage;
ProfileStorage public profileStorage;
StakingStorage public stakingStorage;
ServiceAgreementStorageProxy public serviceAgreementStorageProxy;
ShardingTableStorageV2 public shardingTableStorage;
IERC20 public tokenContract;

// solhint-disable-next-line no-empty-blocks
constructor(address hubAddress) ContractStatus(hubAddress) {}

modifier onlyAdmin(uint72 identityId) {
_checkAdmin(identityId);
_;
}

function initialize() public onlyHubOwner {
shardingTableContract = ShardingTableV2(hub.getContractAddress("ShardingTable"));
identityStorage = IdentityStorageV2(hub.getContractAddress("IdentityStorage"));
parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage"));
profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage"));
stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage"));
serviceAgreementStorageProxy = ServiceAgreementStorageProxy(
hub.getContractAddress("ServiceAgreementStorageProxy")
);
shardingTableStorage = ShardingTableStorageV2(hub.getContractAddress("ShardingTableStorage"));
tokenContract = IERC20(hub.getContractAddress("Token"));
}

function name() external pure virtual override returns (string memory) {
return _NAME;
}

function version() external pure virtual override returns (string memory) {
return _VERSION;
}

function addStake(address sender, uint72 identityId, uint96 stakeAmount) external onlyContracts {
_addStake(sender, identityId, stakeAmount);
}

function addStake(uint72 identityId, uint96 stakeAmount) external {
_addStake(msg.sender, identityId, stakeAmount);
}

function startStakeWithdrawal(uint72 identityId, uint96 sharesToBurn) external {
if (sharesToBurn == 0) {
revert StakingErrors.ZeroSharesAmount();
}

ProfileStorage ps = profileStorage;
StakingStorage ss = stakingStorage;

if (!ps.profileExists(identityId)) {
revert ProfileErrors.ProfileDoesntExist(identityId);
}

Shares sharesContract = Shares(ps.getSharesContractAddress(identityId));

if (sharesToBurn > sharesContract.balanceOf(msg.sender)) {
revert TokenErrors.TooLowBalance(address(sharesContract), sharesContract.balanceOf(msg.sender));
}

uint96 oldStake = ss.totalStakes(identityId);
uint96 stakeWithdrawalAmount = uint96((uint256(oldStake) * sharesToBurn) / sharesContract.totalSupply());
uint96 newStake = oldStake - stakeWithdrawalAmount;
uint96 newStakeWithdrawalAmount = ss.getWithdrawalRequestAmount(identityId, msg.sender) + stakeWithdrawalAmount;

ParametersStorage params = parametersStorage;

uint256 withdrawalPeriodEnd = block.timestamp + params.stakeWithdrawalDelay();
ss.createWithdrawalRequest(identityId, msg.sender, newStakeWithdrawalAmount, withdrawalPeriodEnd);
ss.setTotalStake(identityId, newStake);
sharesContract.burnFrom(msg.sender, sharesToBurn);

if (shardingTableStorage.nodeExists(identityId) && (newStake < params.minimumStake())) {
shardingTableContract.removeNode(identityId);
}

emit StakeWithdrawalStarted(
identityId,
ps.getNodeId(identityId),
msg.sender,
oldStake,
newStake,
withdrawalPeriodEnd
);
}

function withdrawStake(uint72 identityId) external {
ProfileStorage ps = profileStorage;

if (!ps.profileExists(identityId)) {
revert ProfileErrors.ProfileDoesntExist(identityId);
}

StakingStorage ss = stakingStorage;

uint96 stakeWithdrawalAmount;
uint256 withdrawalTimestamp;
(stakeWithdrawalAmount, withdrawalTimestamp) = ss.withdrawalRequests(identityId, msg.sender);

if (stakeWithdrawalAmount == 0) {
revert StakingErrors.WithdrawalWasntInitiated();
}
if (withdrawalTimestamp >= block.timestamp) {
revert StakingErrors.WithdrawalPeriodPending(withdrawalTimestamp);
}

ss.deleteWithdrawalRequest(identityId, msg.sender);
ss.transferStake(msg.sender, stakeWithdrawalAmount);

emit StakeWithdrawn(identityId, ps.getNodeId(identityId), msg.sender, stakeWithdrawalAmount);
}

function addReward(bytes32 agreementId, uint72 identityId, uint96 rewardAmount) external onlyContracts {
ServiceAgreementStorageProxy sasProxy = serviceAgreementStorageProxy;
StakingStorage ss = stakingStorage;

uint96 operatorFee = (rewardAmount * ss.operatorFees(identityId)) / 100;
uint96 delegatorsReward = rewardAmount - operatorFee;

ProfileStorage ps = profileStorage;

uint96 oldAccumulatedOperatorFee = ps.getAccumulatedOperatorFee(identityId);
uint96 oldStake = ss.totalStakes(identityId);

if (operatorFee != 0) {
ps.setAccumulatedOperatorFee(identityId, oldAccumulatedOperatorFee + operatorFee);
sasProxy.transferAgreementTokens(agreementId, address(ps), operatorFee);
}

if (delegatorsReward != 0) {
ss.setTotalStake(identityId, oldStake + delegatorsReward);
sasProxy.transferAgreementTokens(agreementId, address(ss), delegatorsReward);

if (!shardingTableStorage.nodeExists(identityId) && oldStake >= parametersStorage.minimumStake()) {
shardingTableContract.insertNode(identityId);
}
}

emit AccumulatedOperatorFeeIncreased(
identityId,
ps.getNodeId(identityId),
oldAccumulatedOperatorFee,
oldAccumulatedOperatorFee + operatorFee
);

address sasAddress;
if (sasProxy.agreementV1Exists(agreementId)) {
sasAddress = sasProxy.agreementV1StorageAddress();
} else {
sasAddress = sasProxy.agreementV1U1StorageAddress();
}

emit StakeIncreased(identityId, ps.getNodeId(identityId), sasAddress, oldStake, oldStake + delegatorsReward);
}

// solhint-disable-next-line no-empty-blocks
function slash(uint72 identityId) external onlyContracts {
// TBD
}

function setOperatorFee(uint72 identityId, uint8 operatorFee) external onlyAdmin(identityId) {
if (operatorFee > 100) {
revert StakingErrors.InvalidOperatorFee();
}
stakingStorage.setOperatorFee(identityId, operatorFee);

emit OperatorFeeUpdated(identityId, profileStorage.getNodeId(identityId), operatorFee);
}

function _addStake(address sender, uint72 identityId, uint96 stakeAmount) internal virtual {
StakingStorage ss = stakingStorage;
ProfileStorage ps = profileStorage;
ParametersStorage params = parametersStorage;
IERC20 tknc = tokenContract;

uint96 oldStake = ss.totalStakes(identityId);
uint96 newStake = oldStake + stakeAmount;

if (!ps.profileExists(identityId)) {
revert ProfileErrors.ProfileDoesntExist(identityId);
}
if (stakeAmount > tknc.allowance(sender, address(this))) {
revert TokenErrors.TooLowAllowance(address(tknc), tknc.allowance(sender, address(this)));
}
if (newStake > params.maximumStake()) {
revert StakingErrors.MaximumStakeExceeded(params.maximumStake());
}

Shares sharesContract = Shares(ps.getSharesContractAddress(identityId));

uint256 sharesMinted;
if (sharesContract.totalSupply() == 0) {
sharesMinted = stakeAmount;
} else {
sharesMinted = ((uint256(stakeAmount) * sharesContract.totalSupply()) / oldStake);
}
sharesContract.mint(sender, sharesMinted);

ss.setTotalStake(identityId, newStake);
tknc.transferFrom(sender, address(ss), stakeAmount);

if (!shardingTableStorage.nodeExists(identityId) && newStake >= params.minimumStake()) {
shardingTableContract.insertNode(identityId);
}

emit StakeIncreased(identityId, ps.getNodeId(identityId), sender, oldStake, newStake);
}

function _checkAdmin(uint72 identityId) internal view virtual {
if (!identityStorage.keyHasPurpose(identityId, keccak256(abi.encodePacked(msg.sender)), ADMIN_KEY)) {
revert GeneralErrors.OnlyProfileAdminFunction(msg.sender);
}
}
}
7 changes: 7 additions & 0 deletions contracts/v2/errors/ProfileErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.16;

library ProfileErrors {
error ProfileDoesntExist(uint72 identityId);
}
Loading

0 comments on commit 122b318

Please sign in to comment.