Skip to content

Commit f809bef

Browse files
committed
chore: check governance
1 parent 39a5fc4 commit f809bef

File tree

7 files changed

+228
-29
lines changed

7 files changed

+228
-29
lines changed

yarn-project/ethereum/src/contracts/fee_asset_handler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,24 @@ export class FeeAssetHandlerContract {
99
public address: EthAddress;
1010

1111
constructor(
12-
address: Hex,
12+
address: Hex | EthAddress,
1313
public readonly txUtils: L1TxUtils,
1414
) {
15+
if (address instanceof EthAddress) {
16+
address = address.toString();
17+
}
1518
this.address = EthAddress.fromString(address);
1619
}
1720

21+
public async getOwner(): Promise<EthAddress> {
22+
const contract = getContract({
23+
abi: FeeAssetHandlerAbi,
24+
address: this.address.toString(),
25+
client: this.txUtils.client,
26+
});
27+
return EthAddress.fromString(await contract.read.owner());
28+
}
29+
1830
public getMintAmount() {
1931
const contract = getContract({
2032
abi: FeeAssetHandlerAbi,

yarn-project/ethereum/src/contracts/fee_juice.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ export class FeeJuiceContract {
99
private readonly feeJuiceContract: GetContractReturnType<typeof FeeJuiceAbi, ViemClient>;
1010

1111
constructor(
12-
address: Hex,
12+
address: Hex | EthAddress,
1313
public readonly client: ViemClient,
1414
) {
15+
if (address instanceof EthAddress) {
16+
address = address.toString();
17+
}
1518
this.feeJuiceContract = getContract({ address, abi: FeeJuiceAbi, client });
1619
}
1720

1821
public get address() {
1922
return EthAddress.fromString(this.feeJuiceContract.address);
2023
}
2124

25+
public async getOwner(): Promise<EthAddress> {
26+
return EthAddress.fromString(await this.feeJuiceContract.read.owner());
27+
}
28+
2229
private assertWalletFeeJuice(): GetContractReturnType<typeof FeeJuiceAbi, ExtendedViemWalletClient> {
2330
if (!isExtendedClient(this.client)) {
2431
throw new Error('Wallet client is required for this operation');

yarn-project/ethereum/src/contracts/governance.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,12 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
136136
protected override readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ExtendedViemWalletClient>;
137137

138138
constructor(
139-
address: Hex,
139+
address: Hex | EthAddress,
140140
public override readonly client: ExtendedViemWalletClient,
141141
) {
142+
if (address instanceof EthAddress) {
143+
address = address.toString();
144+
}
142145
super(address, client);
143146
if (!isExtendedClient(client)) {
144147
throw new Error('GovernanceContract has to be instantiated with a wallet client.');

yarn-project/ethereum/src/contracts/governance_proposer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ export class GovernanceProposerContract implements IEmpireBase {
2121

2222
constructor(
2323
public readonly client: ViemClient,
24-
address: Hex,
24+
address: Hex | EthAddress,
2525
) {
26+
if (address instanceof EthAddress) {
27+
address = address.toString();
28+
}
2629
this.proposer = getContract({ address, abi: GovernanceProposerAbi, client });
2730
}
2831

yarn-project/ethereum/src/contracts/gse.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ export class GSEContract {
3939
this.gse = getContract({ address, abi: GSEAbi, client });
4040
}
4141

42+
public async getOwner(): Promise<EthAddress> {
43+
return EthAddress.fromString(await this.gse.read.owner());
44+
}
45+
46+
public async getGovernance(): Promise<EthAddress> {
47+
return EthAddress.fromString(await this.gse.read.getGovernance());
48+
}
49+
4250
getAttestersFromIndicesAtTime(instance: Hex | EthAddress, ts: bigint, indices: bigint[]) {
4351
if (instance instanceof EthAddress) {
4452
instance = instance.toString();

yarn-project/ethereum/src/deploy_l1_contracts.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
1010
import { createEthereumChain } from './chain.js';
1111
import { createExtendedL1Client } from './client.js';
1212
import { DefaultL1ContractsConfig } from './config.js';
13+
import { GovernanceContract } from './contracts/governance.js';
14+
import { GSEContract } from './contracts/gse.js';
15+
import { RegistryContract } from './contracts/registry.js';
1316
import { RollupContract } from './contracts/rollup.js';
1417
import { type DeployL1ContractsArgs, type Operator, deployL1Contracts } from './deploy_l1_contracts.js';
1518
import { startAnvil } from './test/start_anvil.js';
@@ -159,4 +162,99 @@ describe('deploy_l1_contracts', () => {
159162
BigInt(initialValidators.length),
160163
);
161164
});
165+
166+
it('ensure governance is the owner', async () => {
167+
// Runs the deployment script and checks if we have handed over things correctly to the governance.
168+
169+
const deployment = await deployL1Contracts(
170+
[rpcUrl!],
171+
privateKey,
172+
createEthereumChain([rpcUrl!], chainId).chainInfo,
173+
logger,
174+
{
175+
...DefaultL1ContractsConfig,
176+
salt: undefined,
177+
vkTreeRoot,
178+
protocolContractTreeRoot,
179+
genesisArchiveRoot,
180+
l1TxConfig: { checkIntervalMs: 100 },
181+
realVerifier: false,
182+
},
183+
);
184+
185+
const governance = new GovernanceContract(deployment.l1ContractAddresses.governanceAddress, client);
186+
const registry = new RegistryContract(client, deployment.l1ContractAddresses.registryAddress);
187+
const rollup = new RollupContract(client, deployment.l1ContractAddresses.rollupAddress);
188+
const gse = new GSEContract(client, await rollup.getGSE());
189+
190+
// Checking the shared
191+
expect(await registry.getOwner()).toEqual(governance.address);
192+
expect(await gse.getOwner()).toEqual(governance.address);
193+
expect(await gse.getGovernance()).toEqual(governance.address);
194+
expect(await getOwner(deployment.l1ContractAddresses.rewardDistributorAddress, 'REGISTRY')).toEqual(
195+
registry.address,
196+
);
197+
expect(await getOwner(deployment.l1ContractAddresses.coinIssuerAddress)).toEqual(governance.address);
198+
199+
expect(await getOwner(deployment.l1ContractAddresses.feeJuiceAddress)).toEqual(
200+
deployment.l1ContractAddresses.coinIssuerAddress,
201+
);
202+
203+
// The rollup contract should be owned by the governance contract as well.
204+
expect(await getOwner(EthAddress.fromString(rollup.address))).toEqual(governance.address);
205+
206+
// Make sure that the fee asset handler is the minter of the fee asset.
207+
expect(
208+
await isMinter(
209+
deployment.l1ContractAddresses.feeJuiceAddress,
210+
deployment.l1ContractAddresses.feeAssetHandlerAddress!,
211+
),
212+
).toBeTruthy();
213+
});
214+
215+
const isContract = async (address: EthAddress) => {
216+
const bytecode = await client.getBytecode({ address: address.toString() });
217+
return bytecode !== undefined && bytecode !== '0x';
218+
};
219+
220+
const getOwner = async (address: EthAddress, name: string = 'owner') => {
221+
if (!(await isContract(address))) {
222+
throw new Error(`Address ${address} have no bytecode, is it deployed?`);
223+
}
224+
return EthAddress.fromString(
225+
await client.readContract({
226+
address: address.toString(),
227+
abi: [
228+
{
229+
name: name,
230+
type: 'function',
231+
inputs: [],
232+
outputs: [{ type: 'address' }],
233+
stateMutability: 'view',
234+
},
235+
],
236+
functionName: name,
237+
}),
238+
);
239+
};
240+
241+
const isMinter = async (address: EthAddress, minter: EthAddress) => {
242+
if (!(await isContract(address))) {
243+
throw new Error(`Address ${address} have no bytecode, is it deployed?`);
244+
}
245+
return await client.readContract({
246+
address: address.toString(),
247+
abi: [
248+
{
249+
name: 'minters',
250+
type: 'function',
251+
inputs: [{ type: 'address' }],
252+
outputs: [{ type: 'bool' }],
253+
stateMutability: 'view',
254+
},
255+
],
256+
functionName: 'minters',
257+
args: [minter.toString()],
258+
});
259+
};
162260
});

yarn-project/ethereum/src/deploy_l1_contracts.ts

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -246,36 +246,14 @@ export const deploySharedContracts = async (
246246

247247
const coinIssuerAddress = await deployer.deploy(CoinIssuerArtifact, [
248248
feeAssetAddress.toString(),
249-
1n * 10n ** 18n, // @todo #8084
250-
governanceAddress.toString(),
249+
1_000_000n * 10n ** 18n, // @todo #8084
250+
l1Client.account.address,
251251
]);
252252
logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
253253

254-
const feeAsset = getContract({
255-
address: feeAssetAddress.toString(),
256-
abi: FeeAssetArtifact.contractAbi,
257-
client: l1Client,
258-
});
259-
260254
logger.verbose(`Waiting for deployments to complete`);
261255
await deployer.waitForDeployments();
262256

263-
if (args.acceleratedTestDeployments || !(await feeAsset.read.minters([coinIssuerAddress.toString()]))) {
264-
const { txHash } = await deployer.sendTransaction(
265-
{
266-
to: feeAssetAddress.toString(),
267-
data: encodeFunctionData({
268-
abi: FeeAssetArtifact.contractAbi,
269-
functionName: 'addMinter',
270-
args: [coinIssuerAddress.toString()],
271-
}),
272-
},
273-
{ gasLimit: 100_000n },
274-
);
275-
logger.verbose(`Added coin issuer ${coinIssuerAddress} as minter on fee asset in ${txHash}`);
276-
txHashes.push(txHash);
277-
}
278-
279257
// Registry ownership will be transferred to governance later, after rollup is added
280258

281259
let feeAssetHandlerAddress: EthAddress | undefined = undefined;
@@ -499,7 +477,12 @@ export const deployRollup = async (
499477
>,
500478
addresses: Pick<
501479
L1ContractAddresses,
502-
'feeJuiceAddress' | 'registryAddress' | 'rewardDistributorAddress' | 'stakingAssetAddress' | 'gseAddress'
480+
| 'feeJuiceAddress'
481+
| 'registryAddress'
482+
| 'rewardDistributorAddress'
483+
| 'stakingAssetAddress'
484+
| 'gseAddress'
485+
| 'governanceAddress'
503486
>,
504487
logger: Logger,
505488
) => {
@@ -677,6 +660,24 @@ export const deployRollup = async (
677660
);
678661
}
679662

663+
// If the owner is not the Governance contract, transfer ownership to the Governance contract
664+
logger.verbose(addresses.governanceAddress.toString());
665+
if (getAddress(await rollupContract.getOwner()) !== getAddress(addresses.governanceAddress.toString())) {
666+
// TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
667+
const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
668+
to: rollupContract.address,
669+
data: encodeFunctionData({
670+
abi: RegistryArtifact.contractAbi,
671+
functionName: 'transferOwnership',
672+
args: [getAddress(addresses.governanceAddress.toString())],
673+
}),
674+
});
675+
logger.verbose(
676+
`Transferring the ownership of the rollup contract at ${rollupContract.address} to the Governance ${addresses.governanceAddress} in tx ${transferOwnershipTxHash}`,
677+
);
678+
txHashes.push(transferOwnershipTxHash);
679+
}
680+
680681
await deployer.waitForDeployments();
681682
await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
682683
logger.verbose(`Rollup deployed`);
@@ -689,6 +690,8 @@ export const handoverToGovernance = async (
689690
deployer: L1Deployer,
690691
registryAddress: EthAddress,
691692
gseAddress: EthAddress,
693+
coinIssuerAddress: EthAddress,
694+
feeAssetAddress: EthAddress,
692695
governanceAddress: EthAddress,
693696
logger: Logger,
694697
acceleratedTestDeployments: boolean | undefined,
@@ -706,6 +709,18 @@ export const handoverToGovernance = async (
706709
client: extendedClient,
707710
});
708711

712+
const coinIssuerContract = getContract({
713+
address: getAddress(coinIssuerAddress.toString()),
714+
abi: CoinIssuerArtifact.contractAbi,
715+
client: extendedClient,
716+
});
717+
718+
const feeAsset = getContract({
719+
address: getAddress(feeAssetAddress.toString()),
720+
abi: FeeAssetArtifact.contractAbi,
721+
client: extendedClient,
722+
});
723+
709724
const txHashes: Hex[] = [];
710725

711726
// If the owner is not the Governance contract, transfer ownership to the Governance contract
@@ -745,6 +760,54 @@ export const handoverToGovernance = async (
745760
txHashes.push(transferOwnershipTxHash);
746761
}
747762

763+
if (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString()) {
764+
const { txHash } = await deployer.sendTransaction(
765+
{
766+
to: feeAssetAddress.toString(),
767+
data: encodeFunctionData({
768+
abi: FeeAssetArtifact.contractAbi,
769+
functionName: 'transferOwnership',
770+
args: [coinIssuerAddress.toString()],
771+
}),
772+
},
773+
{ gasLimit: 500_000n },
774+
);
775+
logger.verbose(`Transfer ownership of fee asset to coin issuer ${coinIssuerAddress} in ${txHash}`);
776+
txHashes.push(txHash);
777+
778+
const { txHash: acceptTokenOwnershipTxHash } = await deployer.sendTransaction(
779+
{
780+
to: coinIssuerAddress.toString(),
781+
data: encodeFunctionData({
782+
abi: CoinIssuerArtifact.contractAbi,
783+
functionName: 'acceptTokenOwnership',
784+
}),
785+
},
786+
{ gasLimit: 500_000n },
787+
);
788+
logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
789+
txHashes.push(acceptTokenOwnershipTxHash);
790+
}
791+
792+
// If the owner is not the Governance contract, transfer ownership to the Governance contract
793+
if (
794+
acceleratedTestDeployments ||
795+
(await coinIssuerContract.read.owner()) !== getAddress(governanceAddress.toString())
796+
) {
797+
const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
798+
to: coinIssuerContract.address,
799+
data: encodeFunctionData({
800+
abi: CoinIssuerArtifact.contractAbi,
801+
functionName: 'transferOwnership',
802+
args: [getAddress(governanceAddress.toString())],
803+
}),
804+
});
805+
logger.verbose(
806+
`Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
807+
);
808+
txHashes.push(transferOwnershipTxHash);
809+
}
810+
748811
// Wait for all actions to be mined
749812
await deployer.waitForDeployments();
750813
await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
@@ -961,6 +1024,7 @@ export const deployL1Contracts = async (
9611024
governanceAddress,
9621025
rewardDistributorAddress,
9631026
zkPassportVerifierAddress,
1027+
coinIssuerAddress,
9641028
} = await deploySharedContracts(l1Client, deployer, args, logger);
9651029
const { rollup, slashFactoryAddress } = await deployRollup(
9661030
l1Client,
@@ -972,6 +1036,7 @@ export const deployL1Contracts = async (
9721036
gseAddress,
9731037
rewardDistributorAddress,
9741038
stakingAssetAddress,
1039+
governanceAddress,
9751040
},
9761041
logger,
9771042
);
@@ -985,6 +1050,8 @@ export const deployL1Contracts = async (
9851050
deployer,
9861051
registryAddress,
9871052
gseAddress,
1053+
coinIssuerAddress,
1054+
feeAssetAddress,
9881055
governanceAddress,
9891056
logger,
9901057
args.acceleratedTestDeployments,
@@ -1029,6 +1096,7 @@ export const deployL1Contracts = async (
10291096
feeAssetHandlerAddress,
10301097
stakingAssetHandlerAddress,
10311098
zkPassportVerifierAddress,
1099+
coinIssuerAddress,
10321100
},
10331101
};
10341102
};

0 commit comments

Comments
 (0)