#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract(env = serai_extension::SeraiEnvironment)]
mod multisig {
use scale::Encode;
use ink_storage::{traits::SpreadAllocate, Mapping};
use ink_env::{hash::Blake2x256, hash_encoded};
/// A contract which tracks the current multisig keys.
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct Multisig {
/// Validator set currently holding the multisig.
validator_set: [u8; 32],
/// Mapping from a curve's index to the multisig's current public key for it.
// This is a mapping due to ink's eager loading. Considering we're right now only considering
// secp256k1 and Ed25519, it may be notably more efficient to use a Vec here.
keys: Mapping<u8, Vec<u8>>,
/// Voter + Keys -> Voted already or not
voted: Mapping<(AccountId, [u8; 32]), ()>,
/// Validator Set + Keys -> Vote Count
votes: Mapping<([u8; 32], [u8; 32]), u16>,
}
/// Event emitted when a new set of multisig keys is voted on. Only for the first vote on a set
// of keys will they be present in this event.
#[ink(event)]
pub struct Vote {
/// Validator who issued the vote.
#[ink(topic)]
validator: AccountId,
/// Validator set for which keys are being generated.
#[ink(topic)]
validator_set: [u8; 32],
/// Hash of the keys voted on.
#[ink(topic)]
hash: [u8; 32],
/// Keys voted on.
keys: Option<Vec<Vec<u8>>>,
}
/// Event emitted when the new keys are fully generated for all curves, having been fully voted
/// on.
#[ink(event)]
pub struct KeyGen {
#[ink(topic)]
hash: [u8; 32],
}
/// The Multisig error types.
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// Returned if a curve index doesn't have a key registered for it.
NonExistentCurve,
/// Returned if a non-validator is voting.
NotValidator,
/// Returned if this validator set already generated keys.
AlreadyGeneratedKeys,
/// Returned if this validator has already voted for these keys.
AlreadyVoted,
}
/// The Multisig result type.
pub type Result<T> = core::result::Result<T, Error>;
impl Multisig {
/// Deploys the Multisig contract.
#[ink(constructor)]
pub fn new() -> Self {
ink_lang::utils::initialize_contract(|_| {})
}
/// Validator set currently holding the multisig.
#[ink(message)]
pub fn validator_set(&self) -> [u8; 32] {
self.validator_set
}
/// Returns the key currently in-use for a given curve ID. This is then bound to a given chain
/// by applying a personalized additive offset, as done by the processor. Each chain then has
/// its own way of receiving funds to these keys, leaving this not for usage by wallets, nor
/// the processor which is expected to track events for this information. This is really solely
/// for debugging purposes.
#[ink(message)]
pub fn key(&self, curve: u8) -> Result<Vec<u8>> {
self.keys.get(curve).ok_or(Error::NonExistentCurve)
}
// TODO: voted
// TODO: votes
fn hash<T: Encode>(value: &T) -> [u8; 32] {
let mut output = [0; 32];
hash_encoded::<Blake2x256, _>(value, &mut output);
output
}
/// Vote for a given set of keys.
#[ink(message)]
pub fn vote(&mut self, keys: Vec<Vec<u8>>) -> Result<()> {
if keys.len() > 256 {
Err(Error::NonExistentCurve)?;
}
let validator = self.env().caller();
if !self.env().extension().is_active_validator(&validator) {
Err(Error::NotValidator)?;
}
let validator_set = self.env().extension().validator_set_id();
if self.validator_set == validator_set {
Err(Error::AlreadyGeneratedKeys)?;
}
let keys_hash = Self::hash(&keys);
if self.voted.get((validator, keys_hash)).is_some() {
Err(Error::AlreadyVoted)?;
}
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: None });
votes + 1
} else {
self.env().emit_event(Vote {
validator,
validator_set,
hash: keys_hash,
keys: Some(keys.clone()),
});
1
};
// We could skip writing this if we've reached consensus, yet best to keep our ducks in a row
self.votes.insert((validator_set, keys_hash), &votes);
// If we've reached consensus, action this.
if votes == self.env().extension().active_validators_len() {
self.validator_set = validator_set;
for (k, key) in keys.iter().enumerate() {
self.keys.insert(u8::try_from(k).unwrap(), key);
}
self.env().emit_event(KeyGen { hash: keys_hash });
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use lazy_static::lazy_static;
use ink_env::{
hash::{CryptoHash, Blake2x256},
AccountId,
topics::PrefixedValue,
};
use ink_lang as ink;
use serai_extension::{test_validators, test_register};
use super::*;
type Event = <Multisig as ::ink_lang::reflect::ContractEventBase>::Type;
lazy_static! {
static ref keys: Vec<Vec<u8>> = vec![vec![0, 1], vec![2, 3]];
}
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_keys: Option<()>,
) {
let mut expected_hash = [0; 32];
ink_env::hash_encoded::<Blake2x256, _>(&*keys, &mut expected_hash);
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: actual_keys }) = decoded_event
{
assert_eq!(validator, expected_validator);
assert_eq!(validator_set, expected_validator_set);
assert_eq!(hash, expected_hash);
assert_eq!(actual_keys.as_ref(), expected_keys.map(|_| &*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]);
}
/// Non-existent curves error accordingly.
#[ink::test]
fn non_existent_curve() {
assert_eq!(Multisig::new().key(0), Err(Error::NonExistentCurve));
}
#[ink::test]
fn success() {
test_register();
let mut multisig = Multisig::new().try_lock().unwrap();
/// Test voting on keys works without issue, emitting the keys for the first vote
for (i, validator) in test_validators().iter().enumerate() {
ink_env::test::set_caller::<ink_env::DefaultEnvironment>(test_validators()[i]);
multisig.vote(keys.clone()).unwrap();
let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
assert_eq!(emitted_events.len(), i + 1);
assert_vote(
&emitted_events[i],
AccountId::from([2; 32]),
[0xff; 32],
// Only the first event for this hash should have the keys
Some(()).filter(|_| i == 0),
);
}
}
}
}
Absolutely not minimal, sorry, yet the relevant section is the doc comment on the for loop. This is not an ICE when I remove one of the slashes.
warning: unused doc comment
--> contracts/multisig/lib.rs:247:7
|
247 | /// Test voting on keys works without issue, emitting the keys for the first vote
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
248 | / for (i, validator) in test_validators().iter().enumerate() {
249 | | ink_env::test::set_caller::<ink_env::DefaultEnvironment>(test_validators()[i]);
250 | | multisig.vote(keys.clone()).unwrap();
251 | |
... |
260 | | );
261 | | }
| |_______- rustdoc does not generate documentation for expressions
|
= note: `#[warn(unused_doc_comments)]` on by default
= help: use `//` for a plain comment
thread 'rustc' panicked at 'internal error: entered unreachable code: in literal form when lowering mac args eq: Lit { token: Lit { kind: Str, symbol: " Test voting on keys works without issue, emitting the keys for the first vote", suffix: None }, kind: Str(" Test voting on keys works without issue, emitting the keys for the first vote", Cooked), span: contracts/multisig/lib.rs:247:7: 247:88 (#0) }', compiler/rustc_ast_lowering/src/lib.rs:914:17
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: rustc 1.64.0-nightly (c2f428d2f 2022-07-14) running on x86_64-unknown-linux-gnu
note: compiler flags: -C embed-bitcode=no -C debuginfo=2 -C incremental
note: some of the compiler flags provided by cargo are hidden
query stack during panic:
#0 [hir_crate] get the crate HIR
#1 [entry_fn] looking up the entry function of a crate
#2 [analysis] running analysis passes on this crate
end of query stack
Code
Absolutely not minimal, sorry, yet the relevant section is the doc comment on the for loop. This is not an ICE when I remove one of the slashes.
Meta
rustc --version --verbose
:Error output
Backtrace
``` stack backtrace: 0: 0x7fb68e4a2950 - std::backtrace_rs::backtrace::libunwind::trace::heeafe1f1ea6b4c2f at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5 1: 0x7fb68e4a2950 - std::backtrace_rs::backtrace::trace_unsynchronized::hf08684e78cd6c167 at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5 2: 0x7fb68e4a2950 - std::sys_common::backtrace::_print_fmt::had9e99c2c8763a1e at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys_common/backtrace.rs:66:5 3: 0x7fb68e4a2950 -::fmt::h1b4c432d2a1e6303
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys_common/backtrace.rs:45:22
4: 0x7fb68e4fbf2c - core::fmt::write::h87085de871a99231
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/core/src/fmt/mod.rs:1198:17
5: 0x7fb68e494015 - std::io::Write::write_fmt::h7635d2f423aa55dc
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/io/mod.rs:1672:15
6: 0x7fb68e4a55e1 - std::sys_common::backtrace::_print::hc003bc1c22b7967b
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys_common/backtrace.rs:48:5
7: 0x7fb68e4a55e1 - std::sys_common::backtrace::print::h1baa1ab7758e52b0
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys_common/backtrace.rs:35:9
8: 0x7fb68e4a55e1 - std::panicking::default_hook::{{closure}}::he2f5e84c6ab77817
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/panicking.rs:295:22
9: 0x7fb68e4a52b3 - std::panicking::default_hook::h3f96069db270c68f
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/panicking.rs:314:9
10: 0x7fb68ed49de4 - rustc_driver[1ce26eb46f30f4d]::DEFAULT_HOOK::{closure#0}::{closure#0}
11: 0x7fb68e4a5db6 - std::panicking::rust_panic_with_hook::hed0c1597bbc695a6
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/panicking.rs:702:17
12: 0x7fb68e4a5c07 - std::panicking::begin_panic_handler::{{closure}}::h0fc9e6b3154da131
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/panicking.rs:588:13
13: 0x7fb68e4a2e34 - std::sys_common::backtrace::__rust_end_short_backtrace::hb9c2240a67931ff9
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys_common/backtrace.rs:138:18
14: 0x7fb68e4a5932 - rust_begin_unwind
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/panicking.rs:584:5
15: 0x7fb68e469c33 - core::panicking::panic_fmt::h6bda1b0556b509cd
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/core/src/panicking.rs:142:14
16: 0x7fb6902a91eb - as core[32c218dbf3427c26]::iter::traits::collect::Extend>::extend::, ::lower_attrs::{closure#0}>>
17: 0x7fb6902d1c1e - ::alloc_from_iter::, ::lower_attrs::{closure#0}>>
18: 0x7fb6902780e4 - ::expr
19: 0x7fb69111de78 - ::lower_expr_for
20: 0x7fb690271b64 - ::lower_expr_mut
21: 0x7fb69026d9fe - ::lower_block_noalloc
22: 0x7fb690270c1d - ::lower_expr_mut
23: 0x7fb69026d9fe - ::lower_block_noalloc
24: 0x7fb690270c1d - ::lower_expr_mut
25: 0x7fb69026e1d2 - ::lower_block_noalloc
26: 0x7fb690270c1d - ::lower_expr_mut
27: 0x7fb69026d9fe - ::lower_block_noalloc
28: 0x7fb690270c1d - ::lower_expr_mut
29: 0x7fb69111b5e1 - ::lower_expr_closure
30: 0x7fb69027170c - ::lower_expr_mut
31: 0x7fb69027044d - ::lower_expr_mut
32: 0x7fb690270770 - ::lower_expr_mut
33: 0x7fb69026dfa7 - ::lower_block_noalloc
34: 0x7fb69027f9d4 - ::lower_maybe_async_body
35: 0x7fb6902797fa - ::lower_item_kind
36: 0x7fb69029bb59 - ::with_hir_id_owner::<::with_lctx<::lower_item::{closure#0}>::{closure#0}>
37: 0x7fb6902acffd - ::lower_node
38: 0x7fb690289278 - rustc_ast_lowering[3f3b0ef9be439d1a]::lower_to_hir
39: 0x7fb691413bec - >::with_task::
40: 0x7fb69147ef02 - rustc_query_system[688aca7ce0d190ff]::query::plumbing::try_execute_query::>
41: 0x7fb68f75a12f - rustc_query_system[688aca7ce0d190ff]::query::plumbing::force_query::
42: 0x7fb68f7367a6 - rustc_query_impl[e840a35d18f93fd8]::query_callbacks::hir_crate::force_from_dep_node
43: 0x7fb690ddf7f1 - ::try_force_from_dep_node
44: 0x7fb69088b316 - >::try_mark_previous_green::
45: 0x7fb69088ab96 - >::try_mark_previous_green::
46: 0x7fb69088a420 - >::try_mark_green::
47: 0x7fb6914d8cbc - rustc_query_system[688aca7ce0d190ff]::query::plumbing::try_load_from_disk_and_cache_in_memory::>
48: 0x7fb6914ac124 - rustc_query_system[688aca7ce0d190ff]::query::plumbing::try_execute_query::>>
49: 0x7fb69150a9c0 - rustc_query_system[688aca7ce0d190ff]::query::plumbing::get_query::
50: 0x7fb6914564d2 - ::entry_fn
51: 0x7fb690f5608d - ::time::, rustc_interface[694c7ab70a1d38bc]::passes::analysis::{closure#0}::{closure#0}::{closure#0}>
52: 0x7fb690f59a60 - ::time::<(), rustc_interface[694c7ab70a1d38bc]::passes::analysis::{closure#0}>
53: 0x7fb690f44e4e - rustc_interface[694c7ab70a1d38bc]::passes::analysis
54: 0x7fb69141206c - >::with_task::>
55: 0x7fb6914acd1b - rustc_query_system[688aca7ce0d190ff]::query::plumbing::try_execute_query::>>
56: 0x7fb69150a74e - rustc_query_system[688aca7ce0d190ff]::query::plumbing::get_query::
57: 0x7fb690f2841e - ::enter::>
58: 0x7fb690f0c09e - ::enter::, rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>
59: 0x7fb690f081ff - rustc_span[717885ec7c6cd182]::with_source_map::, rustc_interface[694c7ab70a1d38bc]::interface::create_compiler_and_run, rustc_driver[1ce26eb46f30f4d]::run_compiler::{closure#1}>::{closure#1}>
60: 0x7fb690f244b0 - rustc_interface[694c7ab70a1d38bc]::interface::create_compiler_and_run::, rustc_driver[1ce26eb46f30f4d]::run_compiler::{closure#1}>
61: 0x7fb690f385b2 - >::set::, rustc_driver[1ce26eb46f30f4d]::run_compiler::{closure#1}>::{closure#0}, core[32c218dbf3427c26]::result::Result<(), rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>
62: 0x7fb690f0a78f - std[167b23ae759531ff]::sys_common::backtrace::__rust_begin_short_backtrace::, rustc_driver[1ce26eb46f30f4d]::run_compiler::{closure#1}>::{closure#0}, core[32c218dbf3427c26]::result::Result<(), rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>::{closure#0}, core[32c218dbf3427c26]::result::Result<(), rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>
63: 0x7fb690f24909 - <::spawn_unchecked_, rustc_driver[1ce26eb46f30f4d]::run_compiler::{closure#1}>::{closure#0}, core[32c218dbf3427c26]::result::Result<(), rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>::{closure#0}, core[32c218dbf3427c26]::result::Result<(), rustc_errors[3b3e5bcd1e9c2834]::ErrorGuaranteed>>::{closure#1} as core[32c218dbf3427c26]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
64: 0x7fb68e4af803 - as core::ops::function::FnOnce>::call_once::h86b1834fb0da834b
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/alloc/src/boxed.rs:1934:9
65: 0x7fb68e4af803 - as core::ops::function::FnOnce>::call_once::h124d05192aaf60e0
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/alloc/src/boxed.rs:1934:9
66: 0x7fb68e4af803 - std::sys::unix::thread::Thread::new::thread_start::h01e8d05fb6e030ea
at /rustc/c2f428d2f3340a0e7d995f4726223db91b93704c/library/std/src/sys/unix/thread.rs:108:17
67: 0x7fb68e08c54d -
68: 0x7fb68e111874 - clone
69: 0x0 -
```