diff --git a/Cargo.lock b/Cargo.lock
index 2681d850..c198df8f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7513,6 +7513,7 @@ version = "0.1.0"
 dependencies = [
  "ink_env",
  "ink_lang",
+ "parity-scale-codec",
 ]
 
 [[package]]
diff --git a/contracts/extension/Cargo.toml b/contracts/extension/Cargo.toml
index f0f0c23d..b36450a2 100644
--- a/contracts/extension/Cargo.toml
+++ b/contracts/extension/Cargo.toml
@@ -7,6 +7,8 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
 edition = "2021"
 
 [dependencies]
+scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
+
 ink_env = { version = "3", default-features = false }
 ink_lang = { version = "3", default-features = false }
 
diff --git a/contracts/extension/src/lib.rs b/contracts/extension/src/lib.rs
index a29cb754..ab1e4d24 100644
--- a/contracts/extension/src/lib.rs
+++ b/contracts/extension/src/lib.rs
@@ -33,3 +33,59 @@ impl Environment for SeraiEnvironment {
 
   type ChainExtension = SeraiExtension;
 }
+
+pub fn test_register() {
+  struct ExtensionLen;
+  impl ink_env::test::ChainExtension for ExtensionLen {
+    fn func_id(&self) -> u32 {
+      0
+    }
+
+    fn call(&mut self, _: &[u8], output: &mut Vec<u8>) -> u32 {
+      scale::Encode::encode_to(&5u16, output);
+      0
+    }
+  }
+  ink_env::test::register_chain_extension(ExtensionLen);
+
+  struct ExtensionId;
+  impl ink_env::test::ChainExtension for ExtensionId {
+    fn func_id(&self) -> u32 {
+      1
+    }
+
+    fn call(&mut self, _: &[u8], output: &mut Vec<u8>) -> u32 {
+      scale::Encode::encode_to(&[0xffu8; 32], output);
+      0
+    }
+  }
+  ink_env::test::register_chain_extension(ExtensionId);
+
+  struct ExtensionActive;
+  impl ink_env::test::ChainExtension for ExtensionActive {
+    fn func_id(&self) -> u32 {
+      2
+    }
+
+    fn call(&mut self, input: &[u8], output: &mut Vec<u8>) -> u32 {
+      use scale::Decode;
+      let potential = AccountId::decode(&mut &input[1 ..]).unwrap(); // TODO: Why is this 1 ..?
+
+      let mut presence = false;
+      for validator in [
+        AccountId::from([1; 32]),
+        AccountId::from([2; 32]),
+        AccountId::from([3; 32]),
+        AccountId::from([4; 32]),
+        AccountId::from([5; 32])
+      ].clone() {
+        if potential == validator {
+          presence = true;
+        }
+      }
+      scale::Encode::encode_to(&presence, output);
+      0
+    }
+  }
+  ink_env::test::register_chain_extension(ExtensionActive);
+}
diff --git a/contracts/multisig/lib.rs b/contracts/multisig/lib.rs
index 9ba129c3..bbf62802 100644
--- a/contracts/multisig/lib.rs
+++ b/contracts/multisig/lib.rs
@@ -39,7 +39,7 @@ mod multisig {
     #[ink(topic)]
     hash: [u8; 32],
     /// Keys voted on.
-    keys: Vec<Vec<u8>>,
+    keys: Option<Vec<Vec<u8>>>,
   }
 
   /// Event emitted when the new keys are fully generated for all curves, having been fully voted
@@ -123,14 +123,14 @@ mod multisig {
       self.voted.insert((validator, keys_hash), &());
 
       let votes = if let Some(votes) = self.votes.get((validator_set, keys_hash)) {
-        self.env().emit_event(Vote { validator, validator_set, hash: keys_hash, keys: vec![] });
+        self.env().emit_event(Vote { validator, validator_set, hash: keys_hash, keys: None });
         votes + 1
       } else {
         self.env().emit_event(Vote {
           validator,
           validator_set,
           hash: keys_hash,
-          keys: keys.clone(),
+          keys: Some(keys.clone()),
         });
         1
       };
@@ -149,4 +149,104 @@ mod multisig {
       Ok(())
     }
   }
+
+  #[cfg(test)]
+  mod tests {
+    use super::*;
+
+    use ink_env::{
+      hash::{CryptoHash, Blake2x256},
+      AccountId,
+      topics::PrefixedValue,
+    };
+    use ink_lang as ink;
+
+    type Event = <Multisig as ::ink_lang::reflect::ContractEventBase>::Type;
+
+    fn hash_prefixed<T: scale::Encode>(prefixed: PrefixedValue<T>) -> [u8; 32] {
+      let encoded = prefixed.encode();
+      let mut hash = [0; 32];
+      if encoded.len() < 32 {
+        hash[.. encoded.len()].copy_from_slice(&encoded);
+      } else {
+        Blake2x256::hash(&encoded, &mut hash);
+      }
+      hash
+    }
+
+    fn assert_vote(
+      event: &ink_env::test::EmittedEvent,
+      expected_validator: AccountId,
+      expected_validator_set: [u8; 32],
+      expected_hash: [u8; 32],
+      expected_keys: Option<Vec<Vec<u8>>>,
+    ) {
+      let decoded_event = <Event as scale::Decode>::decode(&mut &event.data[..])
+        .expect("encountered invalid contract event data buffer");
+
+      if let Event::Vote(Vote { validator, validator_set, hash, keys }) = decoded_event {
+        assert_eq!(validator, expected_validator);
+        assert_eq!(validator_set, expected_validator_set);
+        assert_eq!(hash, expected_hash);
+        assert_eq!(keys, expected_keys);
+      } else {
+        panic!("invalid Vote event")
+      }
+
+      let expected_topics = vec![
+        hash_prefixed(PrefixedValue { prefix: b"", value: b"Multisig::Vote" }),
+        hash_prefixed(PrefixedValue {
+          prefix: b"Multisig::Vote::validator",
+          value: &expected_validator,
+        }),
+        hash_prefixed(PrefixedValue {
+          prefix: b"Multisig::Vote::validator_set",
+          value: &expected_validator_set,
+        }),
+        hash_prefixed(PrefixedValue { prefix: b"Multisig::Vote::hash", value: &expected_hash }),
+      ];
+
+      for (n, (actual_topic, expected_topic)) in
+        event.topics.iter().zip(expected_topics).enumerate()
+      {
+        assert_eq!(actual_topic, &expected_topic, "encountered invalid topic at {}", n);
+      }
+    }
+
+    /// The default constructor does its job.
+    #[ink::test]
+    fn new() {
+      let multisig = Multisig::new();
+      assert_eq!(multisig.validator_set(), [0; 32]);
+    }
+
+    #[ink::test]
+    fn non_existent_curve() {
+      assert_eq!(Multisig::new().key(0), Err(Error::NonExistentCurve));
+    }
+
+    #[ink::test]
+    fn vote() {
+      serai_extension::test_register();
+      ink_env::test::set_caller::<ink_env::DefaultEnvironment>(AccountId::from([1; 32]));
+
+      let mut multisig = Multisig::new();
+
+      let keys = vec![vec![0, 1], vec![2, 3]];
+      multisig.vote(keys.clone()).unwrap();
+      let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
+      assert_eq!(emitted_events.len(), 1);
+      assert_vote(
+        &emitted_events[0],
+        AccountId::from([1; 32]),
+        [0xff; 32],
+        {
+          let mut hash = [0; 32];
+          ink_env::hash_encoded::<Blake2x256, _>(&keys, &mut hash);
+          hash
+        },
+        Some(keys),
+      );
+    }
+  }
 }