diff --git a/contracts/topos-core/SubnetRegistrator.sol b/contracts/topos-core/SubnetRegistrator.sol index 63f3e1f..20874e6 100644 --- a/contracts/topos-core/SubnetRegistrator.sol +++ b/contracts/topos-core/SubnetRegistrator.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.9; import "./Bytes32Sets.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; type SubnetId is bytes32; -contract SubnetRegistrator is Ownable { +contract SubnetRegistrator is Initializable, Ownable { using Bytes32SetsLib for Bytes32SetsLib.Set; struct Subnet { @@ -47,6 +48,13 @@ contract SubnetRegistrator is Ownable { return SubnetId.wrap(subnetSet.keyAtIndex(index)); } + /// @notice Contract initializer + /// @dev Can only be called once + /// @param admin address of the admin + function initialize(address admin) public initializer { + _transferOwnership(admin); + } + /// @notice Register a new subnet /// @param endpoint JSON RPC endpoint of a subnet /// @param logoURL URL for the logo of a subnet diff --git a/scripts/deploy-subnet-registrator.ts b/scripts/deploy-subnet-registrator.ts new file mode 100644 index 0000000..cef0855 --- /dev/null +++ b/scripts/deploy-subnet-registrator.ts @@ -0,0 +1,60 @@ +import { ContractFactory, providers, utils, Wallet } from 'ethers' +import subnetRegistratorJSON from '../artifacts/contracts/topos-core/SubnetRegistrator.sol/SubnetRegistrator.json' +import { Arg, deployContractConstant } from './const-addr-deployer' + +const main = async function (..._args: Arg[]) { + const [providerEndpoint, sequencerPrivateKey, salt, gasLimit, ...args] = _args + const provider = new providers.JsonRpcProvider(providerEndpoint) + + // Fetch the sequencer wallet + const sequencerPrivateKeyHex = sanitizeHexString( + sequencerPrivateKey || '' + ) + if (!utils.isHexString(sequencerPrivateKeyHex, 32)) { + console.error('ERROR: Please provide a valid private key!') + return + } + const sequencerWallet = new Wallet(sequencerPrivateKeyHex || '', provider) + + // Fetch the deployer wallet + const privateKey = process.env.PRIVATE_KEY + if (!privateKey || !utils.isHexString(privateKey, 32)) { + console.error('ERROR: Please provide a valid private key! (PRIVATE_KEY)') + return + } + const deployerWallet = new Wallet(process.env.PRIVATE_KEY || '', provider) + + // Deploy SubnetRegistrator contract with constant address + let address + try { + address = ( + await deployContractConstant( + deployerWallet, + subnetRegistratorJSON, + salt, + [...args], + gasLimit + ) + ).address + } catch (error) { + console.error(error) + return + } + console.log(address) + + // Initialize SubnetRegistrator contract + const SubnetRegistratorFactory = new ContractFactory( + subnetRegistratorJSON.abi, + subnetRegistratorJSON.bytecode, + deployerWallet + ) + const subnetRegistrator = SubnetRegistratorFactory.attach(address) + subnetRegistrator.initialize(sequencerWallet.address) +} + +const sanitizeHexString = function (hexString: string) { + return hexString.startsWith('0x') ? hexString : `0x${hexString}` +} + +const args = process.argv.slice(2) +main(...args) diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 79678bf..2774d86 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,5 +1,4 @@ -/* eslint-disable no-case-declarations */ -import { ContractFactory, providers, utils, Wallet } from 'ethers' +import { providers, utils, Wallet } from 'ethers' import fs from 'fs' import { @@ -9,14 +8,7 @@ import { } from './const-addr-deployer' const main = async function (..._args: Arg[]) { - const [ - providerEndpoint, - contractJsonPath, - salt, - gasLimit, - deployConstant, - ...args - ] = _args + const [providerEndpoint, contractJsonPath, salt, gasLimit, ...args] = _args const provider = new providers.JsonRpcProvider(providerEndpoint) const privateKey = process.env.PRIVATE_KEY @@ -47,37 +39,17 @@ const main = async function (..._args: Arg[]) { return } - switch (deployConstant) { - case 'true': - const address = await deployContractConstant( - wallet, - contractJson, - salt, - args, - gasLimit - ) - .then(({ address }) => address) - .catch(console.error) - - console.log(address) - break - case 'false': - const contractFactory = new ContractFactory( - contractJson.abi, - contractJson.bytecode, - wallet - ) - const contract = await contractFactory.deploy(...args, { - gasLimit: BigInt(gasLimit), - }) - await contract.deployed() - console.log(contract.address) - break - default: - console.error( - `ERROR: Please provide a valid deployConstant flag! (true|false)` - ) - } + const address = await deployContractConstant( + wallet, + contractJson, + salt, + args, + gasLimit + ) + .then(({ address }) => address) + .catch(console.error) + + console.log(address) } const args = process.argv.slice(2) diff --git a/scripts/register-subnet.ts b/scripts/register-subnet.ts index f7aab3b..45e0558 100644 --- a/scripts/register-subnet.ts +++ b/scripts/register-subnet.ts @@ -77,7 +77,7 @@ const main = async function (...args: string[]) { process.exit(1) } - const wallet = new Wallet(toposDeployerPrivateKey, provider) + const wallet = new Wallet(sequencerPrivateKey, provider) const contract = new Contract( subnetRegistratorAddress, diff --git a/test/topos-core/SubnetRegistrator.test.ts b/test/topos-core/SubnetRegistrator.test.ts index 40b1451..486b69b 100644 --- a/test/topos-core/SubnetRegistrator.test.ts +++ b/test/topos-core/SubnetRegistrator.test.ts @@ -1,4 +1,4 @@ -import { Contract } from 'ethers' +import { Contract, Wallet } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' @@ -12,16 +12,26 @@ describe('SubnetRegistrator', () => { const subnetCurrencySymbol = 'SUB' const chainId = 1 - beforeEach(async () => { + async function deploySubnetRegistratorFixture() { + const [admin, nonAdmin, toposDeployer] = await ethers.getSigners() const SubnetRegistrator = await ethers.getContractFactory( 'SubnetRegistrator' ) - subnetRegistrator = await SubnetRegistrator.deploy() - }) + subnetRegistrator = await SubnetRegistrator.connect(toposDeployer).deploy() + await subnetRegistrator.deployed() + await subnetRegistrator.initialize(admin.address) + return { + admin, + nonAdmin, + subnetRegistrator, + toposDeployer, + } + } describe('registerSubnet', () => { it('reverts if non-admin tries to register a subnet', async () => { - const [, nonAdmin] = await ethers.getSigners() + const { nonAdmin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await expect( subnetRegistrator .connect(nonAdmin) @@ -37,13 +47,17 @@ describe('SubnetRegistrator', () => { }) it('reverts if the subnet is already registered', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) await expect( registerSubnet( @@ -52,19 +66,25 @@ describe('SubnetRegistrator', () => { subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) ).to.be.revertedWith('Bytes32Set: key already exists in the set.') }) it('registers a subnet', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) const subnet = await subnetRegistrator.subnets(subnetId) expect(subnet.name).to.equal(subnetName) @@ -75,45 +95,59 @@ describe('SubnetRegistrator', () => { }) it('gets the subnet count', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) const count = await subnetRegistrator.getSubnetCount() expect(count).to.equal(1) }) it('gets the subnet at a given index', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) const id = await subnetRegistrator.getSubnetIdAtIndex(0) expect(id).to.equal(subnetId) }) it('checks if a subnet exists', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) const exists = await subnetRegistrator.subnetExists(subnetId) expect(exists).to.be.true }) it('emits a new subnet registered event', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await expect( registerSubnet( endpoint, @@ -121,7 +155,9 @@ describe('SubnetRegistrator', () => { subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) ) .to.emit(subnetRegistrator, 'NewSubnetRegistered') @@ -138,21 +174,27 @@ describe('SubnetRegistrator', () => { }) it('reverts when removing a non-existent subnet', async () => { - await expect(removeSubnet(subnetId)).to.be.revertedWith( - 'Bytes32Set: key does not exist in the set.' - ) + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() + await expect( + removeSubnet(subnetId, subnetRegistrator, admin) + ).to.be.revertedWith('Bytes32Set: key does not exist in the set.') }) it('emit a subnet removed event', async () => { + const { admin, subnetRegistrator } = + await deploySubnetRegistratorFixture() await registerSubnet( endpoint, logoURL, subnetName, subnetId, subnetCurrencySymbol, - chainId + chainId, + subnetRegistrator, + admin ) - await expect(removeSubnet(subnetId)) + await expect(removeSubnet(subnetId, subnetRegistrator, admin)) .to.emit(subnetRegistrator, 'SubnetRemoved') .withArgs(subnetId) }) @@ -164,19 +206,27 @@ describe('SubnetRegistrator', () => { subnetName: string, subnetId: string, subnetCurrencySymbol: string, - chainId: number + chainId: number, + subnetRegistrator: Contract, + admin: Wallet ) { - return await subnetRegistrator.registerSubnet( - endpoint, - logoURL, - subnetName, - subnetId, - subnetCurrencySymbol, - chainId - ) + return await subnetRegistrator + .connect(admin) + .registerSubnet( + endpoint, + logoURL, + subnetName, + subnetId, + subnetCurrencySymbol, + chainId + ) } - async function removeSubnet(subnetId: string) { - return await subnetRegistrator.removeSubnet(subnetId) + async function removeSubnet( + subnetId: string, + subnetRegistrator: Contract, + admin: Wallet + ) { + return await subnetRegistrator.connect(admin).removeSubnet(subnetId) } })