Skip to content

Commit

Permalink
Staking base updates (#328)
Browse files Browse the repository at this point in the history
* extend IERCxxxReceiver in staking bases

* remove restriction from Staking20Base: reward and staking tokens can be same now

* re-add restriction: staking and reward tokens can't be same

* add native token support, other functionality

* docs

* fix slither action

* fix slither action

* slither action v3
  • Loading branch information
kumaryash90 authored Feb 6, 2023
1 parent 79ac32b commit 3f9b050
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/slither.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
submodules: recursive

Expand Down
126 changes: 110 additions & 16 deletions contracts/base/Staking1155Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import "../extension/Multicall.sol";
import "../extension/Ownable.sol";
import "../extension/Staking1155.sol";

import "../eip/ERC165.sol";
import "../eip/interface/IERC20.sol";
import "../eip/interface/IERC1155Receiver.sol";

import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";

/**
*
Expand All @@ -31,56 +35,146 @@ import "../eip/interface/IERC20.sol";
* - Multicall capability to perform multiple actions atomically.
*
*/
contract Staking1155Base is ContractMetadata, Multicall, Ownable, Staking1155 {

/// note: This contract is provided as a base contract.
// This is to support a variety of use-cases that can be build on top of this base.
//
// Additional functionality such as deposit functions, reward-minting, etc.
// must be implemented by the deployer of this contract, as needed for their use-case.

contract Staking1155Base is ContractMetadata, Multicall, Ownable, Staking1155, ERC165, IERC1155Receiver {
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
address public rewardToken;

/// @dev The address of the native token wrapper contract.
address internal immutable nativeTokenWrapper;

/// @dev Total amount of reward tokens in the contract.
uint256 private rewardTokenBalance;

constructor(
uint256 _defaultTimeUnit,
uint256 _defaultRewardsPerUnitTime,
address _stakingToken,
address _rewardToken
address _rewardToken,
address _nativeTokenWrapper
) Staking1155(_stakingToken) {
_setupOwner(msg.sender);
_setDefaultStakingCondition(_defaultTimeUnit, _defaultRewardsPerUnitTime);

rewardToken = _rewardToken;
nativeTokenWrapper = _nativeTokenWrapper;
}

/// @dev Lets the contract receive ether to unwrap native tokens.
receive() external payable virtual {
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper.");
}

/// @dev Admin deposits reward tokens.
function depositRewardTokens(uint256 _amount) external payable nonReentrant {
_depositRewardTokens(_amount); // override this for custom logic.
}

/// @dev Admin can withdraw excess reward tokens.
function withdrawRewardTokens(uint256 _amount) external nonReentrant {
_withdrawRewardTokens(_amount); // override this for custom logic.
}

/// @notice View total rewards available in the staking contract.
function getRewardTokenBalance() external view virtual override returns (uint256 _rewardsAvailableInContract) {
return IERC20(rewardToken).balanceOf(address(this));
function getRewardTokenBalance() external view virtual override returns (uint256) {
return rewardTokenBalance;
}

/*///////////////////////////////////////////////////////////////
ERC 165 / 721 logic
//////////////////////////////////////////////////////////////*/

function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external returns (bytes4) {
require(isStaking == 2, "Direct transfer");
return this.onERC1155Received.selector;
}

function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4) {}

function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}

/*//////////////////////////////////////////////////////////////
Minting logic
//////////////////////////////////////////////////////////////*/

/**
* @dev Mint ERC20 rewards to the staker. Must override.
* @dev Mint ERC20 rewards to the staker. Override for custom logic.
*
* @param _staker Address for which to calculated rewards.
* @param _rewards Amount of tokens to be given out as reward.
*
*/
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
// Mint or transfer reward-tokens here.
// e.g.
//
// IERC20(rewardToken).transfer(_staker, _rewards);
//
// OR
//
// Use a mintable ERC20, such as thirdweb's `TokenERC20.sol`
//
// TokenERC20(rewardToken).mintTo(_staker, _rewards);
// note: The staking contract should have minter role to mint tokens.
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
rewardTokenBalance -= _rewards;
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
_staker,
_rewards,
nativeTokenWrapper
);
}

/*//////////////////////////////////////////////////////////////
Other Internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Admin deposits reward tokens -- override for custom logic.
function _depositRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");

address _rewardToken = rewardToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : rewardToken;

uint256 balanceBefore = IERC20(_rewardToken).balanceOf(address(this));
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
msg.sender,
address(this),
_amount,
nativeTokenWrapper
);
uint256 actualAmount = IERC20(_rewardToken).balanceOf(address(this)) - balanceBefore;

rewardTokenBalance += actualAmount;
}

/// @dev Admin can withdraw excess reward tokens -- override for custom logic.
function _withdrawRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");

// to prevent locking of direct-transferred tokens
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;

CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
msg.sender,
_amount,
nativeTokenWrapper
);
}

/// @dev Returns whether staking restrictions can be set in given execution context.
function _canSetStakeConditions() internal view virtual override returns (bool) {
return msg.sender == owner();
Expand Down
95 changes: 81 additions & 14 deletions contracts/base/Staking20Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import "../extension/Staking20.sol";
import "../eip/interface/IERC20.sol";
import "../eip/interface/IERC20Metadata.sol";

import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";

/**
*
* EXTENSION: Staking20
Expand All @@ -32,10 +34,20 @@ import "../eip/interface/IERC20Metadata.sol";
* - Multicall capability to perform multiple actions atomically.
*
*/

/// note: This contract is provided as a base contract.
// This is to support a variety of use-cases that can be build on top of this base.
//
// Additional functionality such as deposit functions, reward-minting, etc.
// must be implemented by the deployer of this contract, as needed for their use-case.

contract Staking20Base is ContractMetadata, Multicall, Ownable, Staking20 {
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
address public rewardToken;

/// @dev Total amount of reward tokens in the contract.
uint256 private rewardTokenBalance;

constructor(
uint256 _timeUnit,
uint256 _rewardRatioNumerator,
Expand All @@ -58,40 +70,95 @@ contract Staking20Base is ContractMetadata, Multicall, Ownable, Staking20 {
rewardToken = _rewardToken;
}

/// @dev Lets the contract receive ether to unwrap native tokens.
receive() external payable virtual {
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper.");
}

/// @dev Admin deposits reward tokens.
function depositRewardTokens(uint256 _amount) external payable nonReentrant {
_depositRewardTokens(_amount); // override this for custom logic.
}

/// @dev Admin can withdraw excess reward tokens.
function withdrawRewardTokens(uint256 _amount) external nonReentrant {
_withdrawRewardTokens(_amount); // override this for custom logic.
}

/// @notice View total rewards available in the staking contract.
function getRewardTokenBalance() external view virtual override returns (uint256 _rewardsAvailableInContract) {
return IERC20(rewardToken).balanceOf(address(this));
function getRewardTokenBalance() external view virtual override returns (uint256) {
return rewardTokenBalance;
}

/*//////////////////////////////////////////////////////////////
Minting logic
//////////////////////////////////////////////////////////////*/

/**
* @dev Mint ERC20 rewards to the staker. Must override.
* @dev Mint ERC20 rewards to the staker. Override for custom logic.
*
* @param _staker Address for which to calculated rewards.
* @param _rewards Amount of tokens to be given out as reward.
*
*/
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
// Mint or transfer reward-tokens here.
// e.g.
//
// IERC20(rewardToken).transfer(_staker, _rewards);
//
// OR
//
// Use a mintable ERC20, such as thirdweb's `TokenERC20.sol`
//
// TokenERC20(rewardToken).mintTo(_staker, _rewards);
// note: The staking contract should have minter role to mint tokens.
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
rewardTokenBalance -= _rewards;
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
_staker,
_rewards,
nativeTokenWrapper
);
}

/*//////////////////////////////////////////////////////////////
Other Internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Admin deposits reward tokens -- override for custom logic.
function _depositRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");

address _rewardToken = rewardToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : rewardToken;

uint256 balanceBefore = IERC20(_rewardToken).balanceOf(address(this));
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
msg.sender,
address(this),
_amount,
nativeTokenWrapper
);
uint256 actualAmount = IERC20(_rewardToken).balanceOf(address(this)) - balanceBefore;

rewardTokenBalance += actualAmount;
}

/// @dev Admin can withdraw excess reward tokens -- override for custom logic.
function _withdrawRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");

// to prevent locking of direct-transferred tokens
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;

CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
msg.sender,
_amount,
nativeTokenWrapper
);

// The withdrawal shouldn't reduce staking token balance. `>=` accounts for any accidental transfers.
address _stakingToken = stakingToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : stakingToken;
require(
IERC20(_stakingToken).balanceOf(address(this)) >= stakingTokenBalance,
"Staking token balance reduced."
);
}

/// @dev Returns whether staking restrictions can be set in given execution context.
function _canSetStakeConditions() internal view virtual override returns (bool) {
return msg.sender == owner();
Expand Down
Loading

0 comments on commit 3f9b050

Please sign in to comment.