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

contracts: Sushiswap OHM/LQTY rewards #704

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

import "../../Dependencies/IERC20.sol";

// solhint-disable avoid-low-level-calls

library BoringERC20 {
bytes4 private constant SIG_SYMBOL = 0x95d89b41; // symbol()
bytes4 private constant SIG_NAME = 0x06fdde03; // name()
bytes4 private constant SIG_DECIMALS = 0x313ce567; // decimals()
bytes4 private constant SIG_BALANCE_OF = 0x70a08231; // balanceOf(address)
bytes4 private constant SIG_TRANSFER = 0xa9059cbb; // transfer(address,uint256)
bytes4 private constant SIG_TRANSFER_FROM = 0x23b872dd; // transferFrom(address,address,uint256)

function returnDataToString(bytes memory data) internal pure returns (string memory) {
if (data.length >= 64) {
return abi.decode(data, (string));
} else if (data.length == 32) {
uint8 i = 0;
while(i < 32 && data[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && data[i] != 0; i++) {
bytesArray[i] = data[i];
}
return string(bytesArray);
} else {
return "???";
}
}

/// @notice Provides a safe ERC20.symbol version which returns '???' as fallback string.
/// @param token The address of the ERC-20 token contract.
/// @return (string) Token symbol.
function safeSymbol(IERC20 token) internal view returns (string memory) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_SYMBOL));
return success ? returnDataToString(data) : "???";
}

/// @notice Provides a safe ERC20.name version which returns '???' as fallback string.
/// @param token The address of the ERC-20 token contract.
/// @return (string) Token name.
function safeName(IERC20 token) internal view returns (string memory) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_NAME));
return success ? returnDataToString(data) : "???";
}

/// @notice Provides a safe ERC20.decimals version which returns '18' as fallback value.
/// @param token The address of the ERC-20 token contract.
/// @return (uint8) Token decimals.
function safeDecimals(IERC20 token) internal view returns (uint8) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_DECIMALS));
return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
}

/// @notice Provides a gas-optimized balance check to avoid a redundant extcodesize check in addition to the returndatasize check.
/// @param token The address of the ERC-20 token.
/// @param to The address of the user to check.
/// @return amount The token amount.
function safeBalanceOf(IERC20 token, address to) internal view returns (uint256 amount) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_BALANCE_OF, to));
require(success && data.length >= 32, "BoringERC20: BalanceOf failed");
amount = abi.decode(data, (uint256));
}

/// @notice Provides a safe ERC20.transfer version for different ERC-20 implementations.
/// Reverts on a failed transfer.
/// @param token The address of the ERC-20 token.
/// @param to Transfer tokens to.
/// @param amount The token amount.
function safeTransfer(
IERC20 token,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(SIG_TRANSFER, to, amount));
require(success && (data.length == 0 || abi.decode(data, (bool))), "BoringERC20: Transfer failed");
}

/// @notice Provides a safe ERC20.transferFrom version for different ERC-20 implementations.
/// Reverts on a failed transfer.
/// @param token The address of the ERC-20 token.
/// @param from Transfer tokens from.
/// @param to Transfer tokens to.
/// @param amount The token amount.
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(SIG_TRANSFER_FROM, from, to, amount));
require(success && (data.length == 0 || abi.decode(data, (bool))), "BoringERC20: TransferFrom failed");
}
}
66 changes: 66 additions & 0 deletions packages/contracts/contracts/LPRewards/Dependencies/BoringMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

/// @notice A library for performing overflow-/underflow-safe math,
/// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math).
library BoringMath {
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}

function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}

function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow");
}

function to128(uint256 a) internal pure returns (uint128 c) {
require(a <= uint128(-1), "BoringMath: uint128 Overflow");
c = uint128(a);
}

function to64(uint256 a) internal pure returns (uint64 c) {
require(a <= uint64(-1), "BoringMath: uint64 Overflow");
c = uint64(a);
}

function to32(uint256 a) internal pure returns (uint32 c) {
require(a <= uint32(-1), "BoringMath: uint32 Overflow");
c = uint32(a);
}
}

/// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint128.
library BoringMath128 {
function add(uint128 a, uint128 b) internal pure returns (uint128 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}

function sub(uint128 a, uint128 b) internal pure returns (uint128 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}
}

/// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint64.
library BoringMath64 {
function add(uint64 a, uint64 b) internal pure returns (uint64 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}

function sub(uint64 a, uint64 b) internal pure returns (uint64 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}
}

/// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint32.
library BoringMath32 {
function add(uint32 a, uint32 b) internal pure returns (uint32 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}

function sub(uint32 a, uint32 b) internal pure returns (uint32 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.6.11;


library SignedSafeMath {
int256 constant private _INT256_MIN = -2**255;

/**
* @dev Returns the multiplication of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(int256 a, int256 b) internal pure returns (int256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}

require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow");

int256 c = a * b;
require(c / a == b, "SignedSafeMath: multiplication overflow");

return c;
}

/**
* @dev Returns the integer division of two signed integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "SignedSafeMath: division by zero");
require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow");

int256 c = a / b;

return c;
}

/**
* @dev Returns the subtraction of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(int256 a, int256 b) internal pure returns (int256) {
int256 c = a - b;
require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow");

return c;
}

/**
* @dev Returns the addition of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(int256 a, int256 b) internal pure returns (int256) {
int256 c = a + b;
require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow");

return c;
}

function toUInt256(int256 a) internal pure returns (uint256) {
require(a >= 0, "Integer < 0");
return uint256(a);
}
}
11 changes: 11 additions & 0 deletions packages/contracts/contracts/LPRewards/Interfaces/IRewarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

import "../Dependencies/BoringERC20.sol";


interface IRewarder {
using BoringERC20 for IERC20;
function onSushiReward(uint256 pid, address user, address recipient, uint256 sushiAmount, uint256 newLpAmount) external;
function pendingTokens(uint256 pid, address user, uint256 sushiAmount) external view returns (IERC20[] memory, uint256[] memory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

import "./Interfaces/IRewarder.sol";
import "./Dependencies/BoringERC20.sol";
import "./Dependencies/BoringMath.sol";


// Based on:
// https://github.com/sushiswap/sushiswap/blob/master/contracts/mocks/RewarderMock.sol
contract SushiSwapOhmLqtyRewarder is IRewarder {
using BoringMath for uint256;
using BoringERC20 for IERC20;

uint256 private immutable ohmRewardMultiplier;
IERC20 private immutable ohmToken;
uint256 private immutable lqtyRewardMultiplier;
IERC20 private immutable lqtyToken;
uint256 private constant REWARD_TOKEN_DIVISOR = 1e18;
address private immutable MASTERCHEF_V2;

// Make sure to pass correct multipliers at deployment !!
// See mainnetDeployment/test/SushiSwapOhmLqtyRewarderTest_mainnet.js for a real example
constructor (
uint256 _ohmRewardMultiplier,
IERC20 _ohmToken,
uint256 _lqtyRewardMultiplier,
IERC20 _lqtyToken,
address _MASTERCHEF_V2
) public {
ohmRewardMultiplier = _ohmRewardMultiplier;
ohmToken = _ohmToken;
lqtyRewardMultiplier = _lqtyRewardMultiplier;
lqtyToken = _lqtyToken;
MASTERCHEF_V2 = _MASTERCHEF_V2;
}

function onSushiReward (uint256, address, address to, uint256 sushiAmount, uint256) onlyMCV2 override external {
// OHM rewards
uint256 ohmPendingReward = sushiAmount.mul(ohmRewardMultiplier) / REWARD_TOKEN_DIVISOR;
Copy link
Collaborator

@RickGriff RickGriff Sep 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the OHM token uses 9-digit decimal precision:
https://etherscan.io/address/0x383518188c0c6d7730d91b2c03a03c837814a899#readContract
and SUSHI and LQTY use 18.

It seems here that ohmRewardMultiplier and lqtyRewardMultiplier variables are 18-digit decimal precision, and that's why they're divided by REWARD_TOKEN_DIVISOR.

Perhaps we can add comments to note that:

  • These multiplier variables are 18-digit precision
  • The value of ohmRewardMultiplier should take into account the different decimal precision of SUSHI and OHM when it is set at construction.

e.g. if we wanted to reward 2 OHM per SUSHI, the raw value of ohmRewardMultiplier should be 2e9. Then when calculating the OHM pending reward, we'd get:

ohmPendingReward = sushiAmount * 2e9 / 1e18

= sushiAmount * 2 / 1e9

Which is equivalent to 2 OHM per SUSHI, given OHM's 9-digit precision.

Basically at deployment we just need to remember to pass an _ohmRewardMultiplier on the order of 1e9, and _lqtyRewardMultiplier should be on the order of 1e18.

I think tests don't catch this because the OZ ERC20.sol hardcodes decimals = 18.

uint256 ohmBal = ohmToken.balanceOf(address(this));
uint256 ohmReward = ohmPendingReward > ohmBal ? ohmBal : ohmPendingReward;
if (ohmReward > 0) {
ohmToken.safeTransfer(to, ohmReward);
}

// LQTY rewards
uint256 lqtyPendingReward = sushiAmount.mul(lqtyRewardMultiplier) / REWARD_TOKEN_DIVISOR;
uint256 lqtyBal = lqtyToken.balanceOf(address(this));
uint256 lqtyReward = lqtyPendingReward > lqtyBal ? lqtyBal : lqtyPendingReward;
if (lqtyReward > 0) {
lqtyToken.safeTransfer(to, lqtyReward);
}
}

function pendingTokens(uint256, address, uint256 sushiAmount) override external view returns (IERC20[] memory rewardTokens, uint256[] memory rewardAmounts) {
IERC20[] memory _rewardTokens = new IERC20[](2);
_rewardTokens[0] = ohmToken;
_rewardTokens[1] = lqtyToken;
uint256[] memory _rewardAmounts = new uint256[](2);
_rewardAmounts[0] = sushiAmount.mul(ohmRewardMultiplier) / REWARD_TOKEN_DIVISOR;
_rewardAmounts[1] = sushiAmount.mul(lqtyRewardMultiplier) / REWARD_TOKEN_DIVISOR;
return (_rewardTokens, _rewardAmounts);
}

modifier onlyMCV2 {
require(
msg.sender == MASTERCHEF_V2,
"Only MCV2 can call this function."
);
_;
}
}
Loading