10
10
UpgradeContract ,
11
11
} ,
12
12
GovernanceInstruction ,
13
+ GovernanceModule ,
13
14
} ,
14
15
msg:: {
15
16
InstantiateMsg ,
@@ -112,7 +113,12 @@ pub fn instantiate(
112
113
Ok ( Response :: default ( ) )
113
114
}
114
115
115
- pub fn parse_vaa ( deps : DepsMut , block_time : u64 , data : & Binary ) -> StdResult < ParsedVAA > {
116
+ /// Verify that `data` represents an authentic Wormhole VAA.
117
+ ///
118
+ /// *Warning* this function does not verify the emitter of the wormhole message; it only checks
119
+ /// that the wormhole signatures are valid. The caller is responsible for checking that the message
120
+ /// originates from the expected emitter.
121
+ pub fn parse_and_verify_vaa ( deps : DepsMut , block_time : u64 , data : & Binary ) -> StdResult < ParsedVAA > {
116
122
let cfg = config_read ( deps. storage ) . load ( ) ?;
117
123
let vaa: ParsedVAA = deps. querier . query ( & QueryRequest :: Wasm ( WasmQuery :: Smart {
118
124
contract_addr : cfg. wormhole_contract . to_string ( ) ,
@@ -134,6 +140,11 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
134
140
}
135
141
}
136
142
143
+ /// Update the on-chain price feeds given the array of price update VAAs `data`.
144
+ /// Each price update VAA must be a valid Wormhole message and sent from an authorized emitter.
145
+ ///
146
+ /// This method additionally requires the caller to pay a fee to the contract; the
147
+ /// magnitude of the fee depends on both the data and the current contract configuration.
137
148
fn update_price_feeds (
138
149
mut deps : DepsMut ,
139
150
env : Env ,
@@ -142,6 +153,7 @@ fn update_price_feeds(
142
153
) -> StdResult < Response > {
143
154
let state = config_read ( deps. storage ) . load ( ) ?;
144
155
156
+ // Check that a sufficient fee was sent with the message
145
157
if state. fee . amount . u128 ( ) > 0
146
158
&& !has_coins ( info. funds . as_ref ( ) , & get_update_fee ( & deps. as_ref ( ) , data) ?)
147
159
{
@@ -151,7 +163,7 @@ fn update_price_feeds(
151
163
let mut total_attestations: usize = 0 ;
152
164
let mut new_attestations: usize = 0 ;
153
165
for datum in data {
154
- let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , datum) ?;
166
+ let vaa = parse_and_verify_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , datum) ?;
155
167
verify_vaa_from_data_source ( & state, & vaa) ?;
156
168
157
169
let data = & vaa. payload ;
@@ -170,19 +182,24 @@ fn update_price_feeds(
170
182
. add_attribute ( "num_updated" , format ! ( "{new_attestations}" ) ) )
171
183
}
172
184
185
+ /// Execute a governance instruction provided as the VAA `data`.
186
+ /// The VAA must come from an authorized governance emitter.
187
+ /// See [GovernanceInstruction] for descriptions of the supported operations.
173
188
fn execute_governance_instruction (
174
189
mut deps : DepsMut ,
175
190
env : Env ,
176
191
_info : MessageInfo ,
177
192
data : & Binary ,
178
193
) -> StdResult < Response > {
179
- let vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
194
+ let vaa = parse_and_verify_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , data) ?;
180
195
let state = config_read ( deps. storage ) . load ( ) ?;
196
+ verify_vaa_from_governance_source ( & state, & vaa) ?;
181
197
182
198
// store updates to the config as a result of this action in here.
183
199
let mut updated_config: ConfigInfo = state. clone ( ) ;
184
- verify_vaa_from_governance_source ( & state, & vaa) ?;
185
200
201
+ // Governance messages must be applied in order. This check prevents replay attacks where
202
+ // previous messages are re-applied.
186
203
if vaa. sequence <= state. governance_sequence_number {
187
204
return Err ( PythContractError :: OldGovernanceMessage ) ?;
188
205
} else {
@@ -193,10 +210,18 @@ fn execute_governance_instruction(
193
210
let instruction = GovernanceInstruction :: deserialize ( & data[ ..] )
194
211
. map_err ( |_| PythContractError :: InvalidGovernancePayload ) ?;
195
212
213
+ // Check that the instruction is intended for this chain.
214
+ // chain_id = 0 means the instruction applies to all chains
196
215
if instruction. target_chain_id != state. chain_id && instruction. target_chain_id != 0 {
197
216
return Err ( PythContractError :: InvalidGovernancePayload ) ?;
198
217
}
199
218
219
+ // Check that the instruction is intended for this target chain contract (as opposed to
220
+ // other Pyth contracts that may live on the same chain).
221
+ if instruction. module != GovernanceModule :: Target {
222
+ return Err ( PythContractError :: InvalidGovernancePayload ) ?;
223
+ }
224
+
200
225
let response = match instruction. action {
201
226
UpgradeContract { code_id } => {
202
227
if instruction. target_chain_id == 0 {
@@ -205,7 +230,8 @@ fn execute_governance_instruction(
205
230
upgrade_contract ( & env. contract . address , code_id) ?
206
231
}
207
232
AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
208
- let parsed_claim_vaa = parse_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , & claim_vaa) ?;
233
+ let parsed_claim_vaa =
234
+ parse_and_verify_vaa ( deps. branch ( ) , env. block . time . seconds ( ) , & claim_vaa) ?;
209
235
transfer_governance ( & mut updated_config, & state, & parsed_claim_vaa) ?
210
236
}
211
237
SetDataSources { data_sources } => {
@@ -252,8 +278,9 @@ fn execute_governance_instruction(
252
278
Ok ( response)
253
279
}
254
280
255
- /// Transfers governance to the data source provided in `parsed_claim_vaa`. Stores the new
256
- /// governance parameters in `next_config`.
281
+ /// Transfers governance to the data source provided in `parsed_claim_vaa`.
282
+ /// This function updates the contract config in `next_config`; it is the caller's responsibility
283
+ /// to save this configuration in the on-chain storage.
257
284
fn transfer_governance (
258
285
next_config : & mut ConfigInfo ,
259
286
current_config : & ConfigInfo ,
@@ -263,6 +290,10 @@ fn transfer_governance(
263
290
GovernanceInstruction :: deserialize ( parsed_claim_vaa. payload . as_slice ( ) )
264
291
. map_err ( |_| PythContractError :: InvalidGovernancePayload ) ?;
265
292
293
+ // Check that the requester is asking to govern this target chain contract.
294
+ // chain_id == 0 means they're asking for governance of all target chain contracts.
295
+ // (this check doesn't matter for security because we have already checked the information
296
+ // in the authorization message.)
266
297
if claim_vaa_instruction. target_chain_id != current_config. chain_id
267
298
&& claim_vaa_instruction. target_chain_id != 0
268
299
{
@@ -342,6 +373,7 @@ fn verify_vaa_from_governance_source(state: &ConfigInfo, vaa: &ParsedVAA) -> Std
342
373
Ok ( ( ) )
343
374
}
344
375
376
+ /// Update the on-chain storage for any new price updates provided in `batch_attestation`.
345
377
fn process_batch_attestation (
346
378
deps : & mut DepsMut ,
347
379
env : & Env ,
@@ -454,15 +486,18 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
454
486
}
455
487
}
456
488
457
- pub fn query_price_feed ( deps : & Deps , address : & [ u8 ] ) -> StdResult < PriceFeedResponse > {
458
- match price_info_read ( deps. storage ) . load ( address) {
489
+ /// Get the most recent value of the price feed indicated by `feed_id`.
490
+ pub fn query_price_feed ( deps : & Deps , feed_id : & [ u8 ] ) -> StdResult < PriceFeedResponse > {
491
+ match price_info_read ( deps. storage ) . load ( feed_id) {
459
492
Ok ( price_info) => Ok ( PriceFeedResponse {
460
493
price_feed : price_info. price_feed ,
461
494
} ) ,
462
495
Err ( _) => Err ( PythContractError :: PriceFeedNotFound ) ?,
463
496
}
464
497
}
465
498
499
+ /// Get the fee that a caller must pay in order to submit a price update.
500
+ /// The fee depends on both the current contract configuration and the update data `vaas`.
466
501
pub fn get_update_fee ( deps : & Deps , vaas : & [ Binary ] ) -> StdResult < Coin > {
467
502
let config = config_read ( deps. storage ) . load ( ) ?;
468
503
0 commit comments