Feature/sma 4 add nominator pool support#39
Feature/sma 4 add nominator pool support#39mrnkslv wants to merge 8 commits intorelease/nodectl/v0.4.0from
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for the TON Nominator Pool (also called TONCore pool or pool-core) to the node control system. Previously, only Single Nominator Pools (SNP) were supported. The implementation includes contract wrappers for pool interaction, message builders for pool operations, configuration support with optional deployment parameters, and CLI commands for pool management.
Changes:
- Added
NominatorPoolWrapperImplwrapper for interacting with deployed TON Nominator Pools - Implemented pool contract state initialization with support for configurable deployment parameters (max nominators, min stakes)
- Added message builders for pool operations (accept coins, process withdrawals, update validator set, etc.)
- Extended configuration to support TONCore pools with optional deploy-time parameters
- Updated deployment commands to support deploying TON Nominator Pools alongside SNP
- Added configuration commands for managing core pools in the CLI
- Updated documentation with TONCore pool configuration details
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/node-control/contracts/src/ton_core_nominator/ | New module implementing nominator pool wrapper and message builders with comprehensive tests |
| src/node-control/service/src/runtime_config.rs | Added support for opening TONCore pools during runtime configuration |
| src/node-control/common/src/app_config.rs | Extended PoolConfig enum with TONCore variant including optional deployment parameters, with serialization tests |
| src/node-control/commands/src/commands/nodectl/deploy_cmd.rs | Updated pool deployment command to handle both SNP and TONCore pools |
| src/node-control/commands/src/commands/nodectl/config_wallet_cmd.rs | Added TONCore pool support for manual stake operations |
| src/node-control/commands/src/commands/nodectl/config_pool_cmd.rs | Added CLI commands for managing core pools with dedicated configuration options |
| src/node-control/contracts/src/lib.rs | Exported new nominator pool types and functions |
| src/node-control/README.md | Updated documentation for TONCore pool configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Made-with: Cursor
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| min_nominator_stake, | ||
| } => { | ||
| let configured_validator = | ||
| addresses[0].parse::<MsgAddressInt>().context("invalid TONCore addresses[0]")?; |
There was a problem hiding this comment.
use with_context(|| format!("invalid pool address: {}", addresses[0]))
| min_validator_stake, | ||
| min_nominator_stake, | ||
| } => { | ||
| let configured_validator = |
There was a problem hiding this comment.
Why it is called `XXX_validator? It's a pool address, isn't it?
| owner: Option<String>, | ||
| #[arg(long = "addr1", help = "Core: addresses[0] (with --kind core)")] | ||
| addr1: Option<String>, | ||
| #[arg(long = "addr2", help = "Core: addresses[1] (with --kind core)")] |
There was a problem hiding this comment.
Use a clear description for that option.
| if validator_addr != wallet_address { | ||
| return Err(set_err(anyhow::anyhow!( | ||
| "TONCore addresses[0] must match this node's wallet (expected {}, config has {})", | ||
| wallet_address, |
There was a problem hiding this comment.
Design issue: addresses[0] is not the validator wallet.
Both addresses[0] and addresses[1] should be pool contract addresses (for even/odd validation rounds, as pool.fc itself notes: "The validator in most cases have two pools").
The validator wallet address is already available from the wallets config section via the node binding — no need to store it in the pool config.
This same wrong assumption (addresses[0] == validator wallet) is repeated in runtime_config.rs:525-531 and config_wallet_cmd.rs:716-722.
Fix: get the validator address from the wallet config (already resolved as wallet_address here), and treat both addresses[0] and addresses[1] as the two pool contract addresses. Derive/validate both against (wallet_address, validator_share, deploy_params).
| - `validator_share` — validator reward share (basis points; stored as `u16` on-chain) | ||
| - `max_nominators` — optional; if omitted, defaults from the contracts module are used (40 nominators) | ||
| - `min_validator_stake` — optional (nanotons); if omitted, defaults from the contracts module are used (100 TON) | ||
| - `min_nominator_stake` — optional (nanotons); if omitted, defaults from the contracts module are used (10 TON) |
There was a problem hiding this comment.
Bug: defaults are off by 1000x.
DEFAULT_DEPLOY_MIN_VALIDATOR_STAKE_NANOTONS: u64 = 100_000_000_000_000; // = 100,000 TON
DEFAULT_DEPLOY_MIN_NOMINATOR_STAKE_NANOTONS: u64 = 10_000_000_000_000; // = 10,000 TONThe README says 100 TON / 10 TON but the actual code defaults are 100,000 TON / 10,000 TON. This will mislead operators who skip the optional fields.
| /// Validator sends coins to increase their own stake in the pool. | ||
| /// Attach the desired amount of TON to the message; 1 TON is deducted as a processing fee. | ||
| pub fn deposit_validator(query_id: u64) -> anyhow::Result<Cell> { | ||
| let mut builder = BuilderData::new(); |
There was a problem hiding this comment.
Missing integration: deposit_validator / withdraw_validator are defined but never wired to any CLI command or automated flow.
After deployment, the pool starts with validator_amount = 0. The pool contract will reject every new_stake at:
throw_unless(83, validator_amount >= min_validator_stake); // 0 >= 100K TON → FAIL
Without a CLI command (e.g. nodectl wallet deposit-validator) or automated deposit step, the pool is deployed but cannot participate in elections. This is a blocker for the feature.
| max_nominators_count, | ||
| min_validator_stake, | ||
| max_nominators_stake: min_nominator_stake, | ||
| }, |
There was a problem hiding this comment.
Confusing field reuse. max_nominators_stake (from the SNP model) stores min_nominator_stake for TONCore — semantically opposite names.
Anyone consuming PoolData downstream will read max_nominators_stake and assume it means a maximum, not a minimum. Consider renaming the shared field or adding a method that returns the correct semantic value per pool kind.
| TONCore { | ||
| addresses: [String; 2], | ||
| validator_share: u64, | ||
| /// Deploy-time pool parameters; if omitted, defaults are applied in `contracts` (`resolve_deploy_pool_params`). |
There was a problem hiding this comment.
Loose typing. validator_share is u64 here but must fit u16 on-chain (pool.fc stores it as store_uint(validator_reward_share, 16), range 0..=10000 basis points).
Every use site does u16::try_from(*validator_share) — a value like 70000 is accepted at config parse time but fails at deploy/runtime. Using u16 directly (or a serde deserialize validator) would catch this earlier.
| )] | ||
| owner: Option<String>, | ||
| #[arg(long = "addr1", help = "Core: addresses[0] (with --kind core)")] | ||
| addr1: Option<String>, |
There was a problem hiding this comment.
Unclear naming. --addr1 / --addr2 with help text addresses[0] / addresses[1] does not tell the operator what these addresses represent.
Since both should be pool contract addresses (for even/odd validation rounds), consider naming them --pool-addr-even / --pool-addr-odd or at minimum documenting what they are in the help text.
| min_nominator_stake, | ||
| } => { | ||
| let configured_validator = MsgAddressInt::from_str(addresses[0].as_str()) | ||
| .context(format!("invalid TONCore addresses[0]: {}", addresses[0]))?; |
There was a problem hiding this comment.
Copy-paste: this ~25-line block is duplicated in 3 files.
The same sequence — parse addresses[0], validate against validator, convert validator_share to u16, call resolve_deploy_pool_params, call calculate_address, validate addresses[1] — is repeated in:
deploy_cmd.rs:330-367runtime_config.rs:522-556(here)config_wallet_cmd.rs:710-746
Only the post-validation action differs (deploy / open wrapper / return address). Consider extracting a helper into the contracts crate, e.g.:
pub fn resolve_toncore_pool(
config: &PoolConfig::TONCore,
validator_addr: &MsgAddressInt,
) -> anyhow::Result<(MsgAddressInt, u16, u16, u64, u64)>that validates addresses, converts types, resolves defaults, and returns the resolved params.
| ) | ||
| .map_err(set_err)?; | ||
| let state_init = NominatorPoolWrapperImpl::build_state_init( | ||
| &validator_addr, |
There was a problem hiding this comment.
Redundant work: build_state_init is called twice with the same arguments.
calculate_address (line 354) already calls build_state_init internally to hash the StateInit into an address. Then build_state_init is called again here with identical parameters.
Use from_init_data instead (which stores both the address and the StateInit), or add a method that returns (MsgAddressInt, StateInit) in one pass.
Summary
Changes
contracts/src/ton_core_nominator/— NominatorPoolWrapperImpl (embedded pool code, build_state_init / calculate_address / from_init_data), get_pool_data / get_roles, resolve_deploy_pool_params + defaultscontracts/src/ton_core_nominator/messages.rs— pool-specific opcodes and message builders (+ unit tests on cells)contracts/src/lib.rs, ton_core_nominator.rs— module wiring and re-exportscommon/src/app_config.rs— TONCore optional deploy fields (max_nominators, min_* stakes), serde tests including omitted vs explicit JSON keyscommands/.../config_pool_cmd.rs— config pool add --kind core, resolved deploy params in success message, pool ls for Corecommands/.../deploy_cmd.rs— deploy TON Nominator Pool without --owner; SNP unchangedcommands/.../config_wallet_cmd.rs— manual stake path validates addresses[0]/[1] vs calculate_address for coreservice/src/runtime_config.rs— open_nominator_pool for TONCore via NominatorPoolWrapperImplREADME.md— pools schema for core (addresses, validator_share, optional deploy fields)Closes SMA-4