Skip to content

Commit 747d806

Browse files
authored
Merge pull request #686 from bancorprotocol/rewards
Add Merkle-tree rewards airdrop contract + refactor existing tests
2 parents 848d010 + 26ff36a commit 747d806

File tree

11 files changed

+55911
-103
lines changed

11 files changed

+55911
-103
lines changed

components/Contracts.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import {
2222
NetworkSettings__factory,
2323
Owned__factory,
2424
StakingRewards__factory,
25+
StakingRewardsClaim__factory,
2526
StakingRewardsStore__factory,
2627
StandardPoolConverter__factory,
2728
StandardPoolConverterFactory__factory,
2829
TestBancorNetwork__factory,
30+
TestBancorNetworkV3__factory,
2931
TestCheckpointStore__factory,
3032
TestContractRegistryClient__factory,
3133
TestConverterFactory__factory,
@@ -157,6 +159,7 @@ const getContracts = (signer?: Signer) => {
157159
NetworkSettings: deployOrAttach('NetworkSettings', NetworkSettings__factory, signer),
158160
Owned: deployOrAttach('Owned', Owned__factory, signer),
159161
StakingRewards: deployOrAttach('StakingRewards', StakingRewards__factory, signer),
162+
StakingRewardsClaim: deployOrAttach('StakingRewardsClaim', StakingRewardsClaim__factory, signer),
160163
StakingRewardsStore: deployOrAttach('StakingRewardsStore', StakingRewardsStore__factory, signer),
161164
StandardPoolConverter: deployOrAttach('StandardPoolConverter', StandardPoolConverter__factory, signer),
162165
StandardPoolConverterFactory: deployOrAttach(
@@ -165,6 +168,7 @@ const getContracts = (signer?: Signer) => {
165168
signer
166169
),
167170
TestBancorNetwork: deployOrAttach('TestBancorNetwork', TestBancorNetwork__factory, signer),
171+
TestBancorNetworkV3: deployOrAttach('TestBancorNetworkV3', TestBancorNetworkV3__factory, signer),
168172
TestCheckpointStore: deployOrAttach('TestCheckpointStore', TestCheckpointStore__factory, signer),
169173
TestContractRegistryClient: deployOrAttach(
170174
'TestContractRegistryClient',

contracts/helpers/TestBancorNetwork.sol

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,6 @@ pragma solidity 0.6.12;
33

44
import "../BancorNetwork.sol";
55

6-
contract OldConverter {
7-
uint256 private _returnAmount;
8-
9-
constructor(uint256 initialAmount) public {
10-
_returnAmount = initialAmount;
11-
}
12-
13-
function getReturn(
14-
IReserveToken, /* sourceToken */
15-
IReserveToken, /* targetToken */
16-
uint256 /* amount */
17-
) external view returns (uint256) {
18-
return (_returnAmount);
19-
}
20-
}
21-
22-
contract NewConverter {
23-
uint256 private _returnAmount;
24-
uint256 private _fee;
25-
26-
constructor(uint256 amount, uint256 fee) public {
27-
_returnAmount = amount;
28-
_fee = fee;
29-
}
30-
31-
function getReturn(
32-
IReserveToken, /* sourceToken */
33-
IReserveToken, /* targetToken */
34-
uint256 /* amount */
35-
) external view returns (uint256, uint256) {
36-
return (_returnAmount, _fee);
37-
}
38-
}
39-
406
contract ConverterV27OrLowerWithoutFallback {
417
receive() external payable {
428
revert();
@@ -64,53 +30,9 @@ contract ConverterV28OrHigherWithFallback {
6430
}
6531

6632
contract TestBancorNetwork is BancorNetwork {
67-
OldConverter private _oldConverter;
68-
NewConverter private _newConverter;
69-
address private _networkToken;
70-
address private _bancorVault;
71-
72-
constructor(
73-
IContractRegistry registry,
74-
uint256 amount,
75-
uint256 fee
76-
) public BancorNetwork(registry) {
77-
_oldConverter = new OldConverter(amount);
78-
_newConverter = new NewConverter(amount, fee);
79-
}
33+
constructor(IContractRegistry registry) public BancorNetwork(registry) {}
8034

8135
function isV28OrHigherConverterExternal(IConverter converter) external view returns (bool) {
8236
return super._isV28OrHigherConverter(converter);
8337
}
84-
85-
function getReturnOld() external view returns (uint256, uint256) {
86-
return _getReturn(IConverter(payable(address(_oldConverter))), IReserveToken(0), IReserveToken(0), uint256(0));
87-
}
88-
89-
function getReturnNew() external view returns (uint256, uint256) {
90-
return _getReturn(IConverter(payable(address(_newConverter))), IReserveToken(0), IReserveToken(0), uint256(0));
91-
}
92-
93-
function setNetworkToken(address networkToken) external {
94-
_networkToken = networkToken;
95-
}
96-
97-
function setBancorVault(address bancorVault) external {
98-
_bancorVault = bancorVault;
99-
}
100-
101-
function migrateLiquidity(
102-
IReserveToken reserveToken,
103-
address, /* provider */
104-
uint256, /* amount */
105-
uint256 availableAmount,
106-
uint256 /* originalAmount */
107-
) external payable {
108-
if (reserveToken.isNativeToken()) {
109-
assert(msg.value == availableAmount);
110-
reserveToken.safeTransfer(_bancorVault, availableAmount);
111-
} else {
112-
require(msg.value == 0);
113-
reserveToken.safeTransferFrom(msg.sender, _bancorVault, availableAmount);
114-
}
115-
}
11638
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
2+
pragma solidity 0.6.12;
3+
4+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
import "../BancorNetwork.sol";
7+
8+
contract TestBancorNetworkV3 is BancorNetwork {
9+
IERC20 private _networkToken;
10+
address private _bancorVault;
11+
12+
constructor(IContractRegistry registry) public BancorNetwork(registry) {}
13+
14+
function setNetworkToken(IERC20 networkToken) external {
15+
_networkToken = networkToken;
16+
}
17+
18+
function setBancorVault(address bancorVault) external {
19+
_bancorVault = bancorVault;
20+
}
21+
22+
function depositFor(
23+
address, /* provider */
24+
address, /* pool */
25+
uint256 tokenAmount
26+
) external payable returns (uint256) {
27+
_networkToken.transferFrom(msg.sender, _bancorVault, tokenAmount);
28+
}
29+
30+
function migrateLiquidity(
31+
IReserveToken reserveToken,
32+
address, /* provider */
33+
uint256, /* amount */
34+
uint256 availableAmount,
35+
uint256 /* originalAmount */
36+
) external payable {
37+
if (reserveToken.isNativeToken()) {
38+
assert(msg.value == availableAmount);
39+
reserveToken.safeTransfer(_bancorVault, availableAmount);
40+
} else {
41+
require(msg.value == 0);
42+
reserveToken.safeTransferFrom(msg.sender, _bancorVault, availableAmount);
43+
}
44+
}
45+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
2+
pragma solidity 0.8.13;
3+
4+
import { MerkleProof } from "@openzeppelin/contracts-4.6.0/utils/cryptography/MerkleProof.sol";
5+
import { IERC20 } from "@openzeppelin/contracts-4.6.0/token/ERC20/IERC20.sol";
6+
7+
interface ITokenGovernance {
8+
function token() external view returns (IERC20);
9+
10+
function mint(address to, uint256 amount) external;
11+
}
12+
13+
interface IBancorNetworkV3 {
14+
function depositFor(
15+
address provider,
16+
address pool,
17+
uint256 tokenAmount
18+
) external payable returns (uint256);
19+
}
20+
21+
/**
22+
* @dev this contract allows claiming/staking V2.1 pending rewards
23+
*/
24+
contract StakingRewardsClaim {
25+
error AccessDenied();
26+
error AlreadyClaimed();
27+
error InvalidAddress();
28+
error InvalidClaim();
29+
error ZeroValue();
30+
31+
// the V3 network contract
32+
IBancorNetworkV3 private immutable _networkV3;
33+
34+
// the address of the BNT token governance
35+
ITokenGovernance private immutable _bntGovernance;
36+
37+
// the address of the BNT token
38+
IERC20 private immutable _bnt;
39+
40+
// the merkle root of the pending rewards merkle tree
41+
bytes32 private immutable _merkleRoot;
42+
43+
// the total claimed amount
44+
uint256 private _totalClaimed;
45+
46+
// a mapping of providers which have already claimed their rewards
47+
mapping(address => bool) private _claimed;
48+
49+
/**
50+
* @dev triggered when rewards are claimed
51+
*/
52+
event RewardsClaimed(address indexed provider, uint256 amount);
53+
54+
/**
55+
* @dev triggered when rewards are staked
56+
*/
57+
event RewardsStaked(address indexed provider, uint256 amount);
58+
59+
modifier validAddress(address addr) {
60+
_validAddress(addr);
61+
62+
_;
63+
}
64+
65+
modifier greaterThanZero(uint256 value) {
66+
_greaterThanZero(value);
67+
68+
_;
69+
}
70+
71+
/**
72+
* @dev initializes the merkle-tree rewards airdrop contract
73+
*/
74+
constructor(
75+
IBancorNetworkV3 initNetworkV3,
76+
ITokenGovernance initBNTGovernance,
77+
bytes32 initMerkleRoot
78+
) validAddress(address(initNetworkV3)) validAddress(address(initBNTGovernance)) {
79+
_networkV3 = initNetworkV3;
80+
_bntGovernance = initBNTGovernance;
81+
_bnt = initBNTGovernance.token();
82+
83+
_merkleRoot = initMerkleRoot;
84+
}
85+
86+
/**
87+
* @dev returns the merkle root of the pending rewards merkle tree
88+
*/
89+
function merkleRoot() external view returns (bytes32) {
90+
return _merkleRoot;
91+
}
92+
93+
/**
94+
* @dev returns the total claimed amount
95+
*/
96+
function totalClaimed() external view returns (uint256) {
97+
return _totalClaimed;
98+
}
99+
100+
/**
101+
* @dev returns whether providers have already claimed their rewards
102+
*/
103+
function hasClaimed(address account) external view returns (bool) {
104+
return _claimed[account];
105+
}
106+
107+
/**
108+
* @dev claims rewards by providing a merkle proof (a { provider, amount } leaf and a merkle path)
109+
*
110+
* requirements:
111+
*
112+
* - the claim can be only made by the beneficiary of the reward
113+
*/
114+
function claimRewards(
115+
address provider,
116+
uint256 fullAmount,
117+
bytes32[] calldata proof
118+
) external greaterThanZero(fullAmount) {
119+
_claimRewards(msg.sender, provider, fullAmount, proof, false);
120+
}
121+
122+
/**
123+
* @dev claims rewards by providing a merkle proof (a { provider, amount } leaf and a merkle path) and stakes them
124+
* in V3
125+
*
126+
* requirements:
127+
*
128+
* - the claim can be only made by the beneficiary of the reward
129+
*/
130+
function stakeRewards(
131+
address provider,
132+
uint256 fullAmount,
133+
bytes32[] calldata proof
134+
) external greaterThanZero(fullAmount) {
135+
_claimRewards(msg.sender, provider, fullAmount, proof, true);
136+
}
137+
138+
/**
139+
* @dev claims or stakes rewards
140+
*/
141+
function _claimRewards(
142+
address caller,
143+
address provider,
144+
uint256 fullAmount,
145+
bytes32[] calldata proof,
146+
bool stake
147+
) private {
148+
// allow users to opt-it for receiving their rewards
149+
if (caller != provider) {
150+
revert AccessDenied();
151+
}
152+
153+
// ensure that the user can't claim or stake rewards twice
154+
if (_claimed[provider]) {
155+
revert AlreadyClaimed();
156+
}
157+
158+
// ensure that the claim is valid
159+
bytes32 leaf = keccak256(abi.encodePacked(provider, fullAmount));
160+
if (!MerkleProof.verify(proof, _merkleRoot, leaf)) {
161+
revert InvalidClaim();
162+
}
163+
164+
_claimed[provider] = true;
165+
_totalClaimed += fullAmount;
166+
167+
if (stake) {
168+
// mint the full rewards to the contract itself and deposit them on behalf of the provider
169+
_bntGovernance.mint(address(this), fullAmount);
170+
171+
_bnt.approve(address(_networkV3), fullAmount);
172+
_networkV3.depositFor(provider, address(_bnt), fullAmount);
173+
174+
emit RewardsStaked(provider, fullAmount);
175+
} else {
176+
// mint the rewards directly to the provider
177+
_bntGovernance.mint(provider, fullAmount);
178+
179+
emit RewardsClaimed(provider, fullAmount);
180+
}
181+
}
182+
183+
/**
184+
* @dev verifies that a given address is valid
185+
*/
186+
function _validAddress(address addr) internal pure {
187+
if (addr == address(0)) {
188+
revert InvalidAddress();
189+
}
190+
}
191+
192+
/**
193+
* @dev verifies that a given amount is greater than zero
194+
*/
195+
function _greaterThanZero(uint256 value) internal pure {
196+
if (value == 0) {
197+
revert ZeroValue();
198+
}
199+
}
200+
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"!/contracts/helpers",
2020
"/artifacts",
2121
"/typechain",
22-
"/dist"
22+
"/dist",
23+
"/snapshot"
2324
],
2425
"scripts": {
2526
"build": "hardhat compile",
@@ -47,6 +48,7 @@
4748
"@nomiclabs/hardhat-ethers": "^2.0.5",
4849
"@nomiclabs/hardhat-waffle": "^2.0.3",
4950
"@openzeppelin/contracts": "3.4.0",
51+
"@openzeppelin/contracts-4.6.0": "npm:@openzeppelin/contracts@4.6.0",
5052
"@snyk/protect": "^1.913.0",
5153
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
5254
"@typechain/ethers-v5": "^10.0.0",

snapshot/snapshot-merkle-tree.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)