forked from 1inch/merkle-distribution
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #149 from threshold-network/with-taco
New Threshold rewards distribution contract
- Loading branch information
Showing
17 changed files
with
11,421 additions
and
25,508 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Start using a common code style with Prettier and ESLint | ||
ca739bb30b6c4f251cac9948d94c75abbbc7401d | ||
88189677d296d8b832b248bc24f4cd2478c3fcc1 | ||
48d52958a80fda49f9b92d6887140f23119d7ce8 |
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
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
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,265 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.9; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import "./interfaces/IRewardsAggregator.sol"; | ||
import "./interfaces/ICumulativeMerkleDrop.sol"; | ||
import "./interfaces/IApplication.sol"; | ||
|
||
/** | ||
* @title Rewards Aggregator | ||
* @notice RewardsAggregator is the contract responsible for the distribution | ||
* of Threshold Network staking rewards, which are generated by two | ||
* different sources: a Merkle tree distribution and the Threshold | ||
* Network applications. | ||
*/ | ||
contract RewardsAggregator is Ownable, IRewardsAggregator { | ||
using SafeERC20 for IERC20; | ||
using MerkleProof for bytes32[]; | ||
|
||
address public immutable override token; | ||
address public rewardsHolder; | ||
|
||
bytes32 public override merkleRoot; | ||
mapping(address => uint256) internal cumulativeClaimed; | ||
|
||
// TODO: Generalize to an array of IApplication in the future. | ||
// For the moment, it will only be used for TACo app. | ||
IApplication public immutable application; | ||
|
||
ICumulativeMerkleDrop public immutable oldCumulativeMerkleDrop; | ||
|
||
struct MerkleClaim { | ||
address stakingProvider; | ||
address beneficiary; | ||
uint256 amount; | ||
bytes32[] proof; | ||
} | ||
|
||
constructor( | ||
address _token, | ||
IApplication _application, | ||
ICumulativeMerkleDrop _oldCumulativeMerkleDrop, | ||
address _rewardsHolder, | ||
address newOwner | ||
) { | ||
require(IERC20(_token).totalSupply() > 0, "Token contract must be set"); | ||
require( | ||
_rewardsHolder != address(0), | ||
"Rewards Holder must be an address" | ||
); | ||
require( | ||
address(_application) != address(0), | ||
"Application must be an address" | ||
); | ||
require( | ||
_token == _oldCumulativeMerkleDrop.token(), | ||
"Incompatible old Merkle Distribution contract" | ||
); | ||
|
||
transferOwnership(newOwner); | ||
token = _token; | ||
application = _application; | ||
rewardsHolder = _rewardsHolder; | ||
oldCumulativeMerkleDrop = _oldCumulativeMerkleDrop; | ||
} | ||
|
||
function setMerkleRoot(bytes32 _merkleRoot) external override onlyOwner { | ||
emit MerkleRootUpdated(merkleRoot, _merkleRoot); | ||
merkleRoot = _merkleRoot; | ||
} | ||
|
||
/** | ||
* @notice Sets the address from where Merkle rewards are being pulled. | ||
*/ | ||
function setMerkleRewardsHolder(address _rewardsHolder) external onlyOwner { | ||
require( | ||
_rewardsHolder != address(0), | ||
"Rewards Holder must be an address" | ||
); | ||
emit RewardsHolderUpdated(rewardsHolder, _rewardsHolder); | ||
rewardsHolder = _rewardsHolder; | ||
} | ||
|
||
/** | ||
* @notice Returns the amount of rewards that a given stake has already | ||
* claimed from the Merkle distribution, including the old Merkle | ||
* distribution contract. The returned amount does not include the | ||
* claimed rewards generated by the applications. | ||
*/ | ||
function cumulativeMerkleClaimed( | ||
address stakingProvider | ||
) public view returns (uint256) { | ||
uint256 newAmount = cumulativeClaimed[stakingProvider]; | ||
if (newAmount > 0) { | ||
return newAmount; | ||
} else { | ||
return oldCumulativeMerkleDrop.cumulativeClaimed(stakingProvider); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Claim the rewards that have been generated by the Merkle | ||
* distribution mechanism. | ||
*/ | ||
function claimMerkle( | ||
address stakingProvider, | ||
address beneficiary, | ||
uint256 merkleCumulativeAmount, | ||
bytes32 expectedMerkleRoot, | ||
bytes32[] calldata merkleProof | ||
) public { | ||
require(merkleRoot == expectedMerkleRoot, "Merkle root was updated"); | ||
|
||
// Verify the merkle proof | ||
bytes32 leaf = keccak256( | ||
abi.encodePacked(stakingProvider, beneficiary, merkleCumulativeAmount) | ||
); | ||
require( | ||
verifyMerkleProof(merkleProof, expectedMerkleRoot, leaf), | ||
"Invalid proof" | ||
); | ||
|
||
// Mark it claimed (potentially taking into consideration state in old | ||
// MerkleDistribution contract) | ||
uint256 preclaimed = cumulativeMerkleClaimed(stakingProvider); | ||
require(preclaimed < merkleCumulativeAmount, "Nothing to claim"); | ||
cumulativeClaimed[stakingProvider] = merkleCumulativeAmount; | ||
|
||
// Send the tokens | ||
unchecked { | ||
uint256 amount = merkleCumulativeAmount - preclaimed; | ||
IERC20(token).safeTransferFrom(rewardsHolder, beneficiary, amount); | ||
emit MerkleClaimed( | ||
stakingProvider, | ||
amount, | ||
beneficiary, | ||
expectedMerkleRoot | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Check if a particular stake has available rewards to claim. | ||
*/ | ||
function canClaimApps(address stakingProvider) public view returns (bool) { | ||
return application.availableRewards(stakingProvider) > 0; | ||
} | ||
|
||
/** | ||
* @notice Claim the rewards generated by the Threshold Network | ||
* applications. | ||
*/ | ||
function claimApps(address stakingProvider) public { | ||
application.withdrawRewards(stakingProvider); | ||
} | ||
|
||
/** | ||
* @notice Claim the rewards generated by both the Merkle distributions | ||
* and the Threshold Network applications. | ||
*/ | ||
function claim( | ||
address stakingProvider, | ||
address beneficiary, | ||
uint256 merkleCumulativeAmount, | ||
bytes32 expectedMerkleRoot, | ||
bytes32[] calldata merkleProof | ||
) public { | ||
if ( | ||
merkleCumulativeAmount != 0 && | ||
expectedMerkleRoot != bytes32(0) && | ||
merkleProof.length != 0 | ||
) { | ||
claimMerkle( | ||
stakingProvider, | ||
beneficiary, | ||
merkleCumulativeAmount, | ||
expectedMerkleRoot, | ||
merkleProof | ||
); | ||
} | ||
if (canClaimApps(stakingProvider)) { | ||
claimApps(stakingProvider); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Claim the rewards generated by the Threshold Network | ||
* applications. | ||
*/ | ||
function claim(address stakingProvider) public { | ||
claimApps(stakingProvider); | ||
} | ||
|
||
/** | ||
* @notice Claim a batch of rewards generated by the Merkle distributions. | ||
*/ | ||
function batchClaimMerkle( | ||
bytes32 expectedMerkleRoot, | ||
MerkleClaim[] calldata Claims | ||
) external { | ||
for (uint i; i < Claims.length; i++) { | ||
claimMerkle( | ||
Claims[i].stakingProvider, | ||
Claims[i].beneficiary, | ||
Claims[i].amount, | ||
expectedMerkleRoot, | ||
Claims[i].proof | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Claim a batch of rewards generated by the Threshold Network | ||
* applications. | ||
*/ | ||
function batchClaimApps(address[] calldata stakingProviders) external { | ||
for (uint i; i < stakingProviders.length; i++) { | ||
claimApps(stakingProviders[i]); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Claim a batch of rewards generated by both the Merkle | ||
* distribution and the Threshold Network applications. | ||
*/ | ||
function batchClaim( | ||
bytes32 expectedMerkleRoot, | ||
MerkleClaim[] calldata Claims | ||
) external { | ||
for (uint i; i < Claims.length; i++) { | ||
claim( | ||
Claims[i].stakingProvider, | ||
Claims[i].beneficiary, | ||
Claims[i].amount, | ||
expectedMerkleRoot, | ||
Claims[i].proof | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Claim a batch of rewards generated by the Threshold Network | ||
* applications. | ||
*/ | ||
function batchClaim(address[] calldata stakingProviders) external { | ||
for (uint i; i < stakingProviders.length; i++) { | ||
claim(stakingProviders[i]); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Check if a merkle proof is valid. | ||
*/ | ||
function verifyMerkleProof( | ||
bytes32[] calldata merkleProof, | ||
bytes32 root, | ||
bytes32 leaf | ||
) public pure returns (bool) { | ||
return merkleProof.verify(root, leaf); | ||
} | ||
} |
Oops, something went wrong.