Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @dev Errors.
interface IErrorsTokenomics {
/// @dev Only `manager` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param manager Required sender address as a manager.
error ManagerOnly(address sender, address manager);
/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);
/// @dev Provided zero address.
error ZeroAddress();
/// @dev Wrong length of two arrays.
/// @param numValues1 Number of values in a first array.
/// @param numValues2 Number of values in a second array.
error WrongArrayLength(uint256 numValues1, uint256 numValues2);
/// @dev Service Id does not exist in registry records.
/// @param serviceId Service Id.
error ServiceDoesNotExist(uint256 serviceId);
/// @dev Zero value when it has to be different from zero.
error ZeroValue();
/// @dev Non-zero value when it has to be zero.
error NonZeroValue();
/// @dev Value overflow.
/// @param provided Overflow value.
/// @param max Maximum possible value.
error Overflow(uint256 provided, uint256 max);
/// @dev Service was never deployed.
/// @param serviceId Service Id.
error ServiceNeverDeployed(uint256 serviceId);
/// @dev Token is disabled or not whitelisted.
/// @param tokenAddress Address of a token.
error UnauthorizedToken(address tokenAddress);
/// @dev Provided token address is incorrect.
/// @param provided Provided token address.
/// @param expected Expected token address.
error WrongTokenAddress(address provided, address expected);
/// @dev Bond is not redeemable (does not exist or not matured).
/// @param bondId Bond Id.
error BondNotRedeemable(uint256 bondId);
/// @dev The product is expired.
/// @param tokenAddress Address of a token.
/// @param productId Product Id.
/// @param deadline The program expiry time.
/// @param curTime Current timestamp.
error ProductExpired(address tokenAddress, uint256 productId, uint256 deadline, uint256 curTime);
/// @dev The product is already closed.
/// @param productId Product Id.
error ProductClosed(uint256 productId);
/// @dev The product supply is low for the requested payout.
/// @param tokenAddress Address of a token.
/// @param productId Product Id.
/// @param requested Requested payout.
/// @param actual Actual supply left.
error ProductSupplyLow(address tokenAddress, uint256 productId, uint256 requested, uint256 actual);
/// @dev Received lower value than the expected one.
/// @param provided Provided value is lower.
/// @param expected Expected value.
error LowerThan(uint256 provided, uint256 expected);
/// @dev Wrong amount received / provided.
/// @param provided Provided amount.
/// @param expected Expected amount.
error WrongAmount(uint256 provided, uint256 expected);
/// @dev Insufficient token allowance.
/// @param provided Provided amount.
/// @param expected Minimum expected amount.
error InsufficientAllowance(uint256 provided, uint256 expected);
/// @dev Failure of a transfer.
/// @param token Address of a token.
/// @param from Address `from`.
/// @param to Address `to`.
/// @param amount Token amount.
error TransferFailed(address token, address from, address to, uint256 amount);
/// @dev Incentives claim has failed.
/// @param account Account address.
/// @param reward Reward amount.
/// @param topUp Top-up amount.
error ClaimIncentivesFailed(address account, uint256 reward, uint256 topUp);
/// @dev Caught reentrancy violation.
error ReentrancyGuard();
/// @dev Failure of treasury re-balance during the reward allocation.
/// @param epochNumber Epoch number.
error TreasuryRebalanceFailed(uint256 epochNumber);
/// @dev Operation with a wrong component / agent Id.
/// @param unitId Component / agent Id.
/// @param unitType Type of the unit (component / agent).
error WrongUnitId(uint256 unitId, uint256 unitType);
/// @dev The donator address is blacklisted.
/// @param account Donator account address.
error DonatorBlacklisted(address account);
/// @dev The contract is already initialized.
error AlreadyInitialized();
/// @dev The contract has to be delegate-called via proxy.
error DelegatecallOnly();
/// @dev The contract is paused.
error Paused();
/// @dev Caught an operation that is not supposed to happen in the same block.
error SameBlockNumberViolation();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IOLAS {
/// @dev Mints OLA tokens.
/// @param account Account address.
/// @param amount OLA token amount.
function mint(address account, uint256 amount) external;
/// @dev Provides OLA token time launch.
/// @return Time launch.
function timeLaunch() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @dev Required interface for the service registry.
interface IServiceRegistry {
enum UnitType {
Component,
Agent
}
/// @dev Checks if the service Id exists.
/// @param serviceId Service Id.
/// @return true if the service exists, false otherwise.
function exists(uint256 serviceId) external view returns (bool);
/// @dev Gets the full set of linearized components / canonical agent Ids for a specified service.
/// @notice The service must be / have been deployed in order to get the actual data.
/// @param serviceId Service Id.
/// @return numUnitIds Number of component / agent Ids.
/// @return unitIds Set of component / agent Ids.
function getUnitIdsOfService(UnitType unitType, uint256 serviceId) external view
returns (uint256 numUnitIds, uint32[] memory unitIds);
/// @dev Gets the value of slashed funds from the service registry.
/// @return amount Drained amount.
function slashedFunds() external view returns (uint256 amount);
/// @dev Drains slashed funds.
/// @return amount Drained amount.
function drain() external returns (uint256 amount);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @dev Generic token interface for IERC20 and IERC721 tokens.
interface IToken {
/// @dev Gets the amount of tokens owned by a specified account.
/// @param account Account address.
/// @return Amount of tokens owned.
function balanceOf(address account) external view returns (uint256);
/// @dev Gets the owner of the token Id.
/// @param tokenId Token Id.
/// @return Token Id owner address.
function ownerOf(uint256 tokenId) external view returns (address);
/// @dev Gets the total amount of tokens stored by the contract.
/// @return Amount of tokens.
function totalSupply() external view returns (uint256);
/// @dev Transfers the token amount.
/// @param to Address to transfer to.
/// @param amount The amount to transfer.
/// @return True if the function execution is successful.
function transfer(address to, uint256 amount) external returns (bool);
/// @dev Gets remaining number of tokens that the `spender` can transfer on behalf of `owner`.
/// @param owner Token owner.
/// @param spender Account address that is able to transfer tokens on behalf of the owner.
/// @return Token amount allowed to be transferred.
function allowance(address owner, address spender) external view returns (uint256);
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @param spender Account address that will be able to transfer tokens on behalf of the caller.
/// @param amount Token amount.
/// @return True if the function execution is successful.
function approve(address spender, uint256 amount) external returns (bool);
/// @dev Transfers the token amount that was previously approved up until the maximum allowance.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param amount Amount to transfer to.
/// @return True if the function execution is successful.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @dev Interface for tokenomics management.
interface ITokenomics {
/// @dev Gets effective bond (bond left).
/// @return Effective bond.
function effectiveBond() external pure returns (uint256);
/// @dev Record global data to the checkpoint
function checkpoint() external returns (bool);
/// @dev Tracks the deposited ETH service donations during the current epoch.
/// @notice This function is only called by the treasury where the validity of arrays and values has been performed.
/// @param donator Donator account address.
/// @param serviceIds Set of service Ids.
/// @param amounts Correspondent set of ETH amounts provided by services.
/// @param donationETH Overall service donation amount in ETH.
function trackServiceDonations(
address donator,
uint256[] memory serviceIds,
uint256[] memory amounts,
uint256 donationETH
) external;
/// @dev Reserves OLAS amount from the effective bond to be minted during a bond program.
/// @notice Programs exceeding the limit in the epoch are not allowed.
/// @param amount Requested amount for the bond program.
/// @return True if effective bond threshold is not reached.
function reserveAmountForBondProgram(uint256 amount) external returns(bool);
/// @dev Refunds unused bond program amount.
/// @param amount Amount to be refunded from the bond program.
function refundFromBondProgram(uint256 amount) external;
/// @dev Gets component / agent owner incentives and clears the balances.
/// @param account Account address.
/// @param unitTypes Set of unit types (component / agent).
/// @param unitIds Set of corresponding unit Ids where account is the owner.
/// @return reward Reward amount.
/// @return topUp Top-up amount.
function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
returns (uint256 reward, uint256 topUp);
/// @dev Gets inverse discount factor with the multiple of 1e18 of the last epoch.
/// @return idf Discount factor with the multiple of 1e18.
function getLastIDF() external view returns (uint256 idf);
/// @dev Gets the service registry contract address
/// @return Service registry contract address;
function serviceRegistry() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./interfaces/IErrorsTokenomics.sol";
import "./interfaces/IOLAS.sol";
import "./interfaces/IToken.sol";
import "./interfaces/IServiceRegistry.sol";
import "./interfaces/ITokenomics.sol";
/*
* In this contract we consider both ETH and OLAS tokens.
* For ETH tokens, there are currently about 121 million tokens.
* Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply.
* Lately the inflation rate was lower and could actually be deflationary.
*
* For OLAS tokens, the initial numbers will be as follows:
* - For the first 10 years there will be the cap of 1 billion (1e27) tokens;
* - After 10 years, the inflation rate is capped at 2% per year.
* Starting from a year 11, the maximum number of tokens that can be reached per the year x is 1e27 * (1.02)^x.
* To make sure that a unit(n) does not overflow the total supply during the year x, we have to check that
* 2^n - 1 >= 1e27 * (1.02)^x. We limit n by 96, thus it would take 220+ years to reach that total supply.
*
* We then limit each time variable to last until the value of 2^32 - 1 in seconds.
* 2^32 - 1 gives 136+ years counted in seconds starting from the year 1970.
* Thus, this counter is safe until the year 2106.
*
* The number of blocks cannot be practically bigger than the number of seconds, since there is more than one second
* in a block. Thus, it is safe to assume that uint32 for the number of blocks is also sufficient.
*
* In conclusion, this contract is only safe to use until 2106.
*/
/// @title Treasury - Smart contract for managing OLAS Treasury
/// @author AL
/// @author Aleksandr Kuperman - <[email protected]>
/// Invariant does not support a failing call() function while transferring ETH when using the CEI pattern:
/// revert TransferFailed(address(0), address(this), to, tokenAmount);
/// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
contract Treasury is IErrorsTokenomics {
event OwnerUpdated(address indexed owner);
event TokenomicsUpdated(address indexed tokenomics);
event DepositoryUpdated(address indexed depository);
event DispenserUpdated(address indexed dispenser);
event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
event EnableToken(address indexed token);
event DisableToken(address indexed token);
event ReceiveETH(address indexed sender, uint256 amount);
event UpdateTreasuryBalances(uint256 ETHOwned, uint256 ETHFromServices);
event PauseTreasury();
event UnpauseTreasury();
event MinAcceptedETHUpdated(uint256 amount);
// A well-known representation of an ETH as address
address public constant ETH_TOKEN_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
// Owner address
address public owner;
// ETH received from services
// Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply
uint96 public ETHFromServices;
// OLAS token address
address public olas;
// ETH owned by treasury
// Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply
uint96 public ETHOwned;
// Tkenomics contract address
address public tokenomics;
// Minimum accepted donation value
uint96 public minAcceptedETH = 0.065 ether;
// Depository contract address
address public depository;
// Contract pausing
uint8 public paused = 1;
// Reentrancy lock
uint8 internal _locked;
// Dispenser contract address
address public dispenser;
// Token address => token reserves
mapping(address => uint256) public mapTokenReserves;
// Token address => enabled / disabled status
mapping(address => bool) public mapEnabledTokens;
/// @dev Treasury constructor.
/// @param _olas OLAS token address.
/// @param _tokenomics Tokenomics address.
/// @param _depository Depository address.
/// @param _dispenser Dispenser address.
constructor(address _olas, address _tokenomics, address _depository, address _dispenser) payable {
owner = msg.sender;
_locked = 1;
// Check for at least one zero contract address
if (_olas == address(0) || _tokenomics == address(0) || _depository == address(0) || _dispenser == address(0)) {
revert ZeroAddress();
}
olas = _olas;
tokenomics = _tokenomics;
depository = _depository;
dispenser = _dispenser;
// Assign an initial contract address ETH balance
// If msg.value is passed in the constructor, it is already accounted for in the address balance
// This way the balance also accounts for possible transfers before the contract was created
ETHOwned = uint96(address(this).balance);
}
/// @dev Receives ETH.
/// #if_succeeds {:msg "we do not touch the balance of developers" } old(ETHFromServices) == ETHFromServices;
/// #if_succeeds {:msg "conservation law"} old(ETHOwned) + msg.value + old(ETHFromServices) <= type(uint96).max && ETHOwned == old(ETHOwned) + msg.value
/// ==> address(this).balance == ETHFromServices + ETHOwned;
/// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
receive() external payable {
if (msg.value < minAcceptedETH) {
revert LowerThan(msg.value, minAcceptedETH);
}
uint256 amount = ETHOwned;
amount += msg.value;
// Check for the overflow values, specifically when fuzzing, since practically these amounts are not realistic
if (amount + ETHFromServices > type(uint96).max) {
revert Overflow(amount, type(uint96).max);
}
ETHOwned = uint96(amount);
emit ReceiveETH(msg.sender, msg.value);
}
/// @dev Changes the owner address.
/// @param newOwner Address of a new owner.
function changeOwner(address newOwner) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address
if (newOwner == address(0)) {
revert ZeroAddress();
}
owner = newOwner;
emit OwnerUpdated(newOwner);
}
/// @dev Changes various managing contract addresses.
/// @param _tokenomics Tokenomics address.
/// @param _depository Depository address.
/// @param _dispenser Dispenser address.
function changeManagers(address _tokenomics, address _depository, address _dispenser) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Change Tokenomics contract address
if (_tokenomics != address(0)) {
tokenomics = _tokenomics;
emit TokenomicsUpdated(_tokenomics);
}
// Change Depository contract address
if (_depository != address(0)) {
depository = _depository;
emit DepositoryUpdated(_depository);
}
// Change Dispenser contract address
if (_dispenser != address(0)) {
dispenser = _dispenser;
emit DispenserUpdated(_dispenser);
}
}
/// @dev Changes minimum accepted ETH amount by the Treasury.
/// @param _minAcceptedETH New minimum accepted ETH amount.
/// #if_succeeds {:msg "Min accepted ETH"} minAcceptedETH > 0 && minAcceptedETH <= type(uint96).max;
function changeMinAcceptedETH(uint256 _minAcceptedETH) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero value
if (_minAcceptedETH == 0) {
revert ZeroValue();
}
// Check for the overflow value
if (_minAcceptedETH > type(uint96).max) {
revert Overflow(_minAcceptedETH, type(uint96).max);
}
minAcceptedETH = uint96(_minAcceptedETH);
emit MinAcceptedETHUpdated(_minAcceptedETH);
}
/// @dev Allows the depository to deposit LP tokens for OLAS.
/// @notice Only depository contract can call this function.
/// @param account Account address making a deposit of LP tokens for OLAS.
/// @param tokenAmount Token amount to get OLAS for.
/// @param token Token address.
/// @param olasMintAmount Amount of OLAS token issued.
/// #if_succeeds {:msg "we do not touch the total eth balance"} old(address(this).balance) == address(this).balance;
/// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
/// #if_succeeds {:msg "OLAS balances"} IToken(olas).balanceOf(msg.sender) == old(IToken(olas).balanceOf(msg.sender)) + olasMintAmount;
/// #if_succeeds {:msg "OLAS supply"} IToken(olas).totalSupply() == old(IToken(olas).totalSupply()) + olasMintAmount;
function depositTokenForOLAS(address account, uint256 tokenAmount, address token, uint256 olasMintAmount) external {
// Check for the depository access
if (depository != msg.sender) {
revert ManagerOnly(msg.sender, depository);
}
// Check if the token is authorized by the registry
if (!mapEnabledTokens[token]) {
revert UnauthorizedToken(token);
}
// Increase the amount of LP token reserves
uint256 reserves = mapTokenReserves[token] + tokenAmount;
mapTokenReserves[token] = reserves;
// Uniswap allowance implementation does not revert with the accurate message, need to check before the transfer is engaged
if (IToken(token).allowance(account, address(this)) < tokenAmount) {
revert InsufficientAllowance(IToken(token).allowance((account), address(this)), tokenAmount);
}
// Transfer tokens from account to treasury and add to the token treasury reserves
// We assume that authorized LP tokens in the protocol are safe as they are enabled via the governance
// UniswapV2ERC20 realization has a standard transferFrom() function that returns a boolean value
bool success = IToken(token).transferFrom(account, address(this), tokenAmount);
if (!success) {
revert TransferFailed(token, account, address(this), tokenAmount);
}
// Mint specified number of OLAS tokens corresponding to tokens bonding deposit
// The olasMintAmount is guaranteed by the product supply limit, which is limited by the effectiveBond
IOLAS(olas).mint(msg.sender, olasMintAmount);
emit DepositTokenFromAccount(account, token, tokenAmount, olasMintAmount);
}
/// @dev Deposits service donations in ETH.
/// @notice Each provided service Id must be deployed at least once, otherwise its components and agents are undefined.
/// @notice If a specific service is terminated with agent Ids being updated, incentives will be issued to its old
/// configuration component / agent owners until the service is re-deployed when new agent Ids are accounted for.
/// @param serviceIds Set of service Ids.
/// @param amounts Set of corresponding amounts deposited on behalf of each service Id.
/// #if_succeeds {:msg "we do not touch the owners balance"} old(ETHOwned) == ETHOwned;
/// #if_succeeds {:msg "updated ETHFromServices"} old(ETHFromServices) + msg.value + old(ETHOwned) <= type(uint96).max && ETHFromServices == old(ETHFromServices) + msg.value
/// ==> address(this).balance == ETHFromServices + ETHOwned;
/// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
function depositServiceDonationsETH(uint256[] memory serviceIds, uint256[] memory amounts) external payable {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check that the amount donated has at least a practical minimal value
if (msg.value < minAcceptedETH) {
revert LowerThan(msg.value, minAcceptedETH);
}
// Check for the same length of arrays
uint256 numServices = serviceIds.length;
if (amounts.length != numServices) {
revert WrongArrayLength(numServices, amounts.length);
}
uint256 totalAmount;
for (uint256 i = 0; i < numServices; ++i) {
if (amounts[i] == 0) {
revert ZeroValue();
}
totalAmount += amounts[i];
}
// Check if the total transferred amount corresponds to the sum of amounts from services
if (msg.value != totalAmount) {
revert WrongAmount(msg.value, totalAmount);
}
// Accumulate received donation from services
uint256 donationETH = ETHFromServices + msg.value;
// Check for the overflow values, specifically when fuzzing, since realistically these amounts are assumed to be not possible
if (donationETH + ETHOwned > type(uint96).max) {
revert Overflow(donationETH, type(uint96).max);
}
ETHFromServices = uint96(donationETH);
emit DonateToServicesETH(msg.sender, serviceIds, amounts, msg.value);
// Track service donations on the Tokenomics side
ITokenomics(tokenomics).trackServiceDonations(msg.sender, serviceIds, amounts, msg.value);
_locked = 1;
}
/// @dev Allows owner to transfer tokens from treasury reserves to a specified address.
/// @param to Address to transfer funds to.
/// @param tokenAmount Token amount to get reserves from.
/// @param token Token or ETH address.
/// @return success True if the transfer is successful.
/// #if_succeeds {:msg "we do not touch the balance of developers"} old(ETHFromServices) == ETHFromServices;
/// #if_succeeds {:msg "updated ETHOwned"} token == ETH_TOKEN_ADDRESS ==> ETHOwned == old(ETHOwned) - tokenAmount;
/// #if_succeeds {:msg "ETH balance"} token == ETH_TOKEN_ADDRESS ==> address(this).balance == old(address(this).balance) - tokenAmount;
/// #if_succeeds {:msg "updated token reserves"} token != ETH_TOKEN_ADDRESS ==> mapTokenReserves[token] == old(mapTokenReserves[token]) - tokenAmount;
/// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
function withdraw(address to, uint256 tokenAmount, address token) external returns (bool success) {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check that the withdraw address is not treasury itself
if (to == address(this)) {
revert TransferFailed(token, address(this), to, tokenAmount);
}
// Check for the zero withdraw amount
if (tokenAmount == 0) {
revert ZeroValue();
}
// ETH address is taken separately, and all the LP tokens must be validated with corresponding token reserves
if (token == ETH_TOKEN_ADDRESS) {
uint256 amountOwned = ETHOwned;
// Check if treasury has enough amount of owned ETH
if (amountOwned >= tokenAmount) {
// This branch is used to transfer ETH to a specified address
amountOwned -= tokenAmount;
ETHOwned = uint96(amountOwned);
emit Withdraw(ETH_TOKEN_ADDRESS, to, tokenAmount);
// Send ETH to the specified address
(success, ) = to.call{value: tokenAmount}("");
if (!success) {
revert TransferFailed(ETH_TOKEN_ADDRESS, address(this), to, tokenAmount);
}
} else {
// Insufficient amount of treasury owned ETH
revert LowerThan(tokenAmount, amountOwned);
}
} else {
// Only approved token reserves can be used for redemptions
if (!mapEnabledTokens[token]) {
revert UnauthorizedToken(token);
}
// Decrease the global LP token reserves record
uint256 reserves = mapTokenReserves[token];
if (reserves >= tokenAmount) {
reserves -= tokenAmount;
mapTokenReserves[token] = reserves;
emit Withdraw(token, to, tokenAmount);
// Transfer LP tokens
// We assume that LP tokens enabled in the protocol are safe by default
// UniswapV2ERC20 realization has a standard transfer() function
success = IToken(token).transfer(to, tokenAmount);
if (!success) {
revert TransferFailed(token, address(this), to, tokenAmount);
}
} else {
// Insufficient amount of LP tokens
revert LowerThan(tokenAmount, reserves);
}
}
}
/// @dev Withdraws ETH and / or OLAS amounts to the requested account address.
/// @notice Only dispenser contract can call this function.
/// @notice Reentrancy guard is on a dispenser side.
/// @notice Zero account address is not possible, since the dispenser contract interacts with msg.sender.
/// @param account Account address.
/// @param accountRewards Amount of account rewards.
/// @param accountTopUps Amount of account top-ups.
/// @return success True if the function execution is successful.
/// #if_succeeds {:msg "we do not touch the owners balance"} old(ETHOwned) == ETHOwned;
/// #if_succeeds {:msg "updated ETHFromServices"} accountRewards > 0 && ETHFromServices >= accountRewards ==> ETHFromServices == old(ETHFromServices) - accountRewards;
/// #if_succeeds {:msg "ETH balance"} accountRewards > 0 && ETHFromServices >= accountRewards ==> address(this).balance == old(address(this).balance) - accountRewards;
/// #if_succeeds {:msg "updated OLAS balances"} accountTopUps > 0 ==> IToken(olas).balanceOf(account) == old(IToken(olas).balanceOf(account)) + accountTopUps;
/// #if_succeeds {:msg "OLAS supply"} IToken(olas).totalSupply() == old(IToken(olas).totalSupply()) + accountTopUps;
/// #if_succeeds {:msg "unpaused"} paused == 1;
function withdrawToAccount(address account, uint256 accountRewards, uint256 accountTopUps) external
returns (bool success)
{
// Check if the contract is paused
if (paused == 2) {
revert Paused();
}
// Check for the dispenser access
if (dispenser != msg.sender) {
revert ManagerOnly(msg.sender, dispenser);
}
uint256 amountETHFromServices = ETHFromServices;
// Send ETH rewards, if any
if (accountRewards > 0 && amountETHFromServices >= accountRewards) {
amountETHFromServices -= accountRewards;
ETHFromServices = uint96(amountETHFromServices);
emit Withdraw(ETH_TOKEN_ADDRESS, account, accountRewards);
(success, ) = account.call{value: accountRewards}("");
if (!success) {
revert TransferFailed(address(0), address(this), account, accountRewards);
}
}
// Send OLAS top-ups
if (accountTopUps > 0) {
// Tokenomics has already accounted for the account's top-up amount,
// thus the the mint does not break the inflation schedule
IOLAS(olas).mint(account, accountTopUps);
success = true;
emit Withdraw(olas, account, accountTopUps);
}
}
/// @dev Re-balances treasury funds to account for the treasury reward for a specific epoch.
/// @param treasuryRewards Treasury rewards.
/// @return success True, if the function execution is successful.
/// #if_succeeds {:msg "we do not touch the total eth balance"} old(address(this).balance) == address(this).balance;
/// #if_succeeds {:msg "conservation law"} old(ETHFromServices + ETHOwned) == ETHFromServices + ETHOwned;
/// #if_succeeds {:msg "unpaused"} paused == 1;
function rebalanceTreasury(uint256 treasuryRewards) external returns (bool success) {
// Check if the contract is paused
if (paused == 2) {
revert Paused();
}
// Check for the tokenomics contract access
if (msg.sender != tokenomics) {
revert ManagerOnly(msg.sender, tokenomics);
}
// Collect treasury's own reward share
success = true;
if (treasuryRewards > 0) {
uint256 amountETHFromServices = ETHFromServices;
if (amountETHFromServices >= treasuryRewards) {
// Update ETH from services value
amountETHFromServices -= treasuryRewards;
// Update treasury ETH owned values
uint256 amountETHOwned = ETHOwned;
amountETHOwned += treasuryRewards;
// Assign back to state variables
ETHOwned = uint96(amountETHOwned);
ETHFromServices = uint96(amountETHFromServices);
emit UpdateTreasuryBalances(amountETHOwned, amountETHFromServices);
} else {
// There is not enough amount from services to allocate to the treasury
success = false;
}
}
}
/// @dev Drains slashed funds from the service registry.
/// @return amount Drained amount.
/// #if_succeeds {:msg "correct update total eth balance"} address(this).balance == old(address(this).balance) + amount;
/// #if_succeeds {:msg "conservation law"} ETHFromServices + ETHOwned == old(ETHFromServices + ETHOwned) + amount;
///if_succeeds {:msg "slashed funds check"} IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds() >= minAcceptedETH
/// ==> old(IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds()) == amount;
function drainServiceSlashedFunds() external returns (uint256 amount) {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Get the service registry contract address
address serviceRegistry = ITokenomics(tokenomics).serviceRegistry();
// Check if the amount of slashed funds are at least the minimum required amount to receive by the Treasury
uint256 slashedFunds = IServiceRegistry(serviceRegistry).slashedFunds();
if (slashedFunds < minAcceptedETH) {
revert LowerThan(slashedFunds, minAcceptedETH);
}
// Call the service registry drain function
amount = IServiceRegistry(serviceRegistry).drain();
}
/// @dev Enables an LP token to be bonded for OLAS.
/// @param token Token address.
function enableToken(address token) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address token
if (token == address(0)) {
revert ZeroAddress();
}
// Authorize the token
if (!mapEnabledTokens[token]) {
mapEnabledTokens[token] = true;
emit EnableToken(token);
}
}
/// @dev Disables an LP token from the ability to bond for OLAS.
/// @param token Token address.
function disableToken(address token) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
if (mapEnabledTokens[token]) {
// The reserves of a token must be zero in order to disable it
if (mapTokenReserves[token] > 0) {
revert NonZeroValue();
}
mapEnabledTokens[token] = false;
emit DisableToken(token);
}
}
/// @dev Gets information about token being enabled for bonding.
/// @param token Token address.
/// @return enabled True if token is enabled.
function isEnabled(address token) external view returns (bool enabled) {
enabled = mapEnabledTokens[token];
}
/// @dev Pauses the contract.
function pause() external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
paused = 2;
emit PauseTreasury();
}
/// @dev Unpauses the contract.
function unpause() external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
paused = 1;
emit UnpauseTreasury();
}
}