-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathRewards.sol
More file actions
190 lines (147 loc) · 6.7 KB
/
Rewards.sol
File metadata and controls
190 lines (147 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IBarn.sol";
contract Rewards is Ownable {
using SafeMath for uint256;
uint256 constant decimals = 10 ** 18;
struct Pull {
address source;
uint256 startTs;
uint256 endTs;
uint256 totalDuration;
uint256 totalAmount;
}
Pull public pullFeature;
bool public disabled;
uint256 public lastPullTs;
uint256 public balanceBefore;
uint256 public currentMultiplier;
mapping(address => uint256) public userMultiplier;
mapping(address => uint256) public owed;
IBarn public barn;
IERC20 public rewardToken;
event Claim(address indexed user, uint256 amount);
constructor(address _owner, address _token, address _barn) {
require(_token != address(0), "reward token must not be 0x0");
require(_barn != address(0), "barn address must not be 0x0");
transferOwnership(_owner);
rewardToken = IERC20(_token);
barn = IBarn(_barn);
}
// registerUserAction is called by the Barn every time the user does a deposit or withdrawal in order to
// account for the changes in reward that the user should get
// it updates the amount owed to the user without transferring the funds
function registerUserAction(address user) public {
require(msg.sender == address(barn), 'only callable by barn');
_calculateOwed(user);
}
// claim calculates the currently owed reward and transfers the funds to the user
function claim() public returns (uint256){
_calculateOwed(msg.sender);
uint256 amount = owed[msg.sender];
require(amount > 0, "nothing to claim");
owed[msg.sender] = 0;
rewardToken.transfer(msg.sender, amount);
// acknowledge the amount that was transferred to the user
ackFunds();
emit Claim(msg.sender, amount);
return amount;
}
// ackFunds checks the difference between the last known balance of `token` and the current one
// if it goes up, the multiplier is re-calculated
// if it goes down, it only updates the known balance
function ackFunds() public {
uint256 balanceNow = rewardToken.balanceOf(address(this));
if (balanceNow == 0 || balanceNow <= balanceBefore) {
balanceBefore = balanceNow;
return;
}
uint256 totalStakedBond = barn.bondStaked();
// if there's no bond staked, it doesn't make sense to ackFunds because there's nobody to distribute them to
// and the calculation would fail anyways due to division by 0
if (totalStakedBond == 0) {
return;
}
uint256 diff = balanceNow.sub(balanceBefore);
uint256 multiplier = currentMultiplier.add(diff.mul(decimals).div(totalStakedBond));
balanceBefore = balanceNow;
currentMultiplier = multiplier;
}
// setupPullToken is used to setup the rewards system; only callable by contract owner
// set source to address(0) to disable the functionality
function setupPullToken(address source, uint256 startTs, uint256 endTs, uint256 amount) public {
require(msg.sender == owner(), "!owner");
require(!disabled, "contract is disabled");
if (pullFeature.source != address(0)) {
require(source == address(0), "contract is already set up, source must be 0x0");
disabled = true;
} else {
require(source != address(0), "contract is not setup, source must be != 0x0");
}
if (source == address(0)) {
require(startTs == 0, "disable contract: startTs must be 0");
require(endTs == 0, "disable contract: endTs must be 0");
require(amount == 0, "disable contract: amount must be 0");
} else {
require(endTs > startTs, "setup contract: endTs must be greater than startTs");
require(amount > 0, "setup contract: amount must be greater than 0");
}
pullFeature.source = source;
pullFeature.startTs = startTs;
pullFeature.endTs = endTs;
pullFeature.totalDuration = endTs.sub(startTs);
pullFeature.totalAmount = amount;
if (lastPullTs < startTs) {
lastPullTs = startTs;
}
}
// setBarn sets the address of the BarnBridge Barn into the state variable
function setBarn(address _barn) public {
require(_barn != address(0), 'barn address must not be 0x0');
require(msg.sender == owner(), '!owner');
barn = IBarn(_barn);
}
// _pullToken calculates the amount based on the time passed since the last pull relative
// to the total amount of time that the pull functionality is active and executes a transferFrom from the
// address supplied as `pullTokenFrom`, if enabled
function _pullToken() internal {
if (
pullFeature.source == address(0) ||
block.timestamp < pullFeature.startTs
) {
return;
}
uint256 timestampCap = pullFeature.endTs;
if (block.timestamp < pullFeature.endTs) {
timestampCap = block.timestamp;
}
if (lastPullTs >= timestampCap) {
return;
}
uint256 timeSinceLastPull = timestampCap.sub(lastPullTs);
uint256 shareToPull = timeSinceLastPull.mul(decimals).div(pullFeature.totalDuration);
uint256 amountToPull = pullFeature.totalAmount.mul(shareToPull).div(decimals);
lastPullTs = block.timestamp;
rewardToken.transferFrom(pullFeature.source, address(this), amountToPull);
}
// _calculateOwed calculates and updates the total amount that is owed to an user and updates the user's multiplier
// to the current value
// it automatically attempts to pull the token from the source and acknowledge the funds
function _calculateOwed(address user) internal {
_pullToken();
ackFunds();
uint256 reward = _userPendingReward(user);
owed[user] = owed[user].add(reward);
userMultiplier[user] = currentMultiplier;
}
// _userPendingReward calculates the reward that should be based on the current multiplier / anything that's not included in the `owed[user]` value
// it does not represent the entire reward that's due to the user unless added on top of `owed[user]`
function _userPendingReward(address user) internal view returns (uint256) {
uint256 multiplier = currentMultiplier.sub(userMultiplier[user]);
return barn.balanceOf(user).mul(multiplier).div(decimals);
}
}