fix(dao): prepend (cons) Created lock instead of append in proposal_create

Agora's stake validator (ppermitVote) enforces output_locks =
pcons NEW_LOCK old_locks (head-cons, not append). proposal_create
was using Vec::push which appends, so when the stake had any
pre-existing locks the output lock order didn't match what the
validator expected and the chain rejected with CekError on the
stake validator (5178-byte script, hash 57d6b17f...).

The bug went undetected on 2026-05-07 because that day's first
proposal_create ran on a stake with locked_by = [], where push
and prepend produce the same single-element vector. Today's
proposal #1 attempt — stake already holding a Created lock for #0 —
flushed it out.

Mirror the prepend pattern that proposal_cosign and proposal_vote
already use: build new_locks with the new lock at index 0, then
extend with the old locks.
This commit is contained in:
Kayos 2026-05-07 19:43:10 -07:00
parent 5e6cb7056b
commit b7074fd81b

View file

@ -352,12 +352,21 @@ pub fn build_unsigned_proposal_create(
starting_time: args.starting_time_ms,
};
// New stake datum: copy old, append Created lock for the new proposal.
let mut new_stake = args.stake_in.datum.clone();
new_stake.locked_by.push(ProposalLock {
// New stake datum: copy old, PREPEND a Created lock for the new
// proposal. Order matters — the stake validator's ppermitVote uses
// `pcons NEW_LOCK old_locks` (head-cons, NOT append). If we append
// and the input had pre-existing locks, the chain rejects with
// CekError on the stake validator. Caught 2026-05-08 trying to
// create proposal #1 while the stake still held a Created lock
// from proposal #0; cosign + vote builders already prepend.
let mut new_locks = Vec::with_capacity(args.stake_in.datum.locked_by.len() + 1);
new_locks.push(ProposalLock {
proposal_id: new_proposal_id,
action: ProposalAction::Created,
});
new_locks.extend(args.stake_in.datum.locked_by.iter().cloned());
let mut new_stake = args.stake_in.datum.clone();
new_stake.locked_by = new_locks;
let new_governor_datum_pd = new_governor.to_plutus_data()?;
let new_proposal_datum_pd = new_proposal.to_plutus_data()?;