2023-04-08 21:43:42 +03:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
pragma solidity ^0.6.0;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
|
|
|
import "torn-token/contracts/ENS.sol";
|
|
|
|
import "./utils/FloatMath.sol";
|
|
|
|
|
|
|
|
/**
|
|
|
|
Let's imagine we have 1M TORN tokens for anonymity mining to distribute during 1 year (~31536000 seconds).
|
|
|
|
The contract should constantly add liquidity to a pool of claimed rewards to TORN (REWD/TORN). At any time user can exchange REWD->TORN using
|
|
|
|
this pool. The rate depends on current available TORN liquidity - the more TORN are withdrawn the worse the swap rate is.
|
|
|
|
|
|
|
|
The contract starts with some virtual balance liquidity and adds some TORN tokens every second to the balance. Users will decrease
|
|
|
|
this balance by swaps.
|
|
|
|
|
|
|
|
Exchange rate can be calculated as following:
|
|
|
|
BalanceAfter = BalanceBefore * e^(-rewardAmount/poolWeight)
|
|
|
|
tokens = BalanceBefore - BalanceAfter
|
|
|
|
*/
|
|
|
|
|
|
|
|
contract RewardSwap is EnsResolve {
|
|
|
|
using SafeMath for uint256;
|
|
|
|
|
|
|
|
uint256 public constant DURATION = 365 days;
|
|
|
|
|
|
|
|
IERC20 public immutable torn;
|
|
|
|
address public immutable miner;
|
|
|
|
uint256 public immutable startTimestamp;
|
|
|
|
uint256 public immutable initialLiquidity;
|
|
|
|
uint256 public immutable liquidity;
|
|
|
|
uint256 public tokensSold;
|
|
|
|
uint256 public poolWeight;
|
|
|
|
|
|
|
|
event Swap(address indexed recipient, uint256 pTORN, uint256 TORN);
|
|
|
|
event PoolWeightUpdated(uint256 newWeight);
|
|
|
|
|
|
|
|
modifier onlyMiner() {
|
|
|
|
require(msg.sender == miner, "Only Miner contract can call");
|
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
2023-06-12 18:50:25 +03:00
|
|
|
constructor(
|
|
|
|
bytes32 _torn,
|
|
|
|
bytes32 _miner,
|
|
|
|
uint256 _miningCap,
|
|
|
|
uint256 _initialLiquidity,
|
|
|
|
uint256 _poolWeight
|
|
|
|
) public {
|
2023-04-08 21:43:42 +03:00
|
|
|
require(_initialLiquidity <= _miningCap, "Initial liquidity should be lower than mining cap");
|
|
|
|
torn = IERC20(resolve(_torn));
|
|
|
|
miner = resolve(_miner);
|
|
|
|
initialLiquidity = _initialLiquidity;
|
|
|
|
liquidity = _miningCap.sub(_initialLiquidity);
|
|
|
|
poolWeight = _poolWeight;
|
|
|
|
startTimestamp = getTimestamp();
|
|
|
|
}
|
|
|
|
|
|
|
|
function swap(address _recipient, uint256 _amount) external onlyMiner returns (uint256) {
|
|
|
|
uint256 tokens = getExpectedReturn(_amount);
|
|
|
|
tokensSold += tokens;
|
|
|
|
require(torn.transfer(_recipient, tokens), "transfer failed");
|
|
|
|
emit Swap(_recipient, _amount, tokens);
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@dev
|
|
|
|
*/
|
|
|
|
function getExpectedReturn(uint256 _amount) public view returns (uint256) {
|
|
|
|
uint256 oldBalance = tornVirtualBalance();
|
|
|
|
int128 pow = FloatMath.neg(FloatMath.divu(_amount, poolWeight));
|
|
|
|
int128 exp = FloatMath.exp(pow);
|
|
|
|
uint256 newBalance = FloatMath.mulu(exp, oldBalance);
|
|
|
|
return oldBalance.sub(newBalance);
|
|
|
|
}
|
|
|
|
|
|
|
|
function tornVirtualBalance() public view returns (uint256) {
|
|
|
|
uint256 passedTime = getTimestamp().sub(startTimestamp);
|
|
|
|
if (passedTime < DURATION) {
|
|
|
|
return initialLiquidity.add(liquidity.mul(passedTime).div(DURATION)).sub(tokensSold);
|
|
|
|
} else {
|
|
|
|
return torn.balanceOf(address(this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setPoolWeight(uint256 _newWeight) external onlyMiner {
|
|
|
|
poolWeight = _newWeight;
|
|
|
|
emit PoolWeightUpdated(_newWeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTimestamp() public view virtual returns (uint256) {
|
|
|
|
return block.timestamp;
|
|
|
|
}
|
|
|
|
}
|