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

[KIP-149] Unified system contract management process #150

Merged
merged 26 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
245 changes: 245 additions & 0 deletions KIPs/kip-149.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
---
kip: 149
title: Unified System Contract Management Process
author: Lewis (@hyeonLewis), Ian (@ian0371), Ollie (@blukat29), Lake (@hyunsooda), and Aidan (@aidan-kwon)
discussions-to: https://github.com/klaytn/kips/issues/149
status: Draft
type: Standards Track
category: Core
created: 2023-09-20
---

## Simple Summary

A unified deployment and management process for system contracts.

## Abstract

This standard defines a unified deployment and management process for system contracts. To effectively manage system contracts, it also introduces a Registry contract that manages all system contracts.

## Motivation

Currently, system contracts are deployed and managed without any defined standards. For example, the `AddressBook` contract was deployed by a bytecode injection at the genesis block with a reserved address, while EOA deployed `TreasuryRebalance`, and its address is set in the chain config. As more system contracts will be deployed in the future, it’s essential to have a standard way to deploy and manage system contracts.

## Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

This standard defines the separation of data and logic contracts in system contracts. This method, often called the proxy pattern, allows the change of the logic contract while keeping the data, which can greatly reduce the cost of contract updates. Upgrading a logic contract will not affect the Registry since the Registry only holds the address of the proxy(data) contract. Delegating ownership of a proxy contract to a governance contract can solve the centralized issue and potential private key loss.
ian0371 marked this conversation as resolved.
Show resolved Hide resolved
ian0371 marked this conversation as resolved.
Show resolved Hide resolved

### Definitions

- System contract: A contract that affects protocol. The currently deployed system contracts are as follows: **AddressBook**, **Voting**([KIP-81](https://github.com/klaytn/kips/blob/main/KIPs/kip-81.md)), **StakingTracker**, **TreasuryRebalance**([KIP-103](https://github.com/klaytn/kips/blob/main/KIPs/kip-103.md))

- System contract upgrade: The process of updating an existing logic contract while maintaining a proxy contract. The upgraded logic contract must be compatible with the previous interface.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

- System contract replacement: The deployment of a new system contract that is then registered to the Registry using the same name as its predecessor. The new contract effectively deprecates the previous system contract.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

### Smart Contracts Overview

The proposed smart contract will be implemented in Solidity and compatible with the Ethereum Virtual Machine (EVM).

The smart contract will have the following features:

1. Registry

- Register a new system contract with an activation block.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

- Return the state of the system contracts.

2. Proxy

- Delegate a call to logic contract.

- Upgrade a logic contract.

#### 1. Registry

The registry should have data for the pre-deployed system contracts as soon as it is deployed. It will be done by state injection, which injects data into the Registry directly using the `state.SetState`. A reference implementation is introduce in [Implementation](#implementation).

#### Interface of Registry

```solidity
pragma solidity ^0.8.0;

interface Registry {
blukat29 marked this conversation as resolved.
Show resolved Hide resolved
/* ========== TYPES ========== */
/// @dev Struct of system contracts
struct Record {
address addr;
uint256 activation;
}

/* ========== VARIABLES ========== */
/// The following variables are baked in the interface because their storage layout matters in protocol consensus
/// when inject initial states (pre-deployed system contracts, owner) of the Registry.
/// @dev Mapping of system contracts
mapping(string => Record[]) public records;

/// @dev Array of system contract names
string[] public contractNames;

/// @dev Owner of contract
address private _owner;

/* ========== EVENTS ========== */
/// @dev Emitted when the contract owner is updated by `transferOwnership`.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/// @dev Emitted when a new system contract is registered.
event Registered(string name, address indexed addr, uint256 indexed activation);

/* ========== MUTATORS ========== */
/// @dev Registers a new system contract.
function register(string memory name, address addr, uint256 activation) external;

/// @dev Transfers ownership to newOwner.
function transferOwnership(address newOwner) external;

/* ========== GETTERS ========== */
/// @dev Getter for a public state variable: records.
function records(string memory name, uint256 index) external returns (address, uint256, uint256);

/// @dev Getter for a public state variable: contractNames.
function contractNames(uint256 index) external returns (string memory);

/// @dev Returns an address for active system contracts registered as name if exists.
/// It returns a zero address when there's no active system contract with name.
function getActiveAddr(string memory name) external returns (address);

/// @dev Returns all system contracts registered as name.
function getAllRecords(string memory name) external view returns (Record[] memory);

/// @dev Returns all names of registered system contracts.
function getAllNames() external view returns (string[] memory);

/// @dev Returns owner of contract.
function owner() external view returns (address);
}
```

#### Methods

```solidity
function register(string memory name, address addr, uint256 activation)
```

Registers a new system contract. It will be activated at `activation`. It overwrites the predecessor if a predecessor system contract exists and is not yet active. Passing `addr == address(0)` is an implicit deprecation for the `name`, meaning the `name` will no longer be used.

The function validates the following requirements:

- The function caller MUST be a governance contract.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

- The function MUST revert if a `name` is an empty string.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

- The function MUST revert if `activation < block.number`.

kjhman21 marked this conversation as resolved.
Show resolved Hide resolved
The function emits a `Registered` event.

```solidity
function getActiveAddr(string memory name) view returns (address)
```

Returns the address of the active system contract with the `name`. It returns a zero address if there’s no registered system contract with the `name` or the `name` has been deprecated by registering a zero address.

#### 2. Proxy

The implementation of the proxy contract comes from [EIP-1967](https://eips.ethereum.org/EIPS/eip-1967).

### System Contracts Life Cycle
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

The Registry contract manages system contracts based on the current block number. Its state will be managed implicitly, which means there’s no explicit state variable(e.g., enum State). It has three implicit states and cannot be reversed to the previous state:

- Registered: It has been registered but has not been activated yet.

- Active: The current block number exceeds its activation, and no active successor system contract exists.

- Deprecated: There’s an active successor system contract.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

![](../assets/kip-149/LifeCycle.png)

#### Upgrade System Contracts

When upgrading system contracts, its logic contract will be changed by governance proposal. The Registry will not be updated since it only manages the address of the proxy contract.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

![](../assets/kip-149/UpgradeProcess.png)

#### Replace System Contracts

If current system contract updates can’t be done by upgrading the logic contract, it must be replaced with the newly deployed system contract. The predecessor doesn’t need to be explicitly deprecated since a new system contract will implicitly replace and deprecate it. The replacement process is the same as the initial registration for the system contract except for having a predecessor.

![](../assets/kip-149/ReplacementProcess.png)

### Core Logic Overview

After a target block number, a Klaytn node should read all the active addresses of system contracts through the Registry. A Klaytn node deploys the Registry at the configured block number at the reserved address by bytecode injection.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

#### Chain Configuration
ian0371 marked this conversation as resolved.
Show resolved Hide resolved

In the Chain Config, the following field is introduced. All node operators in a network must update `genesis.json` configuration with the same value. The configuration values for Baobab and Cypress networks are hard-coded on the client source code.
kjhman21 marked this conversation as resolved.
Show resolved Hide resolved

- `kip149CompatibleBlock`: the target block number that a Klaytn node deploys Registry.

#### Execution

The Registry deployment is executed at the `Finalize` function, which means the end of the block generation. It reads the reserved address and runtime bytecode and deploys the Registry by the bytecode injection. Also, it injects the state for pre-deployed system contracts here. Note that the Registry contract will be deployed at th parent block of the `kip149CompatibleBlock` since it needs to be ready at the `kip149CompatibleBlock`.

```go
// Note that it is deployed at the parent block of the kip-149 fork block
if chain.Config().IsKIP149ForkBlockParent(header.Number) {
// Inject the bytecode for the Registry contract and the state for pre-deployed system contracts
err := registry.InstallRegistry(state)
if err != nil {
logger.Error("failed to set the registry contract code", "err", err)
} else {
logger.Info("successfully set the registry contract code", "block", header.Number.Uint64())
}
}
```

#### Resolver

A Klaytn node will have a resolver to read the active addresses of system contracts from the Registry.

## Rationale

### Bytecode Injection for Registry Deployment

In the future, all system contracts will be registered in the Registry, and a Klaytn node will read the active addresses of system contracts from the Registry. Not only for a Klaytn node but also for other ecosystem participants who will use the registry to read the system contracts they need, meaning the registry should be registered at an easily accessible reserved address.

### State Injection for Pre-deployed System Contracts

The Registry should hold data for pre-deployed system contracts as soon as it is deployed. However, pre-deployed system contracts have been deployed and managed differently by networks. (e.g., the Voting contract on the Klaytn cypress network is currently not used on the Klaytn baobab network.) To handle this, there are three main approaches:

1. Send multiple register transactions after deploying the Registry.

2. Use a fallback logic in the getter to return the state of pre-deployed system contracts.

3. Use state injection, which injects state for pre-deployed system contracts by the `state.SetState`.

The first approach seems straightforward. However, the Registry will be set in the `engine.Finalize`, which is the last part of the block generation. It means the transaction cannot be processed in the same block and must be entered in the first order of the next block. This requires additional implementation and can't be applied when `KIP149CompatibleBlock == 0`. In the second approach, the Registry contract should have different codes by the network, requiring direct code modification in the getter(e.g., add/remove hard-coded system contracts and modify if-else statements). It can cause potential security vulnerabilities and increase costs for getter calls permanently. On the other hand, the last approach is much safer because it's more structured and doesn't require modifying any code. It can also set the necessary configuration without working with the additional contract's constructor.

### Separate Data and Logic Contract

This proxy pattern simplifies the process of system contract update because the existing data can be used even if the logic contract is changed. The main issue of the proxy pattern is the centralization and potential private key loss. But delegating ownership to upgrade a logic contract to Governance can solve those problems.

## Backward Compatibility

### Deployed System Contracts

It needs to calculate storage slots for variables in the Registry contract for state injection. It will follow the [solidity layout rule](https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html).

## Implementation

- A reference implementation for the Registry contract: [Implementation](https://github.com/klaytn/system-contracts/tree/registry)
- A reference implementation for core logic: [Implementation](https://github.com/klaytn/klaytn/pull/1968)

## References

- Binance Smart Chain: https://github.com/bnb-chain/bsc/tree/master/core/systemcontracts

- Celo: https://docs.celo.org/community/release-process/smart-contracts

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Binary file added assets/kip-149/LifeCycle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/kip-149/ReplacementProcess.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/kip-149/UpgradeProcess.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading