-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy pathBaseBridgeReceiver.sol
150 lines (128 loc) · 5.35 KB
/
BaseBridgeReceiver.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../ITimelock.sol";
contract BaseBridgeReceiver {
/** Custom errors **/
error AlreadyInitialized();
error BadData();
error InvalidProposalId();
error InvalidTimelockAdmin();
error ProposalNotExecutable();
error TransactionAlreadyQueued();
error Unauthorized();
/** Events **/
event Initialized(address indexed govTimelock, address indexed localTimelock);
event ProposalCreated(address indexed rootMessageSender, uint id, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint eta);
event ProposalExecuted(uint indexed id);
/** Public variables **/
/// @notice Address of the governing contract that this bridge receiver expects to
/// receive messages from; likely an address from another chain (e.g. mainnet)
address public govTimelock;
/// @notice Address of the timelock on this chain that the bridge receiver
/// will send messages to
address public localTimelock;
/// @notice Whether contract has been initialized
bool public initialized;
/// @notice Total count of proposals generated
uint public proposalCount;
struct Proposal {
uint id;
address[] targets;
uint[] values;
string[] signatures;
bytes[] calldatas;
uint eta;
bool executed;
}
/// @notice Mapping of proposal ids to their full proposal data
mapping (uint => Proposal) public proposals;
enum ProposalState {
Queued,
Expired,
Executed
}
/**
* @notice Initialize the contract
* @param _govTimelock Address of the governing contract that this contract
* will receive messages from (likely on another chain)
* @param _localTimelock Address of the timelock contract that this contract
* will send messages to
*/
function initialize(address _govTimelock, address _localTimelock) external {
if (initialized) revert AlreadyInitialized();
if (ITimelock(_localTimelock).admin() != address(this)) revert InvalidTimelockAdmin();
govTimelock = _govTimelock;
localTimelock = _localTimelock;
initialized = true;
emit Initialized(_govTimelock, _localTimelock);
}
/**
* @notice Process a message sent from the governing timelock (across a bridge)
* @param rootMessageSender Address of the contract that sent the bridged message
* @param data ABI-encoded bytes containing the transactions to be queued on the local timelock
*/
function processMessage(
address rootMessageSender,
bytes calldata data
) internal {
if (rootMessageSender != govTimelock) revert Unauthorized();
address[] memory targets;
uint256[] memory values;
string[] memory signatures;
bytes[] memory calldatas;
(targets, values, signatures, calldatas) = abi.decode(
data,
(address[], uint256[], string[], bytes[])
);
if (values.length != targets.length) revert BadData();
if (signatures.length != targets.length) revert BadData();
if (calldatas.length != targets.length) revert BadData();
uint delay = ITimelock(localTimelock).delay();
uint eta = block.timestamp + delay;
for (uint i = 0; i < targets.length; i++) {
if (ITimelock(localTimelock).queuedTransactions(keccak256(abi.encode(targets[i], values[i], signatures[i], calldatas[i], eta)))) revert TransactionAlreadyQueued();
ITimelock(localTimelock).queueTransaction(targets[i], values[i], signatures[i], calldatas[i], eta);
}
proposalCount++;
Proposal memory proposal = Proposal({
id: proposalCount,
targets: targets,
values: values,
signatures: signatures,
calldatas: calldatas,
eta: eta,
executed: false
});
proposals[proposal.id] = proposal;
emit ProposalCreated(rootMessageSender, proposal.id, targets, values, signatures, calldatas, eta);
}
/**
* @notice Execute a queued proposal
* @param proposalId The id of the proposal to execute
*/
function executeProposal(uint proposalId) external {
if (state(proposalId) != ProposalState.Queued) revert ProposalNotExecutable();
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
for (uint i = 0; i < proposal.targets.length; i++) {
ITimelock(localTimelock).executeTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
}
emit ProposalExecuted(proposalId);
}
/**
* @notice Get the state of a proposal
* @param proposalId Id of the proposal
* @return The state of the given proposal (queued, expired or executed)
*/
function state(uint proposalId) public view returns (ProposalState) {
if (proposalId > proposalCount || proposalId == 0) revert InvalidProposalId();
Proposal memory proposal = proposals[proposalId];
if (proposal.executed) {
return ProposalState.Executed;
} else if (block.timestamp > (proposal.eta + ITimelock(localTimelock).GRACE_PERIOD())) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
}
}