Skip to content

Commit 8c3dd28

Browse files
committed
feat: Add workflow for transferring admin role via Safe multisig
1 parent 93f1fe6 commit 8c3dd28

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
name: Transfer Admin Role via Safe Multisig
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
operation:
7+
description: 'Admin role operation'
8+
required: true
9+
type: choice
10+
options:
11+
- begin-transfer
12+
- accept-transfer
13+
network:
14+
description: 'Network to perform operation on'
15+
required: true
16+
type: choice
17+
options:
18+
- ethereum
19+
- arbitrum
20+
- sepolia
21+
- arbitrum_sepolia
22+
default: sepolia
23+
new_admin_address:
24+
description: 'New admin address (required for begin-transfer)'
25+
required: false
26+
type: string
27+
dry_run:
28+
description: 'Dry run mode (only prepare and display transaction, do not propose to Safe)'
29+
required: false
30+
type: boolean
31+
default: true
32+
33+
jobs:
34+
prepare-admin-transfer:
35+
runs-on: ubuntu-latest
36+
environment: ${{ inputs.network }}
37+
outputs:
38+
transactions: ${{ steps.prepare.outputs.transactions }}
39+
safe-address: ${{ steps.prepare.outputs.safe-address }}
40+
41+
steps:
42+
- name: Checkout repository
43+
uses: actions/checkout@v4
44+
with:
45+
submodules: recursive
46+
47+
- name: Install Foundry
48+
uses: foundry-rs/foundry-toolchain@v1
49+
with:
50+
version: stable
51+
cache: true
52+
53+
- name: Validate inputs
54+
if: inputs.operation == 'begin-transfer' && inputs.new_admin_address == ''
55+
run: |
56+
echo "Error: new_admin_address is required for begin-transfer operation"
57+
exit 1
58+
59+
- name: Prepare admin transfer transaction calldata
60+
id: prepare
61+
env:
62+
CHAIN: ${{ inputs.network }}
63+
run: |
64+
# Read contract addresses from config
65+
APPROVAL_REQUIRED=$(jq -r ".chains.${CHAIN}.approvalRequired" config/config.json)
66+
BRIDGE_ADDRESS=$(jq -r ".chains.${CHAIN}.iexecLayerZeroBridgeAddress" config/config.json)
67+
68+
if [ "$APPROVAL_REQUIRED" = "true" ]; then
69+
TOKEN_CONTRACT=$(jq -r ".chains.${CHAIN}.rlcLiquidityUnifierAddress" config/config.json)
70+
else
71+
TOKEN_CONTRACT=$(jq -r ".chains.${CHAIN}.rlcCrosschainTokenAddress" config/config.json)
72+
fi
73+
74+
# Prepare transactions array for both contracts
75+
TRANSACTIONS='[]'
76+
77+
if [ "${{ inputs.operation }}" = "begin-transfer" ]; then
78+
# beginDefaultAdminTransfer(address newAdmin)
79+
TRANSACTION_DATA=$(cast calldata "beginDefaultAdminTransfer(address)" "${{ inputs.new_admin_address }}")
80+
FUNCTION_NAME="beginDefaultAdminTransfer(address)"
81+
82+
# Create transaction for token contract
83+
TRANSACTIONS=$(echo "$TRANSACTIONS" | jq --arg to "$TOKEN_CONTRACT" --arg data "$TRANSACTION_DATA" \
84+
'. += [{"to": $to, "value": "0", "data": $data}]')
85+
86+
# Create transaction for bridge contract
87+
TRANSACTIONS=$(echo "$TRANSACTIONS" | jq --arg to "$BRIDGE_ADDRESS" --arg data "$TRANSACTION_DATA" \
88+
'. += [{"to": $to, "value": "0", "data": $data}]')
89+
else
90+
# acceptDefaultAdminTransfer()
91+
TRANSACTION_DATA=$(cast calldata "acceptDefaultAdminTransfer()")
92+
FUNCTION_NAME="acceptDefaultAdminTransfer()"
93+
94+
# Create transaction for token contract
95+
TRANSACTIONS=$(echo "$TRANSACTIONS" | jq --arg to "$TOKEN_CONTRACT" --arg data "$TRANSACTION_DATA" \
96+
'. += [{"to": $to, "value": "0", "data": $data}]')
97+
98+
# Create transaction for bridge contract
99+
TRANSACTIONS=$(echo "$TRANSACTIONS" | jq --arg to "$BRIDGE_ADDRESS" --arg data "$TRANSACTION_DATA" \
100+
'. += [{"to": $to, "value": "0", "data": $data}]')
101+
102+
echo "transactions=$TRANSACTIONS" >> $GITHUB_OUTPUT
103+
echo "safe-address=${{ secrets.SAFE_ADDRESS }}" >> $GITHUB_OUTPUT
104+
105+
# Display transaction details for dry-run or verification
106+
echo "=========================================="
107+
echo "Transaction Details"
108+
echo "=========================================="
109+
echo " • Network: ${{ inputs.network }}"
110+
echo " • Operation: ${{ inputs.operation }}"
111+
echo " • Function: $FUNCTION_NAME"
112+
echo " • New Admin: ${{ inputs.new_admin_address }}"
113+
echo " • Safe Address: ${{ secrets.SAFE_ADDRESS }}"
114+
echo " • Dry Run: ${{ inputs.dry_run }}"
115+
echo ""
116+
echo "────────────────────────────────────────────────────────────────────────────────"
117+
echo ""
118+
echo "Transaction #1 - Token Contract:"
119+
echo " • Target: $TOKEN_CONTRACT"
120+
echo " • Value: 0 ETH"
121+
echo " • Data: $TRANSACTION_DATA"
122+
echo ""
123+
echo "Transaction #2 - Bridge Contract:"
124+
echo " • Target: $BRIDGE_ADDRESS"
125+
echo " • Value: 0 ETH"
126+
echo " • Data: $TRANSACTION_DATA"
127+
echo ""
128+
echo "────────────────────────────────────────────────────────────────────────────────"
129+
echo ""
130+
131+
if [ "${{ inputs.dry_run }}" == "true" ]; then
132+
echo "✅ DRY RUN MODE: Transactions prepared successfully"
133+
echo "ℹ️ These transactions would be proposed to Safe multisig"
134+
echo "ℹ️ Re-run with dry_run=false to actually propose to Safe"
135+
fi
136+
137+
propose-token-contract-to-safe:
138+
needs: prepare-admin-transfer
139+
if: ${{ inputs.dry_run == false }}
140+
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/propose-safe-multisig-tx.yml@main
141+
secrets:
142+
safe-proposer-private-key: ${{ secrets.SAFE_PROPOSER_PRIVATE_KEY }}
143+
safe-api-key: ${{ secrets.SAFE_API_KEY }}
144+
with:
145+
rpc-url: ${{ github.secrets.RPC_URL }}
146+
safe-address: ${{ needs.prepare-admin-transfer.outputs.safe-address }}
147+
transaction-to: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[0].to }}
148+
transaction-value: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[0].value }}
149+
transaction-data: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[0].data }}
150+
151+
propose-bridge-to-safe:
152+
needs: prepare-admin-transfer
153+
if: ${{ inputs.dry_run == false }}
154+
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/propose-safe-multisig-tx.yml@main
155+
secrets:
156+
safe-proposer-private-key: ${{ secrets.SAFE_PROPOSER_PRIVATE_KEY }}
157+
safe-api-key: ${{ secrets.SAFE_API_KEY }}
158+
with:
159+
rpc-url: ${{ github.secrets.RPC_URL }}
160+
safe-address: ${{ needs.prepare-admin-transfer.outputs.safe-address }}
161+
transaction-to: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[1].to }}
162+
transaction-value: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[1].value }}
163+
transaction-data: ${{ fromJson(needs.prepare-admin-transfer.outputs.transactions)[1].data }}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,13 @@ All critical administrative operations are secured using Safe (Gnosis Safe) mult
355355
356356
- **Pause/Unpause**: Control bridge operations with different pause levels
357357
- **Role Management**: Grant or revoke TOKEN_BRIDGE_ROLE
358+
- **Admin Transfer**: Transfer admin role to new addresses
358359
359360
### GitHub Actions Workflows
360361
361362
- `.github/workflows/bridge-pause-safe.yml` - Propose pause/unpause transactions
362363
- `.github/workflows/manage-token-bridge-role-safe.yml` - Propose role management transactions
364+
- `.github/workflows/transfer-admin-role-safe.yml` - Propose admin role transfer transactions
363365
364366
All workflows use the reusable Safe multisig workflow from [iExecBlockchainComputing/github-actions-workflows](https://github.com/iExecBlockchainComputing/github-actions-workflows).
365367

0 commit comments

Comments
 (0)