Skip to content

Commit f1fef4b

Browse files
authored
feat(evm): save a syscall for most precompile calls (#1679)
Previously, we'd query the remaining gas for every precompile call. Now, we only query the remaining gas when actually making a real call (i.e., only in the "native call" precompile. Unfortunately, we make a syscall to query gas because we don't yet have a way to directly access this information from wasm. Ideally we'd implement filecoin-project/FIPs#845 and avoid this issue entirely, but that's a much larger project. This should save ~14k gas per pre-compile call, which isn't nothing.
1 parent 89ae04c commit f1fef4b

File tree

8 files changed

+19
-38
lines changed

8 files changed

+19
-38
lines changed

actors/evm/src/interpreter/instructions/call.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,7 @@ pub fn call_generic<RT: Runtime>(
172172

173173
let dst: EthAddress = dst.into();
174174
if is_reserved_precompile_address(&dst) {
175-
let context = PrecompileContext {
176-
call_type: kind,
177-
gas_limit: effective_gas_limit(system, gas),
178-
value,
179-
};
175+
let context = PrecompileContext { call_type: kind, gas, value };
180176

181177
if log::log_enabled!(log::Level::Info) {
182178
// log input to the precompile, but make sure we dont log _too_ much.
@@ -204,7 +200,6 @@ pub fn call_generic<RT: Runtime>(
204200
// We provide enough gas for the transfer to succeed in all case.
205201
gas = TRANSFER_GAS_LIMIT;
206202
}
207-
let gas_limit = Some(effective_gas_limit(system, gas));
208203
let params = if input_data.is_empty() {
209204
None
210205
} else {
@@ -225,7 +220,7 @@ pub fn call_generic<RT: Runtime>(
225220
Method::InvokeContract as MethodNum,
226221
params,
227222
value,
228-
gas_limit,
223+
Some(system.call_gas_limit(gas)),
229224
send_flags,
230225
)? {
231226
Ok(resp) => {
@@ -278,7 +273,7 @@ pub fn call_generic<RT: Runtime>(
278273
Method::InvokeContractDelegate as u64,
279274
IpldBlock::serialize_dag_cbor(&params)?,
280275
TokenAmount::from(&value),
281-
Some(effective_gas_limit(system, gas)),
276+
Some(system.call_gas_limit(gas)),
282277
SendFlags::default(),
283278
)
284279
.map_err(|mut ae| ae.take_data())
@@ -334,12 +329,6 @@ pub fn call_generic<RT: Runtime>(
334329
Ok(U256::from(call_result))
335330
}
336331

337-
fn effective_gas_limit<RT: Runtime>(system: &System<RT>, gas: U256) -> u64 {
338-
let gas_rsvp = (63 * system.rt.gas_available()) / 64;
339-
let gas = gas.to_u64_saturating();
340-
std::cmp::min(gas, gas_rsvp)
341-
}
342-
343332
#[cfg(test)]
344333
mod tests {
345334
use crate::evm_unit_test;
@@ -704,7 +693,6 @@ mod tests {
704693
evm_unit_test! {
705694
(rt) {
706695
rt.in_call.replace(true);
707-
rt.expect_gas_available(10_000_000_000);
708696
}
709697
(m) {
710698
// input data

actors/evm/src/interpreter/precompiles/evm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ mod tests {
307307

308308
impl Default for PrecompileContext {
309309
fn default() -> Self {
310-
Self { call_type: CallKind::Call, gas_limit: u64::MAX, value: U256::ZERO }
310+
Self { call_type: CallKind::Call, gas: U256::MAX, value: U256::ZERO }
311311
}
312312
}
313313

actors/evm/src/interpreter/precompiles/fvm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ pub(super) fn call_actor_shared<RT: Runtime>(
181181
method,
182182
params,
183183
TokenAmount::from(&value),
184-
Some(ctx.gas_limit),
184+
Some(system.call_gas_limit(ctx.gas)),
185185
flags,
186186
)?
187187
};

actors/evm/src/interpreter/precompiles/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl From<GroupError> for PrecompileError {
168168
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
169169
pub struct PrecompileContext {
170170
pub call_type: CallKind,
171-
pub gas_limit: u64,
171+
pub gas: U256,
172172
pub value: U256,
173173
}
174174

actors/evm/src/interpreter/system.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@ impl<'r, RT: Runtime> System<'r, RT> {
534534
self.saved_state_root = None;
535535
self.tombstone = Some(crate::current_tombstone(self.rt));
536536
}
537+
538+
/// Return the gas limit for a call given the requested gas limit, ensuring that it's no more than
539+
/// 63/64 of the remaining gas.
540+
pub fn call_gas_limit(&self, gas: U256) -> u64 {
541+
let gas_rsvp = (63 * self.rt.gas_available()) / 64;
542+
let gas = gas.to_u64_saturating();
543+
std::cmp::min(gas, gas_rsvp)
544+
}
537545
}
538546

539547
/// Returns the current transient data lifespan based on the execution environment.

actors/evm/tests/call.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_i
517517

518518
if valid_call_input {
519519
// We only get to the send_generalized if the call params were valid
520+
rt.expect_gas_available(10_000_000_000);
520521
rt.expect_send(
521522
target,
522523
method_num,
@@ -552,7 +553,6 @@ fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_i
552553
expected_exit_code: expected_exit,
553554
precompile_address: util::NativePrecompile::CallActor.eth_address(),
554555
output_size: 32,
555-
gas_avaliable: 10_000_000_000u64,
556556
expected_return: expected_out,
557557
call_op: util::PrecompileCallOpcode::DelegateCall,
558558
input: contract_params,
@@ -591,12 +591,12 @@ fn call_actor_weird_offset() {
591591
expected_exit_code: util::PrecompileExit::Success,
592592
precompile_address: util::NativePrecompile::CallActor.eth_address(),
593593
output_size: 32,
594-
gas_avaliable: 10_000_000_000u64,
595594
expected_return: vec![],
596595
call_op: util::PrecompileCallOpcode::DelegateCall,
597596
input,
598597
};
599598

599+
rt.expect_gas_available(10_000_000_000);
600600
rt.expect_send(
601601
addr,
602602
0,
@@ -640,14 +640,14 @@ fn call_actor_overlapping() {
640640
let mut test = util::PrecompileTest {
641641
precompile_address: util::NativePrecompile::CallActor.eth_address(),
642642
output_size: 32,
643-
gas_avaliable: 10_000_000_000u64,
644643
call_op: util::PrecompileCallOpcode::DelegateCall,
645644
// overwritten in tests
646645
expected_return: vec![],
647646
expected_exit_code: util::PrecompileExit::Success,
648647
input: call_params.clone().into(),
649648
};
650649

650+
rt.expect_gas_available(10_000_000_000);
651651
rt.expect_send(
652652
addr,
653653
0,
@@ -683,14 +683,14 @@ fn call_actor_id_with_full_address() {
683683
let mut test = util::PrecompileTest {
684684
precompile_address: util::NativePrecompile::CallActorId.eth_address(),
685685
output_size: 32,
686-
gas_avaliable: 10_000_000_000u64,
687686
call_op: util::PrecompileCallOpcode::DelegateCall,
688687
// overwritten in tests
689688
expected_return: vec![],
690689
expected_exit_code: util::PrecompileExit::Success,
691690
input: call_params.clone().into(),
692691
};
693692

693+
rt.expect_gas_available(10_000_000_000);
694694
rt.expect_send(
695695
Address::new_id(actual_id_addr),
696696
0,
@@ -722,7 +722,6 @@ fn call_actor_syscall_error() {
722722
let mut test = util::PrecompileTest {
723723
precompile_address: util::NativePrecompile::CallActor.eth_address(),
724724
output_size: 32,
725-
gas_avaliable: 10_000_000_000u64,
726725
call_op: util::PrecompileCallOpcode::DelegateCall,
727726
// overwritten in tests
728727
expected_return: vec![],
@@ -737,6 +736,7 @@ fn call_actor_syscall_error() {
737736
..Default::default()
738737
};
739738

739+
rt.expect_gas_available(10_000_000_000);
740740
rt.expect_send(
741741
addr,
742742
0,
@@ -767,7 +767,6 @@ mod call_actor_invalid {
767767
let mut test = util::PrecompileTest {
768768
precompile_address: util::NativePrecompile::CallActor.eth_address(),
769769
output_size: 32,
770-
gas_avaliable: 10_000_000_000u64,
771770
call_op: util::PrecompileCallOpcode::DelegateCall,
772771
// overwritten in tests
773772
expected_return: vec![],

actors/evm/tests/precompile.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ fn test_precompile_hash() {
5050
// invoke contract
5151
let contract_params = vec![0u8; 32];
5252

53-
rt.expect_gas_available(10_000_000_000u64);
5453
let result = util::invoke_contract(&rt, &contract_params);
5554
let expected =
5655
hex_literal::hex!("ace8597929092c14bd028ede7b07727875788c7e130278b5afed41940d965aba");
@@ -137,7 +136,6 @@ fn test_native_lookup_delegated_address() {
137136
precompile_address: NativePrecompile::LookupDelegatedAddress.eth_address(),
138137
output_size: 32,
139138
expected_exit_code: PrecompileExit::Success,
140-
gas_avaliable: 10_000_000_000,
141139
call_op: util::PrecompileCallOpcode::Call(0),
142140
expected_return: expected,
143141
input: id_to_vec(&id),
@@ -185,7 +183,6 @@ fn test_resolve_delegated() {
185183
rt.add_id_address(bls, bls_target);
186184

187185
fn test_resolve(rt: &MockRuntime, addr: FILAddress, expected: Vec<u8>) {
188-
rt.expect_gas_available(10_000_000_000u64);
189186
let input = addr.to_bytes();
190187
let result = util::invoke_contract(rt, &input);
191188
rt.verify();
@@ -202,14 +199,12 @@ fn test_resolve_delegated() {
202199
test_resolve(&rt, unbound_del, vec![]);
203200

204201
// invalid first param fails
205-
rt.expect_gas_available(10_000_000_000u64);
206202
let result = util::invoke_contract(&rt, &[0xff; 1]);
207203
rt.verify();
208204
assert_eq!(&[0u8], result.as_slice());
209205
rt.reset();
210206

211207
// invalid second param fails
212-
rt.expect_gas_available(10_000_000_000u64);
213208
let input = {
214209
// first word is len
215210
let mut v = U256::from(5).to_bytes().to_vec();
@@ -237,7 +232,6 @@ fn test_precompile_randomness() {
237232
precompile_address: NativePrecompile::GetRandomness.eth_address(),
238233
output_size: 32,
239234
expected_exit_code: PrecompileExit::Success,
240-
gas_avaliable: 10_000_000_000,
241235
call_op: util::PrecompileCallOpcode::StaticCall,
242236
input: rand_epoch_u256.to_bytes().to_vec(),
243237
expected_return: result.to_vec(),
@@ -251,7 +245,6 @@ fn test_precompile_randomness() {
251245
precompile_address: NativePrecompile::GetRandomness.eth_address(),
252246
output_size: 32,
253247
expected_exit_code: PrecompileExit::Reverted, // Precompile reverts due to syscall failure
254-
gas_avaliable: 10_000_000_000,
255248
call_op: util::PrecompileCallOpcode::StaticCall,
256249
input: rand_epoch_u256.to_bytes().to_vec(),
257250
expected_return: vec![],
@@ -275,7 +268,6 @@ fn test_precompile_transfer() {
275268
precompile_address: addr,
276269
output_size: 32,
277270
expected_exit_code: PrecompileExit::Success,
278-
gas_avaliable: 10_000_000_000,
279271
call_op: util::PrecompileCallOpcode::Call(1),
280272
input: vec![0xff; 32],
281273
expected_return: vec![],
@@ -308,7 +300,6 @@ fn test_precompile_transfer_nothing() {
308300
precompile_address: addr,
309301
output_size: 32,
310302
expected_exit_code: PrecompileExit::Success,
311-
gas_avaliable: 10_000_000_000,
312303
call_op: util::PrecompileCallOpcode::Call(0),
313304
input: vec![0xff; 32],
314305
expected_return: vec![],
@@ -324,14 +315,12 @@ fn test_precompile_failure() {
324315
let rt = util::construct_and_verify(bytecode);
325316

326317
// invalid input fails
327-
rt.expect_gas_available(10_000_000_000u64);
328318
let result = util::invoke_contract(&rt, &[0xff; 32]);
329319
rt.verify();
330320
assert_eq!(&[0u8], result.as_slice());
331321
rt.reset();
332322

333323
// not found succeeds with empty
334-
rt.expect_gas_available(10_000_000_000u64);
335324
let input = FILAddress::new_delegated(111, b"foo").unwrap().to_bytes();
336325
let result = util::invoke_contract(&rt, &input);
337326
rt.verify();

actors/evm/tests/util.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ pub struct PrecompileTest {
187187
pub expected_exit_code: PrecompileExit,
188188
pub precompile_address: EthAddress,
189189
pub output_size: u32,
190-
pub gas_avaliable: u64,
191190
pub call_op: PrecompileCallOpcode,
192191
pub input: Vec<u8>,
193192
pub expected_return: Vec<u8>,
@@ -202,15 +201,13 @@ impl Debug for PrecompileTest {
202201
.field("output_size", &self.output_size)
203202
.field("input", &hex::encode(&self.input))
204203
.field("expected_output", &hex::encode(&self.expected_return))
205-
.field("gas_avaliable", &self.gas_avaliable)
206204
.finish()
207205
}
208206
}
209207

210208
impl PrecompileTest {
211209
#[allow(dead_code)]
212210
pub fn run_test(&self, rt: &MockRuntime) {
213-
rt.expect_gas_available(self.gas_avaliable);
214211
log::trace!("{:#?}", &self);
215212
// first byte is precompile number, second is output buffer size, rest is input to precompile
216213
let result = invoke_contract(

0 commit comments

Comments
 (0)