Skip to content
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
6 changes: 6 additions & 0 deletions client-sdk/go/modules/evm/signed_calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math/big"

ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
Expand Down Expand Up @@ -52,6 +53,11 @@ func NewSignedCallDataPack(signer RSVSigner, chainID uint64, caller, callee []by
}

func makeSignableCall(chainID uint64, caller, callee []byte, gasLimit uint64, gasPrice *big.Int, value *big.Int, data []byte, leash Leash) apitypes.TypedData {
if callee == nil {
var zeroAddress ethCommon.Address
callee = zeroAddress.Bytes()
}

if value == nil {
value = big.NewInt(0)
}
Expand Down
2 changes: 1 addition & 1 deletion client-sdk/go/modules/evm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type SimulateCallQuery struct {
GasPrice []byte `json:"gas_price"`
GasLimit uint64 `json:"gas_limit"`
Caller []byte `json:"caller"`
Address []byte `json:"address"`
Address []byte `json:"address,omitempty"`
Value []byte `json:"value"`
Data []byte `json:"data"`
}
Expand Down
175 changes: 106 additions & 69 deletions runtime-sdk/modules/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,27 +316,12 @@ impl<Cfg: Config> API for Module<Cfg> {
Self::decode_call_data(ctx, init_code, tx_call_format, tx_index, true)?
.expect("processing always proceeds");

Self::do_evm(
caller,
ctx,
|exec, gas_limit| {
let address = exec.create_address(evm::CreateScheme::Legacy {
caller: caller.into(),
});
let (exit_reason, exit_value) =
exec.transact_create(caller.into(), value.into(), init_code, gas_limit, vec![]);
if exit_reason.is_succeed() {
// If successful return the contract deployed address.
(exit_reason, address.as_bytes().to_vec())
} else {
// Otherwise propagate the exit value.
(exit_reason, exit_value)
}
},
// If in simulation, this must be EstimateGas query.
// Use estimate mode if not doing binary search for exact gas costs.
is_simulation && <C::Runtime as Runtime>::Core::estimate_gas_search_max_iters(ctx) == 0,
)
// If in simulation, this must be EstimateGas query.
// Use estimate mode if not doing binary search for exact gas costs.
let estimate_gas =
is_simulation && <C::Runtime as Runtime>::Core::estimate_gas_search_max_iters(ctx) == 0;

Self::evm_create(ctx, caller, value, init_code, estimate_gas)
}

fn call<C: Context>(
Expand All @@ -360,23 +345,12 @@ impl<Cfg: Config> API for Module<Cfg> {
Self::decode_call_data(ctx, data, tx_call_format, tx_index, true)?
.expect("processing always proceeds");

let evm_result = Self::do_evm(
caller,
ctx,
|exec, gas_limit| {
exec.transact_call(
caller.into(),
address.into(),
value.into(),
data,
gas_limit,
vec![],
)
},
// If in simulation, this must be EstimateGas query.
// Use estimate mode if not doing binary search for exact gas costs.
is_simulation && <C::Runtime as Runtime>::Core::estimate_gas_search_max_iters(ctx) == 0,
);
// If in simulation, this must be EstimateGas query.
// Use estimate mode if not doing binary search for exact gas costs.
let estimate_gas =
is_simulation && <C::Runtime as Runtime>::Core::estimate_gas_search_max_iters(ctx) == 0;

let evm_result = Self::evm_call(ctx, caller, address, value, data, estimate_gas);
Self::encode_evm_result(ctx, evm_result, tx_metadata)
}

Expand Down Expand Up @@ -415,20 +389,46 @@ impl<Cfg: Config> API for Module<Cfg> {
tx_metadata,
) = Self::decode_simulate_call_query(ctx, call)?;

let call_tx = transaction::Transaction {
let (method, body, exec): (_, _, Box<dyn FnOnce() -> Result<_, _>>) = match address {
Some(address) => {
// Address is set, this is a simulated `evm.Call`.
(
"evm.Call",
cbor::to_value(types::Call {
address,
value,
data: data.clone(),
}),
Box::new(move || Self::evm_call(ctx, caller, address, value, data, false)),
)
}
None => {
// Address is not set, this is a simulated `evm.Create`.
(
"evm.Create",
cbor::to_value(types::Create {
value,
init_code: data.clone(),
}),
Box::new(|| Self::evm_create(ctx, caller, value, data, false)),
)
}
};
let tx = transaction::Transaction {
version: 1,
call: transaction::Call {
format: transaction::CallFormat::Plain,
method: "evm.Call".to_owned(),
body: cbor::to_value(types::Call {
address,
value,
data: data.clone(),
}),
method: method.to_owned(),
body,
..Default::default()
},
auth_info: transaction::AuthInfo {
signer_info: vec![],
signer_info: vec![transaction::SignerInfo {
address_spec: transaction::AddressSpec::Internal(
transaction::CallerAddress::EthAddress(caller.into()),
),
nonce: 0,
}],
fee: transaction::Fee {
amount: token::BaseUnits::new(
gas_price
Expand All @@ -443,38 +443,76 @@ impl<Cfg: Config> API for Module<Cfg> {
..Default::default()
},
};

let evm_result = CurrentState::with_transaction_opts(
Options::new()
.with_tx(TransactionWithMeta::internal(call_tx))
.with_tx(TransactionWithMeta::internal(tx))
.with_mode(Mode::Simulate),
|| {
let result = Self::do_evm(
caller,
ctx,
|exec, gas_limit| {
exec.transact_call(
caller.into(),
address.into(),
value.into(),
data,
gas_limit,
vec![],
)
},
// Simulate call is never called from EstimateGas.
false,
);

TransactionResult::Rollback(result)
},
|| TransactionResult::Rollback(exec()),
);
Self::encode_evm_result(ctx, evm_result, tx_metadata)
}
}

impl<Cfg: Config> Module<Cfg> {
fn do_evm<C, F>(source: H160, ctx: &C, f: F, estimate_gas: bool) -> Result<Vec<u8>, Error>
fn evm_call<C: Context>(
ctx: &C,
caller: H160,
address: H160,
value: U256,
data: Vec<u8>,
estimate_gas: bool,
) -> Result<Vec<u8>, Error> {
Self::evm_execute(
ctx,
caller,
|exec, gas_limit| {
exec.transact_call(
caller.into(),
address.into(),
value.into(),
data,
gas_limit,
vec![],
)
},
estimate_gas,
)
}

fn evm_create<C: Context>(
ctx: &C,
caller: H160,
value: U256,
init_code: Vec<u8>,
estimate_gas: bool,
) -> Result<Vec<u8>, Error> {
Self::evm_execute(
ctx,
caller,
|exec, gas_limit| {
// Precompute the address as execution can modify state such that the subsequent
// address derivation would be incorrect (e.g. due to nonce increments).
let address = exec.create_address(evm::CreateScheme::Legacy {
Comment thread
kostko marked this conversation as resolved.
caller: caller.into(),
});
let (exit_reason, exit_value) =
exec.transact_create(caller.into(), value.into(), init_code, gas_limit, vec![]);
if exit_reason.is_succeed() {
// If successful return the contract deployed address.
(exit_reason, address.as_bytes().to_vec())
} else {
// Otherwise propagate the exit value.
(exit_reason, exit_value)
}
},
estimate_gas,
)
}

fn evm_execute<C, F>(ctx: &C, source: H160, f: F, estimate_gas: bool) -> Result<Vec<u8>, Error>
where
C: Context,
F: FnOnce(
&mut StackExecutor<
'static,
Expand All @@ -484,7 +522,6 @@ impl<Cfg: Config> Module<Cfg> {
>,
u64,
) -> (evm::ExitReason, Vec<u8>),
C: Context,
{
let is_query = CurrentState::with_env(|env| !env.is_execute());
let cfg = Cfg::evm_config(estimate_gas);
Expand Down
43 changes: 39 additions & 4 deletions runtime-sdk/modules/evm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl EvmSigner {
}

/// Dispatch a query to the given EVM contract method.
pub fn query_evm<C>(
pub fn query_evm_call<C>(
&self,
ctx: &C,
address: H160,
Expand All @@ -89,11 +89,11 @@ impl EvmSigner {
where
C: Context,
{
self.query_evm_opts(ctx, address, name, param_types, params, Default::default())
self.query_evm_call_opts(ctx, address, name, param_types, params, Default::default())
}

/// Dispatch a query to the given EVM contract method.
pub fn query_evm_opts<C>(
pub fn query_evm_call_opts<C>(
&self,
ctx: &C,
address: H160,
Expand All @@ -105,12 +105,47 @@ impl EvmSigner {
where
C: Context,
{
let mut data = [
let data = [
ethabi::short_signature(name, param_types).to_vec(),
ethabi::encode(params),
]
.concat();

self.query_evm_opts(ctx, Some(address), data, opts)
}

/// Dispatch a query to simulate EVM contract creation.
pub fn query_evm_create<C>(&self, ctx: &C, init_code: Vec<u8>) -> Result<Vec<u8>, RuntimeError>
where
C: Context,
{
self.query_evm_opts(ctx, None, init_code, Default::default())
}

/// Dispatch a query to simulate EVM contract creation.
pub fn query_evm_create_opts<C>(
&self,
ctx: &C,
init_code: Vec<u8>,
opts: QueryOptions,
) -> Result<Vec<u8>, RuntimeError>
where
C: Context,
{
self.query_evm_opts(ctx, None, init_code, opts)
}

/// Dispatch a query to the EVM.
pub fn query_evm_opts<C>(
&self,
ctx: &C,
address: Option<H160>,
mut data: Vec<u8>,
opts: QueryOptions,
) -> Result<Vec<u8>, RuntimeError>
where
C: Context,
{
// Handle optional encryption.
let client_keypair = deoxysii::generate_key_pair();
if opts.encrypt {
Expand Down
10 changes: 6 additions & 4 deletions runtime-sdk/modules/evm/src/signed_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fn hash_call(query: &SimulateCallQuery, leash: &Leash) -> [u8; 32] {
hash_encoded(&[
encode_bytes(CALL_TYPE_STR),
Token::Address(query.caller.0.into()),
Token::Address(query.address.0.into()),
Token::Address(query.address.unwrap_or_default().0.into()),
Token::Uint(query.gas_limit.into()),
Token::Uint(ethabi::ethereum_types::U256(query.gas_price.0)),
Token::Uint(ethabi::ethereum_types::U256(query.value.0)),
Expand Down Expand Up @@ -180,9 +180,11 @@ mod test {
caller: "0x11e244400Cf165ade687077984F09c3A037b868F"
.parse()
.unwrap(),
address: "0xb5ed90452AAC09f294a0BE877CBf2Dc4D55e096f"
.parse()
.unwrap(),
address: Some(
"0xb5ed90452AAC09f294a0BE877CBf2Dc4D55e096f"
.parse()
.unwrap(),
),
value: 42u64.into(),
data: cbor::from_value(data_pack.data.body.clone()).unwrap(),
},
Expand Down
Loading