-
Notifications
You must be signed in to change notification settings - Fork 7
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
New Threshold rewards distribution contract #149
Changes from 78 commits
584e57a
ad1311d
3bd7087
32e4131
ee547c3
a4f6b71
63d1651
29a80a2
b1840d2
e4ed12b
b8add39
a2cdc77
9f737df
0a47f40
d7b6cc6
0cb969f
f71afb0
64ff146
62d28d6
11b5af1
a2355aa
1f29f4a
53ccbf8
092580a
e61887c
f4236f9
3a9b5c1
d34c44a
fd03263
43ecbff
40b2db6
5e8178b
e4b4d83
a55ba0c
c8fa562
962946e
e3198d9
599bcda
8e227bc
c19523d
73630dc
8456e51
f206374
59774fc
ae12768
81d597c
b790bf1
1a8f7cd
842500e
a74b53c
c5751d9
9bb2587
1d535b3
5e4e37d
e9cf8cc
402a943
b093a7f
0a6adc7
608004b
6d4c8a2
f4787e2
044a37c
6b059d8
8b5479c
23608a7
177d19c
7216e24
74a0d18
a51f0b8
68f1f7f
3f0f6df
69d9626
3508f55
b6d8db5
48d5295
687da6d
41a1c92
8846ab7
6aa0a8e
28df6ba
7694f23
86c1ecc
a906b84
69f3da9
747b3fa
2f30fc2
dd53240
a390c28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,262 @@ | ||||||
// 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"; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the open zeppelin contract dependency in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ addressed on dd53240 |
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||||||
import "@threshold-network/solidity-contracts/contracts/staking/IApplication.sol"; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the only thing we're using from this repo, I'd suggest we just copy the interface here and remove the dependency, so we don't pollute ours with their subdependencies. In fact, that would allow us to use OZ v5, otherwise we'd be stuck with v4. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Addressed on 747b3fa Note that we are using v4 of OpenZeppelin contracts because v5 is not compatible with |
||||||
|
||||||
import "./interfaces/IRewardsAggregator.sol"; | ||||||
import "./interfaces/ICumulativeMerkleDrop.sol"; | ||||||
|
||||||
/** | ||||||
* @title Rewards Aggregator | ||||||
* @notice RewardsAggregator is the contract responsible for the distribution | ||||||
* of the Threshold Network staking rewards. The rewards are generated | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ addressed on 28df6ba |
||||||
* 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, | ||||||
cygnusv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you want to keep the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Done in a390c28 ✅ |
||||||
emit MerkleRootUpdated(merkleRoot, merkleRoot_); | ||||||
merkleRoot = merkleRoot_; | ||||||
} | ||||||
|
||||||
function setRewardsHolder(address rewardsHolder_) external onlyOwner { | ||||||
cygnusv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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, | ||||||
cygnusv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
uint256 cumulativeAmount, | ||||||
cygnusv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
bytes32 expectedMerkleRoot, | ||||||
bytes32[] calldata merkleProof | ||||||
) public { | ||||||
require(merkleRoot == expectedMerkleRoot, "Merkle root was updated"); | ||||||
|
||||||
// Verify the merkle proof | ||||||
bytes32 leaf = keccak256( | ||||||
abi.encodePacked(stakingProvider, beneficiary, cumulativeAmount) | ||||||
); | ||||||
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 < cumulativeAmount, "Nothing to claim"); | ||||||
cumulativeClaimed[stakingProvider] = cumulativeAmount; | ||||||
|
||||||
// Send the tokens | ||||||
unchecked { | ||||||
uint256 amount = cumulativeAmount - 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; | ||||||
cygnusv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
/** | ||||||
* @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); | ||||||
} | ||||||
} |
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.
@vzotova Do you think we should increase the version here?
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.
good question, I'd use same as in threshold staking (which is
0.8.9
not^0.8.9
) or latest availableThere 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.
✅ addressed on 6aa0a8e