@@ -40,8 +40,8 @@ use rundler_types::{
40
40
da:: DAGasBlockData ,
41
41
pool:: { Pool , PoolOperation , SimulationViolation } ,
42
42
Entity , EntityInfo , EntityInfos , EntityType , EntityUpdate , EntityUpdateType , GasFees ,
43
- Timestamp , UserOperation , UserOperationVariant , UserOpsPerAggregator , BUNDLE_BYTE_OVERHEAD ,
44
- TIME_RANGE_BUFFER , USER_OP_OFFSET_WORD_SIZE ,
43
+ Timestamp , UserOperation , UserOperationVariant , UserOpsPerAggregator , ValidationRevert ,
44
+ BUNDLE_BYTE_OVERHEAD , TIME_RANGE_BUFFER , USER_OP_OFFSET_WORD_SIZE ,
45
45
} ;
46
46
use rundler_utils:: { emit:: WithEntryPoint , math} ;
47
47
use tokio:: { sync:: broadcast, try_join} ;
@@ -1280,6 +1280,7 @@ impl<UO: UserOperation> ProposalContext<UO> {
1280
1280
} else {
1281
1281
EntityUpdateType :: UnstakedInvalidation
1282
1282
} ,
1283
+ ..Default :: default ( )
1283
1284
} ,
1284
1285
) ;
1285
1286
ret
@@ -1371,33 +1372,48 @@ impl<UO: UserOperation> ProposalContext<UO> {
1371
1372
violations : Vec < SimulationViolation > ,
1372
1373
entity_infos : EntityInfos ,
1373
1374
) {
1375
+ // If a staked factory or sender is present, we attribute errors to them directly.
1376
+
1377
+ // In accordance with [EREP-015]/[EREP-020]/[EREP-030], responsibility for failures lies with the staked factory or account.
1378
+ // Paymasters should not be held accountable, so the paymaster's `opsSeen` count should be decremented accordingly.
1379
+ let is_staked_factory = entity_infos. factory . map_or ( false , |f| f. is_staked ) ;
1380
+ let is_staked_sender = entity_infos. sender . is_staked ;
1381
+ if is_staked_factory || is_staked_sender {
1382
+ if let Some ( paymaster) = entity_infos. paymaster {
1383
+ self . add_erep_015_paymaster_amendment ( paymaster. address ( ) )
1384
+ }
1385
+ }
1386
+
1374
1387
// [EREP-020] When there is a staked factory any error in validation is attributed to it.
1375
- if entity_infos . factory . map_or ( false , |f| f . is_staked ) {
1388
+ if is_staked_factory {
1376
1389
let factory = entity_infos. factory . unwrap ( ) ;
1377
1390
self . entity_updates . insert (
1378
1391
factory. address ( ) ,
1379
1392
EntityUpdate {
1380
1393
entity : factory. entity ,
1381
1394
update_type : EntityUpdateType :: StakedInvalidation ,
1395
+ value : None ,
1382
1396
} ,
1383
1397
) ;
1384
1398
return ;
1385
1399
}
1386
1400
1387
1401
// [EREP-030] When there is a staked sender (without a staked factory) any error in validation is attributed to it.
1388
- if entity_infos . sender . is_staked {
1402
+ if is_staked_sender {
1389
1403
self . entity_updates . insert (
1390
1404
entity_infos. sender . address ( ) ,
1391
1405
EntityUpdate {
1392
1406
entity : entity_infos. sender . entity ,
1393
1407
update_type : EntityUpdateType :: StakedInvalidation ,
1408
+ value : None ,
1394
1409
} ,
1395
1410
) ;
1396
1411
return ;
1397
1412
}
1398
1413
1399
1414
// If not a staked factory or sender, attribute errors to each entity directly.
1400
1415
// For a given op, there can only be a single update per entity so we don't double count the [UREP-030] throttle penalty.
1416
+ let mut paymaster_amendment_required = false ;
1401
1417
for violation in violations {
1402
1418
match violation {
1403
1419
SimulationViolation :: UsedForbiddenOpcode ( entity, _, _) => {
@@ -1450,19 +1466,34 @@ impl<UO: UserOperation> ProposalContext<UO> {
1450
1466
) ;
1451
1467
}
1452
1468
}
1469
+ SimulationViolation :: ValidationRevert ( ValidationRevert :: Operation {
1470
+ entry_point_reason,
1471
+ ..
1472
+ } ) => {
1473
+ // [EREP-015] If user operations error derived from factory or account, paymaster opsSeen amendment is required.
1474
+ paymaster_amendment_required |=
1475
+ matches ! ( & entry_point_reason[ ..3 ] , "AA1" | "AA2" ) ;
1476
+ }
1453
1477
SimulationViolation :: OutOfGas ( entity) => {
1454
1478
self . add_entity_update ( entity, entity_infos)
1455
1479
}
1456
1480
_ => continue ,
1457
1481
}
1458
1482
}
1483
+
1484
+ if paymaster_amendment_required {
1485
+ if let Some ( paymaster) = entity_infos. paymaster {
1486
+ self . add_erep_015_paymaster_amendment ( paymaster. address ( ) )
1487
+ } ;
1488
+ }
1459
1489
}
1460
1490
1461
1491
// Add an entity update for the entity
1462
1492
fn add_entity_update ( & mut self , entity : Entity , entity_infos : EntityInfos ) {
1463
1493
let entity_update = EntityUpdate {
1464
1494
entity,
1465
1495
update_type : ProposalContext :: < UO > :: get_entity_update_type ( entity. kind , entity_infos) ,
1496
+ value : None ,
1466
1497
} ;
1467
1498
self . entity_updates . insert ( entity. address , entity_update) ;
1468
1499
}
@@ -1517,6 +1548,25 @@ impl<UO: UserOperation> ProposalContext<UO> {
1517
1548
}
1518
1549
}
1519
1550
}
1551
+
1552
+ fn add_erep_015_paymaster_amendment ( & mut self , address : Address ) {
1553
+ // Insert to entity_updates if entry is vacant, otherwise increment the value (precicely EntityUpdate.value)
1554
+ self . entity_updates
1555
+ . entry ( address)
1556
+ . and_modify ( |e| {
1557
+ if let Some ( value) = & mut e. value {
1558
+ * value += 1 ;
1559
+ }
1560
+ } )
1561
+ . or_insert_with ( || EntityUpdate {
1562
+ entity : Entity {
1563
+ kind : EntityType :: Paymaster ,
1564
+ address,
1565
+ } ,
1566
+ update_type : EntityUpdateType :: PaymasterOpsSeenDecrement ,
1567
+ value : Some ( 1 ) ,
1568
+ } ) ;
1569
+ }
1520
1570
}
1521
1571
1522
1572
#[ cfg( test) ]
@@ -1991,10 +2041,12 @@ mod tests {
1991
2041
EntityUpdate {
1992
2042
entity: Entity :: paymaster( address( 1 ) ) ,
1993
2043
update_type: EntityUpdateType :: UnstakedInvalidation ,
2044
+ ..Default :: default ( )
1994
2045
} ,
1995
2046
EntityUpdate {
1996
2047
entity: Entity :: factory( address( 3 ) ) ,
1997
2048
update_type: EntityUpdateType :: UnstakedInvalidation ,
2049
+ ..Default :: default ( )
1998
2050
} ,
1999
2051
]
2000
2052
) ;
@@ -2008,6 +2060,212 @@ mod tests {
2008
2060
) ;
2009
2061
}
2010
2062
2063
+ #[ tokio:: test]
2064
+ async fn test_paymaster_amended_by_staked_factory_revert ( ) {
2065
+ let sender = address ( 1 ) ;
2066
+ let staked_factory = address ( 2 ) ;
2067
+ let paymaster = address ( 3 ) ;
2068
+
2069
+ let deposit = parse_units ( "1" , "ether" ) . unwrap ( ) . into ( ) ;
2070
+
2071
+ let entity_infos = EntityInfos {
2072
+ sender : EntityInfo :: new ( Entity :: account ( sender) , false ) ,
2073
+ factory : Some ( EntityInfo :: new ( Entity :: factory ( staked_factory) , true ) ) ,
2074
+ paymaster : Some ( EntityInfo :: new ( Entity :: paymaster ( paymaster) , false ) ) ,
2075
+ aggregator : None ,
2076
+ } ;
2077
+
2078
+ // EREP-015: If a staked factory or sender is present, we attribute errors to them directly.
2079
+ // Expect EntityUpdateType::PaymasterOpsSeenDecrement to be recorded.
2080
+ let op = op_with_sender_factory_paymaster ( sender, staked_factory, paymaster) ;
2081
+ let bundle = mock_make_bundle (
2082
+ vec ! [ MockOp {
2083
+ op: op. clone( ) ,
2084
+ simulation_result: Box :: new( move || {
2085
+ Err ( SimulationError {
2086
+ violation_error: ViolationError :: Violations ( vec![ ] ) ,
2087
+ entity_infos: Some ( entity_infos) ,
2088
+ } )
2089
+ } ) ,
2090
+ } ] ,
2091
+ vec ! [ ] ,
2092
+ vec ! [ ] ,
2093
+ vec ! [ deposit] ,
2094
+ 0 ,
2095
+ 0 ,
2096
+ false ,
2097
+ ExpectedStorage :: default ( ) ,
2098
+ false ,
2099
+ )
2100
+ . await ;
2101
+
2102
+ let mut actual_entity_updates = bundle. entity_updates ;
2103
+ let mut expected_entity_updates = vec ! [
2104
+ EntityUpdate {
2105
+ entity: Entity :: factory( staked_factory) ,
2106
+ update_type: EntityUpdateType :: StakedInvalidation ,
2107
+ ..Default :: default ( )
2108
+ } ,
2109
+ EntityUpdate {
2110
+ entity: Entity :: paymaster( paymaster) ,
2111
+ update_type: EntityUpdateType :: PaymasterOpsSeenDecrement ,
2112
+ value: Some ( 1 ) ,
2113
+ } ,
2114
+ ] ;
2115
+
2116
+ // we want to check that the entity updates are the same regardless of order
2117
+ actual_entity_updates. sort_by ( |a, b| a. entity . address . cmp ( & b. entity . address ) ) ;
2118
+ expected_entity_updates. sort_by ( |a, b| a. entity . address . cmp ( & b. entity . address ) ) ;
2119
+
2120
+ assert_eq ! ( actual_entity_updates, expected_entity_updates) ;
2121
+ }
2122
+
2123
+ #[ tokio:: test]
2124
+ async fn test_paymaster_amended_by_staked_sender_revert ( ) {
2125
+ let sender = address ( 1 ) ;
2126
+ let paymaster = address ( 2 ) ;
2127
+
2128
+ let deposit = parse_units ( "1" , "ether" ) . unwrap ( ) . into ( ) ;
2129
+
2130
+ let entity_infos = EntityInfos {
2131
+ sender : EntityInfo :: new ( Entity :: account ( sender) , true ) ,
2132
+ factory : None ,
2133
+ paymaster : Some ( EntityInfo :: new ( Entity :: paymaster ( paymaster) , false ) ) ,
2134
+ aggregator : None ,
2135
+ } ;
2136
+
2137
+ // EREP-015: If not a staked factory or sender, attribute errors to each entity directly.
2138
+ // Expect EntityUpdateType::PaymasterOpsSeenDecrement to be recorded.
2139
+ let op = op_with_sender_paymaster ( sender, paymaster) ;
2140
+ let bundle = mock_make_bundle (
2141
+ vec ! [ MockOp {
2142
+ op: op. clone( ) ,
2143
+ simulation_result: Box :: new( move || {
2144
+ Err ( SimulationError {
2145
+ violation_error: ViolationError :: Violations ( vec![ ] ) ,
2146
+ entity_infos: Some ( entity_infos) ,
2147
+ } )
2148
+ } ) ,
2149
+ } ] ,
2150
+ vec ! [ ] ,
2151
+ vec ! [ ] ,
2152
+ vec ! [ deposit] ,
2153
+ 0 ,
2154
+ 0 ,
2155
+ false ,
2156
+ ExpectedStorage :: default ( ) ,
2157
+ false ,
2158
+ )
2159
+ . await ;
2160
+
2161
+ let mut actual_entity_updates = bundle. entity_updates ;
2162
+ let mut expected_entity_updates = vec ! [
2163
+ EntityUpdate {
2164
+ entity: Entity :: account( sender) ,
2165
+ update_type: EntityUpdateType :: StakedInvalidation ,
2166
+ ..Default :: default ( )
2167
+ } ,
2168
+ EntityUpdate {
2169
+ entity: Entity :: paymaster( paymaster) ,
2170
+ update_type: EntityUpdateType :: PaymasterOpsSeenDecrement ,
2171
+ value: Some ( 1 ) ,
2172
+ } ,
2173
+ ] ;
2174
+
2175
+ // we want to check that the entity updates are the same regardless of order
2176
+ actual_entity_updates. sort_by ( |a, b| a. entity . address . cmp ( & b. entity . address ) ) ;
2177
+ expected_entity_updates. sort_by ( |a, b| a. entity . address . cmp ( & b. entity . address ) ) ;
2178
+
2179
+ assert_eq ! ( actual_entity_updates, expected_entity_updates) ;
2180
+ }
2181
+
2182
+ #[ tokio:: test]
2183
+ async fn test_paymaster_amended_by_factory_or_sender_revert ( ) {
2184
+ let sender_1 = address ( 1 ) ;
2185
+ let sender_2 = address ( 2 ) ;
2186
+ let factory = address ( 3 ) ;
2187
+ let paymaster = address ( 4 ) ;
2188
+
2189
+ let deposit = parse_units ( "1" , "ether" ) . unwrap ( ) . into ( ) ;
2190
+
2191
+ let entity_infos_1 = EntityInfos {
2192
+ sender : EntityInfo :: new ( Entity :: account ( sender_1) , false ) ,
2193
+ factory : Some ( EntityInfo :: new ( Entity :: factory ( factory) , false ) ) ,
2194
+ paymaster : Some ( EntityInfo :: new ( Entity :: paymaster ( paymaster) , false ) ) ,
2195
+ aggregator : None ,
2196
+ } ;
2197
+
2198
+ let entity_infos_2 = EntityInfos {
2199
+ sender : EntityInfo :: new ( Entity :: account ( sender_2) , false ) ,
2200
+ factory : Some ( EntityInfo :: new ( Entity :: factory ( factory) , false ) ) ,
2201
+ paymaster : Some ( EntityInfo :: new ( Entity :: paymaster ( paymaster) , false ) ) ,
2202
+ aggregator : None ,
2203
+ } ;
2204
+
2205
+ // EREP-015: If a staked factory or sender is present, we attribute errors to them directly.
2206
+ // Expect EntityUpdateType::PaymasterOpsSeenDecrement to be recorded.
2207
+ let op_1 = op_with_sender_factory_paymaster ( sender_1, factory, paymaster) ;
2208
+ let op_2 = op_with_sender_factory_paymaster ( sender_2, factory, paymaster) ;
2209
+ let bundle = mock_make_bundle (
2210
+ vec ! [
2211
+ MockOp {
2212
+ op: op_1. clone( ) ,
2213
+ simulation_result: Box :: new( move || {
2214
+ Err ( SimulationError {
2215
+ violation_error: ViolationError :: Violations ( vec![
2216
+ SimulationViolation :: ValidationRevert (
2217
+ ValidationRevert :: Operation {
2218
+ entry_point_reason: "AA1x: factory related errors"
2219
+ . to_string( ) ,
2220
+ inner_revert_reason: None ,
2221
+ inner_revert_data: Bytes :: new( ) ,
2222
+ } ,
2223
+ ) ,
2224
+ ] ) ,
2225
+ entity_infos: Some ( entity_infos_1) ,
2226
+ } )
2227
+ } ) ,
2228
+ } ,
2229
+ MockOp {
2230
+ op: op_2. clone( ) ,
2231
+ simulation_result: Box :: new( move || {
2232
+ Err ( SimulationError {
2233
+ violation_error: ViolationError :: Violations ( vec![
2234
+ SimulationViolation :: ValidationRevert (
2235
+ ValidationRevert :: Operation {
2236
+ entry_point_reason: "AA2x: sender related errors"
2237
+ . to_string( ) ,
2238
+ inner_revert_reason: None ,
2239
+ inner_revert_data: Bytes :: new( ) ,
2240
+ } ,
2241
+ ) ,
2242
+ ] ) ,
2243
+ entity_infos: Some ( entity_infos_2) ,
2244
+ } )
2245
+ } ) ,
2246
+ } ,
2247
+ ] ,
2248
+ vec ! [ ] ,
2249
+ vec ! [ ] ,
2250
+ vec ! [ deposit, deposit] ,
2251
+ 0 ,
2252
+ 0 ,
2253
+ false ,
2254
+ ExpectedStorage :: default ( ) ,
2255
+ false ,
2256
+ )
2257
+ . await ;
2258
+
2259
+ let actual_entity_updates = bundle. entity_updates ;
2260
+ let expected_entity_updates = vec ! [ EntityUpdate {
2261
+ entity: Entity :: paymaster( paymaster) ,
2262
+ update_type: EntityUpdateType :: PaymasterOpsSeenDecrement ,
2263
+ value: Some ( 2 ) ,
2264
+ } ] ;
2265
+
2266
+ assert_eq ! ( actual_entity_updates, expected_entity_updates) ;
2267
+ }
2268
+
2011
2269
#[ tokio:: test]
2012
2270
async fn test_bundle_gas_limit_simple ( ) {
2013
2271
// Limit is 10M
@@ -2596,6 +2854,20 @@ mod tests {
2596
2854
}
2597
2855
}
2598
2856
2857
+ fn op_with_sender_factory_paymaster (
2858
+ sender : Address ,
2859
+ factory : Address ,
2860
+ paymaster : Address ,
2861
+ ) -> UserOperation {
2862
+ UserOperation {
2863
+ sender,
2864
+ init_code : factory. to_vec ( ) . into ( ) ,
2865
+ paymaster_and_data : paymaster. to_vec ( ) . into ( ) ,
2866
+ pre_verification_gas : DEFAULT_PVG ,
2867
+ ..Default :: default ( )
2868
+ }
2869
+ }
2870
+
2599
2871
fn op_with_sender_and_fees (
2600
2872
sender : Address ,
2601
2873
max_fee_per_gas : u128 ,
0 commit comments