// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import "@openzeppelin/contracts/utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*
* Modified version from openzeppelin/contracts/access/Ownable.sol that allows to
* initialize the owner using a parameter in the constructor
*/
abstract contract Ownable is Context {
address private _owner;
address private _ownerCandidate;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor(address _initialOwner) {
_setOwner(_initialOwner);
}
/**
* @dev Requests transferring ownership of the contract to a new account (`_newOwnerCandidate`).
* Can only be called by the current owner.
*/
function requestTransferOwnership(address _newOwnerCandidate) public virtual onlyOwner {
require(_newOwnerCandidate != address(0), "Ownable: new owner is the zero address");
_ownerCandidate = _newOwnerCandidate;
}
function acceptTransferOwnership() public virtual {
require(_ownerCandidate == _msgSender(), "Ownable: not owner candidate");
_setOwner(_ownerCandidate);
delete _ownerCandidate;
}
function cancelTransferOwnership() public virtual onlyOwner {
delete _ownerCandidate;
}
function rejectTransferOwnership() public virtual {
require(_ownerCandidate == _msgSender(), "Ownable: not owner candidate");
delete _ownerCandidate;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Sets the owner.
*/
function _setOwner(address _newOwner) internal {
address oldOwner = _owner;
_owner = _newOwner;
emit OwnershipTransferred(oldOwner, _newOwner);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import "./utils/Ownable.sol";
/**
* @title TokenUtilityAccounting
* @author NFTfi
* @dev
*/
contract TokenUtilityAccounting is Ownable {
mapping(address => bool) public tokenLocks;
mapping(address => uint256) public weightedAvgLockTimes;
mapping(address => uint256) public amounts;
// these two only needed if we wanted to distribute a finite,
// given amount of rewards proportionally for locking times and amounts acrued
uint256 public totalWeightedAvgLockTime;
uint256 public totalAmount;
event Update(
address indexed _user,
uint256 _weightedAvgLockTime,
uint256 _acruedUserAmount,
uint256 _totalWeightedAvgLockTime,
uint256 _totalAmount
);
constructor(address _admin, address[] memory _tokenLockAddresses) Ownable(_admin) {
_addTokenLocks(_tokenLockAddresses);
}
modifier onlyTokenLock() {
require(tokenLocks[msg.sender], "Only token lock");
_;
}
function lock(address _user, uint256 _amount) external onlyTokenLock {
_updateUserWeightedAvgLockTime(_user, _amount);
_updateTotalWeightedAvgLockTime(_amount);
amounts[_user] += _amount;
totalAmount += _amount;
emit Update(_user, weightedAvgLockTimes[_user], amounts[_user], totalWeightedAvgLockTime, totalAmount);
}
function unlock(address _user, uint256 _amount) external onlyTokenLock {
amounts[_user] -= _amount;
totalAmount -= _amount;
emit Update(_user, weightedAvgLockTimes[_user], amounts[_user], totalWeightedAvgLockTime, totalAmount);
}
/**
* @dev updates weighted avg lock time for a given user based on the added amount
* @param _user -
* @param _amount - amount added
*/
function _updateUserWeightedAvgLockTime(address _user, uint256 _amount) internal {
weightedAvgLockTimes[_user] = _calculateWeightedAvgLockTime(
_amount,
amounts[_user],
weightedAvgLockTimes[_user]
);
}
/**
* @dev updates weighted avg lock time for the whole system based on the added amount
* @param _amount - amount added
*/
function _updateTotalWeightedAvgLockTime(uint256 _amount) internal {
totalWeightedAvgLockTime = _calculateWeightedAvgLockTime(_amount, totalAmount, totalWeightedAvgLockTime);
}
/**
* @dev calculates weightedAvgMultiplier virtual timestamp value with
* a new data point of token _amount weight and the current time
* This function is either called by _updateAvgMultiplierStart or has to be called after
* an explicit stake() or a deleteWithdrawRequest(), or any other possible instances,
* The function takes the existing average and it's weight (existing balance) then calculates
* it with the new value and weight with a weighted avg calculation between the 2 datapoints.
* @param _amount - amount added
* @param _oldAmount - cumulative amount before
* @param _oldWeightedAvgLockTime -
*/
function _calculateWeightedAvgLockTime(
uint256 _amount,
uint256 _oldAmount,
uint256 _oldWeightedAvgLockTime
) internal view returns (uint256) {
if (_oldAmount == 0 || _oldWeightedAvgLockTime == 0) {
// if we are at initial state with just 1 datapoint
return block.timestamp;
} else {
uint256 totalWeight = _oldAmount + _amount;
// weighted avg calculation between the old value and the new lock timestamp
return (_oldAmount * _oldWeightedAvgLockTime + _amount * block.timestamp) / totalWeight;
}
}
function _addTokenLocks(address[] memory _tokenLockAddresses) internal {
for (uint256 index = 0; index < _tokenLockAddresses.length; ++index) {
tokenLocks[_tokenLockAddresses[index]] = true;
}
}
function addTokenLocks(address[] memory _tokenLockAddresses) external onlyOwner {
_addTokenLocks(_tokenLockAddresses);
}
function removeTokenLocks(address[] memory _tokenLockAddresses) external onlyOwner {
for (uint256 index = 0; index < _tokenLockAddresses.length; ++index) {
tokenLocks[_tokenLockAddresses[index]] = false;
}
}
}
{
"compilationTarget": {
"contracts/TokenUtilityAccounting.sol": "TokenUtilityAccounting"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address[]","name":"_tokenLockAddresses","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_user","type":"address"},{"indexed":false,"internalType":"uint256","name":"_weightedAvgLockTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_acruedUserAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_totalWeightedAvgLockTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_totalAmount","type":"uint256"}],"name":"Update","type":"event"},{"inputs":[],"name":"acceptTransferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenLockAddresses","type":"address[]"}],"name":"addTokenLocks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"amounts","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelTransferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rejectTransferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenLockAddresses","type":"address[]"}],"name":"removeTokenLocks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwnerCandidate","type":"address"}],"name":"requestTransferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokenLocks","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeightedAvgLockTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weightedAvgLockTimes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]