Skip to content

feat(staking): add StakingActions to GenLayerClient#75

Merged
MuncleUscles merged 3 commits intomainfrom
feat/staking-sdk
Apr 22, 2026
Merged

feat(staking): add StakingActions to GenLayerClient#75
MuncleUscles merged 3 commits intomainfrom
feat/staking-sdk

Conversation

@MuncleUscles
Copy link
Copy Markdown
Member

Summary

Adds a first-class staking module to the Python SDK so consumers don't have to fall back to raw web3.py. Mirrors the genlayer-js `StakingActions` module one-for-one.

New module: `genlayer_py.staking`

  • `genlayer_py/staking/actions.py` — 18 functions (9 reads, 9 writes)
  • `genlayer_py/staking/abi/staking_abi.json` — full `IGenLayerStaking` interface, 137 functions
  • `genlayer_py/staking/abi/validator_wallet_abi.json` — `ValidatorWallet` interface, 33 functions
  • Both JSONs bundled as package data via `pyproject.toml`

Client surface (on `GenLayerClient`)

Reads: `staking_epoch`, `active_validators`, `active_validators_count`, `is_validator`, `get_validator_info`, `get_stake_info` (→ `stakeOf`), `banned_validators`, `validator_min_stake`, `delegator_min_stake`.

Writes: `validator_join` (payable, operator optional), `validator_deposit`, `validator_exit`, `validator_claim`, `validator_prime`, `set_operator`, `set_identity`, `delegator_join`, `delegator_exit`, `delegator_claim`.

The key routing fix

`validatorDeposit()` and `validatorExit(uint256)` on the `Staking` contract assert `msg.sender == ValidatorWallet`. Calling them from the operator EOA reverts. This SDK routes both through the wallet's own forwarders (same shape as `setOperator`/`setIdentity`), so Staking sees the wallet as sender. The identical fix is going out in genlayer-js#155.

Chain config

`GenLayerChain` gains `staking_contract: Optional[SimpleContractInfo]`. All four existing presets pass `None` — this PR doesn't hard-code staking contract addresses per network. Consumers wire it up at construction time, or we land per-chain addresses in a follow-up once the staking router is published:

```python
from genlayer_py.chains import testnet_bradbury
testnet_bradbury.staking_contract = {
"address": "0x...",
"abi": STAKING_ABI,
}
```

Test plan

  • `pytest tests/unit/staking` — 8/8 pass. Each write asserts selector + target contract (guards the wallet-vs-staking routing).
  • `pytest tests/unit` — 49/49 non-network tests pass (12 pre-existing network smoke failures unchanged).
  • End-to-end: exercise under the ci-core-e2e-runner tooling suite against a live stack.

Non-goals

  • No lifecycle integration tests in this repo (staking needs a live node + validator wallet + operator wallet trio — that lives in ci-core-e2e-runner).
  • No automatic staking contract address per chain — intentional; the addresses move per deployment and will land in a smaller config-only follow-up.

Mirrors genlayer-js StakingActions so Python consumers don't have to
drop to raw web3.py to stake, delegate, rotate operators, etc. Bundles
slimmed IGenLayerStaking and ValidatorWallet ABIs as JSON resources.

Writes are routed according to on-chain sender checks:
  - validatorJoin/Claim/Prime and all delegator methods go to Staking
    with the caller EOA as msg.sender.
  - validatorDeposit/Exit are routed through the ValidatorWallet's own
    forwarders because Staking rejects those two with a wallet-only
    sender assertion. Same constraint is being fixed in genlayer-js.

Reads: epoch, active_validators, active_validators_count, is_validator,
get_validator_info, get_stake_info (stakeOf), banned_validators,
validator_min_stake, delegator_min_stake.

Writes: validator_join (payable, operator optional), validator_deposit,
validator_exit, validator_claim, validator_prime, set_operator,
set_identity, delegator_join, delegator_exit, delegator_claim.

GenLayerChain gains an optional staking_contract (SimpleContractInfo);
all existing presets pass None because staking is only configured when
a caller wires it in. Add the address/ABI manually or via a future
chain preset once the staking contract is published per network.

Unit tests assert that each write encodes the right selector and
targets the right contract — critical for the wallet-vs-staking
routing fix. Lifecycle coverage lives in the ci-core-e2e-runner
tooling suite (needs a live node).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@MuncleUscles has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 48 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 48 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac9931a2-736a-4fff-816a-a29d368eaba8

📥 Commits

Reviewing files that changed from the base of the PR and between e73e121 and 1d4ff75.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .github/workflows/smoke.yml
  • genlayer_py/chains/localnet.py
  • genlayer_py/chains/studionet.py
  • genlayer_py/chains/testnet_asimov.py
  • genlayer_py/chains/testnet_bradbury.py
  • genlayer_py/client/genlayer_client.py
  • genlayer_py/staking/__init__.py
  • genlayer_py/staking/abi/__init__.py
  • genlayer_py/staking/abi/staking_abi.json
  • genlayer_py/staking/abi/validator_wallet_abi.json
  • genlayer_py/staking/actions.py
  • genlayer_py/types/chain.py
  • pyproject.toml
  • tests/unit/smoke/test_bradbury_smoke.py
  • tests/unit/smoke/test_testnet_smoke.py
  • tests/unit/staking/__init__.py
  • tests/unit/staking/test_staking_actions.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/staking-sdk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The smoke tests were constructing a stock Web3.HTTPProvider directly.
web3.py's HTTPProvider starts its request counter at 0, so every fresh
Web3() sends id=0 on its first RPC call. The GenLayer RPC servers
reject id=0 with "Invalid Request: Key: 'Request.ID' Error:Field
validation for 'ID' failed on the 'required' tag" (go-playground
validator's `required` treats int zero as unset), so every fresh
HTTPProvider's first call fails.

GenLayerProvider (the SDK's own provider) ids requests by
int(time.time() * 1000), which is always nonzero, and is the path real
consumers already use. Switch the smoke tests to construct via
create_client() so we're testing the SDK path, not a bypass.

test_rpc_connects no longer calls Web3.is_connected — GenLayerProvider
deliberately doesn't implement BaseProvider.is_connected, and any
successful RPC call is a stronger liveness signal anyway (chain_id
matching 4221 confirms we're reaching the right network too).
@MuncleUscles MuncleUscles merged commit f25452d into main Apr 22, 2026
4 checks passed
@MuncleUscles MuncleUscles deleted the feat/staking-sdk branch April 22, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant