Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

feat: add access control to pushCertificate #98

Merged
merged 5 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions contracts/interfaces/IToposCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ interface IToposCore {

function emitCrossSubnetMessage(SubnetId targetSubnetId) external;

function initialize(address[] memory adminAddresses, uint256 newAdminThreshold) external;

function pushCertificate(bytes calldata certBytes, uint256 position) external;

function setNetworkSubnetId(SubnetId _networkSubnetId) external;

function setup(bytes calldata params) external;

function upgrade(address newImplementation, bytes32 newImplementationCodeHash, bytes calldata setupParams) external;
function upgrade(address newImplementation, bytes32 newImplementationCodeHash) external;

function adminEpoch() external view returns (uint256);

Expand Down
53 changes: 20 additions & 33 deletions contracts/topos-core/ToposCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ pragma solidity ^0.8.9;
import "./AdminMultisigBase.sol";
import "./Bytes32Sets.sol";

import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";

import "./../interfaces/IToposCore.sol";

contract ToposCore is IToposCore, AdminMultisigBase {
contract ToposCore is IToposCore, AdminMultisigBase, Initializable {
using Bytes32SetsLib for Bytes32SetsLib.Set;

/// @dev Storage slot with the address of the current implementation. `keccak256('eip1967.proxy.implementation') - 1`
Expand All @@ -31,7 +33,10 @@ contract ToposCore is IToposCore, AdminMultisigBase {
/// @notice Mapping of transactions root to the certificate ID
mapping(bytes32 => CertificateId) public txRootToCertId;

constructor() {}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Sets the subnet ID
/// @param _networkSubnetId The subnet ID of the subnet this contract is to be deployed on
Expand All @@ -43,31 +48,16 @@ contract ToposCore is IToposCore, AdminMultisigBase {
/// @dev Need to pass the setup parameters to set the admins again
/// @param newImplementation The address of the new implementation
/// @param newImplementationCodeHash The code hash of the new implementation
/// @param setupParams The setup parameters for the new implementation
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata setupParams
) external override onlyAdmin {
function upgrade(address newImplementation, bytes32 newImplementationCodeHash) external override onlyAdmin {
if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash();
_setImplementation(newImplementation);
// AUDIT: If `newImplementation.setup` performs `selfdestruct`, it will result in the loss of _this_ implementation (thereby losing the ToposCore)
// if `upgrade` is entered within the context of _this_ implementation itself.
if (setupParams.length != 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = newImplementation.delegatecall(
abi.encodeWithSelector(IToposCore.setup.selector, setupParams)
);

if (!success) revert SetupFailed();
}
emit Upgraded(newImplementation);
}

/// @notice Push the certificate on-chain
/// @param certBytes The certificate in byte
/// @param position The position of the certificate
function pushCertificate(bytes memory certBytes, uint256 position) external override {
function pushCertificate(bytes memory certBytes, uint256 position) external override onlyAdmin {
(
CertificateId prevId,
SubnetId sourceSubnetId,
Expand Down Expand Up @@ -107,20 +97,6 @@ contract ToposCore is IToposCore, AdminMultisigBase {
emit CertStored(certId, txRoot);
}

/// @notice Sets the admin threshold and admin addresses
/// @dev This function can only be called by the proxy contract
/// @param params Admin threshold and admin addresses
function setup(bytes calldata params) external override {
(address[] memory adminAddresses, uint256 newAdminThreshold) = abi.decode(params, (address[], uint256));
// Prevent setup from being called on a non-proxy (the implementation).
if (implementation() == address(0)) revert NotProxy();

// NOTE: Admin epoch is incremented to easily invalidate current admin-related state.
uint256 newAdminEpoch = _adminEpoch() + uint256(1);
_setAdminEpoch(newAdminEpoch);
_setAdmins(newAdminEpoch, adminAddresses, newAdminThreshold);
}

/// @notice Emits an event to signal a cross subnet message has been sent
/// @param targetSubnetId The subnet ID of the target subnet
function emitCrossSubnetMessage(SubnetId targetSubnetId) external {
Expand All @@ -147,6 +123,17 @@ contract ToposCore is IToposCore, AdminMultisigBase {
}
}

/// @notice Contract initializer
/// @dev Can only be called once
/// @param adminAddresses list of admins
/// @param newAdminThreshold number of admins required to approve a call
function initialize(address[] memory adminAddresses, uint256 newAdminThreshold) public initializer {
// NOTE: Admin epoch is incremented to easily invalidate current admin-related state.
uint256 newAdminEpoch = _adminEpoch() + uint256(1);
_setAdminEpoch(newAdminEpoch);
_setAdmins(newAdminEpoch, adminAddresses, newAdminThreshold);
}

/// @notice Checks if a certificate exists on the ToposCore contract
/// @param certId The Certificate ID
function certificateExists(CertificateId certId) public view returns (bool) {
Expand Down
7 changes: 1 addition & 6 deletions contracts/topos-core/ToposCoreProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ contract ToposCoreProxy is EternalStorage {
bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);

error InvalidImplementation();
error SetupFailed();
error NativeCurrencyNotAccepted();

constructor(address tccImplementation, bytes memory params) {
constructor(address tccImplementation) {
if (tccImplementation.code.length == 0) revert InvalidImplementation();
_setAddress(KEY_IMPLEMENTATION, tccImplementation);

// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = tccImplementation.delegatecall(abi.encodeWithSelector(IToposCore.setup.selector, params));
if (!success) revert SetupFailed();
}

receive() external payable {
Expand Down
52 changes: 36 additions & 16 deletions scripts/deploy-topos-msg-protocol-dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,27 @@ const main = async function (...args: string[]) {
console.log(`Topos Core contract deployed to ${ToposCore.address}`)

// Deploy ToposCoreProxy
const toposCoreProxyParams = utils.defaultAbiCoder.encode(
['address[]', 'uint256'],
[[wallet.address], 1] // TODO: Use a different admin address than ToposDeployer
)
const ToposCoreProxyFactory = new ContractFactory(
toposCoreProxyJSON.abi,
toposCoreProxyJSON.bytecode,
wallet
)
const ToposCoreProxy = await ToposCoreProxyFactory.deploy(
ToposCore.address,
toposCoreProxyParams,
{ gasLimit: 5_000_000 }
)
const ToposCoreProxy = await ToposCoreProxyFactory.deploy(ToposCore.address, {
gasLimit: 5_000_000,
})
await ToposCoreProxy.deployed()
console.log(`Topos Core Proxy contract deployed to ${ToposCoreProxy.address}`)

console.info(`Initializing Topos Core Contract`)
const sequencerWallet = new Wallet(sequencerPrivateKey, provider)
const toposCoreInterface = new Contract(
ToposCoreProxy.address,
toposCoreInterfaceJSON.abi,
sequencerWallet
)
const adminThreshold = 1
await initialize(toposCoreInterface, sequencerWallet, adminThreshold)

// Deploy ERC20Messaging
const ERC20MessagingFactory = new ContractFactory(
erc20MessagingJSON.abi,
Expand All @@ -105,12 +109,7 @@ const main = async function (...args: string[]) {
await ERC20Messaging.deployed()
console.log(`ERC20 Messaging contract deployed to ${ERC20Messaging.address}`)

console.info(`\nSetting subnetId on ToposCore via proxy`)
const toposCoreInterface = new Contract(
ToposCoreProxy.address,
toposCoreInterfaceJSON.abi,
wallet
)
console.info(`Setting subnetId on ToposCore via proxy`)
await toposCoreInterface
.setNetworkSubnetId(subnetId, { gasLimit: 4_000_000 })
.then(async (tx: ContractTransaction) => {
Expand All @@ -130,7 +129,7 @@ const main = async function (...args: string[]) {
process.exit(1)
})

console.info(`\nReading subnet id`)
console.info(`Reading subnet id`)
const networkSubnetId = await toposCoreInterface.networkSubnetId()

console.info(
Expand All @@ -142,5 +141,26 @@ const sanitizeHexString = function (hexString: string) {
return hexString.startsWith('0x') ? hexString : `0x${hexString}`
}

async function initialize(
toposCoreInterface: Contract,
wallet: Wallet,
adminThreshold: number
) {
await toposCoreInterface
.initialize([wallet.address], adminThreshold, { gasLimit: 4_000_000 })
.then(async (tx: ContractTransaction) => {
await tx.wait().catch((error) => {
console.error(`Error: Failed (wait) to initialize ToposCore via proxy!`)
console.error(error)
process.exit(1)
})
})
.catch((error: Error) => {
console.error(`Error: Failed to initialize ToposCore via proxy!`)
console.error(error)
process.exit(1)
})
}

const args = process.argv.slice(2)
main(...args)
47 changes: 33 additions & 14 deletions scripts/deploy-topos-msg-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,23 @@ const main = async function (...args: string[]) {
4_000_000
)

const toposCoreProxyParams = utils.defaultAbiCoder.encode(
['address[]', 'uint256'],
[[wallet.address], 1] // TODO: Use a different admin address than ToposDeployer
)
const toposCoreProxyAddress = await processContract(
wallet,
toposCoreProxyJSON,
toposCoreProxySalt!,
[toposCoreAddress, toposCoreProxyParams],
[toposCoreAddress],
4_000_000
)

const sequencerWallet = new Wallet(sequencerPrivateKey, provider)
const toposCoreInterface = new Contract(
toposCoreProxyAddress,
toposCoreInterfaceJSON.abi,
sequencerWallet
)
const adminThreshold = 1
await initialize(toposCoreInterface, sequencerWallet, adminThreshold)

const erc20MessagingAddresss = await processContract(
wallet,
erc20MessagingJSON,
Expand All @@ -93,7 +98,7 @@ const main = async function (...args: string[]) {
4_000_000
)

setSubnetId(toposCoreProxyAddress, wallet, subnetId)
setSubnetId(toposCoreInterface, subnetId)

console.log(`
export TOPOS_CORE_CONTRACT_ADDRESS=${toposCoreAddress}
Expand Down Expand Up @@ -164,16 +169,9 @@ const processContract = async function (
}

const setSubnetId = async function (
toposCoreProxyAddress: string,
wallet: Wallet,
toposCoreInterface: Contract,
subnetId: string
) {
const toposCoreInterface = new Contract(
toposCoreProxyAddress,
toposCoreInterfaceJSON.abi,
wallet
)

await toposCoreInterface
.setNetworkSubnetId(subnetId, { gasLimit: 4_000_000 })
.then(async (tx: ContractTransaction) => {
Expand All @@ -196,5 +194,26 @@ const setSubnetId = async function (
await toposCoreInterface.networkSubnetId()
}

async function initialize(
toposCoreInterface: Contract,
wallet: Wallet,
adminThreshold: number
) {
await toposCoreInterface
.initialize([wallet.address], adminThreshold, { gasLimit: 4_000_000 })
.then(async (tx: ContractTransaction) => {
await tx.wait().catch((error) => {
console.error(`Error: Failed (wait) to initialize ToposCore via proxy!`)
console.error(error)
process.exit(1)
})
})
.catch((error: Error) => {
console.error(`Error: Failed to initialize ToposCore via proxy!`)
console.error(error)
process.exit(1)
})
}

const args = process.argv.slice(2)
main(...args)
Loading
Loading