Skip to content

Commit c6bee14

Browse files
committed
Add permit flashtestations tx calls from builder
1 parent 440183b commit c6bee14

File tree

4 files changed

+325
-1
lines changed

4 files changed

+325
-1
lines changed

crates/op-rbuilder/src/flashtestations/args.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ pub struct FlashtestationsArgs {
8383
default_value = "1"
8484
)]
8585
pub builder_proof_version: u8,
86+
87+
/// Use permit for the flashtestation builder tx
88+
#[arg(
89+
long = "flashtestations.use-permit",
90+
env = "FLASHTESTATIONS_USE_PERMIT",
91+
default_value = "false"
92+
)]
93+
pub flashtestations_use_permit: bool,
8694
}
8795

8896
impl Default for FlashtestationsArgs {

crates/op-rbuilder/src/flashtestations/builder_tx.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ where
136136
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());
137137
evm.modify_cfg(|cfg| {
138138
cfg.disable_balance_check = true;
139+
cfg.disable_nonce_check = true;
139140
});
140141
let calldata = IFlashtestationRegistry::getRegistrationStatusCall {
141142
teeAddress: self.tee_service_signer.address,
@@ -357,6 +358,207 @@ where
357358
))
358359
}
359360
}
361+
362+
fn get_permit_nonce(
363+
&self,
364+
contract_address: Address,
365+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
366+
evm: &mut OpEvm<
367+
&mut State<StateProviderDatabase<impl StateProvider>>,
368+
NoOpInspector,
369+
PrecompilesMap,
370+
>,
371+
) -> Result<U256, BuilderTransactionError> {
372+
let calldata = IERC20Permit::noncesCall {
373+
owner: self.tee_service_signer.address,
374+
}
375+
.abi_encode();
376+
let SimulationSuccessResult { output, .. } =
377+
self.simulate_call(contract_address, calldata.into(), None, ctx, evm)?;
378+
U256::abi_decode(&output)
379+
.map_err(|_| BuilderTransactionError::InvalidContract(contract_address))
380+
}
381+
382+
fn registration_permit_signature(
383+
&self,
384+
permit_nonce: U256,
385+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
386+
evm: &mut OpEvm<
387+
&mut State<StateProviderDatabase<impl StateProvider>>,
388+
NoOpInspector,
389+
PrecompilesMap,
390+
>,
391+
) -> Result<Signature, BuilderTransactionError> {
392+
let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall {
393+
rawQuote: self.attestation.clone().into(),
394+
extendedRegistrationData: self.extra_registration_data.clone(),
395+
nonce: permit_nonce,
396+
deadline: U256::from(ctx.timestamp()),
397+
}
398+
.abi_encode();
399+
let SimulationSuccessResult { output, .. } = self.simulate_call(
400+
self.registry_address,
401+
struct_hash_calldata.into(),
402+
None,
403+
ctx,
404+
evm,
405+
)?;
406+
let struct_hash = B256::abi_decode(&output)
407+
.map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?;
408+
let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call {
409+
structHash: struct_hash,
410+
}
411+
.abi_encode();
412+
let SimulationSuccessResult { output, .. } = self.simulate_call(
413+
self.registry_address,
414+
typed_data_hash_calldata.into(),
415+
None,
416+
ctx,
417+
evm,
418+
)?;
419+
let typed_data_hash = B256::abi_decode(&output)
420+
.map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?;
421+
let signature = self.tee_service_signer.sign_message(typed_data_hash)?;
422+
Ok(signature)
423+
}
424+
425+
fn signed_registration_permit_tx(
426+
&self,
427+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
428+
evm: &mut OpEvm<
429+
&mut State<StateProviderDatabase<impl StateProvider>>,
430+
NoOpInspector,
431+
PrecompilesMap,
432+
>,
433+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
434+
let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?;
435+
let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?;
436+
let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall {
437+
rawQuote: self.attestation.clone().into(),
438+
extendedRegistrationData: self.extra_registration_data.clone(),
439+
nonce: permit_nonce,
440+
deadline: U256::from(ctx.timestamp()),
441+
signature: signature.as_bytes().into(),
442+
}
443+
.abi_encode();
444+
let SimulationSuccessResult { gas_used, .. } = self.simulate_call(
445+
self.registry_address,
446+
calldata.clone().into(),
447+
Some(TEEServiceRegistered::SIGNATURE_HASH),
448+
ctx,
449+
evm,
450+
)?;
451+
let signed_tx = self.sign_tx(
452+
self.registry_address,
453+
self.builder_key,
454+
gas_used,
455+
calldata.into(),
456+
ctx,
457+
evm.db_mut(),
458+
)?;
459+
let da_size =
460+
op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
461+
Ok(BuilderTransactionCtx {
462+
gas_used,
463+
da_size,
464+
signed_tx,
465+
is_top_of_block: false,
466+
})
467+
}
468+
469+
fn block_proof_permit_signature(
470+
&self,
471+
permit_nonce: U256,
472+
block_content_hash: B256,
473+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
474+
evm: &mut OpEvm<
475+
&mut State<StateProviderDatabase<impl StateProvider>>,
476+
NoOpInspector,
477+
PrecompilesMap,
478+
>,
479+
) -> Result<Signature, BuilderTransactionError> {
480+
let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall {
481+
version: self.builder_proof_version,
482+
blockContentHash: block_content_hash,
483+
nonce: permit_nonce,
484+
}
485+
.abi_encode();
486+
let SimulationSuccessResult { output, .. } = self.simulate_call(
487+
self.builder_policy_address,
488+
struct_hash_calldata.into(),
489+
None,
490+
ctx,
491+
evm,
492+
)?;
493+
let struct_hash = B256::abi_decode(&output)
494+
.map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?;
495+
let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call {
496+
structHash: struct_hash,
497+
}
498+
.abi_encode();
499+
let SimulationSuccessResult { output, .. } = self.simulate_call(
500+
self.builder_policy_address,
501+
typed_data_hash_calldata.into(),
502+
None,
503+
ctx,
504+
evm,
505+
)?;
506+
let typed_data_hash = B256::abi_decode(&output)
507+
.map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?;
508+
let signature = self.tee_service_signer.sign_message(typed_data_hash)?;
509+
Ok(signature)
510+
}
511+
512+
fn signed_block_proof_permit_tx(
513+
&self,
514+
transactions: Vec<OpTransactionSigned>,
515+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
516+
evm: &mut OpEvm<
517+
&mut State<StateProviderDatabase<impl StateProvider>>,
518+
NoOpInspector,
519+
PrecompilesMap,
520+
>,
521+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
522+
let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?;
523+
let block_content_hash = Self::compute_block_content_hash(
524+
transactions.clone(),
525+
ctx.parent_hash(),
526+
ctx.block_number(),
527+
ctx.timestamp(),
528+
);
529+
let signature =
530+
self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?;
531+
let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall {
532+
blockContentHash: block_content_hash,
533+
nonce: U256::from(permit_nonce),
534+
version: self.builder_proof_version,
535+
eip712Sig: signature.as_bytes().into(),
536+
}
537+
.abi_encode();
538+
let SimulationSuccessResult { gas_used, .. } = self.simulate_call(
539+
self.builder_policy_address,
540+
calldata.clone().into(),
541+
Some(BlockBuilderProofVerified::SIGNATURE_HASH),
542+
ctx,
543+
evm,
544+
)?;
545+
let signed_tx = self.sign_tx(
546+
self.builder_policy_address,
547+
self.builder_key,
548+
gas_used,
549+
calldata.into(),
550+
ctx,
551+
evm.db_mut(),
552+
)?;
553+
let da_size =
554+
op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
555+
Ok(BuilderTransactionCtx {
556+
gas_used,
557+
da_size,
558+
signed_tx,
559+
is_top_of_block: false,
560+
})
561+
}
360562
}
361563

362564
impl<ExtraCtx, Extra> BuilderTransactions<ExtraCtx, Extra>

crates/op-rbuilder/src/flashtestations/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ sol!(
115115
}
116116

117117
type WorkloadId is bytes32;
118+
119+
120+
interface IERC20Permit {
121+
function nonces(address owner) external view returns (uint256);
122+
}
118123
);
119124

120125
pub mod args;

crates/op-rbuilder/src/tests/flashtestations.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) ->
9797
// check that only the regular builder tx is in the block
9898
let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
9999
let txs = block.transactions.into_transactions_vec();
100-
101100
if_flashblocks!(
102101
assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx
103102
// Check builder tx
@@ -126,6 +125,116 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) ->
126125
Ok(())
127126
}
128127

128+
#[rb_test(args = OpRbuilderArgs {
129+
chain_block_time: 1000,
130+
enable_revert_protection: true,
131+
flashtestations: FlashtestationsArgs {
132+
flashtestations_enabled: true,
133+
registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
134+
builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
135+
funding_key: Some(flashtestations_signer()),
136+
debug: true,
137+
enable_block_proofs: true,
138+
..Default::default()
139+
},
140+
..Default::default()
141+
})]
142+
async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> {
143+
let driver = rbuilder.driver().await?;
144+
let provider = rbuilder.provider().await?;
145+
setup_flashtestation_contracts(&driver, &provider, false, true).await?;
146+
// verify not registered
147+
let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
148+
let result = contract
149+
.getRegistrationStatus(TEE_DEBUG_ADDRESS)
150+
.call()
151+
.await?;
152+
assert!(
153+
!result.isValid,
154+
"The tee key is registered for invalid quote"
155+
);
156+
// check that only regular builder tx is in the block
157+
let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
158+
let txs = block.transactions.into_transactions_vec();
159+
160+
if_flashblocks!(
161+
assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
162+
// Check builder tx
163+
assert_eq!(
164+
txs[1].to(),
165+
Some(Address::ZERO),
166+
"builder tx should send to zero address"
167+
);
168+
);
169+
if_standard!(
170+
assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof
171+
);
172+
let last_txs = &txs[txs.len() - 2..];
173+
// Check user transaction
174+
assert_eq!(
175+
last_txs[0].inner.tx_hash(),
176+
tx_hash,
177+
"tx hash for user transaction should match"
178+
);
179+
// Check builder tx
180+
assert_eq!(
181+
last_txs[1].to(),
182+
Some(Address::ZERO),
183+
"builder tx should send to zero address"
184+
);
185+
Ok(())
186+
}
187+
188+
#[rb_test(args = OpRbuilderArgs {
189+
chain_block_time: 1000,
190+
enable_revert_protection: true,
191+
flashtestations: FlashtestationsArgs {
192+
flashtestations_enabled: true,
193+
registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
194+
builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
195+
funding_key: Some(flashtestations_signer()),
196+
debug: true,
197+
enable_block_proofs: true,
198+
..Default::default()
199+
},
200+
..Default::default()
201+
})]
202+
async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> {
203+
let driver = rbuilder.driver().await?;
204+
let provider = rbuilder.provider().await?;
205+
setup_flashtestation_contracts(&driver, &provider, true, false).await?;
206+
// check that only the regular builder tx is in the block
207+
let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
208+
let txs = block.transactions.into_transactions_vec();
209+
210+
if_flashblocks!(
211+
assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
212+
// Check builder tx
213+
assert_eq!(
214+
txs[1].to(),
215+
Some(Address::ZERO),
216+
"builder tx should send to zero address"
217+
);
218+
);
219+
if_standard!(
220+
assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof
221+
);
222+
let last_txs = &txs[txs.len() - 2..];
223+
// Check user transaction
224+
assert_eq!(
225+
last_txs[0].inner.tx_hash(),
226+
tx_hash,
227+
"tx hash for user transaction should match"
228+
);
229+
// Check builder tx
230+
assert_eq!(
231+
last_txs[1].to(),
232+
Some(Address::ZERO),
233+
"builder tx should send to zero address"
234+
);
235+
Ok(())
236+
}
237+
129238
#[rb_test(flashblocks, args = OpRbuilderArgs {
130239
chain_block_time: 1000,
131240
enable_revert_protection: true,

0 commit comments

Comments
 (0)