22use cosmwasm_std:: entry_point;
33use cosmwasm_std:: {
44 ensure, from_json, to_json_binary, Binary , Deps , DepsMut , Env , MessageInfo , Order , Response ,
5- StdResult , Uint128 , Uint256 ,
5+ StdError , StdResult , Uint128 , Uint256 ,
66} ;
77use cw2:: { get_contract_version, set_contract_version} ;
88use cw20:: { Cw20ReceiveMsg , Denom } ;
99use cw_storage_plus:: Bound ;
1010use cw_utils:: { must_pay, nonpayable, Duration , Expiration } ;
1111use dao_interface:: voting:: InfoResponse ;
12+ use semver:: Version ;
1213
1314use std:: ops:: Add ;
1415
@@ -27,8 +28,8 @@ use crate::rewards::{
2728use crate :: state:: { DistributionState , EmissionRate , Epoch , COUNT , DISTRIBUTIONS , USER_REWARDS } ;
2829use crate :: ContractError ;
2930
30- const CONTRACT_NAME : & str = env ! ( "CARGO_PKG_NAME" ) ;
31- const CONTRACT_VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
31+ pub ( crate ) const CONTRACT_NAME : & str = env ! ( "CARGO_PKG_NAME" ) ;
32+ pub ( crate ) const CONTRACT_VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
3233
3334pub const DEFAULT_LIMIT : u32 = 10 ;
3435pub const MAX_LIMIT : u32 = 50 ;
@@ -279,36 +280,106 @@ fn execute_fund_native(
279280}
280281
281282fn execute_fund (
283+ deps : DepsMut ,
284+ env : Env ,
285+ distribution : DistributionState ,
286+ amount : Uint128 ,
287+ ) -> Result < Response , ContractError > {
288+ match distribution. active_epoch . emission_rate {
289+ EmissionRate :: Paused { } => execute_fund_paused ( deps, distribution, amount) ,
290+ EmissionRate :: Immediate { } => execute_fund_immediate ( deps, env, distribution, amount) ,
291+ EmissionRate :: Linear { .. } => execute_fund_linear ( deps, env, distribution, amount) ,
292+ }
293+ }
294+
295+ /// funding a paused distribution simply increases the funded amount.
296+ fn execute_fund_paused (
297+ deps : DepsMut ,
298+ mut distribution : DistributionState ,
299+ amount : Uint128 ,
300+ ) -> Result < Response , ContractError > {
301+ distribution. funded_amount += amount;
302+
303+ DISTRIBUTIONS . save ( deps. storage , distribution. id , & distribution) ?;
304+
305+ Ok ( Response :: new ( )
306+ . add_attribute ( "action" , "fund" )
307+ . add_attribute ( "id" , distribution. id . to_string ( ) )
308+ . add_attribute ( "denom" , distribution. get_denom_string ( ) )
309+ . add_attribute ( "amount_funded" , amount) )
310+ }
311+
312+ /// funding an immediate distribution instantly distributes the new amount.
313+ fn execute_fund_immediate (
314+ deps : DepsMut ,
315+ env : Env ,
316+ mut distribution : DistributionState ,
317+ amount : Uint128 ,
318+ ) -> Result < Response , ContractError > {
319+ distribution. funded_amount += amount;
320+
321+ // for immediate distribution, update total_earned_puvp instantly since we
322+ // need to know the change in funded_amount to calculate the new
323+ // total_earned_puvp.
324+ distribution. update_immediate_emission_total_earned_puvp ( deps. as_ref ( ) , & env. block , amount) ?;
325+
326+ DISTRIBUTIONS . save ( deps. storage , distribution. id , & distribution) ?;
327+
328+ Ok ( Response :: new ( )
329+ . add_attribute ( "action" , "fund" )
330+ . add_attribute ( "id" , distribution. id . to_string ( ) )
331+ . add_attribute ( "denom" , distribution. get_denom_string ( ) )
332+ . add_attribute ( "amount_funded" , amount) )
333+ }
334+
335+ /// funding a linear distribution requires some complex logic based on whether
336+ /// or not the distribution is continuous and whether or not it's expired.
337+ ///
338+ /// expired continuous distributions experience backfill with the new funds,
339+ /// whereas expired discontinuous distributions begin anew (and all past rewards
340+ /// must be taken into account before restarting distribution).
341+ fn execute_fund_linear (
282342 deps : DepsMut ,
283343 env : Env ,
284344 mut distribution : DistributionState ,
285345 amount : Uint128 ,
286346) -> Result < Response , ContractError > {
287- // will only be true if emission rate is linear and continuous is true
288347 let continuous =
289348 if let EmissionRate :: Linear { continuous, .. } = distribution. active_epoch . emission_rate {
290349 continuous
291350 } else {
292351 false
293352 } ;
353+ let previously_funded = !distribution. funded_amount . is_zero ( ) ;
354+ let was_expired = distribution. active_epoch . ends_at . is_expired ( & env. block ) ;
355+ let discontinuous_expired = !continuous && was_expired;
294356
295357 // restart the distribution from the current block if it hasn't yet started
296- // (i.e. never been funded), or if it's expired (i.e. all funds have been
297- // distributed) and not continuous. if it is continuous, treat it as if it
298- // weren't expired by simply adding the new funds and recomputing the end
299- // date, keeping start date the same, effectively backfilling rewards.
300- let restart_distribution = if distribution. funded_amount . is_zero ( ) {
301- true
302- } else {
303- !continuous && distribution. active_epoch . ends_at . is_expired ( & env. block )
304- } ;
358+ // (i.e. never been funded) OR if it's both discontinuous and expired (i.e.
359+ // all past funds should have been distributed and we're effectively
360+ // beginning a new distribution with new funds). this ensures the new funds
361+ // start being distributed from now instead of from the past.
362+ //
363+ // if already funded and continuous or not expired (else block), just add
364+ // the new funds and leave start date the same, backfilling rewards.
365+ if !previously_funded || discontinuous_expired {
366+ // if funding an expired discontinuous distribution that's previously
367+ // been funded, ensure all rewards are taken into account before
368+ // restarting, in case users haven't claimed yet, by adding the final
369+ // total rewards earned to the historical value.
370+ if discontinuous_expired && previously_funded {
371+ let final_total_earned_puvp =
372+ get_active_total_earned_puvp ( deps. as_ref ( ) , & env. block , & distribution) ?;
373+ distribution. historical_earned_puvp = distribution
374+ . historical_earned_puvp
375+ . checked_add ( final_total_earned_puvp)
376+ . map_err ( |err| ContractError :: DistributionHistoryTooLarge {
377+ err : err. to_string ( ) ,
378+ } ) ?;
379+ // last updated block is reset to the new start block below
380+ }
305381
306- // if necessary, restart the distribution from the current block so that the
307- // new funds start being distributed from now instead of from the past, and
308- // reset funded_amount to the new amount since we're effectively starting a
309- // new distribution. otherwise, just add the new amount to the existing
310- // funded_amount
311- if restart_distribution {
382+ // reset all starting fields since a new distribution is starting
312383 distribution. funded_amount = amount;
313384 distribution. active_epoch . started_at = match distribution. active_epoch . emission_rate {
314385 EmissionRate :: Paused { } => Expiration :: Never { } ,
@@ -318,10 +389,14 @@ fn execute_fund(
318389 Duration :: Time ( _) => Expiration :: AtTime ( env. block . time ) ,
319390 } ,
320391 } ;
392+ distribution. active_epoch . total_earned_puvp = Uint256 :: zero ( ) ;
393+ distribution. active_epoch . last_updated_total_earned_puvp =
394+ distribution. active_epoch . started_at ;
321395 } else {
322396 distribution. funded_amount += amount;
323397 }
324398
399+ // update the end block based on the new funds and potentially updated start
325400 let new_funded_duration = distribution
326401 . active_epoch
327402 . emission_rate
@@ -331,26 +406,16 @@ fn execute_fund(
331406 None => Expiration :: Never { } ,
332407 } ;
333408
334- // if immediate distribution, update total_earned_puvp instantly since we
335- // need to know the delta in funding_amount to calculate the new
336- // total_earned_puvp.
337- if ( distribution. active_epoch . emission_rate == EmissionRate :: Immediate { } ) {
338- distribution. update_immediate_emission_total_earned_puvp (
339- deps. as_ref ( ) ,
340- & env. block ,
341- amount,
342- ) ?;
343-
344- // if continuous, meaning rewards should have been distributed in the past
345- // but were not due to lack of sufficient funding, ensure the total rewards
346- // earned puvp is up to date.
347- } else if !restart_distribution && continuous {
409+ // if continuous, funds existed in the past, and the distribution was
410+ // expired, some rewards may not have been distributed due to lack of
411+ // sufficient funding. ensure the total rewards earned puvp is up to date
412+ // based on the original start block and the newly updated end block.
413+ if continuous && previously_funded && was_expired {
348414 distribution. active_epoch . total_earned_puvp =
349415 get_active_total_earned_puvp ( deps. as_ref ( ) , & env. block , & distribution) ?;
416+ distribution. active_epoch . bump_last_updated ( & env. block ) ;
350417 }
351418
352- distribution. active_epoch . bump_last_updated ( & env. block ) ;
353-
354419 DISTRIBUTIONS . save ( deps. storage , distribution. id , & distribution) ?;
355420
356421 Ok ( Response :: new ( )
@@ -542,7 +607,8 @@ fn query_pending_rewards(
542607 for ( id, distribution) in distributions {
543608 // first we get the active epoch earned puvp value
544609 let active_total_earned_puvp =
545- get_active_total_earned_puvp ( deps, & env. block , & distribution) ?;
610+ get_active_total_earned_puvp ( deps, & env. block , & distribution)
611+ . map_err ( |e| StdError :: generic_err ( e. to_string ( ) ) ) ?;
546612
547613 // then we add that to the historical rewards earned puvp
548614 let total_earned_puvp =
@@ -592,6 +658,27 @@ fn query_distributions(
592658
593659#[ cfg_attr( not( feature = "library" ) , entry_point) ]
594660pub fn migrate ( deps : DepsMut , _env : Env , _msg : MigrateMsg ) -> Result < Response , ContractError > {
661+ let contract_version = get_contract_version ( deps. storage ) ?;
662+
663+ if contract_version. contract != CONTRACT_NAME {
664+ return Err ( ContractError :: MigrationErrorIncorrectContract {
665+ expected : CONTRACT_NAME . to_string ( ) ,
666+ actual : contract_version. contract ,
667+ } ) ;
668+ }
669+
670+ let new_version: Version = CONTRACT_VERSION . parse ( ) ?;
671+ let current_version: Version = contract_version. version . parse ( ) ?;
672+
673+ // only allow upgrades
674+ if new_version <= current_version {
675+ return Err ( ContractError :: MigrationErrorInvalidVersion {
676+ new : new_version. to_string ( ) ,
677+ current : current_version. to_string ( ) ,
678+ } ) ;
679+ }
680+
595681 set_contract_version ( deps. storage , CONTRACT_NAME , CONTRACT_VERSION ) ?;
682+
596683 Ok ( Response :: default ( ) )
597684}
0 commit comments