diff --git a/crates/aldabra-dao/src/builder/proposal_vote.rs b/crates/aldabra-dao/src/builder/proposal_vote.rs index 739e1c9..497ab2a 100644 --- a/crates/aldabra-dao/src/builder/proposal_vote.rs +++ b/crates/aldabra-dao/src/builder/proposal_vote.rs @@ -110,15 +110,23 @@ pub struct ProposalVoteArgs { pub change_address: String, /// Spendable wallet UTxOs. pub wallet_utxos: Vec, - /// Current chain tip slot. Sets `valid_from_slot(tip_slot)` and - /// `invalid_from_slot(tip_slot + VALIDITY_RANGE_SLOTS)`. + /// Current chain tip slot. Sets `valid_from_slot(tip_slot)`. pub tip_slot: u64, - /// POSIX-ms equivalent of the validity range's UPPER bound (i.e. the - /// slot `tip_slot + VALIDITY_RANGE_SLOTS` converted to ms via the - /// Shelley genesis epoch). Embedded as `Voted.posix_time` on the new - /// stake lock — must match what the validator extracts from - /// `PFullyBoundedTimeRange _ upperBound`. Caller is responsible for - /// the slot↔ms conversion. + /// Tx upper-bound slot. Sets `invalid_from_slot(validity_upper_slot)`. + /// Caller may clamp this to (e.g.) the proposal's voting_end_slot to + /// keep the validity range inside the voting window. MUST be consistent + /// with `validity_upper_ms` — both should encode the SAME slot via the + /// network's slot↔ms conversion. The chain's V2 ScriptContext computes + /// `txInfo.validRange.upperBound` from this slot, and the validator's + /// `ppermitVote` synthesizes the expected `Voted.posix_time` from + /// THAT upper bound. If the slot underlying `validity_upper_ms` differs + /// from this slot, the validator's `passert "Correct outputs"` fails. + pub validity_upper_slot: u64, + /// POSIX-ms equivalent of `validity_upper_slot`. Embedded as + /// `Voted.posix_time` on the new stake lock — must match what the + /// validator extracts from `PFullyBoundedTimeRange _ upperBound`. + /// Caller is responsible for the slot↔ms conversion AND for ensuring + /// `slot_to_posix_ms(validity_upper_slot) == validity_upper_ms`. pub validity_upper_ms: i64, /// Reference UTxO citing the stake validator script. pub stake_validator_ref: ReferenceUtxo, @@ -481,9 +489,13 @@ pub fn build_unsigned_proposal_vote( ); // Validity range — must be inside voting window (already preflighted) - // AND its width must be ≤ votingTimeRangeMaxWidth. + // AND its width must be ≤ votingTimeRangeMaxWidth. The TTL uses + // `validity_upper_slot` (which the caller may have clamped) so the + // chain's reconstructed `txInfo.validRange.upperBound` matches the + // `validity_upper_ms` we embedded in the new `Voted` lock — otherwise + // `ppermitVote`'s `passert "Correct outputs"` would crash silently. staging = staging.valid_from_slot(args.tip_slot); - staging = staging.invalid_from_slot(args.tip_slot + VALIDITY_RANGE_SLOTS); + staging = staging.invalid_from_slot(args.validity_upper_slot); // Disclosed signer: voter pkh. The validator's `pisSignedBy` checks // this against `txInfoSignatories`. @@ -644,6 +656,11 @@ mod tests { }, ], tip_slot: 180_062_536, + // Derive from validity_upper_ms via mainnet shelley_zero. + // shelley_zero = (4_492_800, 1_596_059_091_000). + // slot = 4_492_800 + (validity_upper_ms - 1_596_059_091_000) / 1000. + validity_upper_slot: 4_492_800 + + ((validity_upper_ms - 1_596_059_091_000) / 1000) as u64, validity_upper_ms, stake_validator_ref: ReferenceUtxo { tx_hash_hex: "479b8203c8b84ce20bb0a1c6ee1f527f122d1ce3e655dfc54635061eff622aea".into(), diff --git a/crates/aldabra-mcp/src/tools.rs b/crates/aldabra-mcp/src/tools.rs index 7e41ad3..11fdd8e 100644 --- a/crates/aldabra-mcp/src/tools.rs +++ b/crates/aldabra-mcp/src/tools.rs @@ -3274,6 +3274,7 @@ impl WalletService { change_address: self.inner.address.clone(), wallet_utxos, tip_slot, + validity_upper_slot, validity_upper_ms, stake_validator_ref, proposal_validator_ref,