From e35e9bd69ca8dfe66127ed1782536462243d2427 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Thu, 2 Jul 2026 07:04:06 -0700 Subject: [PATCH] fix: unlock dates ignore leap years Vesting accounts unlock N * 365 days after TGE, not N calendar years, because vesting account end times do not account for the leap day on February 29th, 2024. Verified against vesting account end times in the mainnet genesis file: delayed vesting accounts end October 30th, 2024 and continuous vesting accounts end October 30th, 2025 or 2026. Closes https://github.com/celestiaorg/supply/issues/35 Co-Authored-By: Claude Fable 5 --- internal/circulating_supply_test.go | 12 +++++------ internal/dates.go | 13 +++++++----- internal/dates_test.go | 32 +++++++++++++++++++++++++++++ internal/total_supply_test.go | 6 +++--- 4 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 internal/dates_test.go diff --git a/internal/circulating_supply_test.go b/internal/circulating_supply_test.go index 2c2f0a5..646247d 100644 --- a/internal/circulating_supply_test.go +++ b/internal/circulating_supply_test.go @@ -18,9 +18,9 @@ func TestCirculatingSupply(t *testing.T) { {oneYearAfterTGEMinusOneDay, 220_824_349_667_524}, {oneYearAfterTGE, 398_395_290_749_715}, {oneYearAfterTGEPlusOneDay, 399_602_581_376_425}, - {twoYearsAfterTGE, 833432444153745}, - {threeYearsAfterTGE, 987593533203745}, - {fourYearsAfterTGE, 1079986906381924}, + {twoYearsAfterTGE, 832939859033664}, + {threeYearsAfterTGE, 987338016490616}, + {fourYearsAfterTGE, 1079918280757792}, } for _, tc := range testCases { t.Run(tc.time.String(), func(t *testing.T) { @@ -82,7 +82,7 @@ func Test_coreContributorsCirculating(t *testing.T) { {oneDayAfterTGE, 0}, {oneYearAfterTGEMinusOneDay, 0}, // one day before tokens unlock. {oneYearAfterTGE, coreContributors / 3}, // tokens unlock on October 30th, 2024. - {twoYearsAfterTGE, 117_738_314_725_882}, + {twoYearsAfterTGE, 117_577_249_999_855}, {threeYearsAfterTGE, coreContributors}, {fourYearsAfterTGE, coreContributors}, } @@ -104,8 +104,8 @@ func Test_ecosystemCirculating(t *testing.T) { {TGE, .25 * ecosystem}, {oneDayAfterTGE, .25 * ecosystem}, {oneYearAfterTGE, .25 * ecosystem}, - {twoYearsAfterTGE, 134_155_879_274_657}, - {threeYearsAfterTGE, 201_142_057_024_657}, + {twoYearsAfterTGE, 133_972_355_500_000}, + {threeYearsAfterTGE, 200_958_533_250_000}, {fourYearsAfterTGE, ecosystem}, } for _, tc := range testCases { diff --git a/internal/dates.go b/internal/dates.go index dee9602..90bbe90 100644 --- a/internal/dates.go +++ b/internal/dates.go @@ -18,9 +18,12 @@ var ( // See https://www.mintscan.io/celestia/block/8662012 cip41ActivationDate = time.Date(2025, time.November, 24, 0, 0, 0, 0, time.UTC) - // TODO: verify these dates. The unlock dates may not be exactly N years - // after TGE. Instead, they may be N * 365 days after. - twoYearsAfterTGE = TGE.AddDate(2, 0, 0) - threeYearsAfterTGE = TGE.AddDate(3, 0, 0) - fourYearsAfterTGE = TGE.AddDate(4, 0, 0) + // Unlock dates are N * 365 days after TGE (not N calendar years) because + // vesting accounts do not account for leap years. The vesting accounts in + // the mainnet genesis file have end times on October 30th: one year after + // TGE for delayed vesting accounts, two or three years after TGE for + // continuous vesting accounts. + twoYearsAfterTGE = TGE.AddDate(0, 0, 2*365) // October 30, 2025 + threeYearsAfterTGE = TGE.AddDate(0, 0, 3*365) // October 30, 2026 + fourYearsAfterTGE = TGE.AddDate(0, 0, 4*365) // October 30, 2027 ) diff --git a/internal/dates_test.go b/internal/dates_test.go new file mode 100644 index 0000000..3dc755c --- /dev/null +++ b/internal/dates_test.go @@ -0,0 +1,32 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestUnlockDates verifies that unlock dates are computed as N * 365 days +// after TGE, ignoring leap years. Vesting accounts in the mainnet genesis file +// have end times of October 30th (not October 31st) because they do not +// account for the leap day on February 29th, 2024. +// See https://github.com/celestiaorg/supply/issues/35 +func TestUnlockDates(t *testing.T) { + type testCase struct { + name string + got time.Time + want time.Time + } + testCases := []testCase{ + {"oneYearAfterTGE", oneYearAfterTGE, time.Date(2024, time.October, 30, 0, 0, 0, 0, time.UTC)}, + {"twoYearsAfterTGE", twoYearsAfterTGE, time.Date(2025, time.October, 30, 0, 0, 0, 0, time.UTC)}, + {"threeYearsAfterTGE", threeYearsAfterTGE, time.Date(2026, time.October, 30, 0, 0, 0, 0, time.UTC)}, + {"fourYearsAfterTGE", fourYearsAfterTGE, time.Date(2027, time.October, 30, 0, 0, 0, 0, time.UTC)}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, tc.got) + }) + } +} diff --git a/internal/total_supply_test.go b/internal/total_supply_test.go index 4eab423..860e08c 100644 --- a/internal/total_supply_test.go +++ b/internal/total_supply_test.go @@ -17,9 +17,9 @@ func TestTotalSupply(t *testing.T) { {TGE, initialTotalSupplyInUtia}, {oneDayAfterTGE, 1000219178082191}, {oneYearAfterTGE, 1079999999999715}, - {twoYearsAfterTGE, 1151791486153206}, - {threeYearsAfterTGE, 1180338837179088}, - {fourYearsAfterTGE, 1205929556381924}, + {twoYearsAfterTGE, 1151643489533809}, + {threeYearsAfterTGE, 1180266844240616}, + {fourYearsAfterTGE, 1205860930757792}, } for _, tc := range testCases { got := TotalSupply(tc.time)