forked from poanetwork/posdao-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TxPermission.sol
282 lines (234 loc) · 12.1 KB
/
TxPermission.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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
pragma solidity 0.5.10;
import "./interfaces/IRandomAuRa.sol";
import "./interfaces/IStakingAuRa.sol";
import "./interfaces/ITxPermission.sol";
import "./interfaces/IValidatorSetAuRa.sol";
import "./upgradeability/UpgradeableOwned.sol";
/// @dev Controls the use of zero gas price by validators in service transactions,
/// protecting the network against "transaction spamming" by malicious validators.
/// The protection logic is declared in the `allowedTxTypes` function.
contract TxPermission is UpgradeableOwned, ITxPermission {
// =============================================== Storage ========================================================
// WARNING: since this contract is upgradeable, do not remove
// existing storage variables and do not change their types!
address[] internal _allowedSenders;
/// @dev The address of the `ValidatorSetAuRa` contract.
IValidatorSetAuRa public validatorSetContract;
// ============================================== Constants =======================================================
/// @dev A constant that defines a regular block gas limit.
/// Used by the `blockGasLimit` public getter.
uint256 public constant BLOCK_GAS_LIMIT = 10000000;
/// @dev A constant that defines a reduced block gas limit.
/// Used by the `blockGasLimit` public getter.
uint256 public constant BLOCK_GAS_LIMIT_REDUCED = 2000000;
// ============================================== Modifiers =======================================================
/// @dev Ensures the `initialize` function was called before.
modifier onlyInitialized {
require(isInitialized());
_;
}
// =============================================== Setters ========================================================
/// @dev Initializes the contract at network startup.
/// Can only be called by the constructor of the `InitializerAuRa` contract or owner.
/// @param _allowed The addresses for which transactions of any type must be allowed.
/// See the `allowedTxTypes` getter.
/// @param _validatorSet The address of the `ValidatorSetAuRa` contract.
function initialize(
address[] calldata _allowed,
address _validatorSet
) external {
require(block.number == 0 || msg.sender == _admin());
require(!isInitialized());
require(_validatorSet != address(0));
for (uint256 i = 0; i < _allowed.length; i++) {
_addAllowedSender(_allowed[i]);
}
validatorSetContract = IValidatorSetAuRa(_validatorSet);
}
/// @dev Adds the address for which transactions of any type must be allowed.
/// Can only be called by the `owner`. See also the `allowedTxTypes` getter.
/// @param _sender The address for which transactions of any type must be allowed.
function addAllowedSender(address _sender) public onlyOwner onlyInitialized {
_addAllowedSender(_sender);
}
/// @dev Removes the specified address from the array of addresses allowed
/// to initiate transactions of any type. Can only be called by the `owner`.
/// See also the `addAllowedSender` function and `allowedSenders` getter.
/// @param _sender The removed address.
function removeAllowedSender(address _sender) public onlyOwner onlyInitialized {
uint256 allowedSendersLength = _allowedSenders.length;
for (uint256 i = 0; i < allowedSendersLength; i++) {
if (_sender == _allowedSenders[i]) {
_allowedSenders[i] = _allowedSenders[allowedSendersLength - 1];
_allowedSenders.length--;
return;
}
}
}
// =============================================== Getters ========================================================
/// @dev Returns the contract's name recognizable by node's engine.
function contractName() public pure returns(string memory) {
return "TX_PERMISSION_CONTRACT";
}
/// @dev Returns the contract name hash needed for node's engine.
function contractNameHash() public pure returns(bytes32) {
return keccak256(abi.encodePacked(contractName()));
}
/// @dev Returns the contract's version number needed for node's engine.
function contractVersion() public pure returns(uint256) {
return 3;
}
/// @dev Returns the list of addresses allowed to initiate transactions of any type.
/// For these addresses the `allowedTxTypes` getter always returns the `ALL` bit mask
/// (see https://wiki.parity.io/Permissioning.html#how-it-works-1).
function allowedSenders() public view returns(address[] memory) {
return _allowedSenders;
}
/// @dev Defines the allowed transaction types which may be initiated by the specified sender with
/// the specified gas price and data. Used by node's engine each time a transaction is about to be
/// included into a block. See https://wiki.parity.io/Permissioning.html#how-it-works-1
/// @param _sender Transaction sender address.
/// @param _to Transaction recipient address. If creating a contract, the `_to` address is zero.
/// @param _value Transaction amount in wei.
/// @param _gasPrice Gas price in wei for the transaction.
/// @param _data Transaction data.
/// @return `uint32 typesMask` - Set of allowed transactions for `_sender` depending on tx `_to` address,
/// `_gasPrice`, and `_data`. The result is represented as a set of flags:
/// 0x01 - basic transaction (e.g. ether transferring to user wallet);
/// 0x02 - contract call;
/// 0x04 - contract creation;
/// 0x08 - private transaction.
/// `bool cache` - If `true` is returned, the same permissions will be applied from the same
/// `_sender` without calling this contract again.
function allowedTxTypes(
address _sender,
address _to,
uint256 _value,
uint256 _gasPrice,
bytes memory _data
)
public
view
returns(uint32 typesMask, bool cache)
{
if (isSenderAllowed(_sender)) {
// Let the `_sender` initiate any transaction if the `_sender` is in the `allowedSenders` list
return (ALL, false);
}
// Get the called function's signature
bytes4 signature = bytes4(0);
bytes memory abiParams;
uint256 i;
for (i = 0; _data.length >= 4 && i < 4; i++) {
signature |= bytes4(_data[i]) >> i*8;
}
if (_to == validatorSetContract.randomContract()) {
address randomContract = validatorSetContract.randomContract();
abiParams = new bytes(_data.length - 4 > 32 ? 32 : _data.length - 4);
for (i = 0; i < abiParams.length; i++) {
abiParams[i] = _data[i + 4];
}
if (signature == COMMIT_HASH_SIGNATURE) {
(bytes32 secretHash) = abi.decode(abiParams, (bytes32));
return (IRandomAuRa(randomContract).commitHashCallable(_sender, secretHash) ? CALL : NONE, false);
} else if (signature == REVEAL_SECRET_SIGNATURE) {
(uint256 secret) = abi.decode(abiParams, (uint256));
return (IRandomAuRa(randomContract).revealSecretCallable(_sender, secret) ? CALL : NONE, false);
} else {
return (NONE, false);
}
}
if (_to == address(validatorSetContract)) {
// The rules for the ValidatorSetAuRa contract
if (signature == EMIT_INITIATE_CHANGE_SIGNATURE) {
// The `emitInitiateChange()` can be called by anyone
// if `emitInitiateChangeCallable()` returns `true`
return (validatorSetContract.emitInitiateChangeCallable() ? CALL : NONE, false);
} else if (signature == REPORT_MALICIOUS_SIGNATURE) {
abiParams = new bytes(_data.length - 4 > 64 ? 64 : _data.length - 4);
for (i = 0; i < abiParams.length; i++) {
abiParams[i] = _data[i + 4];
}
(
address maliciousMiningAddress,
uint256 blockNumber
) = abi.decode(
abiParams,
(address, uint256)
);
// The `reportMalicious()` can only be called by the validator's mining address
// when the calling is allowed
(bool callable,) = validatorSetContract.reportMaliciousCallable(
_sender, maliciousMiningAddress, blockNumber
);
return (callable ? CALL : NONE, false);
} else if (_gasPrice > 0) {
// The other functions of ValidatorSetAuRa contract can be called
// by anyone except validators' mining addresses if gasPrice is not zero
return (validatorSetContract.isValidator(_sender) ? NONE : CALL, false);
}
}
if (validatorSetContract.isValidator(_sender) && _gasPrice > 0) {
// Let the validator's mining address send their accumulated tx fees to some wallet
return (_sender.balance > 0 ? BASIC : NONE, false);
}
if (validatorSetContract.isValidator(_to)) {
// Validator's mining address can't receive any coins
return (NONE, false);
}
// In other cases let the `_sender` create any transaction with non-zero gas price,
// don't let them use a zero gas price
return (_gasPrice > 0 ? ALL : NONE, false);
}
/// @dev Returns the current block gas limit which depends on the stage of the current
/// staking epoch: the block gas limit is temporarily reduced for the latest block of the epoch.
function blockGasLimit() public view returns(uint256) {
address stakingContract = validatorSetContract.stakingContract();
uint256 stakingEpochEndBlock = IStakingAuRa(stakingContract).stakingEpochEndBlock();
if (block.number == stakingEpochEndBlock - 1 || block.number == stakingEpochEndBlock) {
return BLOCK_GAS_LIMIT_REDUCED;
}
return BLOCK_GAS_LIMIT;
}
/// @dev Returns a boolean flag indicating if the `initialize` function has been called.
function isInitialized() public view returns(bool) {
return validatorSetContract != IValidatorSetAuRa(0);
}
/// @dev Returns a boolean flag indicating whether the specified address is allowed
/// to initiate transactions of any type. Used by the `allowedTxTypes` getter.
/// See also the `addAllowedSender` and `removeAllowedSender` functions.
/// @param _sender The specified address to check.
function isSenderAllowed(address _sender) public view returns(bool) {
uint256 allowedSendersLength = _allowedSenders.length;
for (uint256 i = 0; i < allowedSendersLength; i++) {
if (_sender == _allowedSenders[i]) {
return true;
}
}
return false;
}
// ============================================== Internal ========================================================
// Allowed transaction types mask
uint32 internal constant NONE = 0;
uint32 internal constant ALL = 0xffffffff;
uint32 internal constant BASIC = 0x01;
uint32 internal constant CALL = 0x02;
uint32 internal constant CREATE = 0x04;
uint32 internal constant PRIVATE = 0x08;
// Function signatures
// bytes4(keccak256("commitHash(bytes32,bytes)"))
bytes4 internal constant COMMIT_HASH_SIGNATURE = 0x0b61ba85;
// bytes4(keccak256("emitInitiateChange()"))
bytes4 internal constant EMIT_INITIATE_CHANGE_SIGNATURE = 0x93b4e25e;
// bytes4(keccak256("reportMalicious(address,uint256,bytes)"))
bytes4 internal constant REPORT_MALICIOUS_SIGNATURE = 0xc476dd40;
// bytes4(keccak256("revealSecret(uint256)"))
bytes4 internal constant REVEAL_SECRET_SIGNATURE = 0x98df67c6;
/// @dev An internal function used by the `addAllowedSender` and `initialize` functions.
/// @param _sender The address for which transactions of any type must be allowed.
function _addAllowedSender(address _sender) internal {
require(!isSenderAllowed(_sender));
require(_sender != address(0));
_allowedSenders.push(_sender);
}
}