Skip to content

Commit

Permalink
Merge pull request #52 from Into-the-Fathom/mainnet-deployment-reaudit
Browse files Browse the repository at this point in the history
Reaudit Fixes with Changes in Multisig Scripts
TonioMacaronio authored Apr 14, 2023
2 parents c5ba9ca + 8d480d4 commit a4af529
Showing 170 changed files with 7,337 additions and 2,472 deletions.
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -9,7 +9,5 @@ privateKey
yarn.lock
package-lock.json
bin

subgraph/build/
subgraph/generated/
subgraph/node_modules/
addresses.json
/config
11 changes: 6 additions & 5 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -2,23 +2,24 @@
"extends": "solhint:recommended",
"plugins": [],
"rules": {
"compiler-version": ["error",">=0.8.0 <0.9.0"],
"max-line-length": ["error",120],
"compiler-version": ["error","0.8.16"],
"max-line-length": ["error", 150],
"reason-string": ["error", {"maxLength": 96}],
"func-visibility": ["error", {"ignoreConstructors": true}],
"not-rely-on-time": "error",
"ordering": "error",
"comprehensive-interface": "error",
"func-param-name-mixedcase": "error",
"modifier-name-mixedcase": "error",
"code-complexity": ["error",7],
"function-max-lines": ["error",50],
"code-complexity": ["error", 7],
"function-max-lines": ["error", 50],
"const-name-snakecase": "error",
"no-complex-fallback": "off",
"no-inline-assembly": "off",
"private-vars-leading-underscore": "off",
"avoid-low-level-calls": "off",
"no-empty-blocks": "off",
"max-states-count": "off"
"max-states-count": "off",
"no-global-import": "off"
}
}
3 changes: 2 additions & 1 deletion .solhintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/contracts/oraclize-api
/contracts/common
/contracts/dao/test
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ Fathom project staking, governance and treasury smart contracts for EVM compatib
- node v16.4.0
- npm v7.18.1
- CoralX v0.2.0
- Solidity =0.8.13 (solc)
- Solidity =0.8.16 (solc)
- Ganache CLI v6.12.2 (ganache-core: 2.13.2)

## Setup
@@ -53,8 +53,8 @@ $ npm run migrate-reset
# Deploy to the apothem
$ npm run migrate-reset-apothem

# Deploy to the goerli
$ npm run migrate-reset-goerli
# Deploy to the sepolia
$ npm run migrate-reset-sepolia

# Deploy to the xdc
$ npm run migrate-reset-xdc
8 changes: 8 additions & 0 deletions contracts/common/SafeERC20Staking.sol
Original file line number Diff line number Diff line change
@@ -19,6 +19,14 @@ import "./Address.sol";
library SafeERC20Staking {
using Address for address;

function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}

function safeTransferFrom(
IERC20 token,
address from,
1 change: 0 additions & 1 deletion contracts/common/math/BoringMath.sol
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ library BoringMath {
c = uint224(a);
}


function to160(uint256 a) internal pure returns (uint208 c) {
require(a <= type(uint160).max, "BoringMath: uint128 Overflow");
c = uint160(a);
2 changes: 1 addition & 1 deletion contracts/common/math/FullMath.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity 0.8.16;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
2 changes: 1 addition & 1 deletion contracts/common/proxy/StakingProxy.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity ^0.8.0;
pragma solidity 0.8.16;
import "./transparent/TransparentUpgradeableProxy.sol";

contract StakingProxy is TransparentUpgradeableProxy {
2 changes: 1 addition & 1 deletion contracts/common/proxy/StakingProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity ^0.8.0;
pragma solidity 0.8.16;
import "./transparent/ProxyAdmin.sol";

contract StakingProxyAdmin is ProxyAdmin {}
2 changes: 1 addition & 1 deletion contracts/common/proxy/VaultProxy.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity ^0.8.0;
pragma solidity 0.8.16;
import "./transparent/TransparentUpgradeableProxy.sol";

contract VaultProxy is TransparentUpgradeableProxy {
2 changes: 1 addition & 1 deletion contracts/common/proxy/VaultProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity ^0.8.0;
pragma solidity 0.8.16;
import "./transparent/ProxyAdmin.sol";

contract VaultProxyAdmin is ProxyAdmin {}
2 changes: 1 addition & 1 deletion contracts/common/proxy/transparent/ProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)

pragma solidity ^0.8.0;
pragma solidity 0.8.16;

import "./TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
454 changes: 297 additions & 157 deletions contracts/dao/governance/Governor.sol

Large diffs are not rendered by default.

151 changes: 93 additions & 58 deletions contracts/dao/governance/MainTokenGovernor.sol
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@
pragma solidity 0.8.16;

import "./Governor.sol";
import "./interfaces/IRelay.sol";
import "./interfaces/ISupportingTokens.sol";
import "./interfaces/IEmergencyStop.sol";
import "./extensions/GovernorSettings.sol";
import "./extensions/GovernorCountingSimple.sol";
import "./extensions/GovernorVotes.sol";
@@ -13,6 +16,9 @@ import "../tokens/ERC20/IERC20.sol";
import "../../common/SafeERC20.sol";

contract MainTokenGovernor is
IRelay,
ISupportingTokens,
IEmergencyStop,
Governor,
GovernorSettings,
GovernorCountingSimple,
@@ -24,6 +30,9 @@ contract MainTokenGovernor is
mapping(address => bool) public isSupportedToken;
address[] public listOfSupportedTokens;

error TokenSupported();
error TokenUnsupported();

constructor(
IVotes _token,
TimelockController _timelock,
@@ -34,13 +43,45 @@ contract MainTokenGovernor is
uint256 _proposalTimeDelay,
uint256 _proposalLifetime
)
Governor("MainTokenGovernor", _multiSig, 20, _proposalTimeDelay,_proposalLifetime)
Governor("MainTokenGovernor", _multiSig, 20, _proposalTimeDelay, _proposalLifetime)
GovernorSettings(_initialVotingDelay, _votingPeriod, _initialProposalThreshold)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4)
GovernorTimelockControl(_timelock)
{}

/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover tokens that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayERC20(address target, bytes calldata data) external virtual override onlyGovernance {
if (!isSupportedToken[target]) {
revert TokenUnsupported();
}
(bool success, bytes memory returndata) = target.call(data);
Address.verifyCallResult(success, returndata, "empty revert");
}

/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover Ether that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayNativeToken(
address target,
uint256 value,
bytes calldata data
) external payable virtual override onlyGovernance {
if (isSupportedToken[target]) {
revert TokenSupported();
}
(bool success, bytes memory returndata) = target.call{ value: value }(data);
Address.verifyCallResult(success, returndata, "empty revert");
}

function propose(
address[] memory targets,
uint256[] memory values,
@@ -50,6 +91,9 @@ contract MainTokenGovernor is
return super.propose(targets, values, calldatas, description);
}

/**
* @dev Cancelling of proposal can be done only through Multisig
*/
function cancelProposal(
address[] memory targets,
uint256[] memory values,
@@ -59,6 +103,45 @@ contract MainTokenGovernor is
return _cancel(targets, values, calldatas, descriptionHash);
}

/**
* @dev A multisig can stop this contract. Once stopped we will have to migrate.
* Once this function is called, the contract cannot be made live again.
*/
function emergencyStop() public override onlyMultiSig {
_emergencyStop();
for (uint256 i = 0; i < listOfSupportedTokens.length; i++) {
address _token = listOfSupportedTokens[i];
uint256 balanceInContract = IERC20(_token).balanceOf(address(this));
if (balanceInContract > 0) {
IERC20(_token).safeTransfer(msg.sender, balanceInContract);
}
}
if (address(this).balance > 0) {
(bool sent, ) = msg.sender.call{ value: (address(this).balance) }("");
if (!sent) {
revert FailedToSendEther();
}
}
}

/**
* @dev Adds supporting tokens so that if there are tokens then it can be transferred
* Only Governance is able to access this function.
* It has to go through proposal and successful voting for execution.
*/
function addSupportingToken(address _token) public override onlyGovernance {
_addSupportedToken(_token);
}

/**
* @dev Removes supporting tokens
* Only Governance is able to access this function.
* It has to go through proposal and successful voting for execution.
*/
function removeSupportingToken(address _token) public override onlyGovernance {
_removeSupportingToken(_token);
}

function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
@@ -83,36 +166,18 @@ contract MainTokenGovernor is
return super.state(proposalId);
}

function emergencyStop() public onlyMultiSig{
_emergencyStop();
for(uint i = 0; i < listOfSupportedTokens.length;i++){
address _token = listOfSupportedTokens[i];
uint256 balanceInContract = IERC20(_token).balanceOf(address(this));
if(balanceInContract > 0){
IERC20(_token).safeTransfer(msg.sender, balanceInContract);
}
}
if (address(this).balance > 0){
(bool sent,) = msg.sender.call{ value: (address(this).balance) }("");
require(sent, "Failed to send ether");
}
}

function addSupportingToken(address _token) public onlyGovernance {
_addSupportedToken(_token);
}
function removeSupportingToken(address _token) public onlyGovernance {
_removeSupportingToken(_token);
}

function _addSupportedToken(address _token) internal {
require(!isSupportedToken[_token], "Token already exists");
if (isSupportedToken[_token]) {
revert TokenSupported();
}
isSupportedToken[_token] = true;
listOfSupportedTokens.push(_token);
}

function _removeSupportingToken(address _token) internal {
require(isSupportedToken[_token], "Token already doesnt exist");
if (!isSupportedToken[_token]) {
revert TokenUnsupported();
}
isSupportedToken[_token] = false;
for (uint256 i = 0; i < listOfSupportedTokens.length; i++) {
if (listOfSupportedTokens[i] == _token) {
@@ -122,38 +187,6 @@ contract MainTokenGovernor is
}
listOfSupportedTokens.pop();
}


/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayERC20(
address target,
bytes calldata data
) external virtual onlyGovernance {
require(isSupportedToken[target], "relayERC20: token not supported");
(bool success, bytes memory returndata) = target.call(data);
Address.verifyCallResult(success, returndata, "Governor: relayERC20 reverted without message");
}

/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayNativeToken(
address target,
uint256 value,
bytes calldata data
) external payable virtual onlyGovernance {
require(!isSupportedToken[target],"relayNativeToken: cant relay native token to supported token");
(bool success, bytes memory returndata) = target.call{ value: value }(data);
Address.verifyCallResult(success, returndata, "Governor: relayNativeToken reverted without message");
}

function _execute(
uint256 proposalId,
@@ -162,7 +195,9 @@ contract MainTokenGovernor is
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) {
require(isConfirmed[proposalId], "MainTokenGovernor: Proposal not confirmed by council");
if (!isConfirmed[proposalId]) {
revert ProposalNotConfirmed();
}
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}

140 changes: 94 additions & 46 deletions contracts/dao/governance/TimelockController.sol
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import "../../common/Address.sol";
import "./interfaces/ITimelockController.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

// solhint-disable not-rely-on-time
contract TimelockController is AccessControl, Initializable, ITimelockController {
bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
@@ -35,6 +36,18 @@ contract TimelockController is AccessControl, Initializable, ITimelockController

event ExecuteTransaction(address indexed owner, bool indexed success, bytes data);

error ZeroValue();
error ZeroAddress();
error LengthMismatch();
error OperationNotPending();
error InsufficientValue();
error FailedToSendEther();
error NotTimelock();
error OperationNotReady();
error OperationAlreadyScheduled();
error InsufficientDelay();
error MissingDependency();

/**
* @dev Modifier to make a function callable only by a certain role. In
* addition to checking the sender's role, `address(0)` 's role is also
@@ -48,14 +61,21 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
_;
}

// solhint-disable-next-line comprehensive-interface
receive() external payable {}

function initialize(
uint256 minDelay,
address admin,
address[] memory proposers,
address[] memory executors
) public override initializer {
require(minDelay != 0, "minDelay should be greater than zero");
require(admin != address(0), "admin should not be zero address");
if (minDelay == 0) {
revert ZeroValue();
}
if (admin == address(0)) {
revert ZeroAddress();
}
_setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
@@ -67,30 +87,32 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
_grantRole(DEFAULT_ADMIN_ROLE, admin);

for (uint256 i = 0; i < proposers.length; ++i) {
require(proposers[i] != address(0), "proposer should not be zero address");
if (proposers[i] == address(0)) {
revert ZeroAddress();
}
_setupRole(PROPOSER_ROLE, proposers[i]);
_setupRole(CANCELLER_ROLE, proposers[i]);
}

for (uint256 i = 0; i < executors.length; ++i) {
require(executors[i] != address(0), "executor should not be zero address");
if (executors[i] == address(0)) {
revert ZeroAddress();
}
_setupRole(EXECUTOR_ROLE, executors[i]);
}

_minDelay = minDelay;
emit MinDelayChange(0, minDelay);
}

receive() external payable {}

function schedule(
address target,
uint256 value,
bytes memory data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
) public virtual override onlyRole(PROPOSER_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_schedule(id, delay);
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
@@ -103,9 +125,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
) public virtual override onlyRole(PROPOSER_ROLE) {
if (targets.length != values.length || targets.length != payloads.length) {
revert LengthMismatch();
}

bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_schedule(id, delay);
@@ -114,8 +137,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
}
}

function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
function cancel(bytes32 id) public virtual override onlyRole(CANCELLER_ROLE) {
if (!isOperationPending(id)) {
revert OperationNotPending();
}
delete _timestamps[id];

emit Cancelled(id);
@@ -127,16 +152,20 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
bytes memory payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
) public payable virtual override onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, payload, predecessor, salt);
require(msg.value >= value, "execute: msg.value insufficient sent");
if (msg.value < value) {
revert InsufficientValue();
}
_beforeCall(id, predecessor);
_execute(target, value, payload);
emit CallExecuted(id, 0, target, value, payload);
_afterCall(id);
if (msg.value > value) {
(bool sent, ) = msg.sender.call{ value: (msg.value - value) }("");
require(sent, "Failed to send ether");
if (!sent) {
revert FailedToSendEther();
}
}
}

@@ -146,9 +175,10 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
bytes[] memory payloads,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
) public payable virtual override onlyRoleOrOpenRole(EXECUTOR_ROLE) {
if (targets.length != values.length || targets.length != payloads.length) {
revert LengthMismatch();
}

bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
uint256 totalValue;
@@ -162,46 +192,62 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
emit CallExecuted(id, i, target, value, payload);
}
_afterCall(id);
require(msg.value >= totalValue,"executeBatch: msg.value insufficient sent");
if(msg.value > totalValue){
if (msg.value < totalValue) {
revert InsufficientValue();
}
if (msg.value > totalValue) {
(bool sent, ) = msg.sender.call{ value: (msg.value - totalValue) }("");
require(sent, "Failed to send ether");
if (!sent) {
revert FailedToSendEther();
}
}
}

function updateDelay(uint256 newDelay) public virtual {
require(msg.sender == address(this), "TimelockController: caller must be timelock");
require(newDelay > 0, "new delay should be greater than zero");
function updateDelay(uint256 newDelay) public virtual override {
if (msg.sender != address(this)) {
revert NotTimelock();
}
if (newDelay == 0) {
revert ZeroValue();
}
emit MinDelayChange(_minDelay, newDelay);
_minDelay = newDelay;
}

function grantRoleByAdmin(bytes32 role, address account) public override onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(role, account);
}

function revokeRoleByAdmin(bytes32 role, address account) public override onlyRole(DEFAULT_ADMIN_ROLE) {
_revokeRole(role, account);
}

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return super.supportsInterface(interfaceId);
}

function isOperation(bytes32 id) public view virtual returns (bool registered) {
function isOperation(bytes32 id) public view virtual override returns (bool registered) {
return getTimestamp(id) > 0;
}

function isOperationPending(bytes32 id) public view virtual returns (bool pending) {
function isOperationPending(bytes32 id) public view virtual override returns (bool pending) {
return getTimestamp(id) > _DONE_TIMESTAMP;
}

function isOperationReady(bytes32 id) public view virtual returns (bool ready) {
function isOperationReady(bytes32 id) public view virtual override returns (bool ready) {
uint256 timestamp = getTimestamp(id);
return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
}

function isOperationDone(bytes32 id) public view virtual returns (bool done) {
function isOperationDone(bytes32 id) public view virtual override returns (bool done) {
return getTimestamp(id) == _DONE_TIMESTAMP;
}

function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) {
function getTimestamp(bytes32 id) public view virtual override returns (uint256 timestamp) {
return _timestamps[id];
}

function getMinDelay() public view virtual returns (uint256 duration) {
function getMinDelay() public view virtual override returns (uint256 duration) {
return _minDelay;
}

@@ -211,7 +257,7 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
bytes memory data,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32 hash) {
) public pure virtual override returns (bytes32 hash) {
return keccak256(abi.encode(target, value, data, predecessor, salt));
}

@@ -221,40 +267,42 @@ contract TimelockController is AccessControl, Initializable, ITimelockController
bytes[] memory payloads,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32 hash) {
) public pure virtual override returns (bytes32 hash) {
return keccak256(abi.encode(targets, values, payloads, predecessor, salt));
}

function grantRoleByAdmin(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(role, account);
}

function revokeRoleByAdmin(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
_revokeRole(role, account);
}

function _execute(
address target,
uint256 value,
bytes memory data
) internal virtual {
(bool success, ) = target.call{ value: value }(data);
emit ExecuteTransaction(msg.sender,success, data);
emit ExecuteTransaction(msg.sender, success, data);
}

function _afterCall(bytes32 id) private {
require(isOperationReady(id), "TimelockController: operation is not ready");
if (!isOperationReady(id)) {
revert OperationNotReady();
}
_timestamps[id] = _DONE_TIMESTAMP;
}

function _schedule(bytes32 id, uint256 delay) private {
require(!isOperation(id), "TimelockController: operation already scheduled");
require(delay >= getMinDelay(), "TimelockController: insufficient delay");
if (isOperation(id)) {
revert OperationAlreadyScheduled();
}
if (delay < getMinDelay()) {
revert InsufficientDelay();
}
_timestamps[id] = block.timestamp + delay;
}

function _beforeCall(bytes32 id, bytes32 predecessor) private view {
require(isOperationReady(id), "TimelockController: operation is not ready");
require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency");
if (!isOperationPending(id)) {
revert OperationNotPending();
}
if (predecessor != bytes32(0) && !isOperationDone(predecessor)) {
revert MissingDependency();
}
}
}
13 changes: 9 additions & 4 deletions contracts/dao/governance/extensions/GovernorCountingSimple.sol
Original file line number Diff line number Diff line change
@@ -22,12 +22,15 @@ abstract contract GovernorCountingSimple is Governor {

mapping(uint256 => ProposalVote) private _proposalVotes;

function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) {
error VoteCasted();
error WrongVoteType();

function hasVoted(uint256 proposalId, address account) external view virtual override returns (bool) {
return _proposalVotes[proposalId].hasVoted[account];
}

function proposalVotes(uint256 proposalId)
public
external
view
virtual
returns (
@@ -49,7 +52,9 @@ abstract contract GovernorCountingSimple is Governor {
) internal virtual override {
ProposalVote storage proposalvote = _proposalVotes[proposalId];

require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already cast");
if (proposalvote.hasVoted[account]) {
revert VoteCasted();
}
proposalvote.hasVoted[account] = true;

if (support == uint8(VoteType.Against)) {
@@ -59,7 +64,7 @@ abstract contract GovernorCountingSimple is Governor {
} else if (support == uint8(VoteType.Abstain)) {
proposalvote.abstainVotes += weight;
} else {
revert("GovernorVotingSimple: invalid value for enum VoteType");
revert WrongVoteType();
}
}

26 changes: 21 additions & 5 deletions contracts/dao/governance/extensions/GovernorSettings.sol
Original file line number Diff line number Diff line change
@@ -15,6 +15,9 @@ abstract contract GovernorSettings is Governor {
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);

error ZeroVotePeriod();
error ZeroThreshold();

constructor(
uint256 initialVotingDelay,
uint256 initialVotingPeriod,
@@ -25,15 +28,24 @@ abstract contract GovernorSettings is Governor {
_setProposalThreshold(initialProposalThreshold);
}

function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance {
/**
* @dev Has to go through proposals and successful voting to update by Governance
*/
function setVotingDelay(uint256 newVotingDelay) external virtual onlyGovernance {
_setVotingDelay(newVotingDelay);
}

function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance {
/**
* @dev Has to go through proposals and successful voting to update by Governance
*/
function setVotingPeriod(uint256 newVotingPeriod) external virtual onlyGovernance {
_setVotingPeriod(newVotingPeriod);
}

function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance {
/**
* @dev Has to go through proposals and successful voting to update by Governance
*/
function setProposalThreshold(uint256 newProposalThreshold) external virtual onlyGovernance {
_setProposalThreshold(newProposalThreshold);
}

@@ -55,14 +67,18 @@ abstract contract GovernorSettings is Governor {
}

function _setVotingPeriod(uint256 newVotingPeriod) internal virtual {
require(newVotingPeriod > 0, "GovernorSettings: voting period too low");
if (newVotingPeriod == 0) {
revert ZeroVotePeriod();
}
emit VotingPeriodSet(_votingPeriod, newVotingPeriod);
_votingPeriod = newVotingPeriod;
}

function _setProposalThreshold(uint256 newProposalThreshold) internal virtual {
if (newProposalThreshold == 0) {
revert ZeroThreshold();
}
emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold);
require(newProposalThreshold > 0, "_setProposalThreshold: Threshold for proposal cant be zero");
_proposalThreshold = newProposalThreshold;
}
}
55 changes: 36 additions & 19 deletions contracts/dao/governance/extensions/GovernorTimelockControl.sol
Original file line number Diff line number Diff line change
@@ -14,8 +14,14 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
mapping(uint256 => bool) private isProposalExecuted;
event TimelockChange(address oldTimelock, address newTimelock);

error NotConfirmed();
error NotSuccessful();
error AlreadyExecuted();

constructor(TimelockController timelockAddress) {
require(address(timelockAddress) != address(0), "timelockAddress: zero address");
if (address(timelockAddress) == address(0)) {
revert ZeroAddress();
}
_updateTimelock(timelockAddress);
}

@@ -24,15 +30,22 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
_updateTimelock(newTimelock);
}

/**
* @notice The proposal must be confirmed by multisig before it can be queued
*/
function queue(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 descriptionHash
) public virtual override returns (uint256) {
) external virtual override returns (uint256) {
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
require(isConfirmed[proposalId], "queue: not confirmed by multisig");
require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful");
if (!isConfirmed[proposalId]) {
revert NotConfirmed();
}
if (state(proposalId) != ProposalState.Succeeded) {
revert NotSuccessful();
}

uint256 delay = _timelock.getMinDelay();
_timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash);
@@ -44,6 +57,15 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
return proposalId;
}

function timelock() external view virtual override returns (address) {
return address(_timelock);
}

function proposalEta(uint256 proposalId) external view virtual override returns (uint256) {
uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]);
return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value
}

function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, Governor) returns (bool) {
return interfaceId == type(IGovernorTimelock).interfaceId || super.supportsInterface(interfaceId);
}
@@ -58,7 +80,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
bytes32 queueid = _timelockIds[proposalId];
if (queueid == bytes32(0)) {
return status;
} else if (isProposalExecuted[proposalId] == true) {
} else if (isProposalExecuted[proposalId]) {
return ProposalState.Executed;
} else if (_timelock.isOperationPending(queueid)) {
return ProposalState.Queued;
@@ -67,23 +89,16 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
}
}

function timelock() public view virtual override returns (address) {
return address(_timelock);
}

function proposalEta(uint256 proposalId) public view virtual override returns (uint256) {
uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]);
return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value
}

function _execute(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal virtual override {
require(!isProposalExecuted[proposalId], "_execute: already executed");
if (isProposalExecuted[proposalId]) {
revert AlreadyExecuted();
}
_timelock.executeBatch{ value: msg.value }(targets, values, calldatas, 0, descriptionHash);
isProposalExecuted[proposalId] = true;
}
@@ -112,7 +127,9 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
}

function _updateTimelock(TimelockController newTimelock) private {
require(address(newTimelock) != address(0), "updateTimelock: zero address");
if (address(newTimelock) == address(0)) {
revert ZeroAddress();
}
emit TimelockChange(address(_timelock), address(newTimelock));
_timelock = newTimelock;
}
4 changes: 3 additions & 1 deletion contracts/dao/governance/extensions/GovernorVotes.sol
Original file line number Diff line number Diff line change
@@ -11,7 +11,9 @@ abstract contract GovernorVotes is Governor {
IVotes public immutable token;

constructor(IVotes tokenAddress) {
require(address(tokenAddress) != address(0), "tokenAddress cant be zero address");
if (address(tokenAddress) == address(0)) {
revert ZeroAddress();
}
token = tokenAddress;
}

Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
uint256 public constant MINIMUM_QUORUM_NUMERATOR = uint256(2);
event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);

error QuorumNumeratorOverflow();
error QuorumNumeratorUnderflow();

/**
* @dev Initialize quorum as a fraction of the token's total supply.
*
@@ -53,8 +56,12 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
}

function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual {
require(newQuorumNumerator <= quorumDenominator(), "quorumNumerator over quorumDenominator");
require(newQuorumNumerator >= MINIMUM_QUORUM_NUMERATOR, "quorumNumerator less than Minimum");
if (newQuorumNumerator > quorumDenominator()) {
revert QuorumNumeratorOverflow();
}
if (newQuorumNumerator < MINIMUM_QUORUM_NUMERATOR) {
revert QuorumNumeratorUnderflow();
}
uint256 oldQuorumNumerator = _quorumNumerator;
_quorumNumerator = newQuorumNumerator;

12 changes: 6 additions & 6 deletions contracts/dao/governance/extensions/IGovernorTimelock.sol
Original file line number Diff line number Diff line change
@@ -10,13 +10,13 @@ abstract contract IGovernorTimelock is IGovernor {
event ProposalQueued(uint256 proposalId, uint256 eta);

function queue(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 descriptionHash
) public virtual returns (uint256 proposalId);
) external virtual returns (uint256 proposalId);

function timelock() public view virtual returns (address);
function timelock() external view virtual returns (address);

function proposalEta(uint256 proposalId) public view virtual returns (uint256);
function proposalEta(uint256 proposalId) external view virtual returns (uint256);
}
11 changes: 11 additions & 0 deletions contracts/dao/governance/interfaces/IEmergencyStop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

interface IEmergencyStop {
/**
* @dev A multisig can stop this contract. Once stopped we will have to migrate.
* Once this function is called, the contract cannot be made live again.
*/
function emergencyStop() external;
}
91 changes: 46 additions & 45 deletions contracts/dao/governance/interfaces/IGovernor.sol
Original file line number Diff line number Diff line change
@@ -39,61 +39,61 @@ abstract contract IGovernor is IERC165 {
event VoteCastWithParams(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 weight, string reason, bytes params);

function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public virtual returns (uint256 proposalId);
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
string calldata description
) external virtual returns (uint256 proposalId);

function cancelProposal(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 descriptionHash
) public virtual returns (uint256);
) external virtual returns (uint256);

function execute(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 descriptionHash
) public payable virtual returns (uint256 proposalId);
) external payable virtual returns (uint256 proposalId);

function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance);
function castVote(uint256 proposalId, uint8 support) external virtual returns (uint256 balance);

function castVoteWithReason(
uint256 proposalId,
uint8 support,
string memory reason
) public virtual returns (uint256 balance);
string calldata reason
) external virtual returns (uint256 balance);

function castVoteWithReasonAndParams(
uint256 proposalId,
uint8 support,
string memory reason,
bytes memory params
) public virtual returns (uint256 balance);
string calldata reason,
bytes calldata params
) external virtual returns (uint256 balance);

function castVoteBySig(
uint256 proposalId,
uint8 support,
uint8 v,
bytes32 r,
bytes32 s
) public virtual returns (uint256 balance);
) external virtual returns (uint256 balance);

function castVoteWithReasonAndParamsBySig(
uint256 proposalId,
uint8 support,
string memory reason,
bytes memory params,
string calldata reason,
bytes calldata params,
uint8 v,
bytes32 r,
bytes32 s
) public virtual returns (uint256 balance);
) external virtual returns (uint256 balance);

function getProposals(uint256 _numIndexes)
public
external
view
virtual
returns (
@@ -102,35 +102,30 @@ abstract contract IGovernor is IERC165 {
string[] memory
);

function getDescription(uint256 _proposalId) public view virtual returns (string memory);
function getDescription(uint256 _proposalId) external view virtual returns (string memory);

function getProposalIds() public view virtual returns (uint256[] memory);
function getProposalIds() external view virtual returns (uint256[] memory);

function name() public view virtual returns (string memory);
function name() external view virtual returns (string memory);

function version() public view virtual returns (string memory);
function version() external view virtual returns (string memory);

function state(uint256 proposalId) public view virtual returns (ProposalState);
function state(uint256 proposalId) external view virtual returns (ProposalState);

function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256);
function proposalSnapshot(uint256 proposalId) external view virtual returns (uint256);

function proposalDeadline(uint256 proposalId) public view virtual returns (uint256);
function proposalDeadline(uint256 proposalId) external view virtual returns (uint256);

function votingDelay() public view virtual returns (uint256);

function votingPeriod() public view virtual returns (uint256);

function quorum(uint256 blockNumber) public view virtual returns (uint256);

function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256);
function getVotes(address account, uint256 blockNumber) external view virtual returns (uint256);

function getVotesWithParams(
address account,
uint256 blockNumber,
bytes memory params
) public view virtual returns (uint256);
bytes calldata params
) external view virtual returns (uint256);

function hasVoted(uint256 proposalId, address account) external view virtual returns (bool);

function hasVoted(uint256 proposalId, address account) public view virtual returns (bool);
/**
* @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to
* be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded
@@ -154,9 +149,15 @@ abstract contract IGovernor is IERC165 {
*/

function hashProposal(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 descriptionHash
) public pure virtual returns (uint256);
) external pure virtual returns (uint256);

function votingDelay() public view virtual returns (uint256);

function votingPeriod() public view virtual returns (uint256);

function quorum(uint256 blockNumber) public view virtual returns (uint256);
}
25 changes: 25 additions & 0 deletions contracts/dao/governance/interfaces/IRelay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

interface IRelay {
/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover tokens that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayERC20(address target, bytes calldata data) external;

/**
* @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor
* is some contract other than the governor itself, like when using a timelock, this function can be invoked
* in a governance proposal to recover Ether that was sent to the governor contract by mistake.
* Note that if the executor is simply the governor itself, use of `relay` is redundant.
*/
function relayNativeToken(
address target,
uint256 value,
bytes calldata data
) external payable;
}
19 changes: 19 additions & 0 deletions contracts/dao/governance/interfaces/ISupportingTokens.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

interface ISupportingTokens {
/**
* @dev Adds supporting tokens so that if there are tokens then it can be transferred
* Only Governance is able to access this function.
* It has to go through proposal and successful voting for execution.
*/
function addSupportingToken(address _token) external;

/**
* @dev Removes supporting tokens
* Only Governance is able to access this function.
* It has to go through proposal and successful voting for execution.
*/
function removeSupportingToken(address _token) external;
}
70 changes: 70 additions & 0 deletions contracts/dao/governance/interfaces/ITimelockController.sol
Original file line number Diff line number Diff line change
@@ -10,4 +10,74 @@ interface ITimelockController {
address[] calldata proposers,
address[] calldata executors
) external;

function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external;

function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external;

function cancel(bytes32 id) external;

function execute(
address target,
uint256 value,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) external payable;

function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) external payable;

function updateDelay(uint256 newDelay) external;

function grantRoleByAdmin(bytes32 role, address account) external;

function revokeRoleByAdmin(bytes32 role, address account) external;

function isOperation(bytes32 id) external view returns (bool registered);

function isOperationPending(bytes32 id) external view returns (bool pending);

function isOperationReady(bytes32 id) external view returns (bool ready);

function isOperationDone(bytes32 id) external view returns (bool done);

function getTimestamp(bytes32 id) external view returns (uint256 timestamp);

function getMinDelay() external view returns (uint256 duration);

function hashOperation(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) external pure returns (bytes32 hash);

function hashOperationBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) external pure returns (bytes32 hash);
}
11 changes: 2 additions & 9 deletions contracts/dao/staking/StakingStorage.sol
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
pragma solidity 0.8.16;

import "./interfaces/IStakingStorage.sol";
import "./library/StakingLibrary.sol";

contract StakingStorage {
uint256 internal constant MAIN_STREAM = 0;
@@ -15,22 +14,16 @@ contract StakingStorage {
uint32 internal constant ONE_MONTH = 2629746;
uint32 internal constant ONE_YEAR = 31536000;
uint32 internal constant ONE_DAY = 86400;
//MAX_LOCK: It is a constant. One WEEK Added as a tolerance.
uint32 internal constant REWARDS_TO_TREASURY_DENOMINATOR = 10000;

uint256 public maxLockPeriod;
///@notice Checks if the staking is initialized

uint256 public minLockPeriod;

uint256 public maxLockPositions;
mapping(address => mapping(uint256 => bool)) internal prohibitedEarlyWithdraw;

uint256 internal touchedAt;

///@notice The below three are used for autocompounding feature and weighted shares
uint256 public totalAmountOfStakedToken;
uint256 public totalStreamShares;
///@notice voteToken -> vote Token
uint256 public totalAmountOfVoteToken;

uint256 public totalPenaltyBalance;
@@ -44,7 +37,7 @@ contract StakingStorage {
address public voteToken;
address public vault;
address public rewardsCalculator;
bool internal councilsInitialized;
address public treasury;
bool internal mainStreamInitialized;

///Weighting coefficient for shares and penalties
1 change: 1 addition & 0 deletions contracts/dao/staking/StakingStructs.sol
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ struct LockedBalance {
struct Stream {
address owner; // stream owned by the ERC-20 reward token owner
address manager; // stream manager handled by Main stream manager role
uint256 percentToTreasury;
address rewardToken;
StreamStatus status;
uint256 rewardDepositAmount; // the reward amount that has been deposited by third party
5 changes: 3 additions & 2 deletions contracts/dao/staking/helpers/IStakingGetterHelper.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright SECURRENCY INC.
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

import "../StakingStructs.sol";
@@ -31,5 +31,6 @@ interface IStakingGetterHelper {
function getFeesForEarlyUnlock(uint256 lockId, address account) external view returns (uint256);

function getLocksLength(address account) external view returns (uint256);
function getWeight() external view returns (Weight memory);

function getWeight() external view returns (Weight memory);
}
2 changes: 1 addition & 1 deletion contracts/dao/staking/helpers/IStakingHelper.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright SECURRENCY INC.
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

import "../StakingStructs.sol";
87 changes: 35 additions & 52 deletions contracts/dao/staking/helpers/StakingGettersHelper.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright SECURRENCY INC.
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022
pragma solidity 0.8.16;

import "./IStakingHelper.sol";
@@ -8,31 +8,30 @@ import "../interfaces/IStakingGetter.sol";
import "../StakingStructs.sol";
import "../../../common/access/AccessControl.sol";

// solhint-disable not-rely-on-time
contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
address private stakingContract;
//uint256 public constant WEIGHT_SLOT = 14; // the storage slot in staking contract where WEIGHT resides

error LockOpenedError();
error LockIdOutOfIndexError();
error LockIdCantBeZeroError();

constructor(address _stakingContract, address admin) {
stakingContract = _stakingContract;
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}

function getLockInfo(address account, uint256 lockId) public view override returns (LockedBalance memory) {
LockedBalance[] memory locks = _getAllLocks(account);
require(lockId <= locks.length, "out of index");
require(lockId > 0, "lockId cant be 0");
return locks[lockId - 1];
}

function getLocksLength(address account) public view override returns (uint256) {
function getLocksLength(address account) external view override returns (uint256) {
LockedBalance[] memory locks = _getAllLocks(account);
return locks.length;
}
function getWeight() public view override returns (Weight memory) {

function getWeight() external view override returns (Weight memory) {
return _getWeight();
}

function getLock(address account, uint256 lockId)
public
external
view
override
returns (
@@ -43,14 +42,11 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
uint256
)
{
LockedBalance[] memory locks = _getAllLocks(account);
LockedBalance memory lock = locks[lockId - 1];
require(lockId <= locks.length, "out of index");
require(lockId > 0, "lockId cant be 0");
return (lock.amountOfToken, lock.positionStreamShares, lock.end, lock.owner,lock.amountOfVoteToken);
LockedBalance memory lock = getLockInfo(account, lockId);
return (lock.amountOfToken, lock.positionStreamShares, lock.end, lock.owner, lock.amountOfVoteToken);
}

function getUserTotalDeposit(address account) public view override returns (uint256) {
function getUserTotalDeposit(address account) external view override returns (uint256) {
LockedBalance[] memory locks = _getAllLocks(account);
if (locks.length == 0) {
return 0;
@@ -62,7 +58,7 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
return totalDeposit;
}

function getStreamClaimableAmount(uint256 streamId, address account) public view override returns (uint256) {
function getStreamClaimableAmount(uint256 streamId, address account) external view override returns (uint256) {
LockedBalance[] memory locks = _getAllLocks(account);
if (locks.length == 0) {
return 0;
@@ -74,7 +70,7 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
return totalRewards;
}

function getUserTotalVotes(address account) public view override returns (uint256) {
function getUserTotalVotes(address account) external view override returns (uint256) {
LockedBalance[] memory locks = _getAllLocks(account);
if (locks.length == 0) {
return 0;
@@ -86,12 +82,11 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
return totalVotes;
}

function getFeesForEarlyUnlock(uint256 lockId, address account) public view override returns (uint256) {
LockedBalance[] memory locks = _getAllLocks(account);
require(lockId <= locks.length, "out of index");
LockedBalance memory lock = locks[lockId - 1];
require(lockId > 0, "lockId cant be 0");
require(lock.end > block.timestamp, "lock opened, no penalty");
function getFeesForEarlyUnlock(uint256 lockId, address account) external view override returns (uint256) {
LockedBalance memory lock = getLockInfo(account, lockId);
if (lock.end <= block.timestamp) {
revert LockOpenedError();
}

uint256 amount = lock.amountOfToken;
uint256 lockEnd = lock.end;
@@ -100,11 +95,21 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
return penalty;
}

function getLockInfo(address account, uint256 lockId) public view override returns (LockedBalance memory) {
LockedBalance[] memory locks = _getAllLocks(account);
if (lockId > locks.length) {
revert LockIdOutOfIndexError();
}
if (lockId == 0) {
revert LockIdCantBeZeroError();
}
return locks[lockId - 1];
}


function _getAllLocks(address account) internal view returns(LockedBalance[] memory) {
function _getAllLocks(address account) internal view returns (LockedBalance[] memory) {
return IStakingHelper(stakingContract).getAllLocks(account);
}

function _weightedPenalty(uint256 lockEnd, uint256 timestamp) internal view returns (uint256) {
Weight memory weight = _getWeight();
uint256 maxLockPeriod = IStakingHelper(stakingContract).maxLockPeriod();
@@ -119,30 +124,8 @@ contract StakingGettersHelper is IStakingGetterHelper, AccessControl {
(weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) /
maxLockPeriod);
}

function _getWeight() internal view returns (Weight memory) {
//bytes32 weight = IStakingHelper(stakingContract).readBySlot(WEIGHT_SLOT);
// uint32 penaltyWeightMultiplier;
// uint32 minWeightPenalty;
// uint32 maxWeightPenalty;
// uint32 minWeightShares;
// uint32 maxWeightShares;
// assembly {
// let value := weight
// maxWeightShares := and(0xffffffff, value)
// //shift right by 32 then, do and by 32 bits to get the value
// let minWeightShares_shifted:= shr(32,value)
// minWeightShares := and(0xffffffff, minWeightShares_shifted)
// //shift right by 64 then, do and by 32 bits to get the value
// let maxWeightPenalty_shifted := shr(64,value)
// maxWeightPenalty := and(0xffffffff, maxWeightPenalty_shifted)
// //shift right by 96 then, do and by 32 bits to get the value
// let minWeightPenalty_shifted := shr(96,value)
// minWeightPenalty := and(0xffffffff, minWeightPenalty_shifted)
// //shift right by 128 then, do and by 32 bits to get the value
// let penaltyWeightMultiplier_shifted := shr(128,value)
// penaltyWeightMultiplier := and(0xffffffff, penaltyWeightMultiplier_shifted)
// }

return IStakingStorage(stakingContract).weight();
return IStakingStorage(stakingContract).weight();
}
}
9 changes: 5 additions & 4 deletions contracts/dao/staking/interfaces/IRewardsHandler.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// SPDX-License-Identifier: AGPL 3.0
// Copyright Fathom 2022

pragma solidity ^0.8.13;
pragma solidity 0.8.16;

import "../StakingStructs.sol";

interface IRewardsHandler {
function validateStreamParameters(
address streamOwner,
address rewardToken,
uint256 percentToTreasury,
uint256 maxDepositAmount,
uint256 minDepositAmount,
uint256[] memory scheduleTimes,
uint256[] memory scheduleRewards,
uint256[] calldata scheduleTimes,
uint256[] calldata scheduleRewards,
uint256 tau
) external view;

function getRewardsAmount(Schedule memory schedule, uint256 lastUpdate) external view returns (uint256);
function getRewardsAmount(Schedule calldata schedule, uint256 lastUpdate) external view returns (uint256);
}
45 changes: 16 additions & 29 deletions contracts/dao/staking/interfaces/IStakingGetter.sol
Original file line number Diff line number Diff line change
@@ -6,38 +6,25 @@ pragma solidity 0.8.16;
import "../StakingStructs.sol";

interface IStakingGetter {
// function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256);

// function getStream(uint256 streamId)
// external
// view
// returns (
// address streamOwner,
// address rewardToken,
// uint256 rewardDepositAmount,
// uint256 rewardClaimedAmount,
// uint256 maxDepositAmount,
// uint256 rps,
// uint256 tau,
// StreamStatus status
// );

function getAllLocks(address account) external view returns (LockedBalance[] memory);

function getUsersPendingRewards(address account, uint256 streamId) external view returns (uint256);
function getStreamClaimableAmountPerLock(
uint256 streamId,
address account,
uint256 lockId
) external view returns (uint256);
//function readBySlot(uint256 slot) external view returns(bytes32);
function getStreamSchedule(uint256 streamId) external view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards);

// function getStreamsCount() external view returns (uint256);
function getStreamClaimableAmountPerLock(
uint256 streamId,
address account,
uint256 lockId
) external view returns (uint256);

// function getLatestRewardsPerShare(uint256 streamId) external view returns (uint256);
function getStreamSchedule(uint256 streamId) external view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards);

// function getWeight() external view returns (Weight memory);
// function getStreamStatus(uint256 streamId) external view returns (
// StreamStatus status
// );
function getStream(uint256 streamId)
external
view
returns (
uint256 rewardDepositAmount,
uint256 rewardClaimedAmount,
uint256 rps,
StreamStatus status
);
}
25 changes: 16 additions & 9 deletions contracts/dao/staking/interfaces/IStakingHandler.sol
Original file line number Diff line number Diff line change
@@ -7,33 +7,34 @@ import "../StakingStructs.sol";
import "./IStakingGetter.sol";

interface IStakingHandler {

function initializeStaking(
address _admin,
address _vault,
address _treasury,
address _mainToken,
address _voteToken,
Weight calldata _weight,
VoteCoefficient memory voteCoef,
VoteCoefficient calldata voteCoef,
uint256 _maxLocks,
address _rewardsContract,
uint256 _minLockPeriod
) external;

function initializeMainStream(
address _owner,
uint256[] memory scheduleTimes,
uint256[] memory scheduleRewards,
uint256[] calldata scheduleTimes,
uint256[] calldata scheduleRewards,
uint256 tau
) external;

function proposeStream(
address streamOwner,
address rewardToken,
uint256 percentToTreasury,
uint256 maxDepositAmount,
uint256 minDepositAmount,
uint256[] memory scheduleTimes,
uint256[] memory scheduleRewards,
uint256[] calldata scheduleTimes,
uint256[] calldata scheduleRewards,
uint256 tau
) external; // only STREAM_MANAGER_ROLE

@@ -59,11 +60,17 @@ interface IStakingHandler {

function withdrawAllStreams() external;

function withdrawPenalty(address penaltyReceiver) external;
function withdrawPenalty() external;

function updateVault(address _vault) external;

function emergencyUnlockAndWithdraw() external;

function createLocksForCouncils(CreateLockParams[] calldata lockParams) external;
function createLockWithoutEarlyWithdrawal(uint256 amount, uint256 lockPeriod) external;
function createFixedLocksOnBehalfOfUserByAdmin(CreateLockParams[] calldata lockPosition) external;

function setMinimumLockPeriod(uint256 _minLockPeriod) external;

function setMaxLockPositions(uint256 newMaxLockPositions) external;

function setTreasuryAddress(address newTreasury) external;
}
2 changes: 1 addition & 1 deletion contracts/dao/staking/interfaces/IStakingStorage.sol
Original file line number Diff line number Diff line change
@@ -17,6 +17,6 @@ interface IStakingStorage {
function totalPenaltyBalance() external view returns (uint256);

function streamTotalUserPendings(uint256 streamId) external view returns (uint256);
function weight() external view returns (Weight memory);

function weight() external view returns (Weight memory);
}
126 changes: 0 additions & 126 deletions contracts/dao/staking/library/RewardsLibrary.sol

This file was deleted.

44 changes: 0 additions & 44 deletions contracts/dao/staking/library/StakingLibrary.sol

This file was deleted.

119 changes: 94 additions & 25 deletions contracts/dao/staking/packages/RewardsCalculator.sol
Original file line number Diff line number Diff line change
@@ -6,36 +6,89 @@ import "../StakingStructs.sol";
import "../interfaces/IRewardsHandler.sol";
import "../../../common/math/FullMath.sol";

// solhint-disable not-rely-on-time
contract RewardsCalculator is IRewardsHandler {
// solhint-disable not-rely-on-time
uint256 public constant MAXIMUM_PERCENT_TO_TREASURY = 10000; // equal to denominator
error BadOwnerError();
error BadRewardTokenError();
error NoMinDepositError();
error BadMinDepositError();
error InvalidMaxDepositError();
error BadExpirationError();
error BadSchedulesLengthError();
error SchedulesShortError();
error BadTauError();
error BadTimesError();
error BadRewardsError();
error BadEndRewardsError();
error BadLastUpdateError();
error BadSchedulesError();
error BadQueryPeriodError();
error QueryBeforeStartError();
error QueryAfterEndError();
error InvalidIndexError();
error BadPercentToTreasuryError();

// solhint-disable code-complexity
function validateStreamParameters(
address streamOwner,
address rewardToken,
uint256 percentToTreasury,
uint256 maxDepositAmount,
uint256 minDepositAmount,
uint256[] memory scheduleTimes,
uint256[] memory scheduleRewards,
uint256[] calldata scheduleTimes,
uint256[] calldata scheduleRewards,
uint256 tau
) public view override {
require(streamOwner != address(0), "bad owner");
require(rewardToken != address(0), "bad reward token");
require(minDepositAmount > 0, "No Min Deposit");
require(minDepositAmount <= maxDepositAmount, "bad Min Deposit");
require(maxDepositAmount == scheduleRewards[0], "Invalid Max Deposit");
// scheduleTimes[0] == proposal expiration time
require(scheduleTimes[0] > block.timestamp, "bad expiration");
require(scheduleTimes.length == scheduleRewards.length, "bad Schedules");
require(scheduleTimes.length >= 2, "Schedules short");
require(tau != 0, "bad Tau");
) external view override {
if (streamOwner == address(0)) {
revert BadOwnerError();
}
if (rewardToken == address(0)) {
revert BadRewardTokenError();
}
if (minDepositAmount == 0) {
revert NoMinDepositError();
}
if (minDepositAmount > maxDepositAmount) {
revert BadMinDepositError();
}
if (maxDepositAmount != scheduleRewards[0]) {
revert InvalidMaxDepositError();
}
if (scheduleTimes[0] <= block.timestamp) {
revert BadExpirationError();
}
if (scheduleTimes.length != scheduleRewards.length) {
revert BadSchedulesLengthError();
}
if (scheduleTimes.length < 2) {
revert SchedulesShortError();
}
if (tau == 0) {
revert BadTauError();
}
if (percentToTreasury > MAXIMUM_PERCENT_TO_TREASURY) {
revert BadPercentToTreasuryError();
}
for (uint256 i = 1; i < scheduleTimes.length; i++) {
require(scheduleTimes[i] > scheduleTimes[i - 1], "bad times");
require(scheduleRewards[i] <= scheduleRewards[i - 1], "bad Rewards");
if (scheduleTimes[i] <= scheduleTimes[i - 1]) {
revert BadTimesError();
}
if (scheduleRewards[i] > scheduleRewards[i - 1]) {
revert BadRewardsError();
}
}
if (scheduleRewards[scheduleRewards.length - 1] > 0) {
revert BadEndRewardsError();
}
require(scheduleRewards[scheduleRewards.length - 1] == 0, "bad End Rewards");
}

function getRewardsAmount(Schedule memory schedule, uint256 lastUpdate) public view override returns (uint256) {
require(lastUpdate <= block.timestamp, "bad last Update");
// solhint-enable code-complexity

function getRewardsAmount(Schedule calldata schedule, uint256 lastUpdate) external view override returns (uint256) {
if (lastUpdate > block.timestamp) {
revert BadLastUpdateError();
}
if (lastUpdate == block.timestamp) return 0; // No more rewards since last update
uint256 streamStart = schedule.time[0];
if (block.timestamp <= streamStart) return 0; // Stream didn't start
@@ -78,7 +131,11 @@ contract RewardsCalculator is IRewardsHandler {
// Here reward = starting from the actual start time, calculated for the first schedule period
// that the rewards start.
reward = schedule.reward[startIndex] - schedule.reward[startIndex + 1];
rewardScheduledAmount = FullMath.mulDiv(reward,(schedule.time[startIndex + 1] - start),(schedule.time[startIndex + 1] - schedule.time[startIndex]));
rewardScheduledAmount = FullMath.mulDiv(
reward,
(schedule.time[startIndex + 1] - start),
(schedule.time[startIndex + 1] - schedule.time[startIndex])
);
// Here reward = from end of start schedule till beginning of end schedule
// Reward during the period from startIndex + 1 to endIndex
rewardScheduledAmount += schedule.reward[startIndex + 1] - schedule.reward[endIndex];
@@ -93,16 +150,25 @@ contract RewardsCalculator is IRewardsHandler {
return rewardScheduledAmount;
}

// solhint-disable code-complexity
function _getStartEndScheduleIndex(
Schedule memory schedule,
uint256 start,
uint256 end
) internal pure returns (uint256 startIndex, uint256 endIndex) {
uint256 scheduleTimeLength = schedule.time.length;
require(scheduleTimeLength >= 2, "bad schedules");
require(end > start, "bad query period");
require(start >= schedule.time[0], "query before start");
require(end <= schedule.time[scheduleTimeLength - 1], "query after end");
if (scheduleTimeLength < 2) {
revert BadSchedulesError();
}
if (end <= start) {
revert BadQueryPeriodError();
}
if (start < schedule.time[0]) {
revert QueryBeforeStartError();
}
if (end > schedule.time[scheduleTimeLength - 1]) {
revert QueryAfterEndError();
}
for (uint256 i = 1; i < scheduleTimeLength; i++) {
if (start < schedule.time[i]) {
startIndex = i - 1;
@@ -120,6 +186,9 @@ contract RewardsCalculator is IRewardsHandler {
}
}
}
require(startIndex <= endIndex, "invalid index");
if (startIndex > endIndex) {
revert InvalidIndexError();
}
}
// solhint-enable code-complexity
}
39 changes: 31 additions & 8 deletions contracts/dao/staking/packages/RewardsInternals.sol
Original file line number Diff line number Diff line change
@@ -6,10 +6,16 @@ pragma solidity 0.8.16;
import "../StakingStorage.sol";
import "../interfaces/IStakingEvents.sol";
import "../interfaces/IRewardsHandler.sol";
//import "../../../common/math/FullMath.sol";

contract RewardsInternals is StakingStorage, IStakingEvents {
// solhint-disable not-rely-on-time

error InactiveStreamError();
error NoStakeError();
error InsufficientRewardsError();
error NoLockError();
error NoSharesError();

function _updateStreamsRewardsSchedules(uint256 streamId, uint256 rewardTokenAmount) internal {
uint256 streamScheduleRewardLength = streams[streamId].schedule.reward.length;
for (uint256 i; i < streamScheduleRewardLength; i++) {
@@ -22,19 +28,28 @@ contract RewardsInternals is StakingStorage, IStakingEvents {
uint256 streamId,
uint256 lockId
) internal {
if (streams[streamId].status != StreamStatus.ACTIVE) {
revert InactiveStreamError();
}
LockedBalance storage lock = locks[account][lockId - 1];
require(streams[streamId].status == StreamStatus.ACTIVE, "inactive");
if (lock.amountOfToken == 0) {
revert NoStakeError();
}

User storage userAccount = users[account];
require(lock.amountOfToken != 0, "No Stake");

uint256 reward = ((streams[streamId].rps - userAccount.rpsDuringLastClaimForLock[lockId][streamId]) * lock.positionStreamShares) /
RPS_MULTIPLIER;
if (reward == 0) return; // All rewards claimed or stream schedule didn't start
if (streams[streamId].rewardClaimedAmount + reward > streams[streamId].rewardDepositAmount) {
revert InsufficientRewardsError();
}

userAccount.pendings[streamId] += reward;
streamTotalUserPendings[streamId] += reward;
userAccount.rpsDuringLastClaimForLock[lockId][streamId] = streams[streamId].rps;
userAccount.releaseTime[streamId] = block.timestamp + streams[streamId].tau;
// If the stream is blacklisted, remaining unclaimed rewards will be transfered out.
require(streams[streamId].rewardClaimedAmount + reward <= streams[streamId].rewardDepositAmount, "insufficient rewards");
// If the stream is blocklisted, remaining unclaimed rewards will be transfered out.
streams[streamId].rewardClaimedAmount += reward;
emit Pending(streamId, account, userAccount.pendings[streamId]);
}
@@ -47,10 +62,14 @@ contract RewardsInternals is StakingStorage, IStakingEvents {
}

function _moveAllLockPositionRewardsToPending(address account, uint256 streamId) internal {
require(streams[streamId].status == StreamStatus.ACTIVE, "inactive");
if (streams[streamId].status != StreamStatus.ACTIVE) {
revert InactiveStreamError();
}
LockedBalance[] storage locksOfAccount = locks[account];
uint256 locksLength = locksOfAccount.length;
require(locksLength > 0, "no lock");
if (locksLength == 0) {
revert NoLockError();
}
for (uint256 i = 1; i <= locksLength; i++) {
_moveRewardsToPending(account, streamId, i);
}
@@ -71,6 +90,7 @@ contract RewardsInternals is StakingStorage, IStakingEvents {
function _validateStreamParameters(
address streamOwner,
address rewardToken,
uint256 percentToTreasury,
uint256 maxDepositAmount,
uint256 minDepositAmount,
uint256[] memory scheduleTimes,
@@ -80,6 +100,7 @@ contract RewardsInternals is StakingStorage, IStakingEvents {
IRewardsHandler(rewardsCalculator).validateStreamParameters(
streamOwner,
rewardToken,
percentToTreasury,
maxDepositAmount,
minDepositAmount,
scheduleTimes,
@@ -93,7 +114,9 @@ contract RewardsInternals is StakingStorage, IStakingEvents {
}

function _getLatestRewardsPerShare(uint256 streamId) internal view returns (uint256) {
require(totalStreamShares != 0, "No Shares");
if (totalStreamShares == 0) {
revert NoSharesError();
}
return streams[streamId].rps + (_getRewardsAmount(streamId, touchedAt) * RPS_MULTIPLIER) / totalStreamShares;
}
}
32 changes: 17 additions & 15 deletions contracts/dao/staking/packages/StakingGetters.sol
Original file line number Diff line number Diff line change
@@ -9,16 +9,24 @@ import "../interfaces/IStakingGetter.sol";
import "./StakingInternals.sol";

contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals {
function getUsersPendingRewards(address account, uint256 streamId) external override view returns (uint256) {
error StreamInactiveError();
error BadIndexError();

function getUsersPendingRewards(address account, uint256 streamId) external view override returns (uint256) {
return users[account].pendings[streamId];
}

function getStreamClaimableAmountPerLock(
uint256 streamId,
address account,
uint256 lockId
) external view override returns (uint256) {
require(streams[streamId].status == StreamStatus.ACTIVE, "stream inactive");
require(lockId <= locks[account].length, "bad index");
if (streams[streamId].status != StreamStatus.ACTIVE) {
revert StreamInactiveError();
}
if (lockId > locks[account].length) {
revert BadIndexError();
}
uint256 latestRps = _getLatestRewardsPerShare(streamId);
User storage userAccount = users[account];
LockedBalance storage lock = locks[account][lockId - 1];
@@ -27,19 +35,18 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals {
return ((latestRps - userRpsPerLock) * userSharesOfLock) / RPS_MULTIPLIER;
}


function getAllLocks(address account) external override view returns (LockedBalance[] memory) {
function getAllLocks(address account) external view override returns (LockedBalance[] memory) {
return locks[account];
}
function getStreamSchedule(uint256 streamId) external override view returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards) {

function getStreamSchedule(uint256 streamId) external view override returns (uint256[] memory scheduleTimes, uint256[] memory scheduleRewards) {
return (streams[streamId].schedule.time, streams[streamId].schedule.reward);
}

function getStream(
uint256 streamId
)
function getStream(uint256 streamId)
external
view
override
returns (
uint256 rewardDepositAmount,
uint256 rewardClaimedAmount,
@@ -48,11 +55,6 @@ contract StakingGetters is StakingStorage, IStakingGetter, StakingInternals {
)
{
Stream storage stream = streams[streamId];
return (
stream.rewardDepositAmount,
stream.rewardClaimedAmount,
stream.rps,
stream.status
);
return (stream.rewardDepositAmount, stream.rewardClaimedAmount, stream.rps, stream.status);
}
}
374 changes: 267 additions & 107 deletions contracts/dao/staking/packages/StakingHandler.sol

Large diffs are not rendered by default.

68 changes: 48 additions & 20 deletions contracts/dao/staking/packages/StakingInternals.sol
Original file line number Diff line number Diff line change
@@ -12,36 +12,30 @@ import "../../tokens/IVMainToken.sol";
import "../../../common/math/BoringMath.sol";
import "../../../common/math/FullMath.sol";


contract StakingInternals is RewardsInternals {
// solhint-disable not-rely-on-time
error ZeroAddress();
error ZeroLocked(uint256 lockId);
error ZeroTotalStakedToken();
error InvalidShareWeights();
error InvalidPenaltyWeights();
error IncorrectWeight();
error ZeroCoefficient();

function _initializeStaking(
address _mainToken,
address _voteToken,
Weight calldata _weight,
address _treasury,
Weight memory _weight,
address _vault,
uint256 _maxLockPositions,
uint256 _voteShareCoef,
uint256 _voteLockCoef
) internal {
if(_mainToken == address(0x00)){
revert ZeroAddress();
}
if(_voteToken == address(0x00)){
revert ZeroAddress();
}
if(_vault == address(0x00)){
revert ZeroAddress();
}
require(_weight.maxWeightShares > _weight.minWeightShares, "bad share");
require(_weight.maxWeightPenalty > _weight.minWeightPenalty, "bad penalty");
require(_weight.penaltyWeightMultiplier * _weight.maxWeightPenalty <= 100000, "wrong weight");
require(_voteLockCoef != 0, "zero coef");
_verifyStaking(_mainToken, _voteToken, _treasury, _weight, _vault, _voteLockCoef);
mainToken = _mainToken;
voteToken = _voteToken;
treasury = _treasury;
weight = _weight;
vault = _vault;
maxLockPositions = _maxLockPositions;
@@ -95,13 +89,11 @@ contract StakingInternals is RewardsInternals {
) internal {
User storage userAccount = users[account];
LockedBalance storage updateLock = locks[account][lockId - 1];
if(updateLock.amountOfToken == 0){
revert ZeroLocked(lockId);
}
if(totalAmountOfStakedToken == 0){

if (totalAmountOfStakedToken == 0) {
revert ZeroTotalStakedToken();
}

uint256 nVoteToken = updateLock.amountOfVoteToken;
/// if you unstake, early or partial or complete,
/// the number of vote tokens for lock position is set to zero
@@ -303,4 +295,40 @@ contract StakingInternals is RewardsInternals {
(weight.penaltyWeightMultiplier * (weight.maxWeightPenalty - weight.minWeightPenalty) * remainingTime) /
maxLockPeriod);
}

// solhint-disable code-complexity
function _verifyStaking(
address _mainToken,
address _voteToken,
address _treasury,
Weight memory _weight,
address _vault,
uint256 _voteLockCoef
) internal pure {
if (_mainToken == address(0x00)) {
revert ZeroAddress();
}
if (_voteToken == address(0x00)) {
revert ZeroAddress();
}
if (_vault == address(0x00)) {
revert ZeroAddress();
}
if (_treasury == address(0x00)) {
revert ZeroAddress();
}
if (_weight.maxWeightShares <= _weight.minWeightShares) {
revert InvalidShareWeights();
}
if (_weight.maxWeightPenalty <= _weight.minWeightPenalty) {
revert InvalidPenaltyWeights();
}
if (_weight.penaltyWeightMultiplier * _weight.maxWeightPenalty > 100000) {
revert IncorrectWeight();
}
if (_voteLockCoef == 0) {
revert ZeroCoefficient();
}
}
// solhint-enable code-complexity
}
11 changes: 3 additions & 8 deletions contracts/dao/staking/vault/interfaces/IVault.sol
Original file line number Diff line number Diff line change
@@ -4,10 +4,7 @@ pragma solidity 0.8.16;
interface IVault {
function initVault(address _admin, address[] calldata supportedTokens) external;

function deposit(
address _token,
uint256 _amount
) external;
function deposit(address _token, uint256 _amount) external;

function addRewardsOperator(address _rewardsOperator) external;

@@ -24,12 +21,10 @@ interface IVault {
function migrate(address vaultPackageMigrateTo) external;

function withdrawExtraSupportedTokens(address _withdrawTo) external;
function withdrawExtraUnsupportedToken(address _token,address _withdrawTo) external;

function withdrawExtraUnsupportedToken(address _token, address _withdrawTo) external;

function isSupportedToken(address token) external view returns (bool);

function migrated() external view returns (bool);


}
81 changes: 44 additions & 37 deletions contracts/dao/staking/vault/packages/VaultPackage.sol
Original file line number Diff line number Diff line change
@@ -13,12 +13,24 @@ import "../../../../common/introspection/ERC165.sol";
// solhint-disable not-rely-on-time
contract VaultPackage is IVault, IVaultEvents, AdminPausable {
using SafeERC20 for IERC20;
bytes32 public constant REWARDS_OPERATOR_ROLE = keccak256("REWARDS_OPERATOR_ROLE");

mapping(address => uint256) public deposited;
mapping(address => bool) public override isSupportedToken;
address[] public listOfSupportedTokens;
bool public override migrated;

bytes32 public constant REWARDS_OPERATOR_ROLE = keccak256("REWARDS_OPERATOR_ROLE");

error NoRewardsOperatorRole();
error UnsupportedToken();
error AmountZero();
error InsufficientDeposit();
error VaultMigrated();
error TokenAlreadyExists();
error TokenInUse();
error ZeroAddress();
error RequiredPause();

constructor() {
_disableInitializers();
}
@@ -39,11 +51,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable {
address _token,
uint256 _amount
) external override pausable(1) {
require(hasRole(REWARDS_OPERATOR_ROLE, msg.sender), "payRewards: No role");
require(isSupportedToken[_token], "Unsupported token");
require(_amount != 0, "amount zero");
require(deposited[_token] >= _amount, "payRewards: not enough deposit");
require(!migrated,"vault already migrated");
if (!hasRole(REWARDS_OPERATOR_ROLE, msg.sender)) revert NoRewardsOperatorRole();
if (!isSupportedToken[_token]) revert UnsupportedToken();
if (_amount == 0) revert AmountZero();
if (deposited[_token] < _amount) revert InsufficientDeposit();
if (migrated) revert VaultMigrated();

uint256 previousBalance = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransfer(_user, _amount);
@@ -52,14 +64,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable {
deposited[_token] -= trueDeposit;
}

function deposit(
address _token,
uint256 _amount
) external override pausable(1) {
require(hasRole(REWARDS_OPERATOR_ROLE, msg.sender), "deposit: No role");
require(isSupportedToken[_token], "Unsupported token");
require(!migrated,"vault already migrated");

function deposit(address _token, uint256 _amount) external override pausable(1) {
if (!hasRole(REWARDS_OPERATOR_ROLE, msg.sender)) revert NoRewardsOperatorRole();
if (!isSupportedToken[_token]) revert UnsupportedToken();
if (migrated) revert VaultMigrated();

uint256 previousBalance = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
uint256 newBalance = IERC20(_token).balanceOf(address(this));
@@ -69,52 +78,53 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable {

/// @notice adds token as a supported rewards token by Vault
/// supported tokens means any future stream token should be
/// whitelisted here
/// allowlisted here
/// @param _token stream ERC20 token address
function addSupportedToken(address _token) external override onlyRole(DEFAULT_ADMIN_ROLE) {
require(!migrated,"vault already migrated");
if (migrated) revert VaultMigrated();
_addSupportedToken(_token);
}

/// @notice removed token as a supported rewards token by Treasury
/// @param _token stream ERC20 token address
function removeSupportedToken(address _token) external override onlyRole(DEFAULT_ADMIN_ROLE) {
require(!migrated,"vault already migrated");
require(isSupportedToken[_token], "Token does not exist");
require(deposited[_token] == 0, "Token is still in use");
if (migrated) revert VaultMigrated();
if (!isSupportedToken[_token]) revert UnsupportedToken();
if (deposited[_token] > 0) revert TokenInUse();

isSupportedToken[_token] = false;
_removeToken(_token);
emit TokenRemoved(_token, msg.sender, block.timestamp);
}

function withdrawExtraSupportedTokens(address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) {
for(uint i = 0; i < listOfSupportedTokens.length;i++){
for (uint256 i = 0; i < listOfSupportedTokens.length; i++) {
uint256 balanceToWithdraw;
address _token = listOfSupportedTokens[i];
uint256 balanceInContract = IERC20(_token).balanceOf(address(this));
if(balanceInContract > deposited[_token]){
balanceToWithdraw = balanceInContract - deposited[_token];
if (balanceInContract > deposited[_token]) {
balanceToWithdraw = balanceInContract - deposited[_token];
}
if(balanceToWithdraw > 0){
if (balanceToWithdraw > 0) {
IERC20(_token).safeTransfer(_withdrawTo, balanceToWithdraw);
}
}
}
}
}

function withdrawExtraUnsupportedToken(address _token,address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) {
require(!isSupportedToken[_token],"token is supported");
function withdrawExtraUnsupportedToken(address _token, address _withdrawTo) external override onlyRole(DEFAULT_ADMIN_ROLE) {
if (isSupportedToken[_token]) revert TokenAlreadyExists();
uint256 balanceInContract = IERC20(_token).balanceOf(address(this));
if(balanceInContract > 0){
if (balanceInContract > 0) {
IERC20(_token).safeTransfer(_withdrawTo, balanceInContract);
}
}

/// @notice we believe newVaultPackage is safe
function migrate(address newVaultPackage) external override onlyRole(DEFAULT_ADMIN_ROLE) {
require(!migrated, "vault already migrated");
require(paused != 0, "required pause");
require(newVaultPackage != address(0), "withdrawTo: Zero addr");
if (migrated) revert VaultMigrated();
if (paused == 0) revert RequiredPause();
if (newVaultPackage == address(0)) revert ZeroAddress();

for (uint256 i = 0; i < listOfSupportedTokens.length; i++) {
address token = listOfSupportedTokens[i];
deposited[token] = 0;
@@ -125,12 +135,11 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable {
}

function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IVault).interfaceId;
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IVault).interfaceId;
}

function _addSupportedToken(address _token) internal {
require(!isSupportedToken[_token], "Token already exists");
if (isSupportedToken[_token]) revert TokenAlreadyExists();
isSupportedToken[_token] = true;
listOfSupportedTokens.push(_token);
emit TokenAdded(_token, msg.sender, block.timestamp);
@@ -145,6 +154,4 @@ contract VaultPackage is IVault, IVaultEvents, AdminPausable {
}
listOfSupportedTokens.pop();
}


}
7 changes: 7 additions & 0 deletions contracts/dao/test/VaultProxyAdminMigrate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity 0.8.16;
import "../../common/proxy/transparent/ProxyAdmin.sol";

contract VaultProxyAdminMigrate is ProxyAdmin {}
13 changes: 13 additions & 0 deletions contracts/dao/test/VaultProxyMigrate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: AGPL 3.0
// Original Copyright Aurora
// Copyright Fathom 2022
pragma solidity 0.8.16;
import "../../common/proxy/transparent/TransparentUpgradeableProxy.sol";

contract VaultProxyMigrate is TransparentUpgradeableProxy {
constructor(
address _logic,
address admin_,
bytes memory _data
) payable TransparentUpgradeableProxy(_logic, admin_, _data) {}
}
158 changes: 158 additions & 0 deletions contracts/dao/test/dex/IUniswapV2Router01.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: MIT
// Original Copyright Uniswap
// Copyright Fathom 2022

pragma solidity >=0.6.2;

interface IUniswapV2Router01 {
function factory() external pure returns (address);

function WETH() external pure returns (address);

function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
)
external
returns (
uint256 amountA,
uint256 amountB,
uint256 liquidity
);

function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
);

function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);

function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);

function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountA, uint256 amountB);

function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountToken, uint256 amountETH);

function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);

function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);

function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) external pure returns (uint256 amountB);

function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountOut);

function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountIn);

function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);

function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts);
}
54 changes: 54 additions & 0 deletions contracts/dao/test/dex/IUniswapV2Router02.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Original Copyright Uniswap
// Copyright Fathom 2022

pragma solidity >=0.6.2;

import "./IUniswapV2Router01.sol";

interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);

function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountETH);

function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;

function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;

function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}
10 changes: 10 additions & 0 deletions contracts/dao/test/stablecoin/IProxyRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.16;

interface IProxyRegistry {
function proxies(address) external view returns (address);

function build(address) external returns (address);

function isProxy(address) external view returns (bool);
}
Loading

0 comments on commit a4af529

Please sign in to comment.