Skip to content
Merged
Changes from all commits
Commits
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
202 changes: 0 additions & 202 deletions contracts/accord/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1506,28 +1506,6 @@ impl AccordContract {
Ok(())
}

/// Disburse a recurring schedule (permissionless crank). Reject if schedule
/// is in a terminal status (Cancelled or Completed) or called too early.
pub fn disburse_recurring(env: Env, id: u64) -> Result<(), ContractError> {
let mut s = read_recurring_schedule(&env, id)?;
if !matches!(s.status, RecurringStatus::Active) {
return Err(ContractError::ProposalNotActive);
}
let now = env.ledger().timestamp();
if s.last_disbursed_at != 0 && now < s.last_disbursed_at.saturating_add(s.interval_secs) {
return Err(ContractError::ProposalNotActive);
}
s.last_disbursed_at = now;
if s.remaining_occurrences > 0 {
s.remaining_occurrences = s.remaining_occurrences.saturating_sub(1);
}
if s.remaining_occurrences == 0 {
s.status = RecurringStatus::Completed;
}
write_recurring_schedule(&env, &s);
Ok(())
}

/// Creates a new transfer proposal with one or more asset transfers.
///
/// # Arguments
Expand Down Expand Up @@ -2109,186 +2087,6 @@ impl AccordContract {
Ok(id)
}

/// Creates a proposal to remove an existing owner from the multisig.
///
/// Automatically transitions the proposal to `Ready` when the approval count reaches threshold.
/// Records `ready_at` the first time the threshold is crossed.
pub fn approve(env: Env, approver: Address, proposal_id: u64) -> Result<(), ContractError> {
approver.require_auth();
require_owner(&env, &approver)?;

let proposal = Proposal {
id,
proposer: proposer.clone(),
description,
deadline,
approvals: 0,
approval_weight: 0,
status: ProposalStatus::Pending,
kind: ProposalKind::RemoveOwner(owner_to_remove),
ready_at: 0,
quorum_weight: threshold,
category: ProposalCategory::Other,
};
write_proposal(&env, &proposal);
register_active_proposal(&env, id)?;

let next_id = id.checked_add(1).ok_or(ContractError::ArithmeticError)?;
write_next_id(&env, next_id);

let total_weight = read_total_weight(&env);
env.events().publish(
(symbol_short!("created"),),
ProposalCreatedEvent {
id,
proposer,
threshold,
category: ProposalCategory::Other,
transfers: Vec::new(&env),
quorum_weight: threshold,
total_weight_at_creation: total_weight,
},
);

Ok(id)
}

/// Creates a proposal to change the M-of-N approval threshold.
///
/// # Arguments
/// * `proposer` - Owner proposing the change. Must authorize.
/// * `new_threshold` - The proposed new threshold. Must be ≥ 1 and ≤ current owner count.
pub fn create_change_threshold_proposal(
env: Env,
proposer: Address,
new_threshold: u32,
description: String,
deadline: u64,
) -> Result<u64, ContractError> {
proposer.require_auth();
require_owner_and_weight(&env, &proposer)?;
require_not_frozen(&env)?;

let total_weight = read_total_weight(&env);

// The threshold is an absolute weight value. Validate it against the
// current total weight so the proposed threshold is always achievable
// given the current weight distribution.
if new_threshold == 0 || new_threshold > total_weight {
return Err(ContractError::InvalidThreshold);
}

if description.is_empty() {
return Err(ContractError::EmptyDescription);
}
if description.len() > MAX_DESCRIPTION_LEN {
return Err(ContractError::DescriptionTooLong);
}

let now = env.ledger().timestamp();
if deadline <= now {
return Err(ContractError::InvalidDeadline);
}
if deadline - now > MAX_PROPOSAL_DURATION {
return Err(ContractError::InvalidDuration);
}

let threshold = read_threshold(&env)?;
let id = read_next_id(&env);

let proposal = Proposal {
id,
proposer: proposer.clone(),
description,
deadline,
approvals: 0,
approval_weight: 0,
status: ProposalStatus::Pending,
kind: ProposalKind::ChangeThreshold(new_threshold),
ready_at: 0,
quorum_weight: threshold,
category: ProposalCategory::Other,
};
write_proposal(&env, &proposal);
register_active_proposal(&env, id)?;

let next_id = id.checked_add(1).ok_or(ContractError::ArithmeticError)?;
write_next_id(&env, next_id);

let total_weight = read_total_weight(&env);
env.events().publish(
(symbol_short!("created"),),
ProposalCreatedEvent {
id,
proposer,
threshold,
category: ProposalCategory::Other,
transfers: Vec::new(&env),
quorum_weight: threshold,
total_weight_at_creation: total_weight,
},
);

Ok(id)
}

/// Approves a proposal. The approver must be an owner and must not have already approved.
///
/// Automatically transitions the proposal to `Ready` when the approval count reaches threshold.
/// Records `ready_at` the first time the threshold is crossed.
pub fn approve(env: Env, approver: Address, proposal_id: u64) -> Result<(), ContractError> {
approver.require_auth();
let owners = read_owners_map(&env)?;
let raw_weight = owners.get(approver.clone()).ok_or(ContractError::Unauthorized)?;
let mut proposal = read_proposal(&env, proposal_id)?;

// Refresh derived status so an already-expired proposal is caught here.
proposal.status = derive_status(&env, &proposal);

if !matches!(
proposal.status,
ProposalStatus::Pending | ProposalStatus::Ready
) {
return Err(ContractError::ProposalNotActive);
}

if read_approval_weight(&env, proposal_id, &approver) > 0 {
return Err(ContractError::AlreadyApproved);
}

// Count the approver's effective (delegation-aware) weight, not just
// their own raw weight — the exact value is stored per-approval so
// `revoke` can later reverse precisely this amount.
let weight = compute_effective_weight(&env, &owners, &approver, raw_weight)?;
write_approval_weight(&env, proposal_id, &approver, weight);

proposal.approvals = checked_weight_add(proposal.approvals, weight)?;

proposal.approval_weight = checked_weight_add(proposal.approval_weight, weight)?;

// Record the timestamp when the proposal first crosses the threshold.
if proposal.ready_at == 0 && proposal.approvals >= proposal.quorum_weight {
proposal.ready_at = env.ledger().timestamp();
}

proposal.status = derive_status(&env, &proposal);
write_proposal(&env, &proposal);

env.events().publish(
(symbol_short!("approved"),),
ProposalApprovedEvent {
id: proposal_id,
approver,
approvals: proposal.approvals,
threshold: proposal.quorum_weight,
weight,
cumulative_weight: proposal.approvals,
},
);

Ok(())
}

/// Revokes the caller's approval from a proposal that has not yet been executed.
///
/// The proposal status is recalculated after the revoke: if approvals fall below
Expand Down
Loading