1- use anyhow:: Context as _;
1+ use anyhow:: { anyhow , Context as _} ;
22use async_trait:: async_trait;
3- use cnidarium:: StateWrite ;
4- use penumbra_sdk_asset:: asset:: Denom ;
3+ use cnidarium:: { StateRead , StateWrite } ;
4+ use cnidarium_component:: ActionHandler ;
5+ use penumbra_sdk_asset:: { asset:: Denom , Value } ;
6+ use penumbra_sdk_governance:: StateReadExt as _;
7+ use penumbra_sdk_num:: Amount ;
58use penumbra_sdk_proof_params:: DELEGATOR_VOTE_PROOF_VERIFICATION_KEY ;
9+ use penumbra_sdk_sct:: component:: clock:: EpochRead as _;
10+ use penumbra_sdk_sct:: epoch:: Epoch ;
11+ use penumbra_sdk_stake:: component:: validator_handler:: ValidatorDataRead as _;
12+ use penumbra_sdk_tct:: Position ;
613use penumbra_sdk_txhash:: TransactionContext ;
714
15+ use crate :: component:: liquidity_tournament:: votes:: StateWriteExt as _;
816use crate :: liquidity_tournament:: {
917 proof:: LiquidityTournamentVoteProofPublic , ActionLiquidityTournamentVote ,
1018 LiquidityTournamentVoteBody , LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES ,
1119} ;
12- use cnidarium_component:: ActionHandler ;
1320
1421fn is_valid_denom ( denom : & Denom ) -> anyhow:: Result < ( ) > {
1522 anyhow:: ensure!(
@@ -26,6 +33,37 @@ fn is_valid_denom(denom: &Denom) -> anyhow::Result<()> {
2633 Ok ( ( ) )
2734}
2835
36+ // Check that the start position is early enough to vote in the current epoch.
37+ async fn start_position_good_for_epoch ( epoch : Epoch , start : Position ) -> anyhow:: Result < ( ) > {
38+ anyhow:: ensure!(
39+ epoch. index > u64 :: from( start. epoch( ) ) ,
40+ "position {start:?} is not before epoch {epoch:?}"
41+ ) ;
42+ Ok ( ( ) )
43+ }
44+
45+ /// Fetch the unbonded equivalent of some purported delegation token.
46+ ///
47+ /// Will fail if (either):
48+ /// - the token is not for a known validator,
49+ /// - the validator does not have any rate data.
50+ async fn unbonded_amount ( state : impl StateRead , value : Value ) -> anyhow:: Result < Amount > {
51+ let validator = state. validator_by_delegation_asset ( value. asset_id ) . await ?;
52+ let rate = state
53+ . get_validator_rate ( & validator)
54+ . await ?
55+ . ok_or_else ( || anyhow ! ( "{} has no rate data" , & validator) ) ?;
56+ Ok ( rate. unbonded_amount ( value. amount ) )
57+ }
58+
59+ // This isolates the logic for how we should handle out of bounds amounts.
60+ fn voting_power ( amount : Amount ) -> u64 {
61+ amount
62+ . value ( )
63+ . try_into ( )
64+ . expect ( "someone acquired {amount:?} > u64::MAX worth of delegation tokens!" )
65+ }
66+
2967#[ async_trait]
3068impl ActionHandler for ActionLiquidityTournamentVote {
3169 type CheckStatelessContext = TransactionContext ;
@@ -70,7 +108,28 @@ impl ActionHandler for ActionLiquidityTournamentVote {
70108 Ok ( ( ) )
71109 }
72110
73- async fn check_and_execute < S : StateWrite > ( & self , _state : S ) -> anyhow:: Result < ( ) > {
74- todo ! ( )
111+ async fn check_and_execute < S : StateWrite > ( & self , mut state : S ) -> anyhow:: Result < ( ) > {
112+ // 1. Check that the nullifier hasn't appeared in this round. (TODO)
113+ // 2. Check that the start position can vote in this round.
114+ let current_epoch = state
115+ . get_current_epoch ( )
116+ . await
117+ . expect ( "failed to fetch current epoch" ) ;
118+ start_position_good_for_epoch ( current_epoch, self . body . start_position ) . await ?;
119+ // 3. Tally.
120+ let power = voting_power ( unbonded_amount ( & state, self . body . value ) . await ?) ;
121+ let incentivized = self
122+ . body
123+ . incentivized_id ( )
124+ . ok_or_else ( || anyhow ! ( "{:?} is not a base denom" , self . body. incentivized) ) ?;
125+ state
126+ . tally (
127+ current_epoch. index ,
128+ incentivized,
129+ power,
130+ & self . body . rewards_recipient ,
131+ )
132+ . await ?;
133+ Ok ( ( ) )
75134 }
76135}
0 commit comments