1
- use clap:: { Parser , Subcommand } ;
2
- use solana_sdk:: { instruction:: Instruction , pubkey:: Pubkey } ;
1
+ use axelar_solana_governance:: instructions:: builder:: IxBuilder ;
2
+ use base64:: Engine ;
3
+ use clap:: { Args , Subcommand } ;
4
+ use solana_sdk:: { instruction:: AccountMeta , instruction:: Instruction , pubkey:: Pubkey } ;
3
5
4
6
use crate :: {
5
7
config:: Config ,
6
8
types:: ChainNameOnAxelar ,
7
9
utils:: {
8
- read_json_file_from_path, write_json_to_file_path, ADDRESS_KEY , CHAINS_KEY ,
9
- CONFIG_ACCOUNT_KEY , CONTRACTS_KEY , GOVERNANCE_ADDRESS_KEY , GOVERNANCE_CHAIN_KEY ,
10
- GOVERNANCE_KEY , UPGRADE_AUTHORITY_KEY ,
10
+ parse_account_meta_string, read_json_file_from_path, write_json_to_file_path, ADDRESS_KEY ,
11
+ CHAINS_KEY , CONFIG_ACCOUNT_KEY , CONTRACTS_KEY , GOVERNANCE_ADDRESS_KEY ,
12
+ GOVERNANCE_CHAIN_KEY , GOVERNANCE_KEY , MINIMUM_PROPOSAL_ETA_DELAY_KEY ,
13
+ UPGRADE_AUTHORITY_KEY ,
11
14
} ,
12
15
} ;
13
16
14
17
#[ derive( Subcommand , Debug ) ]
15
18
pub ( crate ) enum Commands {
16
- #[ clap( long_about = "Initialize the Gateway program" ) ]
19
+ #[ clap( long_about = "Initialize the Governance program" ) ]
17
20
Init ( InitArgs ) ,
21
+
22
+ #[ clap( long_about = "Execute a scheduled proposal after its ETA" ) ]
23
+ ExecuteProposal ( ExecuteProposalArgs ) ,
24
+
25
+ #[ clap( long_about = "Execute an operator-approved proposal (bypasses ETA)" ) ]
26
+ ExecuteOperatorProposal ( ExecuteOperatorProposalArgs ) ,
18
27
}
19
28
20
- #[ derive( Parser , Debug ) ]
29
+ #[ derive( Args , Debug ) ]
21
30
pub ( crate ) struct InitArgs {
22
31
#[ clap( short, long) ]
23
32
governance_chain : String ,
@@ -32,24 +41,86 @@ pub(crate) struct InitArgs {
32
41
operator : Pubkey ,
33
42
}
34
43
44
+ // Common arguments for proposal execution
45
+ #[ derive( Args , Debug , Clone ) ]
46
+ struct ProposalExecutionBaseArgs {
47
+ #[ clap( long, help = "Target program ID for the proposal's instruction" ) ]
48
+ target : Pubkey ,
49
+
50
+ #[ clap(
51
+ long,
52
+ help = "Amount of native value (lamports) to transfer with the proposal"
53
+ ) ]
54
+ native_value : u64 ,
55
+
56
+ #[ clap(
57
+ long,
58
+ help = "Base64 encoded call data for the target program instruction"
59
+ ) ]
60
+ calldata : String ,
61
+
62
+ #[ clap(
63
+ long,
64
+ help = "Account metas required by the target program instruction. Format: 'pubkey:is_signer:is_writable'" ,
65
+ value_parser = parse_account_meta_string,
66
+ ) ]
67
+ target_accounts : Vec < AccountMeta > ,
68
+
69
+ #[ clap( long, help = "Optional account to receive the native value transfer" ) ]
70
+ native_value_receiver : Option < Pubkey > ,
71
+ }
72
+
73
+ #[ derive( Args , Debug ) ]
74
+ pub ( crate ) struct ExecuteProposalArgs {
75
+ #[ clap( flatten) ]
76
+ base : ProposalExecutionBaseArgs ,
77
+ }
78
+
79
+ #[ derive( Args , Debug ) ]
80
+ pub ( crate ) struct ExecuteOperatorProposalArgs {
81
+ #[ clap( flatten) ]
82
+ base : ProposalExecutionBaseArgs ,
83
+
84
+ #[ clap( long, help = "Operator pubkey (must be a signer of the transaction)" ) ]
85
+ operator : Pubkey ,
86
+ }
87
+
88
+ #[ derive( Args , Debug ) ]
89
+ pub ( crate ) struct TransferOperatorshipArgs {
90
+ #[ clap( long, help = "Pubkey of the new operator" ) ]
91
+ new_operator : Pubkey ,
92
+
93
+ #[ clap(
94
+ long,
95
+ help = "Pubkey of the current operator (must be a signer of the transaction)"
96
+ ) ]
97
+ operator : Pubkey ,
98
+ }
99
+
35
100
pub ( crate ) async fn build_instruction (
36
101
fee_payer : & Pubkey ,
37
102
command : Commands ,
38
103
config : & Config ,
39
104
) -> eyre:: Result < Instruction > {
105
+ let ( config_pda, _) = axelar_solana_governance:: state:: GovernanceConfig :: pda ( ) ;
106
+
40
107
match command {
41
- Commands :: Init ( init_args) => init ( fee_payer, init_args, config) . await ,
108
+ Commands :: Init ( init_args) => init ( fee_payer, init_args, config, & config_pda) . await ,
109
+ Commands :: ExecuteProposal ( args) => execute_proposal ( fee_payer, args, & config_pda) . await ,
110
+ Commands :: ExecuteOperatorProposal ( args) => {
111
+ execute_operator_proposal ( fee_payer, args, & config_pda) . await
112
+ }
42
113
}
43
114
}
44
115
45
116
async fn init (
46
117
fee_payer : & Pubkey ,
47
118
init_args : InitArgs ,
48
119
config : & Config ,
120
+ config_pda : & Pubkey ,
49
121
) -> eyre:: Result < Instruction > {
50
122
let chain_hash = solana_sdk:: keccak:: hashv ( & [ init_args. governance_chain . as_bytes ( ) ] ) . 0 ;
51
123
let address_hash = solana_sdk:: keccak:: hashv ( & [ init_args. governance_address . as_bytes ( ) ] ) . 0 ;
52
- let ( config_pda, _bump) = axelar_solana_governance:: state:: GovernanceConfig :: pda ( ) ;
53
124
54
125
let governance_config = axelar_solana_governance:: state:: GovernanceConfig :: new (
55
126
chain_hash,
@@ -66,13 +137,65 @@ async fn init(
66
137
UPGRADE_AUTHORITY_KEY : fee_payer. to_string( ) ,
67
138
GOVERNANCE_ADDRESS_KEY : init_args. governance_address,
68
139
GOVERNANCE_CHAIN_KEY : init_args. governance_chain,
140
+ MINIMUM_PROPOSAL_ETA_DELAY_KEY : init_args. minimum_proposal_eta_delay,
69
141
} ) ;
70
142
71
143
write_json_to_file_path ( & chains_info, & config. chains_info_file ) ?;
72
144
73
- Ok (
74
- axelar_solana_governance:: instructions:: builder:: IxBuilder :: new ( )
75
- . initialize_config ( fee_payer, & config_pda, governance_config)
76
- . build ( ) ,
77
- )
145
+ Ok ( IxBuilder :: new ( )
146
+ . initialize_config ( fee_payer, config_pda, governance_config)
147
+ . build ( ) )
148
+ }
149
+
150
+ async fn execute_proposal (
151
+ fee_payer : & Pubkey ,
152
+ args : ExecuteProposalArgs ,
153
+ config_pda : & Pubkey ,
154
+ ) -> eyre:: Result < Instruction > {
155
+ let calldata_bytes = base64:: engine:: general_purpose:: STANDARD . decode ( args. base . calldata ) ?;
156
+ let native_value_receiver_account = args
157
+ . base
158
+ . native_value_receiver
159
+ . map ( |pk| AccountMeta :: new ( pk, false ) ) ;
160
+
161
+ // Note: ETA is part of the proposal data stored on-chain, not provided here.
162
+ // The builder calculates the proposal hash based on target, calldata, native_value.
163
+ // The ETA value used in `with_proposal_data` is only relevant for *scheduling*,
164
+ // not execution, but the builder requires some value. We use 0 here.
165
+ let builder = IxBuilder :: new ( ) . with_proposal_data (
166
+ args. base . target ,
167
+ args. base . native_value ,
168
+ 0 ,
169
+ native_value_receiver_account,
170
+ & args. base . target_accounts ,
171
+ calldata_bytes,
172
+ ) ;
173
+
174
+ Ok ( builder. execute_proposal ( fee_payer, config_pda) . build ( ) )
175
+ }
176
+
177
+ async fn execute_operator_proposal (
178
+ fee_payer : & Pubkey ,
179
+ args : ExecuteOperatorProposalArgs ,
180
+ config_pda : & Pubkey ,
181
+ ) -> eyre:: Result < Instruction > {
182
+ let calldata_bytes = base64:: engine:: general_purpose:: STANDARD . decode ( args. base . calldata ) ?;
183
+ let native_value_receiver_account = args
184
+ . base
185
+ . native_value_receiver
186
+ . map ( |pk| AccountMeta :: new ( pk, false ) ) ;
187
+
188
+ // ETA is irrelevant for operator execution. Use 0.
189
+ let builder = IxBuilder :: new ( ) . with_proposal_data (
190
+ args. base . target ,
191
+ args. base . native_value ,
192
+ 0 ,
193
+ native_value_receiver_account,
194
+ & args. base . target_accounts ,
195
+ calldata_bytes,
196
+ ) ;
197
+
198
+ Ok ( builder
199
+ . execute_operator_proposal ( fee_payer, config_pda, & args. operator )
200
+ . build ( ) )
78
201
}
0 commit comments