Skip to content
This repository was archived by the owner on Aug 27, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions evm-ds/src/continuations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,13 @@ impl Continuations {
// Sometimes a contract will change the state of another contract
// in this case, we need to find cached state of continuations that
// has now been invalidated by this and update it
pub fn update_states(&mut self, addr: H160, key: H256, value: H256, skip: bool) {

pub fn update_states(&mut self, addr: H160, key: H256, value: H256, skip: bool) {
if skip {
return;
}

// Loop over continuations updating the address if it exists
for (_, continuation) in self.storage.iter_mut() {

if let Some(value_current) = continuation.storages.get_mut(&(addr, key)) {
*value_current = value;
}
Expand Down
8 changes: 7 additions & 1 deletion evm-ds/src/evm_server_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,13 @@ fn build_exit_result(
// Is this call static? if so, we don't want to modify other continuations' state
let storage_proto = storage
.into_iter()
.map(|(k, v)| { continuations.lock().unwrap().update_states(address, k, v, is_static); backend.encode_storage(k, v).into()})
.map(|(k, v)| {
continuations
.lock()
.unwrap()
.update_states(address, k, v, is_static);
backend.encode_storage(k, v).into()
})
.collect();

modify.set_storage(storage_proto);
Expand Down
8 changes: 2 additions & 6 deletions evm-ds/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,8 @@ pub fn get_precompiles() -> BTreeMap<H160, PrecompileFn> {
blake2::blake2 as PrecompileFn,
),
(
H160::from_str("000000000000000000000000000000005a494c51").unwrap(),
scilla_call::scilla_call as PrecompileFn,
),
(
H160::from_str("000000000000000000000000000000005a494c52").unwrap(),
scilla_call::scilla_call_keep_origin as PrecompileFn,
H160::from_str("000000000000000000000000000000005a494c53").unwrap(),
scilla_call::scilla_call_2 as PrecompileFn,
),
(
H160::from_str("000000000000000000000000000000005a494c92").unwrap(),
Expand Down
65 changes: 38 additions & 27 deletions evm-ds/src/precompiles/scilla_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,47 @@ use evm::executor::stack::{PrecompileFailure, PrecompileOutput, PrecompileOutput
use evm::{Context, ExitError};
use std::borrow::Cow;

use crate::precompiles::scilla_common::get_contract_addr_and_name;
use crate::precompiles::scilla_common::parse_invocation_prefix;
use crate::precompiles::scilla_common::substitute_scilla_type_with_sol;
use ethabi::decode;
use ethabi::ethereum_types::Address;
use ethabi::param_type::ParamType;
use ethabi::token::Token;
use hex::ToHex;
use primitive_types::U256;
use serde_json::{json, Value};

// TODO: revisit these consts
const BASE_COST: u64 = 15;
const PER_BYTE_COST: u64 = 3;

pub(crate) fn scilla_call(
pub(crate) fn scilla_call_2(
input: &[u8],
gas_limit: Option<u64>,
_contex: &Context,
backend: &dyn Backend,
_is_static: bool,
) -> Result<(PrecompileOutput, u64), PrecompileFailure> {
scilla_call_common(input, gas_limit, _contex, backend, _is_static, false)
}
let gas_needed = check_gas(input, gas_limit)?;
let (code_address, passed_transition_name, keep_origin) = parse_invocation_prefix(input)?;

pub(crate) fn scilla_call_keep_origin(
input: &[u8],
gas_limit: Option<u64>,
_contex: &Context,
backend: &dyn Backend,
_is_static: bool,
) -> Result<(PrecompileOutput, u64), PrecompileFailure> {
scilla_call_common(input, gas_limit, _contex, backend, _is_static, true)
if keep_origin != U256::from(0) && keep_origin != U256::from(1) {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other(Cow::Borrowed("call mode should be either 0 or 1")),
});
};
let keep_origin = keep_origin == U256::from(1);
scilla_call_common(
input,
backend,
code_address,
&passed_transition_name,
keep_origin,
gas_needed,
)
}

// input should be formed of: scilla_contract_addr, transition_name, arg1, arg2, arg3, ..., argn
fn scilla_call_common(
input: &[u8],
gas_limit: Option<u64>,
_contex: &Context,
backend: &dyn Backend,
_is_static: bool,
keep_origin: bool,
) -> Result<(PrecompileOutput, u64), PrecompileFailure> {
fn check_gas(input: &[u8], gas_limit: Option<u64>) -> Result<u64, PrecompileFailure> {
let gas_needed = match required_gas(input) {
Ok(i) => i,
Err(err) => return Err(PrecompileFailure::Error { exit_status: err }),
Expand All @@ -52,13 +52,24 @@ fn scilla_call_common(
if let Some(gas_limit) = gas_limit {
if gas_limit < gas_needed {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
exit_status: ExitError::Other(Cow::Borrowed(
"Unable to parse scilla contract code",
)),
});
}
}
Ok(gas_needed)
}

let (code_address, passed_transition_name) = get_contract_addr_and_name(input)?;

// input should be formed of: scilla_contract_addr, transition_name, keep_origin, arg1, arg2, arg3, ..., argn
fn scilla_call_common(
input: &[u8],
backend: &dyn Backend,
code_address: Address,
passed_transition_name: &str,
keep_origin: bool,
gas_needed: u64,
) -> Result<(PrecompileOutput, u64), PrecompileFailure> {
let code = backend.code_as_json(code_address);
// If there's no code under given address it doesn't make sense to proceed further - transition call will fail at scilla level later
if code.is_empty() {
Expand All @@ -78,7 +89,7 @@ fn scilla_call_common(
exit_status: ExitError::Other(Cow::Borrowed("Unable to get transitions array from contract json")),
});
};
let mut output_json = build_result_json(input, &passed_transition_name, transitions)?;
let mut output_json = build_result_json(input, passed_transition_name, transitions)?;
output_json["_address"] = Value::String(code_address.encode_hex());
output_json["keep_origin"] = Value::Bool(keep_origin);

Expand All @@ -96,7 +107,7 @@ fn build_result_json(
expected_transition: &str,
transitions: &Vec<Value>,
) -> Result<Value, PrecompileFailure> {
let mut solidity_args = vec![ParamType::Address, ParamType::String];
let mut solidity_args = vec![ParamType::Address, ParamType::String, ParamType::Bool];
let mut scilla_args = vec![];

for transition in transitions {
Expand Down Expand Up @@ -132,7 +143,7 @@ fn build_result_json(
result["_tag"] = Value::String(expected_transition.to_string());

let mut result_arguments = Value::Array(vec![]);
for (scilla_arg, solidity_value) in scilla_args.iter().zip(decoded_values.iter().skip(2)) {
for (scilla_arg, solidity_value) in scilla_args.iter().zip(decoded_values.iter().skip(3)) {
let json_arg: Value = match solidity_value {
Token::Uint(solidity_uint) => {
json!({"vname" : scilla_arg.0, "type" : scilla_arg.1, "value": format!("{}", solidity_uint)})
Expand Down
30 changes: 29 additions & 1 deletion evm-ds/src/precompiles/scilla_common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use ethabi::ethereum_types::Address;
use ethabi::{decode, ParamType, Token};
use ethabi::{decode, ParamType, Token, Uint};
use evm::executor::stack::PrecompileFailure;
use evm::ExitError;
use std::borrow::Cow;
Expand Down Expand Up @@ -51,3 +51,31 @@ pub fn get_contract_addr_and_name(input: &[u8]) -> Result<(Address, String), Pre

Ok((code_address.to_owned(), name.to_owned()))
}

pub fn parse_invocation_prefix(input: &[u8]) -> Result<(Address, String, Uint), PrecompileFailure> {
let partial_types = vec![ParamType::Address, ParamType::String, ParamType::Uint(8)];
let partial_tokens = decode(&partial_types, input);
let Ok(partial_tokens) = partial_tokens else {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other(Cow::Borrowed("Incorrect input")),
});
};

if partial_types.len() < 3 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other(Cow::Borrowed("Incorrect input")),
});
}

let (Token::Address(code_address), Token::String(name), Token::Uint(preserve_sender)) = (&partial_tokens[0], &partial_tokens[1], &partial_tokens[2]) else {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other(Cow::Borrowed("Incorrect input")),
});
};

Ok((
code_address.to_owned(),
name.to_owned(),
preserve_sender.to_owned(),
))
}
1 change: 1 addition & 0 deletions src/libCps/CpsAccountStoreInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct CpsAccountStoreInterface {
virtual void MarkNewLibraryCreated(const Address& address) = 0;
virtual CpsAccountStoreInterface::AccountType GetAccountType(
const Address& address) = 0;
virtual bool isAccountEvmContract(const Address& address) const = 0;
};
} // namespace libCps

Expand Down
43 changes: 23 additions & 20 deletions src/libCps/CpsRunEvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ CpsExecuteResult CpsRunEvm::HandleCallTrap(const evm::EvmResult& result) {
LOG_GENERAL(INFO,
"Saving storage for Address: " << thisContractAddress);
if (!mAccountStore.UpdateStateValue(
thisContractAddress,
DataConversion::StringToCharArray(sit.key()), 0,
DataConversion::StringToCharArray(sit.value()), 0)) {
thisContractAddress,
DataConversion::StringToCharArray(sit.key()), 0,
DataConversion::StringToCharArray(sit.value()), 0)) {
}
}

Expand Down Expand Up @@ -399,7 +399,7 @@ CpsExecuteResult CpsRunEvm::HandlePrecompileTrap(

const auto sender = (jsonData["keep_origin"].isBool() &&
jsonData["keep_origin"].asBool() == true)
? mCpsContext.origSender.hex()
? ProtoToAddress(mProtoArgs.caller()).hex()
: ProtoToAddress(mProtoArgs.address()).hex();

jsonData.removeMember("keep_origin");
Expand Down Expand Up @@ -450,14 +450,16 @@ CpsExecuteResult CpsRunEvm::HandlePrecompileTrap(

const auto destAddress = jsonData["_address"].asString();

ScillaArgs scillaArgs = {.from = ProtoToAddress(mProtoArgs.address()),
.dest = Address{destAddress},
.origin = mCpsContext.origSender,
.value = Amount{},
.calldata = jsonData,
.edge = 0,
.depth = 0,
.gasLimit = remainingGas};
ScillaArgs scillaArgs = {
.from = ProtoToAddress(mProtoArgs.address()),
.dest = Address{destAddress},
.origin = mCpsContext.origSender,
.value = Amount{},
.calldata = jsonData,
.edge = 0,
.depth = 0,
.gasLimit = remainingGas,
.extras = ScillaArgExtras{.scillaReceiverAddress = Address{}}};

auto nextRun = std::make_shared<CpsRunScilla>(
std::move(scillaArgs), mExecutor, mCpsContext, CpsRun::TrapScillaCall);
Expand Down Expand Up @@ -719,14 +721,15 @@ void CpsRunEvm::HandleApply(const evm::EvmResult& result,
// Funds is what we want our contract to become/be modified to.
// Check that the contract funds plus the current funds in our account
// is equal to this value
if(funds != recipientPreFunds + currentContractFunds) {
std::string error =
"Possible zil mint. Funds in destroyed account: " +
currentContractFunds.toWei().convert_to<std::string>() +
", requested: " + (funds - recipientPreFunds).toWei().convert_to<std::string>();

LOG_GENERAL(WARNING, "ERROR IN DESTUCT! " << error);
span.SetError(error);
if (funds != recipientPreFunds + currentContractFunds) {
std::string error =
"Possible zil mint. Funds in destroyed account: " +
currentContractFunds.toWei().convert_to<std::string>() +
", requested: " +
(funds - recipientPreFunds).toWei().convert_to<std::string>();

LOG_GENERAL(WARNING, "ERROR IN DESTUCT! " << error);
span.SetError(error);
}

mAccountStore.TransferBalanceAtomic(accountToRemove, fundsRecipient,
Expand Down
6 changes: 6 additions & 0 deletions src/libCps/CpsRunScilla.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,13 @@ CpsExecuteResult CpsRunScilla::runCall(TransactionReceipt& receipt) {
mAccountStore, mArgs, runnerResult.returnVal, receipt, scillaVersion);

if (!parseCallResults.success) {
// Revert in case of non-recoverable failures
if (parseCallResults.failureType ==
ScillaCallParseResult::NON_RECOVERABLE) {
return {TxnStatus::NOT_PRESENT, false, retScillaVal};
}
// Allow TrapScilla call to fail and let EVM handle errored run accordingly
// (only for recoverable failures)
if (GetType() == CpsRun::TrapScillaCall) {
return {TxnStatus::NOT_PRESENT, true, retScillaVal};
}
Expand Down
6 changes: 6 additions & 0 deletions src/libCps/CpsRunScilla.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ struct ScillaInvokeResult {
std::string returnVal;
};

struct ScillaArgExtras {
using Address = dev::h160;
Address scillaReceiverAddress;
};

struct ScillaArgs {
using Address = dev::h160;
struct CodeData {
Expand All @@ -52,6 +57,7 @@ struct ScillaArgs {
uint32_t edge = 0;
uint32_t depth = 0;
uint64_t gasLimit = 0;
std::optional<ScillaArgExtras> extras = std::nullopt;
};

class CpsRunScilla final : public CpsRun {
Expand Down
41 changes: 34 additions & 7 deletions src/libCps/ScillaHelpersCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ ScillaCallParseResult ScillaHelpersCall::ParseCallContractOutput(
<< r_timer_end(tpStart));
}

return {true, false, {}};
return {true, false};
}

/// parse the output from interpreter for calling and update states
Expand Down Expand Up @@ -195,6 +195,15 @@ ScillaCallParseResult ScillaHelpersCall::ParseCallContractJsonOutput(
return {};
}

// At this point we don't support any named calls from Scilla to EVM
for (const auto &param : msg["params"]) {
if (param.isMember("vname") && param["vname"] == "_EvmCall") {
receipt.AddError(CALL_CONTRACT_FAILED);
return ScillaCallParseResult{
.success = false,
.failureType = ScillaCallParseResult::NON_RECOVERABLE};
}
}
const auto recipient = Address(msg["_recipient"].asString());

// Recipient is contract
Expand All @@ -219,15 +228,32 @@ ScillaCallParseResult ScillaHelpersCall::ParseCallContractJsonOutput(

// ZIL-5165: Don't fail if the recipient is a user account.
{
const CpsAccountStoreInterface::AccountType accountType = acc_store.GetAccountType(recipient);
const CpsAccountStoreInterface::AccountType accountType =
acc_store.GetAccountType(recipient);
LOG_GENERAL(INFO, "Target is accountType " << accountType);
if (accountType == CpsAccountStoreInterface::DoesNotExist ||
accountType == CpsAccountStoreInterface::EOA) {
LOG_GENERAL(INFO, "Target is EOA: processing.");
// Message sent to a non-contract account. Add something to results.entries so that if this
// message attempts to transfer funds, it succeeds.
results.entries.emplace_back(ScillaCallParseResult::SingleResult{
{}, recipient, amount, false});
// Message sent to a non-contract account. Add something to
// results.entries so that if this message attempts to transfer funds,
// it succeeds.
results.entries.emplace_back(
ScillaCallParseResult::SingleResult{{}, recipient, amount, false});
continue;
}
}

if (acc_store.isAccountEvmContract(recipient)) {
// Workaround before we have full interop: treat EVM contracts as EOA
// accounts only if there's receiver_address set to 0x0, otherwise revert
if (!scillaArgs.extras ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has the behaviour that:

  • if you call Scilla from EVM with receiver_address = 0
  • and some contract down the chain sends a message
  • and that message is to an EVM contract
  • and that EVM contract has a handler for the message
  • then we ignore the handler and do nothing.

Which isn't what RFC075 says .. is there a test for this that this code passes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that should be reverted in this case because receiver_address will be missing or (impossible with current impl) be non-zero.

scillaArgs.extras->scillaReceiverAddress != Address{}) {
return ScillaCallParseResult{
.success = false,
.failureType = ScillaCallParseResult::NON_RECOVERABLE};
} else {
results.entries.emplace_back(
ScillaCallParseResult::SingleResult{{}, recipient, amount, false});
continue;
}
}
Expand Down Expand Up @@ -267,7 +293,8 @@ ScillaCallParseResult ScillaHelpersCall::ParseCallContractJsonOutput(
std::move(inputMessage), recipient, amount, isNextContract});
}

LOG_GENERAL(INFO, "Returning success " << results.success << " entries " << results.entries.size());
LOG_GENERAL(INFO, "Returning success " << results.success << " entries "
<< results.entries.size());
return results;
}

Expand Down
Loading