-
Notifications
You must be signed in to change notification settings - Fork 1
/
XStakeController.sol
88 lines (76 loc) · 3.37 KB
/
XStakeController.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
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.25;
import {XApp} from "omni/core/src/pkg/XApp.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {ConfLevel} from "omni/core/src/libraries/ConfLevel.sol";
import {GasLimits} from "./GasLimits.sol";
import {XStaker} from "./XStaker.sol";
/**
* @title XStakeController
*
* @notice The global accountant of our cross-chain staking protocol. This is a
* singleton contract, deployed on Omni. It tracks stakes for all users
* across each supported chain, and directs the XStaker to payout users
* when they unstake.
*
* @dev We initialize our XApp with default ConfLevel.Finalized (see constructor),
* and do not specify conf level in individual xcalls, as we do in XStaker.
* This is bescause XStakeController is deployed on Omni. Omni only
* supports Finalized conf level, as OmniEVM has instant finality.
*/
contract XStakeController is XApp, Ownable {
/// @notice Address of XStaker contract by chain id.
mapping(uint64 => address) public xstakerOn;
/// @notice Map account to chain id to stake.
mapping(address => mapping(uint64 => uint256)) public stakeOn;
constructor(address portal, address owner) XApp(portal, ConfLevel.Finalized) Ownable(owner) {}
/**
* @notice Record `amount` staked by `user` on `xmsg.sourceChainId`.
* Only callable via xcall by a known XStaker contract.
* @param user Account that staked.
* @param amount Amount staked.
*/
function recordStake(address user, uint256 amount) external xrecv {
require(isXCall(), "Controller: only xcall");
require(xstakerOn[xmsg.sourceChainId] != address(0), "Controller: unsupported chain");
require(xstakerOn[xmsg.sourceChainId] == xmsg.sender, "Controller: only xstaker");
stakeOn[user][xmsg.sourceChainId] += amount;
}
/**
* @notice Unstake msg.sender `onChainID`.
* @dev Unstaking starts on the controller, because the controller is the
* source of truth for user stakes. The controller directs the XStaker to
* payout the user via xcall.
*/
function unstake(uint64 onChainID) external payable {
uint256 stake = stakeOn[msg.sender][onChainID];
require(stake > 0, "Controller: no stake");
stakeOn[msg.sender][onChainID] = 0;
uint256 fee = xcall({
destChainId: onChainID,
to: xstakerOn[onChainID],
data: abi.encodeCall(XStaker.withdraw, (msg.sender, stake)),
gasLimit: GasLimits.Withdraw
});
require(msg.value >= fee, "Controller: insufficient fee");
}
/**
* @notice Return the fee required to unstake `onChainID`.
*/
function unstakeFee(uint64 onChainID) external view returns (uint256) {
return feeFor({
destChainId: onChainID,
data: abi.encodeCall(XStaker.withdraw, (msg.sender, stakeOn[msg.sender][onChainID])),
gasLimit: GasLimits.Withdraw
});
}
/**
* @notice Admin function to register an XStaker deployment.
* Deployments must be registered before they can be used.
* @param chainId Chain ID of the XStaker deployment.
* @param addr Deployment address.
*/
function registerXStaker(uint64 chainId, address addr) external onlyOwner {
xstakerOn[chainId] = addr;
}
}