Browse Source

Align behaviour of persistent message fees

Change-Id: Ic9c6c40dbac2399e0eaf3a861dff33254a828a18
Hendrik Hofstadt 4 years ago
parent
commit
7784e74725

+ 4 - 2
design/0004_message_publishing.md

@@ -112,7 +112,9 @@ Module [32]byte = "Core"
 Action uint16 = 3
 Chain uint16
 // Message fee in the native token
-Fee uint64
+Fee uint256
+// Persistent message fee in the native token
+FeePersistent uint256
 ```
 
 TransferFees:
@@ -124,7 +126,7 @@ Module [32]byte = "Core"
 Action uint16 = 4
 Chain uint16
 // Amount being transferred (big-endian uint256)
-Amount [32]uint8
+Amount uint256
 // Address of the recipient. Left-zero-padded if shorter than 32 bytes
 To [32]uint8
 ```

+ 22 - 2
solana/bridge/client/src/main.rs

@@ -79,6 +79,7 @@ fn command_deploy_bridge(
     initial_guardians: Vec<[u8; 20]>,
     guardian_expiration: u32,
     message_fee: u64,
+    message_fee_persistent: u64,
 ) -> CommmandResult {
     println!("Initializing Wormhole bridge {}", bridge);
 
@@ -90,6 +91,7 @@ fn command_deploy_bridge(
         *bridge,
         config.owner.pubkey(),
         message_fee,
+        message_fee_persistent,
         guardian_expiration,
         initial_guardians.as_slice(),
     )
@@ -122,12 +124,19 @@ fn command_post_message(
             None, bridge,
         ))?;
     let bridge_config = BridgeData::try_from_slice(bridge_config_account.data.as_slice())?;
-    println!("Message fee: {} lamports", bridge_config.config.fee);
+    let fee = {
+        if persist {
+            bridge_config.config.fee_persistent
+        } else {
+            bridge_config.config.fee
+        }
+    };
+    println!("Message fee: {} lamports", fee);
 
     let transfer_ix = transfer(
         &config.owner.pubkey(),
         &FeeCollector::key(None, bridge),
-        bridge_config.config.fee,
+        fee,
     );
     let (_, ix) = bridge::instructions::post_message(
         *bridge,
@@ -237,6 +246,15 @@ fn main() {
                         .index(4)
                         .required(true)
                         .help("Initial message posting fee"),
+                )
+                .arg(
+                    Arg::with_name("message_fee_persistent")
+                        .validator(is_u64)
+                        .value_name("MESSAGE_FEE_PERSISTENT")
+                        .takes_value(true)
+                        .index(5)
+                        .required(true)
+                        .help("Initial persistent message posting fee"),
                 ),
         )
         .subcommand(
@@ -315,6 +333,7 @@ fn main() {
             let guardian_expiration: u32 =
                 value_of(arg_matches, "guardian_set_expiration").unwrap();
             let msg_fee: u64 = value_of(arg_matches, "message_fee").unwrap();
+            let msg_fee_persistent: u64 = value_of(arg_matches, "message_fee_persistent").unwrap();
 
             let mut guardian = [0u8; 20];
             guardian.copy_from_slice(&initial_data);
@@ -324,6 +343,7 @@ fn main() {
                 vec![guardian],
                 guardian_expiration,
                 msg_fee,
+                msg_fee_persistent,
             )
         }
         ("post-message", Some(arg_matches)) => {

+ 2 - 2
solana/bridge/program/src/api/governance.rs

@@ -170,8 +170,8 @@ pub struct SetFeesData {}
 pub fn set_fees(ctx: &ExecutionContext, accs: &mut SetFees, _data: SetFeesData) -> Result<()> {
     accs.vaa.claim(ctx, accs.payer.key)?;
 
-    // Set expiration time for the old set
-    accs.bridge.config.fee = accs.vaa.fee;
+    accs.bridge.config.fee = accs.vaa.fee.as_u64();
+    accs.bridge.config.fee_persistent = accs.vaa.persisted_fee.as_u64();
 
     Ok(())
 }

+ 4 - 0
solana/bridge/program/src/api/initialize.rs

@@ -37,6 +37,9 @@ pub struct InitializeData {
     /// Amount of lamports that needs to be paid to the protocol to post a message
     pub fee: u64,
 
+    /// Amount of lamports that needs to be paid to the protocol to post a message
+    pub fee_persistent: u64,
+
     /// Initial Guardian Set
     pub initial_guardians: Vec<[u8; 20]>,
 }
@@ -71,6 +74,7 @@ pub fn initialize(
     accs.bridge.config = BridgeConfig {
         guardian_set_expiration_time: data.guardian_set_expiration_time,
         fee: data.fee,
+        fee_persistent: data.fee_persistent,
     };
 
     // Initialize the fee collector account so it's rent exempt and will keep funds

+ 9 - 2
solana/bridge/program/src/api/post_message.rs

@@ -96,6 +96,13 @@ pub fn post_message(
     accs.message
         .verify_derivation(ctx.program_id, &msg_derivation)?;
 
+    let fee = {
+        if data.persist {
+            accs.bridge.config.fee_persistent
+        } else {
+            accs.bridge.config.fee
+        }
+    };
     // Fee handling, checking previously known balance allows us to not care who is the payer of
     // this submission.
     if accs
@@ -103,11 +110,11 @@ pub fn post_message(
         .lamports()
         .checked_sub(accs.bridge.last_lamports)
         .ok_or(MathOverflow)?
-        < accs.bridge.config.fee
+        < fee
     {
         trace!(
             "Expected fee not found: fee, last_lamports, collector: {} {} {}",
-            accs.bridge.config.fee,
+            fee,
             accs.bridge.last_lamports,
             accs.fee_collector.lamports(),
         );

+ 1 - 1
solana/bridge/program/src/api/post_vaa.rs

@@ -29,9 +29,9 @@ use byteorder::{
 };
 use sha3::Digest;
 use solana_program::{
+    msg,
     program_error::ProgramError,
     pubkey::Pubkey,
-    msg,
 };
 use solitaire::{
     processors::seeded::Seeded,

+ 2 - 0
solana/bridge/program/src/instructions.rs

@@ -44,6 +44,7 @@ pub fn initialize(
     program_id: Pubkey,
     payer: Pubkey,
     fee: u64,
+    fee_persistent: u64,
     guardian_set_expiration_time: u32,
     initial_guardians: &[[u8; 20]],
 ) -> solitaire::Result<Instruction> {
@@ -67,6 +68,7 @@ pub fn initialize(
         data: crate::instruction::Instruction::Initialize(InitializeData {
             initial_guardians: initial_guardians.to_vec(),
             fee,
+            fee_persistent,
             guardian_set_expiration_time,
         })
         .try_to_vec()?,

+ 54 - 33
solana/bridge/program/src/types.rs

@@ -74,6 +74,8 @@ pub struct BridgeConfig {
 
     /// Amount of lamports that needs to be paid to the protocol to post a message
     pub fee: u64,
+    /// Amount of lamports that needs to be paid to the protocol to post a persistent message
+    pub fee_persistent: u64,
 }
 
 #[derive(Default, BorshSerialize, BorshDeserialize)]
@@ -220,34 +222,6 @@ impl Owned for ClaimData {
     }
 }
 
-pub struct GovernancePayloadUpgrade {
-    // Address of the new Implementation
-    pub new_contract: Pubkey,
-}
-
-impl DeserializePayload for GovernancePayloadUpgrade
-where
-    Self: DeserializeGovernancePayload,
-{
-    fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
-        let mut c = Cursor::new(buf);
-
-        Self::check_governance_header(&mut c)?;
-
-        let mut addr = [0u8; 32];
-        c.read_exact(&mut addr)?;
-
-        Ok(GovernancePayloadUpgrade {
-            new_contract: Pubkey::new(&addr[..]),
-        })
-    }
-}
-
-impl DeserializeGovernancePayload for GovernancePayloadUpgrade {
-    const MODULE: &'static str = "CORE";
-    const ACTION: u8 = 2;
-}
-
 pub struct GovernancePayloadGuardianSetChange {
     // New GuardianSetIndex
     pub new_guardian_set_index: u32,
@@ -262,7 +236,7 @@ impl SerializePayload for GovernancePayloadGuardianSetChange {
         v.write_u32::<BigEndian>(self.new_guardian_set_index)?;
         v.write_u8(self.new_guardian_set.len() as u8)?;
         for key in self.new_guardian_set.iter() {
-            v.write(key);
+            v.write(key)?;
         }
         Ok(())
     }
@@ -297,15 +271,52 @@ impl DeserializeGovernancePayload for GovernancePayloadGuardianSetChange {
     const ACTION: u8 = 1;
 }
 
+pub struct GovernancePayloadUpgrade {
+    // Address of the new Implementation
+    pub new_contract: Pubkey,
+}
+
+impl DeserializePayload for GovernancePayloadUpgrade
+where
+    Self: DeserializeGovernancePayload,
+{
+    fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
+        let mut c = Cursor::new(buf);
+
+        Self::check_governance_header(&mut c)?;
+
+        let mut addr = [0u8; 32];
+        c.read_exact(&mut addr)?;
+
+        Ok(GovernancePayloadUpgrade {
+            new_contract: Pubkey::new(&addr[..]),
+        })
+    }
+}
+
+impl DeserializeGovernancePayload for GovernancePayloadUpgrade {
+    const MODULE: &'static str = "CORE";
+    const ACTION: u8 = 2;
+}
+
 pub struct GovernancePayloadSetMessageFee {
     // New fee in lamports
-    pub fee: u64,
+    pub fee: U256,
+    // New fee for persisted messages in lamports
+    pub persisted_fee: U256,
 }
 
 impl SerializePayload for GovernancePayloadSetMessageFee {
     fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
         use byteorder::WriteBytesExt;
-        v.write_u64::<BigEndian>(self.fee)?;
+        let mut fee_data = [0u8; 32];
+        self.fee.to_big_endian(&mut fee_data);
+        v.write(&fee_data[..])?;
+
+        let mut fee_persistent_data = [0u8; 32];
+        self.persisted_fee.to_big_endian(&mut fee_persistent_data);
+        v.write(&fee_persistent_data[..])?;
+
         Ok(())
     }
 }
@@ -317,8 +328,18 @@ where
     fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
         let mut c = Cursor::new(buf);
 
-        let fee = c.read_u64::<BigEndian>()?;
-        Ok(GovernancePayloadSetMessageFee { fee })
+        let mut fee_data: [u8; 32] = [0; 32];
+        c.read_exact(&mut fee_data)?;
+        let fee = U256::from_big_endian(&fee_data);
+
+        let mut fee_persisted_data: [u8; 32] = [0; 32];
+        c.read_exact(&mut fee_persisted_data)?;
+        let fee_persisted = U256::from_big_endian(&fee_persisted_data);
+
+        Ok(GovernancePayloadSetMessageFee {
+            fee,
+            persisted_fee: fee_persisted,
+        })
     }
 }
 

+ 37 - 18
solana/bridge/program/tests/common.rs

@@ -16,8 +16,8 @@ use secp256k1::{
 };
 use sha3::Digest;
 use solana_client::{
-    rpc_client::RpcClient,
     client_error::ClientError,
+    rpc_client::RpcClient,
     rpc_config::RpcSendTransactionConfig,
 };
 use solana_program::{
@@ -42,8 +42,8 @@ use solana_sdk::{
     signature::{
         read_keypair_file,
         Keypair,
-        Signer,
         Signature,
+        Signer,
     },
     transaction::Transaction,
 };
@@ -103,16 +103,15 @@ fn execute(
     let mut transaction = Transaction::new_with_payer(instructions, Some(&payer.pubkey()));
     let recent_blockhash = client.get_recent_blockhash().unwrap().0;
     transaction.sign(&signers.to_vec(), recent_blockhash);
-    client
-        .send_and_confirm_transaction_with_spinner_and_config(
-            &transaction,
-            CommitmentConfig::processed(),
-            RpcSendTransactionConfig {
-                skip_preflight: true,
-                preflight_commitment: None,
-                encoding: None,
-            },
-        )
+    client.send_and_confirm_transaction_with_spinner_and_config(
+        &transaction,
+        CommitmentConfig::processed(),
+        RpcSendTransactionConfig {
+            skip_preflight: true,
+            preflight_commitment: None,
+            encoding: None,
+        },
+    )
 }
 
 mod helpers {
@@ -133,7 +132,10 @@ mod helpers {
     }
 
     /// Fetch account data, the loop is there to re-attempt until data is available.
-    pub fn get_account_data<T: BorshDeserialize>(client: &RpcClient, account: &Pubkey) -> Option<T> {
+    pub fn get_account_data<T: BorshDeserialize>(
+        client: &RpcClient,
+        account: &Pubkey,
+    ) -> Option<T> {
         for _ in 0..5 {
             if let Ok(account) = client.get_account(account) {
                 return Some(T::try_from_slice(&account.data).unwrap());
@@ -219,7 +221,12 @@ mod helpers {
         (vaa, body, body_hash)
     }
 
-    pub fn transfer(client: &RpcClient, from: &Keypair, to: &Pubkey, lamports: u64) -> Result<Signature, ClientError> {
+    pub fn transfer(
+        client: &RpcClient,
+        from: &Keypair,
+        to: &Pubkey,
+        lamports: u64,
+    ) -> Result<Signature, ClientError> {
         execute(
             client,
             from,
@@ -257,14 +264,21 @@ mod helpers {
         nonce: u32,
         data: Vec<u8>,
         fee: u64,
+        persist: bool,
     ) -> Result<Pubkey, ClientError> {
         // Transfer money into the fee collector as it needs a balance/must exist.
         let fee_collector = FeeCollector::<'_>::key(None, program);
 
         // Capture the resulting message, later functions will need this.
-        let (message_key, instruction) =
-            instructions::post_message(*program, payer.pubkey(), emitter.pubkey(), nonce, data)
-                .unwrap();
+        let (message_key, instruction) = instructions::post_message(
+            *program,
+            payer.pubkey(),
+            emitter.pubkey(),
+            nonce,
+            data,
+            persist,
+        )
+        .unwrap();
 
         execute(
             client,
@@ -317,7 +331,12 @@ mod helpers {
         Ok(())
     }
 
-    pub fn post_vaa(client: &RpcClient, program: &Pubkey, payer: &Keypair, vaa: PostVAAData) -> Result<Signature, ClientError> {
+    pub fn post_vaa(
+        client: &RpcClient,
+        program: &Pubkey,
+        payer: &Keypair,
+        vaa: PostVAAData,
+    ) -> Result<Signature, ClientError> {
         execute(
             client,
             payer,

+ 114 - 35
solana/bridge/program/tests/integration.rs

@@ -1,7 +1,17 @@
 #![allow(warnings)]
 
 use borsh::BorshSerialize;
-use secp256k1::Message;
+use byteorder::{
+    BigEndian,
+    WriteBytesExt,
+};
+use hex_literal::hex;
+use secp256k1::{
+    Message,
+    PublicKey,
+    SecretKey,
+};
+use sha3::Digest;
 use solana_client::rpc_client::RpcClient;
 use solana_program::{
     borsh::try_from_slice_unchecked,
@@ -27,27 +37,17 @@ use solana_sdk::{
     },
     transaction::Transaction,
 };
-use byteorder::{
-    BigEndian,
-    WriteBytesExt,
-};
 use std::{
     convert::TryInto,
     io::{
         Cursor,
         Write,
     },
+    time::{
+        Duration,
+        SystemTime,
+    },
 };
-use std::time::{
-    Duration,
-    SystemTime,
-};
-use hex_literal::hex;
-use secp256k1::{
-    PublicKey,
-    SecretKey,
-};
-use sha3::Digest;
 
 use bridge::{
     accounts::GuardianSetDerivationData,
@@ -66,14 +66,15 @@ use bridge::{
     SerializePayload,
     Signature,
 };
+use primitive_types::U256;
 
 mod common;
 
 const GOV_KEY: [u8; 64] = [
-    240, 133, 120, 113, 30, 67, 38, 184, 197, 72, 234, 99, 241, 21, 58, 225, 41, 157, 171, 44,
-    196, 163, 134, 236, 92, 148, 110, 68, 127, 114, 177, 0, 173, 253, 199, 9, 242, 142, 201,
-    174, 108, 197, 18, 102, 115, 0, 31, 205, 127, 188, 191, 56, 171, 228, 20, 247, 149, 170,
-    141, 231, 147, 88, 97, 199,
+    240, 133, 120, 113, 30, 67, 38, 184, 197, 72, 234, 99, 241, 21, 58, 225, 41, 157, 171, 44, 196,
+    163, 134, 236, 92, 148, 110, 68, 127, 114, 177, 0, 173, 253, 199, 9, 242, 142, 201, 174, 108,
+    197, 18, 102, 115, 0, 31, 205, 127, 188, 191, 56, 171, 228, 20, 247, 149, 170, 141, 231, 147,
+    88, 97, 199,
 ];
 
 struct Context {
@@ -109,7 +110,17 @@ fn test_bridge_messages(context: &mut Context) {
     let emitter = Keypair::new();
 
     // Post the message, publishing the data for guardian consumption.
-    let message_key = common::post_message(client, program, payer, &emitter, nonce, message.clone(), 10_000).unwrap();
+    let message_key = common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        10_000,
+        false,
+    )
+    .unwrap();
 
     // Emulate Guardian behaviour, verifying the data and publishing signatures/VAA.
     let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 0);
@@ -127,7 +138,17 @@ fn test_guardian_set_change(context: &mut Context) {
     let emitter = Keypair::from_bytes(&GOV_KEY).unwrap();
 
     // Post the message, publishing the data for guardian consumption.
-    let message_key = common::post_message(client, program, payer, &emitter, nonce, message.clone(), 10_000).unwrap();
+    let message_key = common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        10_000,
+        false,
+    )
+    .unwrap();
 
     // Emulate Guardian behaviour, verifying the data and publishing signatures/VAA.
     let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 0);
@@ -141,9 +162,21 @@ fn test_guardian_set_change(context: &mut Context) {
     let message = GovernancePayloadGuardianSetChange {
         new_guardian_set_index: 1,
         new_guardian_set: new_public_keys.clone(),
-    }.try_to_vec().unwrap();
+    }
+    .try_to_vec()
+    .unwrap();
 
-    let message_key = common::post_message(client, program, payer, &emitter, nonce, message.clone(), 10_000).unwrap();
+    let message_key = common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        10_000,
+        false,
+    )
+    .unwrap();
     let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 0);
     common::verify_signatures(client, program, payer, body, body_hash, &context.secret, 0).unwrap();
     common::post_vaa(client, program, payer, vaa).unwrap();
@@ -157,11 +190,22 @@ fn test_guardian_set_change(context: &mut Context) {
         0,
         1,
         1,
-    ).unwrap();
+    )
+    .unwrap();
 
     // Submit the message a second time with a new nonce.
     let nonce = 12399;
-    let message_key = common::post_message(client, program, payer, &emitter, nonce, message.clone(), 10_000).unwrap();
+    let message_key = common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        10_000,
+        false,
+    )
+    .unwrap();
 
     context.public = new_public_keys;
     context.secret = new_secret_keys;
@@ -183,9 +227,21 @@ fn test_guardian_set_change_fails(context: &mut Context) {
     let message = GovernancePayloadGuardianSetChange {
         new_guardian_set_index: 2,
         new_guardian_set: new_public_keys.clone(),
-    }.try_to_vec().unwrap();
+    }
+    .try_to_vec()
+    .unwrap();
 
-    let message_key = common::post_message(client, program, payer, &emitter, nonce, message.clone(), 10_000).unwrap();
+    let message_key = common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        10_000,
+        false,
+    )
+    .unwrap();
     let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 1);
 
     assert!(common::upgrade_guardian_set(
@@ -197,7 +253,8 @@ fn test_guardian_set_change_fails(context: &mut Context) {
         1,
         2,
         0,
-    ).is_err());
+    )
+    .is_err());
 }
 
 fn test_set_fees(context: &mut Context) {
@@ -206,9 +263,12 @@ fn test_set_fees(context: &mut Context) {
     let emitter = Keypair::from_bytes(&GOV_KEY).unwrap();
 
     let nonce = 12401;
-    let message = GovernancePayloadSetMessageFee { fee: 100 }
-        .try_to_vec()
-        .unwrap();
+    let message = GovernancePayloadSetMessageFee {
+        fee: U256::from(100),
+        persisted_fee: U256::from(100),
+    }
+    .try_to_vec()
+    .unwrap();
 
     let message_key = common::post_message(
         client,
@@ -218,6 +278,7 @@ fn test_set_fees(context: &mut Context) {
         nonce,
         message.clone(),
         10_000,
+        false,
     )
     .unwrap();
     let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 1);
@@ -229,13 +290,31 @@ fn test_set_fees(context: &mut Context) {
     let emitter = Keypair::new();
     let nonce = 12402;
     let message = b"Fail to Pay".to_vec();
-    assert!(
-        common::post_message(client, program, payer, &emitter, nonce, message.clone(), 50).is_err()
-    );
+    assert!(common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        50,
+        false
+    )
+    .is_err());
 
     // And succeeds with the new.
     let emitter = Keypair::new();
     let nonce = 12402;
     let message = b"Fail to Pay".to_vec();
-    common::post_message(client, program, payer, &emitter, nonce, message.clone(), 100).unwrap();
+    common::post_message(
+        client,
+        program,
+        payer,
+        &emitter,
+        nonce,
+        message.clone(),
+        100,
+        false,
+    )
+    .unwrap();
 }

+ 1 - 1
solana/devnet_setup.sh

@@ -44,7 +44,7 @@ spl-token mint "$token" 10000000000 "$account"
 
 # Create the bridge contract at a known address
 # OK to fail on subsequent attempts (already created).
-retry client create-bridge "$bridge_address" "$initial_guardian" 86400 100
+retry client create-bridge "$bridge_address" "$initial_guardian" 86400 100 200
 
 # Let k8s startup probe succeed
 nc -l -p 2000

+ 41 - 6
terra/contracts/wormhole/src/contract.rs

@@ -12,7 +12,7 @@ use crate::msg::{
 use crate::state::{
     config, config_read, guardian_set_get, guardian_set_set, sequence_read, sequence_set,
     vaa_archive_check, ConfigInfo, GovernancePacket, GuardianAddress, GuardianSetInfo,
-    GuardianSetUpgrade, ParsedVAA, TransferFee,
+    GuardianSetUpgrade, ParsedVAA, SetFee, TransferFee,
 };
 
 use k256::ecdsa::recoverable::Id as RecoverableId;
@@ -30,7 +30,7 @@ const CHAIN_ID: u16 = 3;
 
 // Lock assets fee amount and denomination
 const FEE_AMOUNT: u128 = 10000;
-const FEE_DENOMINATION: &str = "uluna";
+pub const FEE_DENOMINATION: &str = "uluna";
 
 pub fn init<S: Storage, A: Api, Q: Querier>(
     deps: &mut Extern<S, A, Q>,
@@ -44,6 +44,7 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
         guardian_set_index: 0,
         guardian_set_expirity: msg.guardian_set_expirity,
         fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
+        fee_persisted: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
     };
     config(&mut deps.storage).save(&state)?;
 
@@ -103,9 +104,10 @@ fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
     }
 
     match gov_packet.action {
-        // 0 is reserved for upgrade / migration
-        1u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
-        2u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
+        // 1 is reserved for upgrade / migration
+        2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
+        3u8 => handle_set_fee(deps, env, &gov_packet.payload),
+        4u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
         _ => ContractError::InvalidVAAAction.std_err(),
     }
 }
@@ -230,6 +232,32 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
     })
 }
 
+pub fn handle_set_fee<S: Storage, A: Api, Q: Querier>(
+    deps: &mut Extern<S, A, Q>,
+    env: Env,
+    data: &Vec<u8>,
+) -> StdResult<HandleResponse> {
+    let set_fee_msg = SetFee::deserialize(&data)?;
+
+    // Save new fees
+    let mut state = config_read(&mut deps.storage).load()?;
+    state.fee = set_fee_msg.fee;
+    state.fee_persisted = set_fee_msg.fee_persistent;
+    config(&mut deps.storage).save(&state)?;
+
+    Ok(HandleResponse {
+        messages: vec![],
+        log: vec![
+            log("action", "fee_change"),
+            log("new_fee.amount", state.fee.amount),
+            log("new_fee.denom", state.fee.denom),
+            log("new_persisted_fee.amount", state.fee_persisted.amount),
+            log("new_persisted_fee.denom", state.fee_persisted.denom),
+        ],
+        data: None,
+    })
+}
+
 pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
     deps: &mut Extern<S, A, Q>,
     env: Env,
@@ -256,9 +284,16 @@ fn handle_post_message<S: Storage, A: Api, Q: Querier>(
     persist: bool,
 ) -> StdResult<HandleResponse> {
     let state = config_read(&deps.storage).load()?;
+    let fee = {
+        if persist {
+            state.fee
+        } else {
+            state.fee_persisted
+        }
+    };
 
     // Check fee
-    if !has_coins(env.message.sent_funds.as_ref(), &state.fee) {
+    if !has_coins(env.message.sent_funds.as_ref(), &fee) {
         return ContractError::FeeTooLow.std_err();
     }
 

+ 38 - 9
terra/contracts/wormhole/src/state.rs

@@ -1,4 +1,4 @@
-use schemars::JsonSchema;
+use schemars::{JsonSchema, Set};
 use serde::{Deserialize, Serialize};
 
 use cosmwasm_std::{Binary, CanonicalAddr, Coin, HumanAddr, StdResult, Storage, Uint128};
@@ -31,8 +31,10 @@ pub struct ConfigInfo {
     pub gov_chain: u16,
     pub gov_address: Vec<u8>,
 
-    // Asset locking fee
+    // Message sending fee
     pub fee: Coin,
+    // Persisted message sending fee
+    pub fee_persisted: Coin,
 }
 
 // Validator Action Approval(VAA) data
@@ -144,6 +146,7 @@ pub struct GuardianAddress {
     pub bytes: Binary, // 20-byte addresses
 }
 
+use crate::contract::FEE_DENOMINATION;
 #[cfg(test)]
 use hex;
 
@@ -263,7 +266,7 @@ impl GovernancePacket {
     }
 }
 
-// action 1
+// action 2
 pub struct GuardianSetUpgrade {
     pub new_guardian_set_index: u32,
     pub new_guardian_set: GuardianSetInfo,
@@ -303,7 +306,34 @@ impl GuardianSetUpgrade {
     }
 }
 
-// action 2
+// action 3
+pub struct SetFee {
+    pub fee: Coin,
+    pub fee_persistent: Coin,
+}
+
+impl SetFee {
+    pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
+        let data = data.as_slice();
+
+        let (_, amount) = data.get_u256(0);
+        let (_, amount_persistent) = data.get_u256(32);
+        let fee = Coin {
+            denom: String::from(FEE_DENOMINATION),
+            amount: Uint128(amount),
+        };
+        let fee_persistent = Coin {
+            denom: String::from(FEE_DENOMINATION),
+            amount: Uint128(amount_persistent),
+        };
+        Ok(SetFee {
+            fee,
+            fee_persistent,
+        })
+    }
+}
+
+// action 4
 pub struct TransferFee {
     pub amount: Coin,
     pub recipient: CanonicalAddr,
@@ -314,12 +344,11 @@ impl TransferFee {
         let data = data.as_slice();
         let recipient = data.get_address(0);
 
-        let amount = Uint128(data.get_u128_be(32));
-        let denom = match String::from_utf8(data[48..].to_vec()) {
-            Ok(s) => s,
-            Err(_) => return ContractError::InvalidVAA.std_err(),
+        let (_, amount) = data.get_u256(32);
+        let amount = Coin {
+            denom: String::from(FEE_DENOMINATION),
+            amount: Uint128(amount),
         };
-        let amount = Coin { denom, amount };
         Ok(TransferFee { amount, recipient })
     }
 }