fn emit_runtime_throw_with_message(instructions: &mut Vec<Instruction>, message: &str) {
instructions.push(Instruction::PushLiteral(LiteralValue::String(
message.as_bytes().to_vec(),
)));
instructions.push(Instruction::Throw);
}
fn try_lower_runtime_member_builtin(
base: &Identifier,
member: &Identifier,
args: &[Expression],
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if base.name != "Runtime" {
return None;
}
match member.name.as_str() {
"initializeServices" => {
if !args.is_empty() {
ctx.record_error(format!(
"Runtime.initializeServices requires 0 argument(s), got {}",
args.len()
));
return Some(false);
}
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
Some(true)
}
"notifyIndexed" => {
if args.len() != 3 {
ctx.record_error(format!(
"Runtime.notifyIndexed requires 3 argument(s), got {}",
args.len()
));
return Some(false);
}
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
let payload_args = [args[1].clone(), args[2].clone()];
if !lower_neo_serialized_arg_array(&payload_args, ctx, instructions) {
return Some(false);
}
validate_runtime_notify_call(
&[
args[0].clone(),
Expression::FunctionCall(
Default::default(),
Box::new(Expression::MemberAccess(
Default::default(),
Box::new(Expression::Variable(Identifier {
loc: Default::default(),
name: "abi".to_string(),
})),
Identifier {
loc: Default::default(),
name: "encode".to_string(),
},
)),
vec![args[1].clone(), args[2].clone()],
),
],
ctx,
);
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeNotify,
arg_count: 2,
});
Some(true)
}
"notify" => {
if args.len() != 2 {
ctx.record_error(format!(
"Runtime.notify requires 2 argument(s), got {}",
args.len()
));
return Some(false);
}
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
if let Some(encoded_args) = extract_abi_encode_args(&args[1]) {
if !lower_neo_serialized_arg_array(encoded_args, ctx, instructions) {
return Some(false);
}
validate_runtime_notify_call(args, ctx);
} else if !lower_expression(&args[1], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeNotify,
arg_count: 2,
});
Some(true)
}
"requireWitness" => {
if args.len() != 1 {
ctx.record_error(format!(
"Runtime.requireWitness requires 1 argument(s), got {}",
args.len()
));
return Some(false);
}
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeCheckWitness,
arg_count: 1,
});
let fail_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::JumpIf { target: fail_label });
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(fail_label));
emit_runtime_throw_with_message(instructions, "Runtime: invalid witness");
instructions.push(Instruction::Label(end_label));
Some(false)
}
"checkAnyWitness" => {
if args.len() != 1 {
ctx.record_error(format!(
"Runtime.checkAnyWitness requires 1 argument(s), got {}",
args.len()
));
return Some(false);
}
let tmp_id = ctx.next_label();
let accounts_slot = ctx.allocate_local(
format!("__runtime_check_any_accounts_{tmp_id}"),
Some(ValueType::Any),
);
let index_slot = ctx.allocate_local(
format!("__runtime_check_any_index_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::StoreLocal(accounts_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::StoreLocal(index_slot));
let loop_label = ctx.next_label();
let advance_label = ctx.next_label();
let done_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::Label(loop_label));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::LoadLocal(accounts_slot));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
instructions.push(Instruction::LoadLocal(accounts_slot));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeCheckWitness,
arg_count: 1,
});
instructions.push(Instruction::JumpIf {
target: advance_label,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(advance_label));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(index_slot));
instructions.push(Instruction::Jump { target: loop_label });
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
instructions.push(Instruction::Label(end_label));
Some(true)
}
"checkAllWitnesses" => {
if args.len() != 1 {
ctx.record_error(format!(
"Runtime.checkAllWitnesses requires 1 argument(s), got {}",
args.len()
));
return Some(false);
}
let tmp_id = ctx.next_label();
let accounts_slot = ctx.allocate_local(
format!("__runtime_check_all_accounts_{tmp_id}"),
Some(ValueType::Any),
);
let index_slot = ctx.allocate_local(
format!("__runtime_check_all_index_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::StoreLocal(accounts_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::StoreLocal(index_slot));
let loop_label = ctx.next_label();
let fail_label = ctx.next_label();
let done_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::Label(loop_label));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::LoadLocal(accounts_slot));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
instructions.push(Instruction::LoadLocal(accounts_slot));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeCheckWitness,
arg_count: 1,
});
instructions.push(Instruction::JumpIf { target: fail_label });
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(index_slot));
instructions.push(Instruction::Jump { target: loop_label });
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(fail_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
instructions.push(Instruction::Label(end_label));
Some(true)
}
"checkMultiSigWitness" => {
if args.len() != 2 {
ctx.record_error(format!(
"Runtime.checkMultiSigWitness requires 2 argument(s), got {}",
args.len()
));
return Some(false);
}
let tmp_id = ctx.next_label();
let signers_slot = ctx.allocate_local(
format!("__runtime_check_multisig_signers_{tmp_id}"),
Some(ValueType::Any),
);
let threshold_slot = ctx.allocate_local(
format!("__runtime_check_multisig_threshold_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
let valid_slot = ctx.allocate_local(
format!("__runtime_check_multisig_valid_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
let index_slot = ctx.allocate_local(
format!("__runtime_check_multisig_index_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
let signer_slot = ctx.allocate_local(
format!("__runtime_check_multisig_signer_{tmp_id}"),
Some(ValueType::Any),
);
let inner_index_slot = ctx.allocate_local(
format!("__runtime_check_multisig_inner_index_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
if !lower_expression(&args[0], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::StoreLocal(signers_slot));
if !lower_expression(&args[1], ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::StoreLocal(threshold_slot));
let threshold_positive_fail_label = ctx.next_label();
let threshold_positive_ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(threshold_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf {
target: threshold_positive_fail_label,
});
instructions.push(Instruction::Jump {
target: threshold_positive_ok_label,
});
instructions.push(Instruction::Label(threshold_positive_fail_label));
emit_runtime_throw_with_message(instructions, "Runtime: threshold must be positive");
instructions.push(Instruction::Label(threshold_positive_ok_label));
let threshold_within_len_fail_label = ctx.next_label();
let threshold_within_len_ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(threshold_slot));
instructions.push(Instruction::LoadLocal(signers_slot));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::BinaryOp(BinaryOperator::Le));
instructions.push(Instruction::JumpIf {
target: threshold_within_len_fail_label,
});
instructions.push(Instruction::Jump {
target: threshold_within_len_ok_label,
});
instructions.push(Instruction::Label(threshold_within_len_fail_label));
emit_runtime_throw_with_message(instructions, "Runtime: threshold exceeds signers");
instructions.push(Instruction::Label(threshold_within_len_ok_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::StoreLocal(valid_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::StoreLocal(index_slot));
let loop_label = ctx.next_label();
let advance_label = ctx.next_label();
let done_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::Label(loop_label));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::LoadLocal(signers_slot));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
instructions.push(Instruction::LoadLocal(signers_slot));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::StoreLocal(signer_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::StoreLocal(inner_index_slot));
let duplicate_scan_loop_label = ctx.next_label();
let duplicate_scan_done_label = ctx.next_label();
let duplicate_scan_continue_label = ctx.next_label();
instructions.push(Instruction::Label(duplicate_scan_loop_label));
instructions.push(Instruction::LoadLocal(inner_index_slot));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf {
target: duplicate_scan_done_label,
});
instructions.push(Instruction::LoadLocal(signers_slot));
instructions.push(Instruction::LoadLocal(inner_index_slot));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::LoadLocal(signer_slot));
instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
instructions.push(Instruction::JumpIf {
target: duplicate_scan_continue_label,
});
emit_runtime_throw_with_message(instructions, "Runtime: duplicate signer");
instructions.push(Instruction::Label(duplicate_scan_continue_label));
instructions.push(Instruction::LoadLocal(inner_index_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(inner_index_slot));
instructions.push(Instruction::Jump {
target: duplicate_scan_loop_label,
});
instructions.push(Instruction::Label(duplicate_scan_done_label));
instructions.push(Instruction::LoadLocal(signer_slot));
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::RuntimeCheckWitness,
arg_count: 1,
});
instructions.push(Instruction::JumpIf {
target: advance_label,
});
instructions.push(Instruction::LoadLocal(valid_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(valid_slot));
instructions.push(Instruction::LoadLocal(valid_slot));
instructions.push(Instruction::LoadLocal(threshold_slot));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf {
target: advance_label,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(advance_label));
instructions.push(Instruction::LoadLocal(index_slot));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(index_slot));
instructions.push(Instruction::Jump { target: loop_label });
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
instructions.push(Instruction::Label(end_label));
Some(true)
}
_ => None,
}
}