Skip to content

Commit f190b64

Browse files
committed
chore: check governance
1 parent 8bd8f20 commit f190b64

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
@@ -247,36 +247,14 @@ export const deploySharedContracts = async (
247247

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

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

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

282260
let feeAssetHandlerAddress: EthAddress | undefined = undefined;
@@ -500,7 +478,12 @@ export const deployRollup = async (
500478
>,
501479
addresses: Pick<
502480
L1ContractAddresses,
503-
'feeJuiceAddress' | 'registryAddress' | 'rewardDistributorAddress' | 'stakingAssetAddress' | 'gseAddress'
481+
| 'feeJuiceAddress'
482+
| 'registryAddress'
483+
| 'rewardDistributorAddress'
484+
| 'stakingAssetAddress'
485+
| 'gseAddress'
486+
| 'governanceAddress'
504487
>,
505488
logger: Logger,
506489
) => {
@@ -678,6 +661,24 @@ export const deployRollup = async (
678661
);
679662
}
680663

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

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

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

764+
if (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString()) {
765+
const { txHash } = await deployer.sendTransaction(
766+
{
767+
to: feeAssetAddress.toString(),
768+
data: encodeFunctionData({
769+
abi: FeeAssetArtifact.contractAbi,
770+
functionName: 'transferOwnership',
771+
args: [coinIssuerAddress.toString()],
772+
}),
773+
},
774+
{ gasLimit: 500_000n },
775+
);
776+
logger.verbose(`Transfer ownership of fee asset to coin issuer ${coinIssuerAddress} in ${txHash}`);
777+
txHashes.push(txHash);
778+
779+
const { txHash: acceptTokenOwnershipTxHash } = await deployer.sendTransaction(
780+
{
781+
to: coinIssuerAddress.toString(),
782+
data: encodeFunctionData({
783+
abi: CoinIssuerArtifact.contractAbi,
784+
functionName: 'acceptTokenOwnership',
785+
}),
786+
},
787+
{ gasLimit: 500_000n },
788+
);
789+
logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
790+
txHashes.push(acceptTokenOwnershipTxHash);
791+
}
792+
793+
// If the owner is not the Governance contract, transfer ownership to the Governance contract
794+
if (
795+
acceleratedTestDeployments ||
796+
(await coinIssuerContract.read.owner()) !== getAddress(governanceAddress.toString())
797+
) {
798+
const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
799+
to: coinIssuerContract.address,
800+
data: encodeFunctionData({
801+
abi: CoinIssuerArtifact.contractAbi,
802+
functionName: 'transferOwnership',
803+
args: [getAddress(governanceAddress.toString())],
804+
}),
805+
});
806+
logger.verbose(
807+
`Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
808+
);
809+
txHashes.push(transferOwnershipTxHash);
810+
}
811+
749812
// Wait for all actions to be mined
750813
await deployer.waitForDeployments();
751814
await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
@@ -964,6 +1027,7 @@ export const deployL1Contracts = async (
9641027
governanceAddress,
9651028
rewardDistributorAddress,
9661029
zkPassportVerifierAddress,
1030+
coinIssuerAddress,
9671031
} = await deploySharedContracts(l1Client, deployer, args, logger);
9681032
const { rollup, slashFactoryAddress } = await deployRollup(
9691033
l1Client,
@@ -975,6 +1039,7 @@ export const deployL1Contracts = async (
9751039
gseAddress,
9761040
rewardDistributorAddress,
9771041
stakingAssetAddress,
1042+
governanceAddress,
9781043
},
9791044
logger,
9801045
);
@@ -988,6 +1053,8 @@ export const deployL1Contracts = async (
9881053
deployer,
9891054
registryAddress,
9901055
gseAddress,
1056+
coinIssuerAddress,
1057+
feeAssetAddress,
9911058
governanceAddress,
9921059
logger,
9931060
args.acceleratedTestDeployments,
@@ -1032,6 +1099,7 @@ export const deployL1Contracts = async (
10321099
feeAssetHandlerAddress,
10331100
stakingAssetHandlerAddress,
10341101
zkPassportVerifierAddress,
1102+
coinIssuerAddress,
10351103
},
10361104
};
10371105
};

0 commit comments

Comments
 (0)