Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
b301574
tally prototype with governors
giunatale Sep 13, 2024
8cb99c2
add protos
giunatale Sep 13, 2024
41b042f
governor object started (copy from Validator)
giunatale Sep 13, 2024
d4a8c03
wip
giunatale Sep 13, 2024
96abd4c
wip
giunatale Sep 14, 2024
f9ad9d8
...
giunatale Sep 14, 2024
700703d
...
giunatale Sep 14, 2024
f6c3392
missing governor election
giunatale Sep 14, 2024
6951d84
not right, cannot iterate on all governors
giunatale Sep 14, 2024
1a8495c
remove percentage and try sorting governors by VP
giunatale Sep 14, 2024
a769118
add msgs
giunatale Sep 14, 2024
ca19aaf
update test mock keepers
giunatale Sep 14, 2024
be1db00
...
giunatale Sep 14, 2024
e1a2bbb
compiles
giunatale Sep 15, 2024
851c005
add query and cli
giunatale Sep 15, 2024
372bca2
allow governor status updates only once a month
giunatale Sep 15, 2024
4ea0dad
genesis
giunatale Sep 15, 2024
fedba97
fix e2e
giunatale Sep 15, 2024
68645f2
add governors vp invariant
giunatale Sep 15, 2024
201b112
iterate over correct values
giunatale Sep 16, 2024
e5c1284
format pass
giunatale Sep 18, 2024
6f37870
fix missing return
giunatale Sep 18, 2024
c0cdec9
cleanup protos
giunatale Sep 19, 2024
80f44c7
Merge branch 'main' into governors
giunatale Sep 24, 2024
afa8d07
fix merge mistake
giunatale Sep 24, 2024
58d6a83
fix merge mistakes
giunatale Sep 24, 2024
44d71e6
approx VP computation for quorum with governors
giunatale Sep 24, 2024
5767cca
fix error in keeper.GetGovernor
giunatale Sep 24, 2024
25d017a
protogen
giunatale Sep 24, 2024
671755c
add min bonding requirements for governors
giunatale Sep 24, 2024
86031e6
governance delegations invariant
giunatale Sep 24, 2024
3a9f71f
fmt pass
giunatale Sep 24, 2024
b8eac36
use math
giunatale Sep 25, 2024
9bcbd0f
remove iteration over map
giunatale Sep 25, 2024
8daaffb
more efficient
giunatale Sep 25, 2024
26ff811
fix
giunatale Sep 25, 2024
ec48fe5
remove ambiguity
giunatale Sep 25, 2024
180e1c1
...
giunatale Sep 25, 2024
e147827
various fixes
giunatale Sep 25, 2024
e0d3865
Merge remote-tracking branch 'origin/main' into governors
giunatale Sep 25, 2024
48f45be
fix error codes
giunatale Sep 25, 2024
0659b9d
add alternative for tallying governors VP
giunatale Sep 25, 2024
8418fed
remove aggregate VP for governors
giunatale Sep 25, 2024
7bd0a30
don't use nil
giunatale Sep 25, 2024
abe5fc2
remove maxGovernors param
giunatale Sep 25, 2024
8b79c43
tally results updated only once only per voter
giunatale Sep 25, 2024
1f8e5a9
...
giunatale Sep 25, 2024
9560c40
Merge branch 'governors' into governors-no-aggregate-vp
giunatale Sep 25, 2024
374c315
Merge remote-tracking branch 'origin/main' into governors
giunatale Sep 27, 2024
de5f46a
Merge branch 'governors' into governors-no-aggregate-vp
giunatale Sep 27, 2024
90f540c
Merge branch 'main' into governors
giunatale Oct 2, 2024
c5e5ce4
fmt
giunatale Oct 2, 2024
a0b0650
Merge branch 'governors' into governors-no-aggregate-vp
giunatale Oct 2, 2024
7a792a8
fix/refac: tally with governors (#2)
tbruyelle Oct 11, 2024
8f1448d
test: keeper governor funcs (#3)
tbruyelle Oct 15, 2024
3d4fc3f
Merge branch 'main' into governors-no-aggregate-vp
giunatale Jan 14, 2025
2713498
fix merge conflicts
giunatale Jan 14, 2025
8dee330
test(x/gov): add e2e tests for governors (#4)
tbruyelle Jan 16, 2025
f25a8a4
fix(x/gov): REST query governor URL
tbruyelle Jan 16, 2025
539ee0e
docs(x/gov): add governors ADR (#5)
giunatale Jan 20, 2025
1b03975
docs(x/gov): add governors documentation (#6)
giunatale Jan 23, 2025
e48e30b
fix: gov init genesis order (#7)
tbruyelle Jan 23, 2025
e3a22b3
perf(x/gov): bunch of tally improvements (#8)
tbruyelle Jan 23, 2025
b39ff26
Merge branch 'main' into governors-no-aggregate-vp
giunatale Mar 12, 2025
fabb95d
Update go.mod
giunatale Mar 12, 2025
b44f837
Merge branch 'main' into governors-no-aggregate-vp
giunatale Jul 16, 2025
78f74da
fix gov e2e test
giunatale Jul 16, 2025
4a93201
Merge branch 'main' into governors-no-aggregate-vp
giunatale Jul 17, 2025
07edfa8
proto-pulsar-gen
giunatale Jul 17, 2025
47d3ed1
fix tally tests
giunatale Jul 17, 2025
dffe42f
fix e2e tests
giunatale Jul 17, 2025
d8a53d2
Merge branch 'main' into governors-no-aggregate-vp
giunatale Jul 17, 2025
3d25bc7
minor fixes
giunatale Jul 17, 2025
c5226ce
typo fix and fmt
giunatale Jul 31, 2025
3371143
Merge branch 'main' into governors-no-aggregate-vp
giunatale Jul 31, 2025
cf47c4e
Merge branch 'main' into governors-no-aggregate-vp
giunatale Aug 4, 2025
2342d60
Merge branch 'main' into governors-no-aggregate-vp
giunatale Aug 8, 2025
501d8fa
Merge branch 'main' into governors-no-aggregate-vp
giunatale Sep 1, 2025
103eedf
fix post-merge issues
giunatale Sep 3, 2025
5a3dadc
fix lint issues
giunatale Sep 4, 2025
b16a6ff
add events and some safety checks
giunatale Sep 24, 2025
b3dcd26
Merge branch 'main' into governors-no-aggregate-vp
giunatale Sep 26, 2025
01504a7
fix merge issue
giunatale Sep 26, 2025
fcded6e
fix tests
giunatale Sep 29, 2025
906a22c
fix e2e tests and revert wrong change
giunatale Sep 29, 2025
9ab91ed
fix lint error
giunatale Sep 29, 2025
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
21 changes: 11 additions & 10 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,6 @@ func NewAppKeeper(
appKeepers.StakingKeeper,
)

// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
appKeepers.StakingKeeper.SetHooks(
stakingtypes.NewMultiStakingHooks(
appKeepers.DistrKeeper.Hooks(),
appKeepers.SlashingKeeper.Hooks(),
appKeepers.CoreDaosKeeper.StakingHooks(),
),
)

evidenceKeeper := evidencekeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(appKeepers.keys[evidencetypes.StoreKey]),
Expand All @@ -284,6 +274,17 @@ func NewAppKeeper(
// If evidence needs to be handled for the app, set routes in router here and seal
appKeepers.EvidenceKeeper = *evidenceKeeper

// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
appKeepers.StakingKeeper.SetHooks(
stakingtypes.NewMultiStakingHooks(
appKeepers.DistrKeeper.Hooks(),
appKeepers.SlashingKeeper.Hooks(),
appKeepers.GovKeeper.StakingHooks(),
appKeepers.CoreDaosKeeper.StakingHooks(),
),
)

// ICA Host keeper
appKeepers.ICAHostKeeper = icahostkeeper.NewKeeper(
appCodec,
Expand Down
2 changes: 1 addition & 1 deletion app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ func orderInitBlockers() []string {
authtypes.ModuleName,
banktypes.ModuleName,
distrtypes.ModuleName,
govtypes.ModuleName,
stakingtypes.ModuleName,
govtypes.ModuleName,
photontypes.ModuleName,
slashingtypes.ModuleName,
minttypes.ModuleName,
Expand Down
121 changes: 121 additions & 0 deletions docs/architecture/adr-006-governors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# ADR 004: Governors System

## Changelog

- 2025-01-17: Initial version

## Status

Implemented (https://github.com/atomone-hub/atomone/pull/73)

## Abstract

This ADR proposes an enhanced governance model introducing “*Governors*” as a
special class of participants within the governance module. Unlike traditional
Cosmos-SDK governance, users can delegate their governance power to active
governors who meet specific eligibility criteria (e.g., minimum governance
self-delegation) while staking delegations to validators are not counted towards
determining their governance voting power (no validator inheritance).
The proposed changes alters vote tallying adding a separate and governance-specific
delegation mechanism to the one in `x/staking`, while trying to minimize impact on
tally and overall chain performance, and also define rules for transitioning
between governor statuses. The aim is to give the governance system a flexible
structure, enabling representative governance while maintaining the separation
of powers between validators and governors.

## Context

While the standard Cosmos-SDK governance module allows validators to vote on
proposals with all their delegated tokens unless delegators override their votes,
AtomOne removes this feature to prevent validators from having undue influence and
segregate the role of validator to securing the network. However, this approach
leads to the necessity for all users to be actively involved in governance, which
is not necessarily ideal. As of now, the governance module primarily relies on
on direct token voting (where each delegator votes with their own staked tokens).
However, as on-chain governance grows more complex, certain community members
might still prefer to delegate their voting power to specialized actors (governors)
that represent their political views. This shift requires formalizing participant
roles and creating processes for delegating power, ensuring alignment with the
network’s interests and enabling accurate tally calculations.

## Decision

1. Introduction of `Governor` Entities:
- A `Governor` is created through a `MsgCreateGovernor` transaction.
- The system enforces a minimum governance self-delegation requirement
before an account can achieve active governor status, which in practice
translates to a minimum stake requirement.
- The system tracks each governor’s address, description, status (e.g., active),
and last status change time (to limit frequent status toggles).

2. Delegation and Undelegation:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(When I am making this comment I haven't read the implementation yet).

I suppose the governor inherits the votes of whoever delegate to them at proposal execution and not voting right?

Given a proposal passing expects super majority it makes sense I think it is okay to have no timeout or maximum redelegation amount (like you have in normal x/gov due to the x/staking redelegation limitations).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, at proposal tally.

Also, the limitations to x/gov for the canonical module are indeed inherited from the dependency with x/staking that we don't have here. So indeed there is no need to have these limitations in place. The representative voting system can be more "liquid" as a result.

- Users (delegators) can delegate their governance power to a governor via
`MsgDelegateGovernor`. Only a single governor can be chosen at a time, and
the delegation is always for the full voting power of the delegator.
- If a delegator wishes to delegate to a different governor, they may do so
directly, automatically redelegating from any existing governor, and this
can be done at any time with no restrictions, except for active governors
themselves which are force to delegate their governance power to themselves.
- A user can also choose to undelegate from a governor with
`MsgUndelegateGovernor`, reverting to direct voting only.

3. Tally Logic:
- Each delegator’s staked tokens contribute to the total voting power of their
chosen governor since governance delegations are for the full voting power.
- During tallying, the system aggregates all delegated voting power plus any
governor’s own staked tokens (minus any deducted shares for direct votes by
delegators).
- Only votes from active governors or direct votes made by delegators are
counted. If a delegator votes directly, the corresponding shares are deducted
from the governor’s aggregated shares to avoid double counting (similarly
to how it's done in canonical Cosmos-SDK governance for validators).

4. Transitioning Governor Status:
- A governor can switch their status (e.g., from inactive to active) by meeting
the min self-delegation threshold and updating their status with a
`MsgUpdateGovernorStatus`. Governors that set themselves to inactive are allowed
to delegate their governance voting power to another governor. A status change
can however only occur after a waiting period (`GovernorStatusChangePeriod`).
- Attempts to rapidly change status are disallowed; changes can only occur
Copy link
Collaborator

@julienrbrt julienrbrt Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a governor having voted to a current proposal, with users delegated to it change its status during the voting period? If so, should it really?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you are proposing to prevent a status change if there is at least a proposal active (i.e. in voting period?)

Anyway, it would be the same as validators, that can unbond regardless of the status of proposals. We can further discuss this, but on a first level, what is your concern?

Copy link
Collaborator

@julienrbrt julienrbrt Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that a governor can abruptly become inactive during a vote, or right before the voting period ends, meaning their delegators that didn't vote because their delegators had voted and no more represented.
It could even change the proposal outcome in some very close scenario.

I agree that a waiting period is required, but instead of having it "in between", i'd instead queue that change for the waiting period duration.
So for any change, force the waiting period before the change takes place, not depending if you have just changed your status or if you waited a long time.
Right now, if you changed a long time ago, the change would be instant.

Copy link
Collaborator

@tbruyelle tbruyelle Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@julienrbrt Similarly, a governor can change this vote, which might also change the outcome of the proposal.
Given that, I'm not sure we should try to delay or prevent a change in his status.

Same for validators, a validator can be rejected from the set or being tombstoned, which immediately affects the votes (for other chains).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, @tbruyelle brings up a great point. I think it's ok to leave the system as is for this reason. Moreover, even if we wanted to change it (something which I actually worked on), we would have the issue of having to also prevent staking undelegations which bring the bonded amount below the min stake requirement, otherwise that would also be a way to bypass the queued change. And in practice the hooks do not allow to distinguish between an undelegation and a redelegation, which make doing this very challenging.

For the reason above and the observation from Thomas, I think it's better if we keep this as is

after the specified waiting period (`GovernorStatusChangePeriod`).

5. Parameters:
- `MinGovernorSelfDelegation`: The minimum number of tokens a governor must
stake to achieve or maintain active status.
- `GovernorStatusChangePeriod`: The time a governor must wait since the last
status change before being allowed to change status again.

## Consequences

### Positive

- Allows specialized participants (governors) to manage governance responsibilities,
potentially improving governance participation.
- Reduces complexity for casual stakers by letting them delegate governance
authority without having to participate actively every proposal, while always retaining
the option to vote directly.
- Retain segregation from the staking delegations system allowing governance and
staking delegations to be managed independently. This however does not prevent a
validator from also being a governor, but the two roles are kept separate.

### Negative

- Introduces more complexity in the governance codebase and state, with potential
performance implications for tallying and querying.
- Requires users to learn how to delegate to or become a governor, which in itself
may be a hurdle for some users.
- Requires clients and frontends to adapt to the new governance model, potentially
requiring changes to existing interfaces.

### Neutral

- The fundamental governance mechanisms (e.g., proposals, deposit structures)
remain largely the same.
- Governor-based delegation is optional; delegators can still vote independently
if they prefer.

## References

- [GIST following discussions during AtomOne Constitution Working Group](https://gist.github.com/giunatale/95e9b43f6e265ba32b29e2769f7b8a37)
- [Governors System PR](https://github.com/atomone-hub/atomone/pull/73)
- [Governors Initial Implementation Draft PR](https://github.com/atomone-hub/atomone/pull/16)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v2 v2.4.0
gotest.tools/v3 v3.5.2
pgregory.net/rapid v1.2.0
)
Expand Down Expand Up @@ -241,7 +242,6 @@ require (
google.golang.org/api v0.222.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.11 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions proto/atomone/gov/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ message GenesisState {
// If unset or set to 0, the quorum for the next law proposal will be set to
// the params.LawMinQuorum value.
string law_participation_ema = 14 [(cosmos_proto.scalar) = "cosmos.Dec"];

// governors defines all the governors present at genesis.
repeated Governor governors = 15;
// governance_delegations defines all the governance delegations present at genesis.
repeated GovernanceDelegation governance_delegations = 16;
}
82 changes: 82 additions & 0 deletions proto/atomone/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@ message Params {

// Achievable quorum for law proposals
QuorumRange law_quorum_range = 28 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Defines the duration of time that need to elapse between governor status changes.
google.protobuf.Duration governor_status_change_period = 29 [(gogoproto.stdduration) = true];

// Defines the minimum amound of bonded tokens, aka the "self-delegation" (because active governors
// must have the governance VP from the base account automatically delegated to them), that a governor
// must have to be considered active.
string min_governor_self_delegation = 30 [(cosmos_proto.scalar) = "cosmos.Int" ];
}

message QuorumRange {
Expand All @@ -382,3 +390,77 @@ message QuorumRange {
// Minimum achievable quorum
string min = 2 [(cosmos_proto.scalar) = "cosmos.Dec"];
}

// Governor defines a governor, together with the total amount of delegated
// validator's bond shares for a set amount of validators. When a delegator
// delegates a percentage of its x/gov power to a governor, the resulting
// shares from each delegators delegations in x/staking are added to the
// governor's total shares.
message Governor {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// governor_address defines the address of the governor; bech32-encoded.
string governor_address = 1;
// status is the status of the governor (active/inactive).
GovernorStatus status = 2;
// description defines the description terms for the governor.
GovernorDescription description = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// last_status_change_time is the time when the governor's status was last changed.
google.protobuf.Timestamp last_status_change_time = 4 [(gogoproto.stdtime) = true];
}

// GovernorStatus is the status of a governor.
enum GovernorStatus {
option (gogoproto.goproto_enum_prefix) = false;

// UNSPECIFIED defines an invalid governor status.
GOVERNOR_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "Unspecified"];
// ACTIVE defines a governor that is active.
GOVERNOR_STATUS_ACTIVE = 1 [(gogoproto.enumvalue_customname) = "Active"];
// INACTIVE defines a governor that is inactive.
GOVERNOR_STATUS_INACTIVE = 2 [(gogoproto.enumvalue_customname) = "Inactive"];
}

// Description defines a governor description.
message GovernorDescription {
option (gogoproto.equal) = true;

// moniker defines a human-readable name for the governor.
string moniker = 1;
// identity defines an optional identity signature (ex. UPort or Keybase).
string identity = 2;
// website defines an optional website link.
string website = 3;
// security_contact defines an optional email for security contact.
string security_contact = 4;
// details define other optional details.
string details = 5;
}

// GovernorValShares holds the number of virtual shares from the
// specific validator that a governor can use to vote on proposals.
message GovernorValShares {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string governor_address = 1;
string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.ValidatorAddressString"];
// shares define the delegation shares available from this validator.
string shares = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];
}

// GovernanceDelegation defines a delegation of governance voting power from a
// delegator to a governor.
message GovernanceDelegation {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string governor_address = 2;
}
Loading
Loading