Skip to content

Commit a01db9a

Browse files
shahafnforshtat
andauthored
OG-767: Allow withdrawing abandoned funds from the RelayHub as a dev fee (opengsn#788)
Co-authored-by: Alex Forshtat <[email protected]>
1 parent 12e73fc commit a01db9a

34 files changed

+426
-67
lines changed

.idea/dictionaries/alexf.xml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/CommandsLogic.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ interface DeployOptions {
6969
stakeManagerAddress?: string
7070
deployTestToken?: boolean
7171
stakingTokenAddress?: string
72-
minimumTokenStake: number| IntString
72+
minimumTokenStake: number | IntString
7373
penalizerAddress?: string
7474
burnAddress?: string
75+
devAddress?: string
7576
verbose?: boolean
7677
skipConfirmation?: boolean
7778
relayHubConfiguration: RelayHubConfiguration
@@ -507,7 +508,7 @@ export class CommandsLogic {
507508
arguments: []
508509
}, deployOptions.relayRegistryAddress, { ...options }, deployOptions.skipConfirmation)
509510
const sInstance = await this.getContractInstance(StakeManager, {
510-
arguments: [defaultEnvironment.maxUnstakeDelay, deployOptions.burnAddress]
511+
arguments: [defaultEnvironment.maxUnstakeDelay, defaultEnvironment.abandonmentDelay, defaultEnvironment.escheatmentDelay, deployOptions.burnAddress, deployOptions.devAddress]
511512
}, deployOptions.stakeManagerAddress, { ...options }, deployOptions.skipConfirmation)
512513
const pInstance = await this.getContractInstance(Penalizer, {
513514
arguments: [

packages/cli/src/GsnTestEnvironment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class GsnTestEnvironmentClass {
5353
const deploymentResult = await commandsLogic.deployGsnContracts({
5454
from,
5555
burnAddress: constants.BURN_ADDRESS,
56+
devAddress: constants.BURN_ADDRESS,
5657
minimumTokenStake: 1,
5758
gasPrice: 1e9.toString(),
5859
gasLimit: 5000000,

packages/common/src/Environments.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export interface Environment {
99
readonly penalizerConfiguration: PenalizerConfiguration
1010
readonly paymasterConfiguration: PaymasterConfiguration
1111
readonly maxUnstakeDelay: number
12+
readonly abandonmentDelay: number
13+
readonly escheatmentDelay: number
1214
readonly gtxdatanonzero: number
1315
readonly gtxdatazero: number
1416
readonly dataOnChainHandlingGasCostPerByte: number
@@ -28,7 +30,7 @@ const defaultPenalizerConfiguration: PenalizerConfiguration = {
2830
}
2931

3032
const defaultRelayHubConfiguration: RelayHubConfiguration = {
31-
gasOverhead: 57501,
33+
gasOverhead: 57435,
3234
postOverhead: 19169,
3335
gasReserve: 100000,
3436
maxWorkerCount: 10,
@@ -55,6 +57,8 @@ const ethereumMainnet: Environment = {
5557
penalizerConfiguration: defaultPenalizerConfiguration,
5658
paymasterConfiguration: defaultPaymasterConfiguration,
5759
maxUnstakeDelay: defaultStakeManagerMaxUnstakeDelay,
60+
abandonmentDelay: 31536000, // 1 year
61+
escheatmentDelay: 2629746, // 1 month
5862
mintxgascost: 21000,
5963
gtxdatanonzero: 16,
6064
gtxdatazero: 4,
@@ -69,6 +73,8 @@ const ganacheLocal: Environment = {
6973
penalizerConfiguration: defaultPenalizerConfiguration,
7074
paymasterConfiguration: defaultPaymasterConfiguration,
7175
maxUnstakeDelay: defaultStakeManagerMaxUnstakeDelay,
76+
abandonmentDelay: 1000,
77+
escheatmentDelay: 500,
7278
mintxgascost: 21000,
7379
gtxdatanonzero: 16,
7480
gtxdatazero: 4,
@@ -97,6 +103,8 @@ const arbitrum: Environment = {
97103
mintxgascost: 700000,
98104
gtxdatanonzero: 2024,
99105
gtxdatazero: 506,
106+
abandonmentDelay: 31536000, // 1 year
107+
escheatmentDelay: 2629746, // 1 month
100108
// there is currently a hard-coded to be 2 at arbitrum:eth.go:43 (commit: 12483cfa17a29e7d68c354c456ebc371b05a6ea2)
101109
// setting factor to 0.6 instead of 0.5 to allow the transaction to pass in case of moderate gas price increase
102110
// note that excess will be collected by the Relay Server as an extra profit

packages/contracts/src/RelayHub.sol

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import "hardhat/console.sol";
1212
// #endif
1313

1414
import "./utils/MinLibBytes.sol";
15+
import "@openzeppelin/contracts/utils/Address.sol";
1516
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
1617
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
1718
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
@@ -37,6 +38,7 @@ import "./interfaces/IStakeManager.sol";
3738
contract RelayHub is IRelayHub, Ownable, ERC165 {
3839
using ERC165Checker for address;
3940
using SafeMath for uint256;
41+
using Address for address;
4042

4143
address private constant DRY_RUN_ADDRESS = 0x0000000000000000000000000000000000000000;
4244

@@ -155,9 +157,11 @@ contract RelayHub is IRelayHub, Ownable, ERC165 {
155157
}
156158

157159
/// @inheritdoc IRelayHub
158-
function verifyCanRegister(address relayManager) external view override {
160+
function onRelayServerRegistered(address relayManager) external override {
161+
require(msg.sender == relayRegistrar, "caller is not relay registrar");
159162
verifyRelayManagerStaked(relayManager);
160163
require(workerCount[relayManager] > 0, "no relay workers");
164+
stakeManager.updateRelayKeepaliveTime(relayManager);
161165
}
162166

163167
/// @inheritdoc IRelayHub
@@ -588,6 +592,20 @@ contract RelayHub is IRelayHub, Ownable, ERC165 {
588592
stakeManager.penalizeRelayManager(relayManager, beneficiary, stakeInfo.stake);
589593
}
590594

595+
/// @inheritdoc IRelayHub
596+
function isRelayEscheatable(address relayManager) public view override returns (bool){
597+
return stakeManager.isRelayEscheatable(relayManager);
598+
}
599+
600+
/// @inheritdoc IRelayHub
601+
function escheatAbandonedRelayBalance(address relayManager) external override onlyOwner {
602+
require(stakeManager.isRelayEscheatable(relayManager), "relay server not escheatable yet");
603+
uint256 balance = balances[relayManager];
604+
balances[relayManager] = 0;
605+
balances[config.devAddress] = balances[config.devAddress].add(balance);
606+
emit AbandonedRelayManagerBalanceEscheated(relayManager, balance);
607+
}
608+
591609
/// @inheritdoc IRelayHub
592610
function aggregateGasleft() public override virtual view returns (uint256){
593611
return gasleft();

packages/contracts/src/StakeManager.sol

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import "@openzeppelin/contracts/utils/math/SafeMath.sol";
88
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
99

1010
import "./interfaces/IStakeManager.sol";
11-
import "./interfaces/IRelayHub.sol";
1211

1312
/**
1413
* @title The StakeManager implementation
@@ -25,6 +24,8 @@ contract StakeManager is IStakeManager, Ownable {
2524
string public override versionSM = "2.2.3+opengsn.stakemanager.istakemanager";
2625
uint256 internal immutable maxUnstakeDelay;
2726

27+
AbandonedRelayServerConfig internal abandonedRelayServerConfig;
28+
2829
address internal burnAddress;
2930
uint256 internal immutable creationBlock;
3031

@@ -48,6 +49,17 @@ contract StakeManager is IStakeManager, Ownable {
4849
return burnAddress;
4950
}
5051

52+
/// @inheritdoc IStakeManager
53+
function setDevAddress(address _devAddress) public override onlyOwner {
54+
abandonedRelayServerConfig.devAddress = _devAddress;
55+
emit DevAddressSet(abandonedRelayServerConfig.devAddress);
56+
}
57+
58+
/// @inheritdoc IStakeManager
59+
function getAbandonedRelayServerConfig() external override view returns (AbandonedRelayServerConfig memory) {
60+
return abandonedRelayServerConfig;
61+
}
62+
5163
/// @inheritdoc IStakeManager
5264
function getMaxUnstakeDelay() external override view returns (uint256) {
5365
return maxUnstakeDelay;
@@ -58,12 +70,18 @@ contract StakeManager is IStakeManager, Ownable {
5870

5971
constructor(
6072
uint256 _maxUnstakeDelay,
61-
address _burnAddress
73+
uint256 _abandonmentDelay,
74+
uint256 _escheatmentDelay,
75+
address _burnAddress,
76+
address _devAddress
6277
) {
6378
require(_burnAddress != address(0), "transfers to address(0) may fail");
6479
setBurnAddress(_burnAddress);
80+
setDevAddress(_devAddress);
6581
creationBlock = block.number;
6682
maxUnstakeDelay = _maxUnstakeDelay;
83+
abandonedRelayServerConfig.abandonmentDelay = _abandonmentDelay;
84+
abandonedRelayServerConfig.escheatmentDelay = _escheatmentDelay;
6785
}
6886

6987
/// @inheritdoc IStakeManager
@@ -179,4 +197,42 @@ contract StakeManager is IStakeManager, Ownable {
179197
stakes[relayManager].token.safeTransfer(beneficiary, reward);
180198
emit StakePenalized(relayManager, beneficiary, stakes[relayManager].token, reward);
181199
}
200+
201+
/// @inheritdoc IStakeManager
202+
function isRelayEscheatable(address relayManager) public view override returns (bool) {
203+
IStakeManager.StakeInfo memory stakeInfo = stakes[relayManager];
204+
return stakeInfo.abandonedTime != 0 && stakeInfo.abandonedTime + abandonedRelayServerConfig.escheatmentDelay < block.timestamp;
205+
}
206+
207+
/// @inheritdoc IStakeManager
208+
function markRelayAbandoned(address relayManager) external override onlyOwner {
209+
StakeInfo storage info = stakes[relayManager];
210+
require(info.stake > 0, "relay manager not staked");
211+
require(info.abandonedTime == 0, "relay manager already abandoned");
212+
require(info.keepaliveTime + abandonedRelayServerConfig.abandonmentDelay < block.timestamp, "relay manager was alive recently");
213+
info.abandonedTime = block.timestamp;
214+
emit RelayServerAbandoned(relayManager, info.abandonedTime);
215+
}
216+
217+
/// @inheritdoc IStakeManager
218+
function escheatAbandonedRelayStake(address relayManager) external override onlyOwner {
219+
StakeInfo storage info = stakes[relayManager];
220+
require(isRelayEscheatable(relayManager), "relay server not escheatable yet");
221+
uint256 amount = info.stake;
222+
info.stake = 0;
223+
info.withdrawTime = 0;
224+
info.token.safeTransfer(abandonedRelayServerConfig.devAddress, amount);
225+
emit AbandonedRelayManagerStakeEscheated(relayManager, msg.sender, info.token, amount);
226+
}
227+
228+
/// @inheritdoc IStakeManager
229+
function updateRelayKeepaliveTime(address relayManager) external override {
230+
StakeInfo storage info = stakes[relayManager];
231+
bool isHubAuthorized = authorizedHubs[relayManager][msg.sender].removalTime == type(uint256).max;
232+
bool isRelayOwner = info.owner == msg.sender;
233+
require(isHubAuthorized || isRelayOwner, "must be called by owner or hub");
234+
info.abandonedTime = 0;
235+
info.keepaliveTime = block.timestamp;
236+
emit RelayServerKeepalive(relayManager, info.keepaliveTime);
237+
}
182238
}

packages/contracts/src/interfaces/IRelayHub.sol

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ interface IRelayHub is IERC165 {
3939
address devAddress;
4040
// 0 < fee < 100, as percentage of total charge from paymaster to relayer
4141
uint8 devFee;
42-
4342
}
4443

4544
/// @notice Emitted when a configuration of the `RelayHub` is changed
@@ -116,6 +115,16 @@ interface IRelayHub is IERC165 {
116115
/// @notice This event is emitted in case this `RelayHub` is deprecated and will stop serving transactions soon.
117116
event HubDeprecated(uint256 deprecationTime);
118117

118+
/**
119+
* @notice This event is emitted in case a `relayManager` has been deemed "abandoned" for being
120+
* unresponsive for a prolonged period of time.
121+
* @notice This event means the entire balance of the relay has been transferred to the `devAddress`.
122+
*/
123+
event AbandonedRelayManagerBalanceEscheated(
124+
address indexed relayManager,
125+
uint256 balance
126+
);
127+
119128
/**
120129
* Error codes that describe all possible failure reasons reported in the `TransactionRelayed` event `status` field.
121130
* @param OK The transaction was successfully relayed and execution successful - never included in the event.
@@ -142,7 +151,10 @@ interface IRelayHub is IERC165 {
142151
*/
143152
function addRelayWorkers(address[] calldata newRelayWorkers) external;
144153

145-
function verifyCanRegister(address relayManager) external;
154+
/**
155+
* @notice The `RelayRegistrar` callback to notify the `RelayHub` that this `relayManager` has updated registration.
156+
*/
157+
function onRelayServerRegistered(address relayManager) external;
146158

147159
// Balance management
148160

@@ -228,6 +240,12 @@ interface IRelayHub is IERC165 {
228240
*/
229241
function deprecateHub(uint256 _deprecationTime) external;
230242

243+
/**
244+
* @notice
245+
* @param relayManager
246+
*/
247+
function escheatAbandonedRelayBalance(address relayManager) external;
248+
231249
/**
232250
* @notice The fee is expressed as a base fee in wei plus percentage of the actual charge.
233251
* For example, a value '40' stands for a 40% fee, so the recipient will be charged for 1.4 times the spent amount.
@@ -289,6 +307,12 @@ interface IRelayHub is IERC165 {
289307
*/
290308
function verifyRelayManagerStaked(address relayManager) external view;
291309

310+
/**
311+
* @notice Uses `StakeManager` to check if the Relay Manager can be considered abandoned or not.
312+
* Returns true if the stake's abandonment time is in the past including the escheatment delay, false otherwise.
313+
*/
314+
function isRelayEscheatable(address relayManager) external view returns (bool);
315+
292316
/// @return `true` if the `RelayHub` is deprecated, `false` it it is not deprecated and can serve transactions.
293317
function isDeprecated() external view returns (bool);
294318

packages/contracts/src/interfaces/IRelayRegistrar.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface IRelayRegistrar is IERC165 {
3636
*/
3737
event RelayServerRegistered(
3838
address indexed relayManager,
39+
address indexed relayHub,
3940
uint256 baseRelayFee,
4041
uint256 pctRelayFee,
4142
bytes32[3] relayUrl

0 commit comments

Comments
 (0)