-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathSimpleGriefing.sol
223 lines (189 loc) · 10.2 KB
/
SimpleGriefing.sol
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
pragma solidity ^0.5.13;
import "../helpers/openzeppelin-solidity/math/SafeMath.sol";
import "../modules/Griefing.sol";
import "../modules/EventMetadata.sol";
import "../modules/Operated.sol";
import "../modules/Template.sol";
/// @title SimpleGriefing
/// @author Stephane Gosselin (@thegostep) for Numerai Inc
/// @dev Security contact: security@numer.ai
/// @dev Version: 1.2.0
/// @dev State Machine: https://github.com/erasureprotocol/erasure-protocol/blob/v1.2.0/docs/state-machines/agreements/SimpleGriefing.png
/// @notice This agreement template allows a staker to grant permission to a counterparty to punish, reward, or release their stake.
/// A new instance is initialized by the factory using the `initData` received. See the `initialize()` function for details on initialization parameters.
/// Notable features:
/// - The staker can increase the stake at any time.
/// - The counterparty can increase, release, or punish the stake at any time.
/// - The agreement can be terminated by the counterparty by releasing or punishing the full stake amount. Note it is always possible for the staker to increase their stake again.
/// - Punishments use griefing which requires the counterparty to pay an appropriate amount based on the desired punishment and a predetermined ratio.
/// - An operator can optionally be defined to grant full permissions to a trusted external address or contract.
contract SimpleGriefing is Griefing, EventMetadata, Operated, Template {
using SafeMath for uint256;
Data private _data;
struct Data {
address staker;
address counterparty;
}
event Initialized(address operator, address staker, address counterparty, uint256 ratio, Griefing.RatioType ratioType, bytes metadata);
/// @notice Constructor used to initialize the agreement parameters.
/// All parameters are passed as ABI-encoded calldata to the factory. This calldata must include the function selector.
/// @dev Access Control: only factory
/// State Machine: before all
/// @param operator address of the operator that overrides access control. Optional parameter. Passing the address(0) will disable operator functionality.
/// @param staker address of the staker who owns the stake. Required parameter. This address is the only one able to retrieve the stake and cannot be changed.
/// @param counterparty address of the counterparty who has the right to reward, release, and punish the stake. Required parameter. This address cannot be changed.
/// @param ratio uint256 number (18 decimals) used to determine punishment cost. Required parameter. See Griefing module for details on valid input.
/// @param ratioType Griefing.RatioType number used to determine punishment cost. Required parameter. See Griefing module for details on valid input.
/// @param metadata bytes data (any format) to emit as event on initialization. Optional parameter.
function initialize(
address operator,
address staker,
address counterparty,
uint256 ratio,
Griefing.RatioType ratioType,
bytes memory metadata
) public initializeTemplate() {
// set storage values
_data.staker = staker;
_data.counterparty = counterparty;
// set operator
if (operator != address(0)) {
Operated._setOperator(operator);
}
// set griefing ratio
Griefing._setRatio(staker, ratio, ratioType);
// set metadata
if (metadata.length != 0) {
EventMetadata._setMetadata(metadata);
}
// log initialization params
emit Initialized(operator, staker, counterparty, ratio, ratioType, metadata);
}
// state functions
/// @notice Emit metadata event
/// @dev Access Control: operator
/// State Machine: always
/// @param metadata bytes data (any format) to emit as event
function setMetadata(bytes memory metadata) public {
// restrict access
require(Operated.isOperator(msg.sender), "only operator");
// update metadata
EventMetadata._setMetadata(metadata);
}
/// @notice Called by the staker to increase the stake
/// - tokens (ERC-20) are transfered from the caller and requires approval of this contract for appropriate amount
/// @dev Access Control: staker OR operator
/// State Machine: anytime
/// @param amountToAdd uint256 amount of NMR (18 decimals) to be added to the stake
function increaseStake(uint256 amountToAdd) public {
// restrict access
require(isStaker(msg.sender) || Operated.isOperator(msg.sender), "only staker or operator");
// add stake
Staking._addStake(_data.staker, msg.sender, amountToAdd);
}
/// @notice Called by the counterparty to increase the stake
/// - tokens (ERC-20) are transfered from the caller and requires approval of this contract for appropriate amount
/// @dev Access Control: counterparty OR operator
/// State Machine: anytime
/// @param amountToAdd uint256 amount of NMR (18 decimals) to be added to the stake
function reward(uint256 amountToAdd) public {
// restrict access
require(isCounterparty(msg.sender) || Operated.isOperator(msg.sender), "only counterparty or operator");
// add stake
Staking._addStake(_data.staker, msg.sender, amountToAdd);
}
/// @notice Called by the counterparty to punish the stake
/// - burns the punishment from the stake and a proportional amount from the counterparty balance
/// - the cost of the punishment is calculated with the `Griefing.getCost()` function using the predetermined griefing ratio
/// - tokens (ERC-20) are burned from the caller and requires approval of this contract for appropriate amount
/// @dev Access Control: counterparty OR operator
/// State Machine: anytime
/// @param punishment uint256 amount of NMR (18 decimals) to be burned from the stake
/// @param message bytes data (any format) to emit as event giving reason for the punishment
/// @return cost uint256 amount of NMR (18 decimals) it cost to perform punishment
function punish(uint256 punishment, bytes memory message) public returns (uint256 cost) {
// restrict access
require(isCounterparty(msg.sender) || Operated.isOperator(msg.sender), "only counterparty or operator");
// execute griefing
cost = Griefing._grief(msg.sender, _data.staker, punishment, message);
}
/// @notice Called by the counterparty to release the stake to the staker
/// @dev Access Control: counterparty OR operator
/// State Machine: anytime
/// @param amountToRelease uint256 amount of NMR (18 decimals) to be released from the stake
function releaseStake(uint256 amountToRelease) public {
// restrict access
require(isCounterparty(msg.sender) || Operated.isOperator(msg.sender), "only counterparty or operator");
// release stake back to the staker
Staking._takeStake(_data.staker, _data.staker, amountToRelease);
}
/// @notice Called by the operator to transfer control to new operator
/// @dev Access Control: operator
/// State Machine: anytime
/// @param operator address of the new operator
function transferOperator(address operator) public {
// restrict access
require(Operated.isOperator(msg.sender), "only operator");
// transfer operator
Operated._transferOperator(operator);
}
/// @notice Called by the operator to renounce control
/// @dev Access Control: operator
/// State Machine: anytime
function renounceOperator() public {
// restrict access
require(Operated.isOperator(msg.sender), "only operator");
// renounce operator
Operated._renounceOperator();
}
// view functions
/// @notice Get the address of the staker (if set)
/// @return staker address of the staker
function getStaker() public view returns (address staker) {
return _data.staker;
}
/// @notice Validate if the address matches the stored staker address
/// @param caller address to validate
/// @return validity bool true if matching address
function isStaker(address caller) internal view returns (bool validity) {
return caller == getStaker();
}
/// @notice Get the address of the counterparty (if set)
/// @return counterparty address of counterparty account
function getCounterparty() public view returns (address counterparty) {
return _data.counterparty;
}
/// @notice Validate if the address matches the stored counterparty address
/// @param caller address to validate
/// @return validity bool true if matching address
function isCounterparty(address caller) internal view returns (bool validity) {
return caller == getCounterparty();
}
/// @notice Get the current stake of the agreement
/// @return stake uint256 amount of NMR (18 decimals) staked
function getCurrentStake() public view returns (uint256 stake) {
return Staking.getStake(_data.staker);
}
enum AgreementStatus { isInitialized, isStaked }
/// @notice Get the status of the state machine
/// @return status AgreementStatus from the following states:
/// - isInitialized: initialized but no deposits made
/// - isStaked: stake is deposited
function getAgreementStatus() public view returns (AgreementStatus status) {
if (getCurrentStake() > 0) {
return AgreementStatus.isStaked;
} else {
return AgreementStatus.isInitialized;
}
}
/// @notice Validate if the state machine is in the AgreementStatus.isInitialized state
/// @return validity bool true if correct state
function isInitialized() internal view returns (bool validity) {
return getAgreementStatus() == AgreementStatus.isInitialized;
}
/// @notice Validate if the state machine is in the AgreementStatus.isStaked state
/// @return validity bool true if correct state
function isStaked() internal view returns (bool validity) {
return getAgreementStatus() == AgreementStatus.isStaked;
}
}