Introduce 'Never' type as a safe alternative to always None options
Unfortunately, as documented in: https://github.com/IntersectMBO/cardano-ledger/issues/4571 Some Option fields in the script context certificates are going to remain set to None, at least until the next Hard fork. There's a risk that people permanently lock their funds if they expect deposits on registration credentials to ever be `Some`. So, we introduce a special type that emulate an `Option` that can only ever be `None`. We call it `Never` and it is the first type of this kind (i.e. with constructors indexes not starting at 0).
This commit is contained in:
parent
ff25fbd970
commit
d74e36d0bc
|
@ -400,6 +400,19 @@ impl TypedDataType {
|
|||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_never(&self) -> bool {
|
||||
self.name == well_known::NEVER
|
||||
&& self.constructors.len() == well_known::NEVER_CONSTRUCTORS.len()
|
||||
&& self.location == Span::empty()
|
||||
&& self
|
||||
.constructors
|
||||
.iter()
|
||||
.zip(well_known::NEVER_CONSTRUCTORS)
|
||||
.all(|(constructor, name)| {
|
||||
name == &constructor.name && constructor.arguments.is_empty()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
@ -16,6 +16,8 @@ pub const LIST: &str = "List";
|
|||
pub const MILLER_LOOP_RESULT: &str = "MillerLoopResult";
|
||||
pub const OPTION: &str = "Option";
|
||||
pub const OPTION_CONSTRUCTORS: &[&str] = &["Some", "None"];
|
||||
pub const NEVER: &str = "Never";
|
||||
pub const NEVER_CONSTRUCTORS: &[&str] = &["__hole", "Never"];
|
||||
pub const ORDERING: &str = "Ordering";
|
||||
pub const ORDERING_CONSTRUCTORS: &[&str] = &["Less", "Equal", "Greater"];
|
||||
pub const PAIR: &str = "Pair";
|
||||
|
@ -297,6 +299,17 @@ impl Type {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn never() -> Rc<Type> {
|
||||
Rc::new(Type::App {
|
||||
public: true,
|
||||
contains_opaque: false,
|
||||
name: NEVER.to_string(),
|
||||
module: "".to_string(),
|
||||
args: vec![],
|
||||
alias: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ordering() -> Rc<Type> {
|
||||
Rc::new(Type::App {
|
||||
public: true,
|
||||
|
|
|
@ -168,6 +168,19 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
|
|||
),
|
||||
);
|
||||
|
||||
// Never
|
||||
prelude.types.insert(
|
||||
well_known::NEVER.to_string(),
|
||||
TypeConstructor::primitive(Type::never()),
|
||||
);
|
||||
prelude.types_constructors.insert(
|
||||
well_known::NEVER.to_string(),
|
||||
ValueConstructor::known_adt(
|
||||
&mut prelude.values,
|
||||
&[(well_known::NEVER_CONSTRUCTORS[1], Type::never())],
|
||||
),
|
||||
);
|
||||
|
||||
// Cardano ScriptContext
|
||||
prelude.types.insert(
|
||||
well_known::SCRIPT_CONTEXT.to_string(),
|
||||
|
@ -1503,6 +1516,15 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDa
|
|||
option_data_type,
|
||||
);
|
||||
|
||||
// Never
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: well_known::NEVER.to_string(),
|
||||
},
|
||||
TypedDataType::never(),
|
||||
);
|
||||
|
||||
// PRNG
|
||||
let prng_data_type = TypedDataType::prng();
|
||||
data_types.insert(
|
||||
|
@ -1609,4 +1631,8 @@ impl TypedDataType {
|
|||
typed_parameters: vec![tipo],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn never() -> Self {
|
||||
DataType::known_enum(well_known::NEVER, well_known::NEVER_CONSTRUCTORS)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1434,7 +1434,9 @@ impl<'a> CodeGenerator<'a> {
|
|||
.unwrap_or_else(|| unreachable!("Failed to find definition for {}", name));
|
||||
|
||||
let then = if props.kind.is_expect()
|
||||
&& (data_type.constructors.len() > 1 || props.full_check)
|
||||
&& (data_type.constructors.len() > 1
|
||||
|| props.full_check
|
||||
|| data_type.is_never())
|
||||
{
|
||||
let (index, _) = data_type
|
||||
.constructors
|
||||
|
@ -1459,8 +1461,8 @@ impl<'a> CodeGenerator<'a> {
|
|||
)
|
||||
} else {
|
||||
assert!(
|
||||
data_type.constructors.len() == 1,
|
||||
"attempted expect on a type with more or less than 1 constructor: \nis_expect? {}\nfull_check? {}\ndata_type={data_type:#?}\n{}",
|
||||
data_type.constructors.len() == 1 || data_type.is_never(),
|
||||
"attempted let-assignment on a type with more or less than 1 constructor: \nis_expect? {}\nfull_check? {}\ndata_type={data_type:#?}\n{}",
|
||||
props.kind.is_expect(),
|
||||
props.full_check,
|
||||
name,
|
||||
|
@ -1978,9 +1980,20 @@ impl<'a> CodeGenerator<'a> {
|
|||
|
||||
let otherwise_delayed = AirTree::local_var("otherwise_delayed", Type::void());
|
||||
|
||||
let is_never = data_type.is_never();
|
||||
|
||||
let constr_clauses = data_type.constructors.iter().enumerate().rfold(
|
||||
otherwise_delayed.clone(),
|
||||
|acc, (index, constr)| {
|
||||
// NOTE: For the Never type, we have an placeholder first constructor
|
||||
// that must be ignored. The Never type is considered to have only one
|
||||
// constructor starting at index 1 so it shouldn't be possible to
|
||||
// cast from Data into the first constructor. There's virtually no
|
||||
// constructor at index 0.
|
||||
if is_never && index == 0 {
|
||||
return acc;
|
||||
}
|
||||
|
||||
let mut constr_args = vec![];
|
||||
|
||||
let constr_then = constr.arguments.iter().enumerate().rfold(
|
||||
|
@ -2169,7 +2182,7 @@ impl<'a> CodeGenerator<'a> {
|
|||
),
|
||||
)
|
||||
} else if let Some(data_type) = data_type {
|
||||
if data_type.constructors.len() > 1 {
|
||||
if data_type.constructors.len() > 1 && !data_type.is_never() {
|
||||
AirTree::clause(
|
||||
&props.original_subject_name,
|
||||
clause_cond,
|
||||
|
@ -5584,8 +5597,18 @@ impl<'a> CodeGenerator<'a> {
|
|||
} => {
|
||||
let tail_name_prefix = "__tail_index";
|
||||
|
||||
let data_type = lookup_data_type_by_tipo(&self.data_types, &tipo)
|
||||
.unwrap_or_else(|| panic!("HOW DID YOU DO THIS ON BOOL OR VOID"));
|
||||
let data_type =
|
||||
lookup_data_type_by_tipo(&self.data_types, &tipo).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Attempted record update on an unknown type!\ntype: {:#?}",
|
||||
tipo
|
||||
)
|
||||
});
|
||||
|
||||
assert!(
|
||||
!data_type.is_never(),
|
||||
"Attempted record update on a Never type.",
|
||||
);
|
||||
|
||||
let constructor_field_count = data_type.constructors[0].arguments.len();
|
||||
let record = arg_stack.pop().unwrap();
|
||||
|
|
|
@ -83,9 +83,11 @@ fn assert_uplc(source_code: &str, expected: Term<Name>, should_fail: bool) {
|
|||
format!("{:#?}", eval.logs())
|
||||
);
|
||||
|
||||
if !should_fail {
|
||||
assert_eq!(eval.result().unwrap(), Term::bool(true));
|
||||
}
|
||||
assert!(if should_fail {
|
||||
eval.failed(false)
|
||||
} else {
|
||||
!eval.failed(false)
|
||||
});
|
||||
}
|
||||
TestType::Validator(func) => {
|
||||
let program = generator.generate(func, &script.1);
|
||||
|
@ -6137,3 +6139,51 @@ fn pattern_bytearray() {
|
|||
|
||||
assert_uplc(src, program, false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cast_never() {
|
||||
let src = r#"
|
||||
test never_ok_cast() {
|
||||
let none: Option<Void> = None
|
||||
let data: Data = none
|
||||
expect _: Never = data
|
||||
}
|
||||
"#;
|
||||
|
||||
let none_or_never = || Term::Constant(Constant::Data(Data::constr(1, vec![])).into());
|
||||
|
||||
let expect_otherwise = Term::Error
|
||||
.delayed_trace(Term::string("expect _: Never = data"))
|
||||
.delay();
|
||||
|
||||
let assert_constr_index = Term::equals_integer()
|
||||
.apply(Term::integer(1.into()))
|
||||
.apply(Term::fst_pair().apply(Term::unconstr_data().apply(none_or_never())));
|
||||
|
||||
let assert_empty_fields = |then: Term<Name>, expect_otherwise: Rc<Name>| {
|
||||
Term::snd_pair()
|
||||
.apply(Term::unconstr_data().apply(none_or_never()))
|
||||
.delay_empty_choose_list(then, Term::Var(expect_otherwise))
|
||||
};
|
||||
|
||||
let program = expect_otherwise.as_var("expect_:Never=data", |expect_otherwise| {
|
||||
let otherwise = Term::Var(expect_otherwise.clone());
|
||||
|
||||
let when_constr = assert_constr_index.delay_true_if_then_else(
|
||||
assert_empty_fields(Term::unit(), expect_otherwise.clone()),
|
||||
Term::Var(expect_otherwise),
|
||||
);
|
||||
|
||||
none_or_never()
|
||||
.choose_data(
|
||||
when_constr.delay(),
|
||||
otherwise.clone(),
|
||||
otherwise.clone(),
|
||||
otherwise.clone(),
|
||||
otherwise,
|
||||
)
|
||||
.force()
|
||||
});
|
||||
|
||||
assert_uplc(src, program, false)
|
||||
}
|
||||
|
|
|
@ -570,20 +570,9 @@ Constr(
|
|||
),
|
||||
Constr(
|
||||
Constr {
|
||||
tag: 121,
|
||||
tag: 122,
|
||||
any_constructor: None,
|
||||
fields: [
|
||||
BigInt(
|
||||
Int(
|
||||
Int(
|
||||
Int {
|
||||
neg: false,
|
||||
val: 3000000,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
fields: [],
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -638,20 +627,9 @@ Constr(
|
|||
),
|
||||
Constr(
|
||||
Constr {
|
||||
tag: 121,
|
||||
tag: 122,
|
||||
any_constructor: None,
|
||||
fields: [
|
||||
BigInt(
|
||||
Int(
|
||||
Int(
|
||||
Int {
|
||||
neg: false,
|
||||
val: 3000000,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
fields: [],
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
@ -52,6 +52,8 @@ struct WithArrayRational<'a, T>(&'a T);
|
|||
|
||||
struct WithPartialCertificates<'a, T>(&'a T);
|
||||
|
||||
struct WithNeverRegistrationDeposit<'a, T>(&'a T);
|
||||
|
||||
pub trait ToPlutusData {
|
||||
fn to_plutus_data(&self) -> PlutusData;
|
||||
}
|
||||
|
@ -203,6 +205,29 @@ impl<'a> ToPlutusData for WithWrappedTransactionId<'a, KeyValuePairs<ScriptPurpo
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ToPlutusData for WithNeverRegistrationDeposit<'a, Vec<Certificate>> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
self.0
|
||||
.iter()
|
||||
.map(WithNeverRegistrationDeposit)
|
||||
.collect::<Vec<_>>()
|
||||
.to_plutus_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToPlutusData for WithNeverRegistrationDeposit<'a, KeyValuePairs<ScriptPurpose, Redeemer>> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![];
|
||||
for (key, value) in self.0.iter() {
|
||||
data_vec.push((
|
||||
WithNeverRegistrationDeposit(key).to_plutus_data(),
|
||||
value.to_plutus_data(),
|
||||
))
|
||||
}
|
||||
PlutusData::Map(KeyValuePairs::Def(data_vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ToPlutusData> ToPlutusData for Option<A> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
|
@ -549,14 +574,16 @@ impl<'a> ToPlutusData for WithPartialCertificates<'a, Certificate> {
|
|||
vec![pool_keyhash.to_plutus_data(), epoch.to_plutus_data()],
|
||||
),
|
||||
|
||||
certificate => unreachable!("unexpected in V1/V2 script context: {certificate:?}"),
|
||||
certificate => {
|
||||
unreachable!("unexpected certificate type in V1/V2 script context: {certificate:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Certificate {
|
||||
impl<'a> ToPlutusData for WithNeverRegistrationDeposit<'a, Certificate> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
match self.0 {
|
||||
Certificate::StakeRegistration(stake_credential) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
|
@ -565,11 +592,11 @@ impl ToPlutusData for Certificate {
|
|||
],
|
||||
),
|
||||
|
||||
Certificate::Reg(stake_credential, deposit) => wrap_multiple_with_constr(
|
||||
Certificate::Reg(stake_credential, _) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
stake_credential.to_plutus_data(),
|
||||
Some(*deposit).to_plutus_data(),
|
||||
None::<PlutusData>.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
|
||||
|
@ -581,11 +608,11 @@ impl ToPlutusData for Certificate {
|
|||
],
|
||||
),
|
||||
|
||||
Certificate::UnReg(stake_credential, deposit) => wrap_multiple_with_constr(
|
||||
Certificate::UnReg(stake_credential, _) => wrap_multiple_with_constr(
|
||||
1,
|
||||
vec![
|
||||
stake_credential.to_plutus_data(),
|
||||
Some(*deposit).to_plutus_data(),
|
||||
None::<PlutusData>.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
|
||||
|
@ -835,32 +862,44 @@ impl ToPlutusData for TxInInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: This is a _small_ abuse of the 'WithWrappedTransactionId'. We know the wrapped
|
||||
// is needed for V1 and V2, and it also appears that for V1 and V2, the certifying
|
||||
// purpose mustn't include the certificate index. So, we also short-circuit it here.
|
||||
impl<'a> ToPlutusData for WithWrappedTransactionId<'a, ScriptPurpose> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self.0 {
|
||||
ScriptPurpose::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()),
|
||||
ScriptPurpose::Spending(out_ref, ()) => {
|
||||
wrap_with_constr(1, WithWrappedTransactionId(out_ref).to_plutus_data())
|
||||
}
|
||||
// NOTE: This is a _small_ abuse of the 'WithWrappedTransactionId'. We know the wrapped
|
||||
// is needed for V1 and V2, and it also appears that for V1 and V2, the certifying
|
||||
// purpose mustn't include the certificate index. So, we also short-circuit it here.
|
||||
ScriptPurpose::Certifying(_, dcert) => wrap_with_constr(3, dcert.to_plutus_data()),
|
||||
otherwise => otherwise.to_plutus_data(),
|
||||
ScriptPurpose::Rewarding(stake_credential) => {
|
||||
wrap_with_constr(2, stake_credential.to_plutus_data())
|
||||
}
|
||||
ScriptPurpose::Certifying(_, dcert) => {
|
||||
wrap_with_constr(3, WithPartialCertificates(dcert).to_plutus_data())
|
||||
}
|
||||
purpose => {
|
||||
unreachable!("unsupported purpose for V1 or V2 script context: {purpose:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for ScriptPurpose {
|
||||
impl<'a> ToPlutusData for WithNeverRegistrationDeposit<'a, ScriptPurpose> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
match self.0 {
|
||||
ScriptPurpose::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()),
|
||||
ScriptPurpose::Spending(out_ref, ()) => wrap_with_constr(1, out_ref.to_plutus_data()),
|
||||
ScriptPurpose::Rewarding(stake_credential) => {
|
||||
wrap_with_constr(2, stake_credential.to_plutus_data())
|
||||
}
|
||||
ScriptPurpose::Certifying(ix, dcert) => {
|
||||
wrap_multiple_with_constr(3, vec![ix.to_plutus_data(), dcert.to_plutus_data()])
|
||||
}
|
||||
ScriptPurpose::Certifying(ix, dcert) => wrap_multiple_with_constr(
|
||||
3,
|
||||
vec![
|
||||
ix.to_plutus_data(),
|
||||
WithNeverRegistrationDeposit(dcert).to_plutus_data(),
|
||||
],
|
||||
),
|
||||
ScriptPurpose::Voting(voter) => {
|
||||
wrap_multiple_with_constr(4, vec![voter.to_plutus_data()])
|
||||
}
|
||||
|
@ -1240,12 +1279,12 @@ impl ToPlutusData for Vote {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ToPlutusData for ScriptInfo<T>
|
||||
impl<'a, T> ToPlutusData for WithNeverRegistrationDeposit<'a, ScriptInfo<T>>
|
||||
where
|
||||
T: ToPlutusData,
|
||||
{
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
match self.0 {
|
||||
ScriptInfo::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()),
|
||||
ScriptInfo::Spending(out_ref, datum) => {
|
||||
wrap_multiple_with_constr(1, vec![out_ref.to_plutus_data(), datum.to_plutus_data()])
|
||||
|
@ -1253,9 +1292,13 @@ where
|
|||
ScriptInfo::Rewarding(stake_credential) => {
|
||||
wrap_with_constr(2, stake_credential.to_plutus_data())
|
||||
}
|
||||
ScriptInfo::Certifying(ix, dcert) => {
|
||||
wrap_multiple_with_constr(3, vec![ix.to_plutus_data(), dcert.to_plutus_data()])
|
||||
}
|
||||
ScriptInfo::Certifying(ix, dcert) => wrap_multiple_with_constr(
|
||||
3,
|
||||
vec![
|
||||
ix.to_plutus_data(),
|
||||
WithNeverRegistrationDeposit(dcert).to_plutus_data(),
|
||||
],
|
||||
),
|
||||
ScriptInfo::Voting(voter) => wrap_multiple_with_constr(4, vec![voter.to_plutus_data()]),
|
||||
ScriptInfo::Proposing(ix, procedure) => {
|
||||
wrap_multiple_with_constr(5, vec![ix.to_plutus_data(), procedure.to_plutus_data()])
|
||||
|
@ -1311,11 +1354,11 @@ impl ToPlutusData for TxInfo {
|
|||
tx_info.outputs.to_plutus_data(),
|
||||
tx_info.fee.to_plutus_data(),
|
||||
tx_info.mint.to_plutus_data(),
|
||||
tx_info.certificates.to_plutus_data(),
|
||||
WithNeverRegistrationDeposit(&tx_info.certificates).to_plutus_data(),
|
||||
tx_info.withdrawals.to_plutus_data(),
|
||||
tx_info.valid_range.to_plutus_data(),
|
||||
tx_info.signatories.to_plutus_data(),
|
||||
tx_info.redeemers.to_plutus_data(),
|
||||
WithNeverRegistrationDeposit(&tx_info.redeemers).to_plutus_data(),
|
||||
tx_info.data.to_plutus_data(),
|
||||
tx_info.id.to_plutus_data(),
|
||||
tx_info.votes.to_plutus_data(),
|
||||
|
@ -1347,7 +1390,7 @@ impl ToPlutusData for ScriptContext {
|
|||
vec![
|
||||
tx_info.to_plutus_data(),
|
||||
redeemer.to_plutus_data(),
|
||||
purpose.to_plutus_data(),
|
||||
WithNeverRegistrationDeposit(purpose).to_plutus_data(),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# This file was generated by Aiken
|
||||
# You typically do not need to edit this file
|
||||
|
||||
requirements = []
|
||||
packages = []
|
||||
|
||||
[etags]
|
|
@ -0,0 +1,9 @@
|
|||
name = "aiken-lang/acceptance_test_110"
|
||||
version = "0.0.0"
|
||||
license = "Apache-2.0"
|
||||
description = "Aiken contracts for project 'aiken-lang/110'"
|
||||
|
||||
[repository]
|
||||
user = "aiken-lang"
|
||||
project = "110"
|
||||
platform = "github"
|
|
@ -0,0 +1,39 @@
|
|||
type Foo {
|
||||
Foo(Int, Never)
|
||||
Bar
|
||||
}
|
||||
|
||||
test never_is_none() {
|
||||
let none: Option<Void> = None
|
||||
|
||||
trace @"Never": Never
|
||||
trace @"None": none
|
||||
|
||||
let data_never: Data = Never
|
||||
let data_none: Data = none
|
||||
|
||||
data_never == data_none
|
||||
}
|
||||
|
||||
test never_pattern_match() {
|
||||
when Foo(14, Never) is {
|
||||
Foo(x, Never) -> x == 14
|
||||
Bar -> False
|
||||
}
|
||||
}
|
||||
|
||||
test never_assignment() {
|
||||
let Never = Never
|
||||
True
|
||||
}
|
||||
|
||||
test never_wrong_cast() fail {
|
||||
let data: Data = Some(42)
|
||||
expect _: Never = data
|
||||
}
|
||||
|
||||
test never_ok_cast() {
|
||||
let none: Option<Void> = None
|
||||
let data: Data = none
|
||||
expect _: Never = data
|
||||
}
|
|
@ -13,4 +13,4 @@ requirements = []
|
|||
source = "github"
|
||||
|
||||
[etags]
|
||||
"aiken-lang/stdlib@v2" = [{ secs_since_epoch = 1724491200, nanos_since_epoch = 427525000 }, "cdbbce58b61deb385e7ea787a2e0fc2dc8fe94db9999e0e6275bc9c70e5796be"]
|
||||
"aiken-lang/stdlib@v2" = [{ secs_since_epoch = 1724760716, nanos_since_epoch = 700202000 }, "cdbbce58b61deb385e7ea787a2e0fc2dc8fe94db9999e0e6275bc9c70e5796be"]
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -71,7 +71,7 @@ validator purposes {
|
|||
Some(
|
||||
RegisterCredential {
|
||||
credential: VerificationKey(only0s),
|
||||
deposit: Some(3_000_000),
|
||||
deposit: None,
|
||||
},
|
||||
) == list.at(certificates, 5)
|
||||
|
||||
|
@ -79,7 +79,7 @@ validator purposes {
|
|||
Some(
|
||||
UnregisterCredential {
|
||||
credential: VerificationKey(only0s),
|
||||
refund: Some(3_000_000),
|
||||
refund: None,
|
||||
},
|
||||
) == list.at(certificates, 6)
|
||||
|
||||
|
|
Loading…
Reference in New Issue