Skip to content

Commit 19c3ff1

Browse files
authored
feat: add governance and protocol contract owner scripts (#107)
Introduces new utility scripts to retrieve and report the owners and delegates of governance and protocol contracts. The following scripts have been added: - `get-governance-oapp-owners.js`: Reports the owner and delegate of the GovernanceOAppSender and GovernanceOAppReceiver. - `get-protocol-contract-owners.js`: Reports the owner and pending owner of the ACL and GatewayConfig contracts. - `get-wrapper-owners.js`: Reports the owner and pending owner of the ConfidentialTokenWrappersRegistry and its associated wrappers. Additionally, updates the `.env.example` file to include new environment variables for the added scripts and modifies the `package.json` to include new script commands for easy execution.
1 parent ec6aea9 commit 19c3ff1

6 files changed

Lines changed: 479 additions & 2 deletions

File tree

contracts/chains-config-checker/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ ZAMA_SAFE_ADMIN_MODULE_GATEWAY="0x57f866b5E7Fb82Fb812Ed3D3C79cdB35E9e91518"
1717
ZAMA_SAFE_BSC="0xa40939fDe3883D2e7Cd5C32f53AB241804d2779B"
1818
ZAMA_SAFE_HYPEREVM="0x0d66642a5Bc6E32e013f47E08f9db9bDb1268827"
1919
ZAMA_ARAGON_DAO="0xB6D69D5F334d8B97B194617B53c6aB62f8681Ef3"
20+
ZAMA_GOVERNANCE_OAPP_SENDER_ETHEREUM="0x1c5D750D18917064915901048cdFb2dB815e0910"
21+
ZAMA_GOVERNANCE_OAPP_RECEIVER_GATEWAY="0x10795261A06285D3718674a9Cf98Ea66F7C6A0c6"
22+
23+
ZAMA_ACL_ETHEREUM="0xcA2E8f1F656CD25C01F05d0b243Ab1ecd4a8ffb6"
24+
ZAMA_GATEWAY_CONFIG_GATEWAY="0xDE537Be194777A56f8B19d14079E6a78249390ab"
25+
26+
ZAMA_WRAPPERS_REGISTRY_ETHEREUM="0xeb5015fF021DB115aCe010f23F55C2591059bBA0"
2027

2128
# Solana
2229
SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"

contracts/chains-config-checker/README.md

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Currently, most useful scripts are:
3131
[*] get-oft-owners
3232
[*] get-multisig-info
3333
[*] get-staking-roles
34+
[*] get-governance-oapp-owners
35+
[*] get-protocol-contract-owners
36+
[*] get-wrapper-owners
3437
```
3538
### getCurrentPausers
3639

@@ -392,4 +395,122 @@ The script will:
392395
[COPROCESSOR]
393396
Zama : 0x31bB...
394397
...
395-
```
398+
```
399+
400+
### getGovernanceOappOwners
401+
402+
```
403+
npm run get-governance-oapp-owners
404+
```
405+
406+
Reports the **owner** and **LayerZero delegate** of the `GovernanceOAppSender` (Ethereum) and `GovernanceOAppReceiver` (Gateway). Uses on-chain view calls only (`owner()`, `endpoint()`, `delegates(oapp)`).
407+
408+
For each configured chain it will:
409+
1. Read the governance OApp to get `owner()` and `endpoint()`.
410+
2. Read the endpoint's `delegates(contractAddress)` to get the current delegate.
411+
3. Print OApp address, owner, and delegate.
412+
4. Exit non-zero if `owner` and `delegate` differ on any chain.
413+
414+
**Environment variables:**
415+
416+
| Chain | RPC env | Contract address env |
417+
|-----------------------------|---------------|------------------------------------------|
418+
| Ethereum (Sender) | `RPC_ETHEREUM` | `ZAMA_GOVERNANCE_OAPP_SENDER_ETHEREUM` |
419+
| Gateway (Receiver) | `RPC_GATEWAY` | `ZAMA_GOVERNANCE_OAPP_RECEIVER_GATEWAY` |
420+
421+
Example output:
422+
423+
```
424+
=== Governance OApp ===
425+
426+
[Ethereum GovernanceOAppSender]
427+
OApp address : 0x1c5D750D18917064915901048cdFb2dB815e0910
428+
Owner : 0x...
429+
Delegate : 0x...
430+
431+
[Gateway GovernanceOAppReceiver]
432+
OApp address : 0x10795261A06285D3718674a9Cf98Ea66F7C6A0c6
433+
Owner : 0x...
434+
Delegate : 0x...
435+
436+
Owner and Delegate should be IDENTICAL on each chain.
437+
```
438+
439+
### getProtocolContractOwners
440+
441+
```
442+
npm run get-protocol-contract-owners
443+
```
444+
445+
Reports the **owner** (and any **pending owner**) of the `ACL` contract on Ethereum and the `GatewayConfig` contract on the Gateway chain. Both contracts use `Ownable2Step`, so a non-zero `pendingOwner()` indicates an in-flight ownership handover and causes the script to exit non-zero.
446+
447+
For each configured contract it will:
448+
1. Call `owner()` and `pendingOwner()`.
449+
2. Print the contract address, owner, and (if non-zero) pending owner.
450+
3. Emit a WARNING summary if any contract has a pending owner. The script still exits zero.
451+
452+
**Environment variables:**
453+
454+
| Chain | RPC env | Contract address env |
455+
|-----------|----------------|----------------------------------|
456+
| Ethereum | `RPC_ETHEREUM` | `ZAMA_ACL_ETHEREUM` |
457+
| Gateway | `RPC_GATEWAY` | `ZAMA_GATEWAY_CONFIG_GATEWAY` |
458+
459+
Example output:
460+
461+
```
462+
=== Protocol Contract Owners ===
463+
464+
[Ethereum ACL]
465+
Contract address : 0x4E35869D1e8106058d11b414876B735F62A325e2
466+
Owner : 0x...
467+
468+
[Gateway GatewayConfig]
469+
Contract address : 0x3B19DA9b424f95f1d2787Ab2d3a43ad56D626AAE
470+
Owner : 0x...
471+
```
472+
473+
### getWrapperOwners
474+
475+
#### Usage
476+
477+
```bash
478+
npm run get-wrapper-owners
479+
```
480+
481+
Reports the **owner** (and any **pending owner**) of the `ConfidentialTokenWrappersRegistry` on Ethereum and of every confidential wrapper registered in it. The list of wrappers is read from the registry on-chain, so no per-wrapper env var is required.
482+
483+
The script will:
484+
1. Read `owner()` and `pendingOwner()` on the registry.
485+
2. Read `getTokenConfidentialTokenPairsLength()` and iterate `getTokenConfidentialTokenPair(i)` on the registry to list all wrappers (including revoked entries).
486+
3. For each wrapper, read `owner()` and `pendingOwner()`.
487+
4. Compare each wrapper's owner to the registry owner. Mismatches are reported as a NOTE and do not cause the script to fail.
488+
5. Emit a WARNING summary if any contract has a pending owner. The script still exits zero.
489+
490+
**Environment variables:**
491+
492+
| Chain | RPC env | Contract address env |
493+
|-----------|----------------|--------------------------------------|
494+
| Ethereum | `RPC_ETHEREUM` | `ZAMA_WRAPPERS_REGISTRY_ETHEREUM` |
495+
496+
Example output:
497+
498+
```
499+
=== Wrappers Registry & Confidential Wrappers ===
500+
501+
[Wrappers Registry]
502+
Address : 0xeb5015fF021DB115aCe010f23F55C2591059bBA0
503+
Owner : 0x...
504+
505+
[Confidential wrapper (underlying ...)]
506+
Address : 0x...
507+
Owner : 0x...
508+
509+
...
510+
511+
--------------------------------------------------
512+
Registry owner : 0x...
513+
Wrappers : 7
514+
515+
All wrapper owners are IDENTICAL to the registry owner.
516+
```

contracts/chains-config-checker/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
"get-oft-owners-solana": "node utils/get-oft-owners-solana.js",
1111
"get-oft-owners": "node utils/get-oft-owners-evm.js && node utils/get-oft-owners-solana.js",
1212
"get-multisig-info": "node utils/get-multisig-info.js",
13-
"get-staking-roles": "node utils/get-staking-roles.js"
13+
"get-staking-roles": "node utils/get-staking-roles.js",
14+
"get-governance-oapp-owners": "node utils/get-governance-oapp-owners.js",
15+
"get-protocol-contract-owners": "node utils/get-protocol-contract-owners.js",
16+
"get-wrapper-owners": "node utils/get-wrapper-owners.js"
1417
},
1518
"dependencies": {
1619
"@layerzerolabs/lz-solana-sdk-v2": "^3.0.136",
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env node
2+
3+
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
4+
const { ethers } = require('ethers');
5+
const { isValidAddress } = require('./get-deployment-block');
6+
7+
const CHAINS = {
8+
ethereumGovernanceOAppSender: {
9+
name: 'Ethereum GovernanceOAppSender',
10+
rpcEnv: 'RPC_ETHEREUM',
11+
addrEnv: 'ZAMA_GOVERNANCE_OAPP_SENDER_ETHEREUM',
12+
},
13+
gatewayGovernanceOAppReceiver: {
14+
name: 'Gateway GovernanceOAppReceiver',
15+
rpcEnv: 'RPC_GATEWAY',
16+
addrEnv: 'ZAMA_GOVERNANCE_OAPP_RECEIVER_GATEWAY',
17+
},
18+
};
19+
20+
const OAPP_ABI = [
21+
'function owner() view returns (address)',
22+
'function endpoint() view returns (address)',
23+
];
24+
25+
const ENDPOINT_ABI = [
26+
'function delegates(address) view returns (address)',
27+
];
28+
29+
function buildChainConfigs() {
30+
return Object.entries(CHAINS).map(([key, chain]) => ({
31+
key,
32+
name: chain.name,
33+
rpcUrl: process.env[chain.rpcEnv],
34+
contractAddress: process.env[chain.addrEnv],
35+
}));
36+
}
37+
38+
async function getOwnerAndDelegateForChain(chainConfig) {
39+
const { name, rpcUrl, contractAddress } = chainConfig;
40+
41+
if (!rpcUrl) throw new Error(`[${name}] RPC URL is not configured`);
42+
if (!contractAddress) throw new Error(`[${name}] Contract address is not configured`);
43+
if (!isValidAddress(contractAddress)) throw new Error(`[${name}] Invalid contract address format`);
44+
45+
const provider = new ethers.JsonRpcProvider(rpcUrl);
46+
const oapp = new ethers.Contract(contractAddress, OAPP_ABI, provider);
47+
48+
const [owner, endpointAddress] = await Promise.all([
49+
oapp.owner(),
50+
oapp.endpoint(),
51+
]);
52+
53+
const endpoint = new ethers.Contract(endpointAddress, ENDPOINT_ABI, provider);
54+
const delegate = await endpoint.delegates(contractAddress);
55+
56+
return {
57+
name,
58+
contractAddress,
59+
owner,
60+
delegate,
61+
equal: owner === delegate,
62+
};
63+
}
64+
65+
function printOwnerAndDelegate(info) {
66+
const { name, contractAddress, owner, delegate } = info;
67+
console.log(`\n[${name}]`);
68+
console.log(` OApp address : ${contractAddress}`);
69+
console.log(` Owner : ${owner}`);
70+
console.log(` Delegate : ${delegate}`);
71+
}
72+
73+
async function main() {
74+
const configs = buildChainConfigs();
75+
const toRun = configs.filter((c) => c.rpcUrl && c.contractAddress && isValidAddress(c.contractAddress));
76+
const toSkip = configs.filter((c) => !c.rpcUrl || !c.contractAddress || !isValidAddress(c.contractAddress || ''));
77+
78+
for (const c of toSkip) {
79+
if (!c.rpcUrl) {
80+
console.log(` Skipping ${c.name}: RPC not configured`);
81+
} else if (!c.contractAddress) {
82+
console.log(` Skipping ${c.name}: Contract address not configured`);
83+
} else {
84+
console.log(` Skipping ${c.name}: Invalid address format`);
85+
}
86+
}
87+
88+
if (toRun.length === 0) {
89+
console.error('Error: No chains configured. Set RPC and contract address env vars in .env');
90+
process.exit(1);
91+
}
92+
93+
let hadError = false;
94+
const equalityResults = [];
95+
96+
console.log('\n=== Governance OApp ===');
97+
for (const chainConfig of toRun) {
98+
try {
99+
const info = await getOwnerAndDelegateForChain(chainConfig);
100+
printOwnerAndDelegate(info);
101+
equalityResults.push({ name: chainConfig.name, equal: info.equal });
102+
} catch (error) {
103+
console.error(`\n[${chainConfig.name}] Error: ${error.message}`);
104+
hadError = true;
105+
}
106+
}
107+
108+
if (hadError) process.exit(1);
109+
110+
const failed = equalityResults.filter((r) => !r.equal);
111+
if (failed.length > 0) {
112+
for (const { name } of failed) {
113+
console.error(`Owner and Delegate are NOT IDENTICAL on ${name}`);
114+
}
115+
process.exit(1);
116+
}
117+
118+
console.log('\nOwner and Delegate should be IDENTICAL on each chain.');
119+
}
120+
121+
main();
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env node
2+
3+
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
4+
const { ethers } = require('ethers');
5+
const { isValidAddress } = require('./get-deployment-block');
6+
7+
const CONTRACTS = {
8+
ethereumAcl: {
9+
name: 'Ethereum ACL',
10+
rpcEnv: 'RPC_ETHEREUM',
11+
addrEnv: 'ZAMA_ACL_ETHEREUM',
12+
},
13+
gatewayConfig: {
14+
name: 'Gateway GatewayConfig',
15+
rpcEnv: 'RPC_GATEWAY',
16+
addrEnv: 'ZAMA_GATEWAY_CONFIG_GATEWAY',
17+
},
18+
};
19+
20+
const OWNABLE2STEP_ABI = [
21+
'function owner() view returns (address)',
22+
'function pendingOwner() view returns (address)',
23+
];
24+
25+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
26+
27+
function buildConfigs() {
28+
return Object.entries(CONTRACTS).map(([key, contract]) => ({
29+
key,
30+
name: contract.name,
31+
rpcUrl: process.env[contract.rpcEnv],
32+
contractAddress: process.env[contract.addrEnv],
33+
}));
34+
}
35+
36+
async function getOwnerInfo(config) {
37+
const { name, rpcUrl, contractAddress } = config;
38+
39+
if (!rpcUrl) throw new Error(`[${name}] RPC URL is not configured`);
40+
if (!contractAddress) throw new Error(`[${name}] Contract address is not configured`);
41+
if (!isValidAddress(contractAddress)) throw new Error(`[${name}] Invalid contract address format`);
42+
43+
const provider = new ethers.JsonRpcProvider(rpcUrl);
44+
const contract = new ethers.Contract(contractAddress, OWNABLE2STEP_ABI, provider);
45+
46+
const [owner, pendingOwner] = await Promise.all([
47+
contract.owner(),
48+
contract.pendingOwner(),
49+
]);
50+
51+
return { name, contractAddress, owner, pendingOwner };
52+
}
53+
54+
function printOwnerInfo(info) {
55+
const { name, contractAddress, owner, pendingOwner } = info;
56+
console.log(`\n[${name}]`);
57+
console.log(` Contract address : ${contractAddress}`);
58+
console.log(` Owner : ${owner}`);
59+
if (pendingOwner !== ZERO_ADDRESS) {
60+
console.log(` Pending owner : ${pendingOwner}`);
61+
console.log(` WARNING: ownership handover in progress`);
62+
}
63+
}
64+
65+
async function main() {
66+
const configs = buildConfigs();
67+
const toRun = configs.filter((c) => c.rpcUrl && c.contractAddress && isValidAddress(c.contractAddress));
68+
const toSkip = configs.filter((c) => !c.rpcUrl || !c.contractAddress || !isValidAddress(c.contractAddress || ''));
69+
70+
for (const c of toSkip) {
71+
if (!c.rpcUrl) {
72+
console.log(` Skipping ${c.name}: RPC not configured`);
73+
} else if (!c.contractAddress) {
74+
console.log(` Skipping ${c.name}: Contract address not configured`);
75+
} else {
76+
console.log(` Skipping ${c.name}: Invalid address format`);
77+
}
78+
}
79+
80+
if (toRun.length === 0) {
81+
console.error('Error: No contracts configured. Set RPC and contract address env vars in .env');
82+
process.exit(1);
83+
}
84+
85+
let hadError = false;
86+
const pending = [];
87+
88+
console.log('\n=== Protocol Contract Owners ===');
89+
for (const config of toRun) {
90+
try {
91+
const info = await getOwnerInfo(config);
92+
printOwnerInfo(info);
93+
if (info.pendingOwner !== ZERO_ADDRESS) pending.push(info.name);
94+
} catch (error) {
95+
console.error(`\n[${config.name}] Error: ${error.message}`);
96+
hadError = true;
97+
}
98+
}
99+
100+
if (hadError) process.exit(1);
101+
102+
if (pending.length > 0) {
103+
console.warn(`\nWARNING: ${pending.length} contract(s) have a pending owner: ${pending.join(', ')}`);
104+
}
105+
}
106+
107+
main();

0 commit comments

Comments
 (0)