Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Central memory bus architecture #496

Open
mconcat opened this issue Feb 3, 2025 · 1 comment
Open

Central memory bus architecture #496

mconcat opened this issue Feb 3, 2025 · 1 comment
Assignees
Labels

Comments

@mconcat
Copy link
Contributor

mconcat commented Feb 3, 2025

1. Introduction and Motivation

To facilitate smoother migrations, emergency bug fixes, and security patches in gnoswap v1, we propose introducing a central communication bus. Currently, contracts call one another directly—for example, staker.MintAndStake() directly invokes position.Mint()—which creates tight coupling and makes coordinated upgrades difficult. By decoupling these interactions via a central bus, we can:

  • Reduce interdependency: Allow individual contracts to be halted, upgraded, or replaced without breaking the overall system.
  • Enable dynamic upgrades: Support on-the-fly version transitions (mounted via governance proposals) without the need for full state or asset migration.
  • Improve security and resilience: Quickly mitigate discovered vulnerabilities while ensuring continued cross-contract communication.

Note: This approach is intended for operational continuity (e.g., mitigating bugs or security vulnerabilities) rather than full state or asset migrations.


2. Proposed Architecture

2.1 Overview

The central communication bus acts as an intermediary between contracts. Instead of contracts invoking each other’s methods directly, they will register their external entrypoints with the bus. When a contract needs to perform an operation, it will query the bus, which then routes the request to the appropriate contract’s current implementation. This indirection enables seamless upgrades by updating registry entries rather than modifying individual contract logic.

2.2 Architectural Components

  • Registry:
    The bus maintains a registry mapping entrypoint identifiers (and, potentially, version tags) to the corresponding contract addresses. This registry is updated via a controlled governance process.

  • Routing Mechanism:
    When a contract makes a call, it does so by querying the bus. The bus then routes the call to the registered contract. This design encourages stateless, pure function calls that pass all necessary context explicitly, reducing risks associated with stateful interactions.

  • Versioning:
    The bus allows for versioned endpoints. When a new version of a contract is approved via a governance proposal, the registry is updated to point to the new version. For the duration of the upgrade, calls are seamlessly redirected without requiring changes to the calling contract.

  • Permissions and Access Controls:
    The bus will assume responsibility for enforcing permissions (e.g., ensuring that only authorized callers access certain functionalities), eliminating the need for contracts to reference prior realms or hard-coded permissions.

2.3 Operational Flow

  1. Registration:
    Upon deployment, each contract registers its entrypoints with the bus. For example, the position contract registers its Mint() function.

  2. Invocation:
    When the staker contract requires a mint operation, it queries the bus to obtain the current address for position.Mint() and then routes the call accordingly.

  3. Upgrade via Governance:
    If an upgrade is needed (for bug fixes or security patches), a governance proposal is submitted to update the registry. Once approved, subsequent calls are automatically routed to the new contract version.


3. Entrypoints and Interfaces

The following interfaces outline the key functions available via the bus. Note that the bus now manages permission checks, reducing the need for “Only” functions within each contract’s individual interface.

3.1 Common Interface

type CommonI interface {
    SetHalt(halt bool)
    IsHalted() bool
    GetLimitCaller() bool
    MustRegistered(tokenPath string)
    ListRegisteredTokens() []string
    GetTokenTeller(tokenPath string) TokenTellerI
    GetTokenPath(tokenPath string) string
}
  • Permissions:
    Functions such as AdminOnly, GovernanceOnly, etc., will be removed from the individual contract interfaces and managed centrally by the bus.

3.2 Emission Interface

type EmissionI interface {
    MintAndDistributeGns()
    ChangeDistribitionPct(devOpsPct uint64) uint64
    GetDistributedToGovStaker() uint64
    ClearDistributedToGovStaker()
}
  • Callback Removal:
    To reduce interdependencies, callbacks will be removed and managed via the bus as needed.

3.3 Gns Interface

type GnsI interface {
    GetAvgBlockTimeInMs() uint64
    SetAvgBlockTimeInMs(avgBlockTimeInMs uint64)
}
  • Simplification:
    Callback dependencies are eliminated, simplifying cross-contract communication.

3.4 Pool Interface

interface PoolI {
    GetPoolCreationFee() uint64
    SetPoolCreationFee(fee uint64)
    GetWithdrawalFee() uint64
    SetWithdrawalFee(fee uint64)
    SetFeeProtocol(feeProtocol0 uint8, feeProtocol1 uint8)
    
    DoesPoolPathExist(poolPath string) bool
    GetPoolPath(tokenPath0 string, tokenPath1 string) string
    GetPoolFromPoolPath(poolPath string) Pool
}
  • Type Compatibility:
    Because the Pool type itself may change during migration, the bus (or an adapter layer) may need to deconstruct and reassemble the type to maintain compatibility.

3.5 Position Interface

type PositionI interface {
    Mint(
        token0Path string,
        token1Path string,
        poolFee uint64,
        tickLower int32,
        tickUpper int32,
        amount0Desired string,
        amount1Desired string,
        amount0Min string,
        amount1Min string,
        timeout int64,
        recipient std.Address,
        sender std.Address,
    ) (uint64, string, string, string) 
}
  • Interface Simplification:
    Given that this function is primarily used by StakeAndMint in the staker contract, we may consider exposing a simplified version or a wrapper function to hide the complexity of the full mint interface.

3.6 ProtocolFee Interface

interface ProtocolFeeI {
    GetDevOpsPct() uint64
    SetDevOpsPct(devOpsPct uint64)
    DistributeProtocolFee()
    GetAccuTransferToGovStaker() uint64
    ClearAccuTransferToGovStaker()
    AddToProtocolFee(tokenPath string, amount uint64)
}
  • Governance and Permissioning:
    Functions will include checks performed by the bus to ensure that only authorized calls are executed.

4. Governance and Upgrade Process

  • Governance Proposals:
    Any updates to the registry—such as mounting a new contract version—will be proposed via governance. Upon approval, the bus registry is updated to route calls to the new version.

  • Fallback and Safety Measures:
    If a new version is found to be problematic post-deployment, the bus can quickly revert to the previous version. Detailed versioning and fallback strategies will be implemented to minimize downtime.

@onlyhyde
Copy link
Member

onlyhyde commented Feb 6, 2025

The proposal seems to be a structure with strong coupling with other contracts, difficulty in upgrading the bus if there is a problem with the bus, and strong coupling with other contracts. Also, the role of the bus is considered beyond simple routing to access permissions between contracts.

We think it would be better to simplify the role and turn it into a weak coupling, so we propose the following modification.

The access permission between contracts is used by implementing RBAC (Role based access control). This means that even if a contract is added, if it is granted a Role, it will be granted access, which is not unreasonable for the behavior.

Contracts are defined by the functions they need to call. For example, the pool contract only looks at the emission contract, and the position contract calls some functions of the pool contract.
The functions that need to be called between contracts are categorized as callbacks, and callback functions can be registered. When a new contract is deployed, the callbacks are re-registered to call the functions of the new contract.
The new contract must have a structure that allows it to see and use the history of the previous contract's book. As a result, the contract must contain a getter that is read only to manage the state of the ledger.

This approach removes the dependency on the GNS, and allows you to deploy and change new contracts in isolation. Granted, this may be a complex structure compared to the upgradable pattern, which separates the contract that owns the ledger(state) from the contract that owns the logic, and upgrades the contract that owns the logic, but I think it's the right level of code work to apply at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants