Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/reward-master-pool #95

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contracts/interfaces/IMasterChef.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.4;

interface IMasterChef {
function sushiPerBlock() external view returns (uint256);
}
21 changes: 21 additions & 0 deletions contracts/interfaces/IRewarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IRewarder {
function onSushiReward(
address pool,
address user,
address recipient,
uint256 sushiAmount,
uint256 newLpAmount
) external;

function pendingTokens(
address pool,
address user,
uint256 sushiAmount
) external view returns (IERC20[] memory, uint256[] memory);
}
90 changes: 39 additions & 51 deletions contracts/pool/HybridPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract HybridPool is IPool, TridentERC20 {
/// has 8, so the multiplier should be 10 ** 18 / 10 ** 8 => 10 ** 10.
uint256 public immutable token0PrecisionMultiplier;
uint256 public immutable token1PrecisionMultiplier;

uint256 public barFee;

uint128 internal reserve0;
Expand All @@ -59,13 +59,13 @@ contract HybridPool is IPool, TridentERC20 {

constructor(bytes memory _deployData, address _masterDeployer) {
(address _token0, address _token1, uint256 _swapFee, uint256 a) = abi.decode(_deployData, (address, address, uint256, uint256));

// @dev Factory ensures that the tokens are sorted.
require(_token0 != address(0), "ZERO_ADDRESS");
require(_token0 != _token1, "IDENTICAL_ADDRESSES");
require(_swapFee <= MAX_FEE, "INVALID_SWAP_FEE");
require(a != 0, "ZERO_A");

(, bytes memory _barFee) = _masterDeployer.staticcall(abi.encodeWithSelector(IMasterDeployer.barFee.selector));
(, bytes memory _barFeeTo) = _masterDeployer.staticcall(abi.encodeWithSelector(IMasterDeployer.barFeeTo.selector));
(, bytes memory _bento) = _masterDeployer.staticcall(abi.encodeWithSelector(IMasterDeployer.bento.selector));
Expand All @@ -85,20 +85,20 @@ contract HybridPool is IPool, TridentERC20 {
token1PrecisionMultiplier = 10**(decimals - abi.decode(_decimals1, (uint8)));
unlocked = 1;
}

/// @dev Mints LP tokens - should be called via the router after transferring `bento` tokens.
/// The router must ensure that sufficient LP tokens are minted by using the return value.
function mint(bytes calldata data) public override lock returns (uint256 liquidity) {
address recipient = abi.decode(data, (address));
(uint256 _reserve0, uint256 _reserve1) = _getReserves();
(uint256 balance0, uint256 balance1) = _balance();
uint256 _totalSupply = totalSupply;

uint256 amount0 = balance0 - _reserve0;
uint256 amount1 = balance1 - _reserve1;
(uint256 fee0, uint256 fee1) = _nonOptimalMintFee(amount0, amount1, _reserve0, _reserve1);
uint256 newLiq = _computeLiquidity(balance0 - fee0, balance1 - fee1);

if (_totalSupply == 0) {
liquidity = newLiq - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY);
Expand All @@ -111,14 +111,14 @@ contract HybridPool is IPool, TridentERC20 {
_updateReserves();
emit Mint(msg.sender, amount0, amount1, recipient);
}

/// @dev Burns LP tokens sent to this contract. The router must ensure that the user gets sufficient output tokens.
function burn(bytes calldata data) public override lock returns (IPool.TokenAmount[] memory withdrawnAmounts) {
(address recipient, bool unwrapBento) = abi.decode(data, (address, bool));
(uint256 balance0, uint256 balance1) = _balance();
uint256 _totalSupply = totalSupply;
uint256 liquidity = balanceOf[address(this)];

uint256 amount0 = (liquidity * balance0) / _totalSupply;
uint256 amount1 = (liquidity * balance1) / _totalSupply;

Expand All @@ -128,7 +128,7 @@ contract HybridPool is IPool, TridentERC20 {

balance0 -= _toShare(token0, amount0);
balance1 -= _toShare(token1, amount1);

_updateReserves();

withdrawnAmounts = new TokenAmount[](2);
Expand All @@ -137,7 +137,7 @@ contract HybridPool is IPool, TridentERC20 {

emit Burn(msg.sender, amount0, amount1, recipient);
}

/// @dev Burns LP tokens sent to this contract and swaps one of the output tokens for another
/// - i.e., the user gets a single token out by burning LP tokens.
function burnSingle(bytes calldata data) public override lock returns (uint256 amountOut) {
Expand All @@ -149,9 +149,9 @@ contract HybridPool is IPool, TridentERC20 {

uint256 amount0 = (liquidity * balance0) / _totalSupply;
uint256 amount1 = (liquidity * balance1) / _totalSupply;

_burn(address(this), liquidity);

if (tokenOut == token1) {
// @dev Swap `token0` for `token1`.
// @dev Calculate `amountOut` as if the user first withdrew balanced liquidity and then swapped `token0` for `token1`.
Expand All @@ -174,15 +174,15 @@ contract HybridPool is IPool, TridentERC20 {
_updateReserves();
emit Burn(msg.sender, amount0, amount1, recipient);
}

/// @dev Swaps one token for another. The router must prefund this contract and ensure there isn't too much slippage.
function swap(bytes calldata data) public override lock returns (uint256 amountOut) {
(address tokenIn, address recipient, bool unwrapBento) = abi.decode(data, (address, address, bool));
(uint256 _reserve0, uint256 _reserve1) = _getReserves();
(uint256 balance0, uint256 balance1) = _balance();
uint256 amountIn;
address tokenOut;

if (tokenIn == token0) {
tokenOut = token1;
amountIn = balance0 - _reserve0;
Expand All @@ -199,7 +199,7 @@ contract HybridPool is IPool, TridentERC20 {
_updateReserves();
emit Swap(recipient, tokenIn, tokenOut, amountIn, amountOut);
}

/// @dev Swaps one token for another with payload. The router must support swap callbacks and ensure there isn't too much slippage.
function flashSwap(bytes calldata data) public override lock returns (uint256 amountOut) {
(address tokenIn, address recipient, bool unwrapBento, uint256 amountIn, bytes memory context) = abi.decode(
Expand All @@ -209,7 +209,7 @@ contract HybridPool is IPool, TridentERC20 {
(uint256 _reserve0, uint256 _reserve1) = _getReserves();
address tokenOut;
uint256 fee;

if (tokenIn == token0) {
tokenOut = token1;
amountIn = _toAmount(token0, amountIn);
Expand All @@ -232,13 +232,13 @@ contract HybridPool is IPool, TridentERC20 {
_updateReserves();
emit Swap(recipient, tokenIn, tokenOut, amountIn, amountOut);
}

/// @dev Updates `barFee` for Trident protocol.
function updateBarFee() public {
(, bytes memory _barFee) = masterDeployer.staticcall(abi.encodeWithSelector(IMasterDeployer.barFee.selector));
barFee = abi.decode(_barFee, (uint256));
}

function _processSwap(
address tokenOut,
address to,
Expand All @@ -249,47 +249,44 @@ contract HybridPool is IPool, TridentERC20 {
_transfer(tokenOut, amountOut, to, unwrapBento);
if (data.length != 0) ITridentCallee(msg.sender).tridentSwapCallback(data);
}

function _getReserves() internal view returns (uint256 _reserve0, uint256 _reserve1) {
(_reserve0, _reserve1) = (reserve0, reserve1);
_reserve0 = _toAmount(token0, _reserve0);
_reserve1 = _toAmount(token1, _reserve1);
}

function _updateReserves() internal {
(uint256 _reserve0, uint256 _reserve1) = _balance();
require(_reserve0 < type(uint128).max && _reserve1 < type(uint128).max, "OVERFLOW");
reserve0 = uint128(_reserve0);
reserve1 = uint128(_reserve1);
emit Sync(_reserve0, _reserve1);
}

function _balance() internal view returns (uint256 balance0, uint256 balance1) {
balance0 = _toAmount(token0, __balance(token0));
balance1 = _toAmount(token1, __balance(token1));
}

function __balance(address token) internal view returns (uint256 balance) {
// @dev balanceOf(address,address).
(, bytes memory ___balance) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.balanceOf.selector,
token, address(this)));
(, bytes memory ___balance) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.balanceOf.selector, token, address(this)));
balance = abi.decode(___balance, (uint256));
}

function _toAmount(address token, uint256 input) internal view returns (uint256 output) {
// @dev toAmount(address,uint256,bool).
(, bytes memory _output) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.toAmount.selector,
token, input, false));
(, bytes memory _output) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.toAmount.selector, token, input, false));
output = abi.decode(_output, (uint256));
}

function _toShare(address token, uint256 input) internal view returns (uint256 output) {
// @dev toShare(address,uint256,bool).
(, bytes memory _output) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.toShare.selector,
token, input, false));
(, bytes memory _output) = bento.staticcall(abi.encodeWithSelector(IBentoBoxMinimal.toShare.selector, token, input, false));
output = abi.decode(_output, (uint256));
}

function _getAmountOut(
uint256 amountIn,
uint256 _reserve0,
Expand All @@ -298,7 +295,7 @@ contract HybridPool is IPool, TridentERC20 {
) internal view returns (uint256 dy) {
uint256 xpIn;
uint256 xpOut;

if (token0In) {
xpIn = _reserve0 * token0PrecisionMultiplier;
xpOut = _reserve1 * token1PrecisionMultiplier;
Expand All @@ -323,13 +320,11 @@ contract HybridPool is IPool, TridentERC20 {
) internal {
if (unwrapBento) {
// @dev withdraw(address,address,address,uint256,uint256).
(bool success, ) = bento.call(abi.encodeWithSelector(IBentoBoxMinimal.withdraw.selector,
token, address(this), to, amount, 0));
(bool success, ) = bento.call(abi.encodeWithSelector(IBentoBoxMinimal.withdraw.selector, token, address(this), to, amount, 0));
require(success, "WITHDRAW_FAILED");
} else {
// @dev transfer(address,address,address,uint256).
(bool success, ) = bento.call(abi.encodeWithSelector(IBentoBoxMinimal.transfer.selector,
token, address(this), to, _toShare(token, amount)));
(bool success, ) = bento.call(abi.encodeWithSelector(IBentoBoxMinimal.transfer.selector, token, address(this), to, _toShare(token, amount)));
require(success, "TRANSFER_FAILED");
}
}
Expand All @@ -346,7 +341,7 @@ contract HybridPool is IPool, TridentERC20 {

function _computeLiquidityFromAdjustedBalances(uint256 xp0, uint256 xp1) internal view returns (uint256 computed) {
uint256 s = xp0 + xp1;

if (s == 0) {
computed = 0;
}
Expand Down Expand Up @@ -406,7 +401,7 @@ contract HybridPool is IPool, TridentERC20 {
uint256 b = s + ((d * A_PRECISION) / N_A);
uint256 yPrev;
y = d;

for (uint256 i = 0; i < MAX_LOOP_LIMIT; i++) {
yPrev = y;
y = (y * y + c) / (y * 2 + b - d);
Expand All @@ -415,13 +410,13 @@ contract HybridPool is IPool, TridentERC20 {
}
}
}

function _handleFee(address tokenIn, uint256 amountIn) internal returns (uint256 fee) {
fee = (amountIn * swapFee) / MAX_FEE;
uint256 _barFee = (fee * barFee) / MAX_FEE;
_transfer(tokenIn, _barFee, barFeeTo, false);
}

/// @dev This fee is charged to cover for `swapFee` when users add unbalanced liquidity.
function _nonOptimalMintFee(
uint256 _amount0,
Expand All @@ -431,42 +426,35 @@ contract HybridPool is IPool, TridentERC20 {
) internal view returns (uint256 token0Fee, uint256 token1Fee) {
if (_reserve0 == 0 || _reserve1 == 0) return (0, 0);
uint256 amount1Optimal = (_amount0 * _reserve1) / _reserve0;

if (amount1Optimal <= _amount1) {
token1Fee = (swapFee * (_amount1 - amount1Optimal)) / (2 * MAX_FEE);
} else {
uint256 amount0Optimal = (_amount1 * _reserve0) / _reserve1;
token0Fee = (swapFee * (_amount0 - amount0Optimal)) / (2 * MAX_FEE);
}
}

function getAssets() public view override returns (address[] memory assets) {
assets = new address[](2);
assets[0] = token0;
assets[1] = token1;
}

function getAmountOut(bytes calldata data) public view override returns (uint256 finalAmountOut) {
(address tokenIn, uint256 amountIn) = abi.decode(data, (address, uint256));
(uint256 _reserve0, uint256 _reserve1) = _getReserves();
amountIn = _toAmount(tokenIn, amountIn);
amountIn -= (amountIn * swapFee) / MAX_FEE;

if (tokenIn == token0) {
finalAmountOut = _getAmountOut(amountIn, _reserve0, _reserve1, true);
} else {
finalAmountOut = _getAmountOut(amountIn, _reserve0, _reserve1, false);
}
}

function getReserves()
public
view
returns (
uint256 _reserve0,
uint256 _reserve1
)
{
function getReserves() public view returns (uint256 _reserve0, uint256 _reserve1) {
(_reserve0, _reserve1) = _getReserves();
}
}
35 changes: 35 additions & 0 deletions contracts/pool/IncentivizedPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.8.0;

import "./IndexPool.sol";
import "../rewards/RewardsManager.sol";

/// @notice A pool that simply is an incentivized version of the index pool.
contract IncentivizedPool is IndexPool {
RewardsManager public rewards;

constructor(bytes memory _deployData, address _masterDeployer) IndexPool(_deployData, _masterDeployer) {
(, , , address _rewards) = abi.decode(_deployData, (address[], uint256[], uint256, address));

rewards = RewardsManager(_rewards);
}

function _beforeTokenTransfer(
address from,
address to,
uint256
) internal override {
if (address(rewards) == address(0)) {
return;
}

if (from != address(0)) {
rewards.claimRewardsFor(this, from);
}

if (to != address(0)) {
rewards.claimRewardsFor(this, to);
}
}
}
21 changes: 21 additions & 0 deletions contracts/pool/IncentivizedPoolFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.8.0;

import "./IncentivizedPool.sol";
import "./PoolDeployer.sol";

/// @notice Contract for deploying Trident exchange Incentivized Pool with configurations.
/// @author Dean Eigenmann
contract IncentivizedPoolFactory is PoolDeployer {
constructor(address _masterDeployer) PoolDeployer(_masterDeployer) {}

function deployPool(bytes memory _deployData) external returns (address pool) {
(address[] memory tokens, , , ) = abi.decode(_deployData, (address[], uint256[], uint256, address));

// @dev Salt is not actually needed since `_deployData` is part of creationCode and already contains the salt.
bytes32 salt = keccak256(_deployData);
pool = address(new IncentivizedPool{salt: salt}(_deployData, masterDeployer));
_registerPool(pool, tokens, salt);
}
}
Loading