Skip to content

Commit 3675843

Browse files
committed
Merge branch 'main' into new/bank-decreaser
2 parents 41d7a8e + 17c0cb9 commit 3675843

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4906
-541
lines changed

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/solidity/ @pdyraga @lukasz-zimnoch @Shadowfiend @nkuba @dimpar @tomaszslabon @piotr-roslaniec
1+
/solidity/ @pdyraga @lukasz-zimnoch @Shadowfiend @nkuba @piotr-roslaniec @lrsaturnino
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
// tBTC Bridge RebateStaking Governance Runbook
2+
//
3+
// Network: Ethereum Mainnet
4+
// Date: November 2025
5+
6+
= tBTC Bridge RebateStaking – Governance Execution Plan
7+
8+
== Overview
9+
10+
This document describes the exact on-chain steps and payloads
11+
required to complete the activation of the tBTC Rebate System by
12+
connecting the `RebateStaking` contract to the tBTC Bridge via the
13+
new `BridgeGovernance.setRebateStaking(address)` entrypoint.
14+
15+
The plan assumes that the Bridge proxy has already been upgraded
16+
to a new implementation that supports rebate staking ("Action 1"
17+
from the October 22 Deployment Plan), and focuses on the missing
18+
governance hook ("Action 2").
19+
20+
The Bridge implementation enforces that the rebate staking address
21+
is configured exactly once and cannot be changed via governance;
22+
changing it later requires a dedicated Bridge implementation
23+
upgrade.
24+
25+
== Key Contracts & Parameters
26+
27+
* Network: Ethereum Mainnet
28+
* Bridge (Proxy):: `0x5e4861a80B55f035D899f66772117F00FA0E8e7B`
29+
* Bridge Implementation (New):: `0x98e19c416100Ad0C66B52d05075F4C899184f2aD`
30+
* BridgeGovernance (OLD):: `0xf286EA706A2512d2B9232FE7F8b2724880230b45`
31+
* BridgeGovernance (NEW):: `<NEW_BRIDGE_GOVERNANCE>`
32+
* RebateStaking (Proxy):: `0x0184739C32edc3471D3e4860c8E39a5f3Ff85A45`
33+
* ProxyAdmin:: `0x16A76d3cd3C1e3CE843C6680d6B37E9116b5C706`
34+
* Governance / Council EOA (BridgeGovernance owner)::
35+
** `0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f`
36+
* Bridge governance delay (seconds):: `172800` (48 hours)
37+
38+
Use the following commands (with a configured `ETHEREUM_RPC_URL`)
39+
to confirm the current state on mainnet:
40+
41+
[,bash]
42+
----
43+
# Bridge governance should be OLD BridgeGovernance
44+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
45+
"governance()(address)"
46+
47+
# Expected: 0xf286EA706A2512d2B9232FE7F8b2724880230b45
48+
49+
# BridgeGovernance (OLD) governance delay should be 48h
50+
cast call 0xf286EA706A2512d2B9232FE7F8b2724880230b45 \
51+
"governanceDelays(uint256)(uint256)" 0
52+
53+
# Expected: 172800
54+
55+
# RebateStaking should not yet be wired
56+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
57+
"getRebateStaking()(address)"
58+
59+
# Expected: 0x0000000000000000000000000000000000000000
60+
----
61+
62+
== Pre-checks (read-only)
63+
64+
* Bridge proxy implementation is already upgraded to the rebate-aware version.
65+
* Bridge governance owner is the OLD BridgeGovernance:
66+
** `cast call <BRIDGE> "governance()(address)"` -> `0xf286EA706A2512d2B9232FE7F8b2724880230b45`
67+
* RebateStaking wiring is not yet set:
68+
** `cast call <BRIDGE> "getRebateStaking()(address)"` -> `0x0000...0000`
69+
* RebateStaking proxy points at the Bridge proxy:
70+
** `cast call <REBATE_STAKING> "bridge()(address)"` -> `<BRIDGE>`
71+
72+
== High-Level Steps
73+
74+
0. (Already executed) Upgrade the Bridge proxy implementation.
75+
1. Deploy NEW `BridgeGovernance` with the new
76+
`setRebateStaking(address)` function.
77+
2. (Optional) Transfer ownership of NEW `BridgeGovernance` to the
78+
Council EOA.
79+
3. Using OLD `BridgeGovernance`, call
80+
`beginBridgeGovernanceTransfer(newBridgeGovernance)` to start the
81+
governance timelock for Bridge governance transfer (48 hours on
82+
mainnet, 60 seconds on Sepolia).
83+
4. After the governance delay elapses, call
84+
`finalizeBridgeGovernanceTransfer()` on OLD `BridgeGovernance` to
85+
make NEW `BridgeGovernance` the Bridge owner.
86+
5. Using NEW `BridgeGovernance`, call
87+
`setRebateStaking(RebateStakingProxy)` once to connect the
88+
RebateStaking contract to the Bridge implementation. This call
89+
reverts if the address is zero or already set.
90+
6. Verify post-conditions on-chain and, optionally, via the
91+
existing Hardhat verification script.
92+
93+
All payloads below are given as raw calldata so they can be
94+
submitted through a multisig UI, a timelock, or directly by the
95+
Council, depending on operational setup.
96+
97+
== Action 0 – (For Reference) Bridge Proxy Upgrade
98+
99+
This action has already been executed and is documented here for
100+
completeness. It upgraded the Bridge proxy to a new
101+
implementation that supports rebate staking.
102+
103+
* Executor:: ProxyAdmin owner
104+
* To:: `0x16A76d3cd3C1e3CE843C6680d6B37E9116b5C706` (ProxyAdmin)
105+
* Method:: `upgrade(address proxy, address implementation)`
106+
107+
[,json]
108+
----
109+
{
110+
"to": "0x16A76d3cd3C1e3CE843C6680d6B37E9116b5C706",
111+
"value": "0",
112+
"data": "0x99a88ec40000000000000000000000005e4861a80b55f035d899f66772117f00fa0e8e7b00000000000000000000000098e19c416100ad0c66b52d05075f4c899184f2ad"
113+
}
114+
----
115+
116+
Decoded:
117+
118+
* `upgrade(proxy = 0x5e48…, implementation = 0x98e1…)`
119+
120+
== Action 1 – Deploy NEW BridgeGovernance
121+
122+
NEW `BridgeGovernance` must be deployed from the PR implementation
123+
that includes:
124+
125+
* `setRebateStaking(address rebateStaking)` forwarding to the
126+
Bridge implementation, and
127+
* The constructor:
128+
`constructor(Bridge _bridge, uint256 _governanceDelay)`.
129+
130+
Constructor arguments:
131+
132+
* `_bridge`:: `0x5e4861a80B55f035D899f66772117F00FA0E8e7B`
133+
* `_governanceDelay`:: `172800`
134+
135+
This step is typically executed via the existing Hardhat deployment
136+
scripts and produces a new mainnet address:
137+
138+
* `BridgeGovernance (NEW): <NEW_BRIDGE_GOVERNANCE>`
139+
140+
That address must be recorded and used in all subsequent steps.
141+
142+
=== Optional – Transfer Ownership of NEW BridgeGovernance
143+
144+
If the deployer is not already the Council EOA, ownership of NEW
145+
`BridgeGovernance` should be transferred to the Council.
146+
147+
* Executor:: Deployer (current `owner()` of NEW BridgeGovernance)
148+
* To:: `<NEW_BRIDGE_GOVERNANCE>`
149+
* Method:: `transferOwnership(address newOwner)`
150+
151+
Parameters:
152+
153+
* `newOwner`:: `0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f`
154+
155+
Calldata (generated via `cast calldata`):
156+
157+
[,bash]
158+
----
159+
cast calldata "transferOwnership(address)" \
160+
0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
161+
162+
# 0xf2fde38b0000000000000000000000009f6e831c8f8939dc0c830c6e492e7cef4f9c2f5f
163+
----
164+
165+
Tx template:
166+
167+
[,json]
168+
----
169+
{
170+
"from": "<DEPLOYER_ADDRESS>",
171+
"to": "<NEW_BRIDGE_GOVERNANCE>",
172+
"value": "0",
173+
"data": "0xf2fde38b0000000000000000000000009f6e831c8f8939dc0c830c6e492e7cef4f9c2f5f"
174+
}
175+
----
176+
177+
Post-condition check:
178+
179+
[,bash]
180+
----
181+
cast call <NEW_BRIDGE_GOVERNANCE> "owner()(address)" --rpc-url $ETHEREUM_RPC_URL
182+
183+
# Expected: 0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
184+
----
185+
186+
== Action 2 – Begin Bridge Governance Transfer (OLD → NEW)
187+
188+
This action starts the on-chain, 48-hour governance delay for
189+
transferring the Bridge governance from OLD to NEW
190+
`BridgeGovernance`.
191+
192+
* Executor:: Council EOA (owner of OLD BridgeGovernance)
193+
* From:: `0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f`
194+
* To:: `0xf286EA706A2512d2B9232FE7F8b2724880230b45`
195+
* Method:: `beginBridgeGovernanceTransfer(address _newBridgeGovernance)`
196+
197+
Parameters:
198+
199+
* `_newBridgeGovernance`:: `<NEW_BRIDGE_GOVERNANCE>`
200+
201+
Calldata (generate with your actual NEW address):
202+
203+
[,bash]
204+
----
205+
cast calldata "beginBridgeGovernanceTransfer(address)" \
206+
<NEW_BRIDGE_GOVERNANCE>
207+
208+
# Example shape (do not copy literally):
209+
# 0x8b18a505000000000000000000000000<NEW_BRIDGE_GOVERNANCE_20_BYTES>
210+
----
211+
212+
Tx template:
213+
214+
[,json]
215+
----
216+
{
217+
"from": "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f",
218+
"to": "0xf286EA706A2512d2B9232FE7F8b2724880230b45",
219+
"value": "0",
220+
"data": "0x8b18a505000000000000000000000000<NEW_BRIDGE_GOVERNANCE_20_BYTES>"
221+
}
222+
----
223+
224+
Post-condition check:
225+
226+
[,bash]
227+
----
228+
cast call 0xf286EA706A2512d2B9232FE7F8b2724880230b45 \
229+
"bridgeGovernanceTransferChangeInitiated()(uint256)"
230+
231+
# Expected: non-zero timestamp after this transaction.
232+
----
233+
234+
== Action 3 – Finalize Bridge Governance Transfer
235+
236+
At least `172800` seconds (48 hours) after the timestamp returned
237+
by `bridgeGovernanceTransferChangeInitiated()`, finalize the
238+
governance transfer so that NEW `BridgeGovernance` becomes the
239+
governance owner of the Bridge.
240+
241+
* Executor:: Council EOA
242+
* From:: `0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f`
243+
* To:: `0xf286EA706A2512d2B9232FE7F8b2724880230b45`
244+
* Method:: `finalizeBridgeGovernanceTransfer()`
245+
246+
Calldata:
247+
248+
[,bash]
249+
----
250+
cast calldata "finalizeBridgeGovernanceTransfer()"
251+
252+
# 0x605da053
253+
----
254+
255+
Tx template:
256+
257+
[,json]
258+
----
259+
{
260+
"from": "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f",
261+
"to": "0xf286EA706A2512d2B9232FE7F8b2724880230b45",
262+
"value": "0",
263+
"data": "0x605da053"
264+
}
265+
----
266+
267+
Post-condition checks:
268+
269+
[,bash]
270+
----
271+
# Bridge governance should now be NEW BridgeGovernance
272+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
273+
"governance()(address)"
274+
275+
# Expected: <NEW_BRIDGE_GOVERNANCE>
276+
277+
# OLD BridgeGovernance should have reset its pending transfer state
278+
cast call 0xf286EA706A2512d2B9232FE7F8b2724880230b45 \
279+
"bridgeGovernanceTransferChangeInitiated()(uint256)"
280+
281+
# Expected: 0
282+
----
283+
284+
== Action 4 – Set RebateStaking via NEW BridgeGovernance
285+
286+
Once the Bridge `governance()` points to NEW `BridgeGovernance`,
287+
the Council can perform the one-off wiring of the RebateStaking
288+
contract to the Bridge.
289+
290+
* Executor:: Council EOA (owner of NEW BridgeGovernance)
291+
* From:: `0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f`
292+
* To:: `<NEW_BRIDGE_GOVERNANCE>`
293+
* Method:: `setRebateStaking(address rebateStaking)`
294+
295+
Parameters:
296+
297+
* `rebateStaking`:: `0x0184739C32edc3471D3e4860c8E39a5f3Ff85A45`
298+
299+
Calldata (fully determined):
300+
301+
[,bash]
302+
----
303+
cast calldata "setRebateStaking(address)" \
304+
0x0184739C32edc3471D3e4860c8E39a5f3Ff85A45
305+
306+
# 0xca73c4620000000000000000000000000184739c32edc3471d3e4860c8e39a5f3ff85a45
307+
----
308+
309+
Tx template:
310+
311+
[,json]
312+
----
313+
{
314+
"from": "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f",
315+
"to": "<NEW_BRIDGE_GOVERNANCE>",
316+
"value": "0",
317+
"data": "0xca73c4620000000000000000000000000184739c32edc3471d3e4860c8e39a5f3ff85a45"
318+
}
319+
----
320+
321+
Decoded call path:
322+
323+
* External:: `BridgeGovernance(NEW).setRebateStaking(RebateStakingProxy)`
324+
* Internal:: `Bridge.setRebateStaking(RebateStakingProxy)`
325+
* Bridge state library:: `BridgeState.setRebateStaking(RebateStakingProxy)`
326+
327+
This transaction is expected to succeed exactly once for a given
328+
Bridge implementation; subsequent attempts will revert on-chain.
329+
330+
Post-condition check:
331+
332+
[,bash]
333+
----
334+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
335+
"getRebateStaking()(address)"
336+
337+
# Expected: 0x0184739C32edc3471D3e4860c8E39a5f3Ff85A45
338+
----
339+
340+
== Final Verification
341+
342+
After all actions above are executed, perform the following checks:
343+
344+
[,bash]
345+
----
346+
# 1. NEW BridgeGovernance controls the Bridge
347+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
348+
"governance()(address)"
349+
350+
# Expected: <NEW_BRIDGE_GOVERNANCE>
351+
352+
# 2. RebateStaking is connected to the Bridge
353+
cast call 0x5e4861a80B55f035D899f66772117F00FA0E8e7B \
354+
"getRebateStaking()(address)"
355+
356+
# Expected: 0x0184739C32edc3471D3e4860c8E39a5f3Ff85A45
357+
358+
# 3. (Optional) Run the existing verification script
359+
npx hardhat deploy --network mainnet --tags VerifyRebateDeployment
360+
361+
== Governance follow-up overview
362+
363+
After Action 1 produces the new governance address, the Council EOA (the owner
364+
of the OLD `BridgeGovernance`) must: (1) call
365+
`beginBridgeGovernanceTransfer(<NEW_BRIDGE_GOVERNANCE>)`, (2) wait the 48-hour
366+
delay, and (3) call `finalizeBridgeGovernanceTransfer()` to hand control of the
367+
Bridge proxy to the new contract. All calldata templates above are illustrative;
368+
replace `<NEW_BRIDGE_GOVERNANCE>` with the coordinate address generated in
369+
Action 1. No other state-changing calls are required until Action 4.
370+
----
371+
372+
If all checks pass, the tBTC Rebate System is fully wired and
373+
governable via the new BridgeGovernance contract.

solidity/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414

1515
# OpenZeppelin
1616
/.openzeppelin/unknown-*.json
17+
solcInputs/

0 commit comments

Comments
 (0)