-
Notifications
You must be signed in to change notification settings - Fork 321
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
bingen
wants to merge
8
commits into
main
Choose a base branch
from
sushiswap_ohm_rewards
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1a21586
contracts: Add Sushiswap OHM-LQTY rewarder contract
bingen 9aa4b6b
contracts: Upgrade solidity-coverage to 0.7.17
bingen 15cb16c
contracts: Add mainnet fork test for Sushiswap rewards
bingen e800165
contracts: Add another test to Sushiswap rewarder
bingen a81365a
contracts: Only transfer Sushi rewards when amount is > 0
bingen a8fa1ec
contracts: Allow to specify decimals to logBN helper
bingen 828f1f6
contracts: Improve tests for Sushi rewarder
bingen 201c748
contracts: Add comment to Sushi rewarder
bingen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
packages/contracts/contracts/LPRewards/Dependencies/BoringERC20.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
66
packages/contracts/contracts/LPRewards/Dependencies/BoringMath.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
packages/contracts/contracts/LPRewards/Dependencies/SignedSafeMath.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
11
packages/contracts/contracts/LPRewards/Interfaces/IRewarder.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
73 changes: 73 additions & 0 deletions
73
packages/contracts/contracts/LPRewards/SushiSwapOhmLqtyRewarder.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
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." | ||
); | ||
_; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
andlqtyRewardMultiplier
variables are 18-digit decimal precision, and that's why they're divided byREWARD_TOKEN_DIVISOR
.Perhaps we can add comments to note that:
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 be2e9
. Then when calculating the OHM pending reward, we'd get: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 of1e9
, and_lqtyRewardMultiplier
should be on the order of1e18
.I think tests don't catch this because the OZ ERC20.sol hardcodes
decimals = 18
.