Skip to content

Commit ba93413

Browse files
authored
revenue-distribution: debt write off activation (#99)
Add a field to the config to enforce when to activate the debt write-off feature. Also add a write-off counter to the distribution account. Also adds some unit tests for `ProgramConfig` and `Distribution` methods. We should prioritize adding more unit tests for these state schemas. Closes malbeclabs/doublezero#2439. Closes malbeclabs/doublezero#2457.
1 parent 7940354 commit ba93413

14 files changed

+778
-87
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ lint:
5656

5757
.PHONY: doc
5858
doc:
59-
cargo doc --all-features --no-deps --document-private-items
59+
cargo doc --all-features --no-deps --document-private-items
60+
61+
.PHONY: write-checksums
62+
write-checksums:
63+
shasum -a 256 artifacts-mainnet-beta/*.so > programs/sha256sums_mainnet_beta.txt
64+
shasum -a 256 artifacts-development/*.so > programs/sha256sums_development.txt

programs/revenue-distribution/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- debt write off activation ([#99])
6+
7+
## [v0.2.0]
8+
59
- add null rewards root protection ([#86])
610
- fix swap balance in journal ([#87])
711
- allow same-distribution debt write-offs ([#91])
@@ -97,5 +101,7 @@
97101
[#93]: https://github.com/doublezerofoundation/doublezero-solana/pull/93
98102
[#94]: https://github.com/doublezerofoundation/doublezero-solana/pull/94
99103
[#95]: https://github.com/doublezerofoundation/doublezero-solana/pull/95
104+
[#99]: https://github.com/doublezerofoundation/doublezero-solana/pull/99
100105
[v0.1.0]: https://github.com/doublezerofoundation/doublezero-solana/tree/revenue-distribution/v0.1.0
101106
[v0.1.1]: https://github.com/doublezerofoundation/doublezero-solana/tree/revenue-distribution/v0.1.1
107+
[v0.2.0]: https://github.com/doublezerofoundation/doublezero-solana/tree/revenue-distribution/v0.2.0

programs/revenue-distribution/src/instruction/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use doublezero_program_tools::{Discriminator, DISCRIMINATOR_LEN};
99
use solana_pubkey::Pubkey;
1010
use svm_hash::{merkle::MerkleProof, sha2::Hash};
1111

12-
use crate::types::{EpochDuration, RewardShare, SolanaValidatorDebt};
12+
use crate::types::{DoubleZeroEpoch, EpochDuration, RewardShare, SolanaValidatorDebt};
1313

1414
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, PartialEq, Eq)]
1515
pub enum ProgramConfiguration {
@@ -38,13 +38,22 @@ pub enum ProgramConfiguration {
3838
DistributeRewardsRelayLamports(u32),
3939
MinimumEpochDurationToFinalizeRewards(u8),
4040
DistributionInitializationGracePeriodMinutes(u16),
41+
FeatureActivation {
42+
feature: ProgramFeatureConfiguration,
43+
activation_epoch: DoubleZeroEpoch,
44+
},
4145
}
4246

4347
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, PartialEq, Eq)]
4448
pub enum ProgramFlagConfiguration {
4549
IsPaused(bool),
4650
}
4751

52+
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Copy, PartialEq, Eq)]
53+
pub enum ProgramFeatureConfiguration {
54+
SolanaValidatorDebtWriteOff,
55+
}
56+
4857
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, PartialEq, Eq)]
4958
pub enum ContributorRewardsConfiguration {
5059
Recipients(Vec<(Pubkey, u16)>),

programs/revenue-distribution/src/processor.rs

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use svm_hash::{merkle::MerkleProof, sha2::Hash};
2727
use crate::{
2828
instruction::{
2929
account::DequeueFillsCpiAccounts, ContributorRewardsConfiguration,
30-
DistributionMerkleRootKind, ProgramConfiguration, ProgramFlagConfiguration,
31-
RevenueDistributionInstructionData,
30+
DistributionMerkleRootKind, ProgramConfiguration, ProgramFeatureConfiguration,
31+
ProgramFlagConfiguration, RevenueDistributionInstructionData,
3232
},
3333
state::{
3434
self, CommunityBurnRateParameters, ContributorRewards, Distribution, Journal,
@@ -542,6 +542,25 @@ fn try_configure_program(accounts: &[AccountInfo], setting: ProgramConfiguration
542542
.distribution_parameters
543543
.initialization_grace_period_minutes = grace_period_minutes;
544544
}
545+
ProgramConfiguration::FeatureActivation {
546+
feature,
547+
activation_epoch,
548+
} => {
549+
if activation_epoch == 0 {
550+
msg!("Cannot activate feature at epoch zero");
551+
return Err(ProgramError::InvalidInstructionData);
552+
}
553+
554+
match feature {
555+
ProgramFeatureConfiguration::SolanaValidatorDebtWriteOff => {
556+
msg!(
557+
"Set Solana validator debt write-off feature activation epoch: {}",
558+
activation_epoch
559+
);
560+
program_config.debt_write_off_feature_activation_epoch = activation_epoch;
561+
}
562+
}
563+
}
545564
}
546565

547566
Ok(())
@@ -1199,14 +1218,14 @@ fn try_distribute_rewards(
11991218
ZeroCopyMutAccount::<Distribution>::try_next_accounts(&mut accounts_iter, Some(&ID))?;
12001219
msg!("DZ epoch: {}", distribution.dz_epoch);
12011220

1202-
// Make sure 2Z tokens have been swept.
1203-
if !distribution.has_swept_2z_tokens() {
1204-
msg!("Distribution has not swept 2Z tokens");
1221+
if distribution.are_all_rewards_distributed() {
1222+
msg!("All rewards have already been distributed");
12051223
return Err(ProgramError::InvalidAccountData);
12061224
}
12071225

1208-
if distribution.distributed_rewards_count == distribution.total_contributors {
1209-
msg!("All rewards have already been distributed");
1226+
// Make sure 2Z tokens have been swept.
1227+
if !distribution.has_swept_2z_tokens() {
1228+
msg!("Distribution has not swept 2Z tokens");
12101229
return Err(ProgramError::InvalidAccountData);
12111230
}
12121231

@@ -1801,6 +1820,22 @@ fn try_enable_solana_validator_debt_write_off(accounts: &[AccountInfo]) -> Progr
18011820
// Make sure the program is not paused.
18021821
program_config.try_require_unpaused()?;
18031822

1823+
// Cannot enable write-offs before the activation epoch.
1824+
if !program_config.is_debt_write_off_feature_activated() {
1825+
let activation_epoch = program_config.debt_write_off_feature_activation_epoch;
1826+
1827+
if activation_epoch == 0 {
1828+
msg!("Debt write-off feature activation epoch not configured");
1829+
} else {
1830+
msg!(
1831+
"Debt write-off feature activates at epoch {}",
1832+
activation_epoch
1833+
);
1834+
}
1835+
1836+
return Err(ProgramError::InvalidAccountData);
1837+
}
1838+
18041839
// Account 1 must be the distribution.
18051840
let mut distribution =
18061841
ZeroCopyMutAccount::<Distribution>::try_next_accounts(&mut accounts_iter, Some(&ID))?;
@@ -1933,6 +1968,8 @@ fn try_write_off_solana_validator_debt(
19331968
return Err(ProgramError::InvalidAccountData);
19341969
}
19351970

1971+
distribution.solana_validator_write_off_count += 1;
1972+
19361973
// Bits indicating whether debt has been written off for specific leaf
19371974
// indices are stored in the distribution's remaining data.
19381975
let write_off_bitmap_range =
@@ -2833,9 +2870,8 @@ fn try_process_remaining_data_leaf_index(
28332870
///
28342871
/// # Why are we migrating?
28352872
///
2836-
/// The program deployed on Solana mainnet-beta had a bug that did not properly
2837-
/// track the 2Z token account balance on the journal. This instruction
2838-
/// processor will correct the journal to fix the balance.
2873+
/// After the last migration, the migrated bit is set to true. This instruction
2874+
/// will reset this bit to false.
28392875
fn try_migrate_program_accounts(accounts: &[AccountInfo]) -> ProgramResult {
28402876
msg!("Migrate program accounts");
28412877

@@ -2844,10 +2880,6 @@ fn try_migrate_program_accounts(accounts: &[AccountInfo]) -> ProgramResult {
28442880
// program).
28452881
// - 1: The program's owner (i.e., upgrade authority).
28462882
// - 2: Program config.
2847-
// - 3: Journal.
2848-
//
2849-
// Remaining accounts are distribution accounts where tokens have been
2850-
// swept.
28512883
let mut accounts_iter = accounts.iter().enumerate();
28522884

28532885
// Account 0 must be the program data belonging to this program.
@@ -2859,59 +2891,8 @@ fn try_migrate_program_accounts(accounts: &[AccountInfo]) -> ProgramResult {
28592891
let mut program_config =
28602892
ZeroCopyMutAccount::<ProgramConfig>::try_next_accounts(&mut accounts_iter, Some(&ID))?;
28612893

2862-
if program_config.is_migrated() {
2863-
msg!("Program has already been migrated. Nothing to do");
2864-
return Ok(());
2865-
}
2866-
2867-
program_config.set_is_migrated(true);
2868-
2869-
// Account 3 must be the journal.
2870-
let mut journal =
2871-
ZeroCopyMutAccount::<Journal>::try_next_accounts(&mut accounts_iter, Some(&ID))?;
2872-
2873-
// Copy balance, which is actually the lifetime amount.
2874-
journal.lifetime_swapped_2z_amount = Uint::from(journal.swap_2z_destination_balance);
2875-
msg!(
2876-
"Fixed lifetime swapped 2Z amount to {}",
2877-
journal.lifetime_swapped_2z_amount
2878-
);
2879-
2880-
let first_epoch = 31;
2881-
let until_epoch = journal.next_dz_epoch_to_sweep_tokens.value();
2882-
2883-
// Iterate over all epochs with rewards. We need to make sure the
2884-
// distribution accounts are passed in the order we expect.
2885-
for epoch in first_epoch..until_epoch {
2886-
let distribution =
2887-
ZeroCopyAccount::<Distribution>::try_next_accounts(&mut accounts_iter, Some(&ID))?;
2888-
2889-
if distribution.dz_epoch != epoch {
2890-
msg!("Invalid distribution epoch: {}", distribution.dz_epoch);
2891-
return Err(ProgramError::InvalidAccountData);
2892-
}
2893-
2894-
// Be extra sure that the distribution has swept 2Z tokens. This check
2895-
// should never fail.
2896-
if !distribution.has_swept_2z_tokens() {
2897-
msg!("Distribution has not swept 2Z tokens for epoch {}", epoch);
2898-
return Err(ProgramError::InvalidAccountData);
2899-
}
2900-
2901-
let collected_2z_amount = distribution.collected_2z_converted_from_sol;
2902-
msg!(" Epoch {}: {} 2Z", epoch, collected_2z_amount);
2903-
2904-
// Catch underflow here.
2905-
journal.swap_2z_destination_balance = journal
2906-
.swap_2z_destination_balance
2907-
.checked_sub(collected_2z_amount)
2908-
.unwrap();
2909-
}
2910-
2911-
msg!(
2912-
"Fixed journal swap 2Z destination balance to {}",
2913-
journal.swap_2z_destination_balance
2914-
);
2894+
program_config.set_is_migrated(false);
2895+
msg!("Set flag is_migrated to false");
29152896

29162897
Ok(())
29172898
}

0 commit comments

Comments
 (0)