Skip to content

Commit

Permalink
runtime-sdk/modules/evm: Add support for simulating CREATE
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Mar 26, 2024
1 parent da60d8d commit da8c76e
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 80 deletions.
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
176 changes: 111 additions & 65 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;

Check warning on line 322 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L322

Added line #L322 was not covered by tests

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;

Check warning on line 351 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L351

Added line #L351 was not covered by tests

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,44 @@ impl<Cfg: Config> API for Module<Cfg> {
tx_metadata,
) = Self::decode_simulate_call_query(ctx, call)?;

let call_tx = transaction::Transaction {
let (method, body) = match address {
Some(address) => {
// Address is set, this is a simulated `evm.Call`.
(
"evm.Call",

Check warning on line 396 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L396

Added line #L396 was not covered by tests
cbor::to_value(types::Call {
address,

Check warning on line 398 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L398

Added line #L398 was not covered by tests
value,
data: data.clone(),
}),
)
}
None => {

Check warning on line 404 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L404

Added line #L404 was not covered by tests
// Address is not set, this is a simulated `evm.Create`.
(
"evm.Create",

Check warning on line 407 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L407

Added line #L407 was not covered by tests
cbor::to_value(types::Create {
value,
init_code: data.clone(),
}),
)
}
};
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,27 +441,22 @@ 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,
);
let result = match address {
Some(address) => {
// Address is set, this is a simulated call.
Self::evm_call(ctx, caller, address, value, data, false)
}
None => {

Check warning on line 455 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L455

Added line #L455 was not covered by tests
// Address is not set, this is a simulated create.
Self::evm_create(ctx, caller, value, data, false)
}
};

TransactionResult::Rollback(result)
},
Expand All @@ -473,8 +466,62 @@ impl<Cfg: Config> API for Module<Cfg> {
}

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,

Check warning on line 478 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L478

Added line #L478 was not covered by tests
caller,
|exec, gas_limit| {
exec.transact_call(
caller.into(),
address.into(),
value.into(),
data,
gas_limit,

Check warning on line 486 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L486

Added line #L486 was not covered by tests
vec![],
)
},
estimate_gas,

Check warning on line 490 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L490

Added line #L490 was not covered by tests
)
}

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,

Check warning on line 502 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L502

Added line #L502 was not covered by tests
caller,
|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)
}
},
estimate_gas,

Check warning on line 518 in runtime-sdk/modules/evm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

runtime-sdk/modules/evm/src/lib.rs#L518

Added line #L518 was not covered by tests
)
}

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 +531,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

0 comments on commit da8c76e

Please sign in to comment.