Skip to content

Commit d1cf086

Browse files
committed
✨ Implement pytypes_resolver
1 parent fe92235 commit d1cf086

7 files changed

Lines changed: 163 additions & 86 deletions

File tree

docs/testing-framework/migrating-from-wake-4.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,20 @@ ARM Linux is now supported through 3rd party [nikitastupin/solc](https://github.
114114
### Better debugger formatting
115115

116116
All (i)pdb responses are now formatted using the Rich library.
117+
118+
### `pytypes_resolver` for enforcing event and error resolution
119+
120+
It is possible to override the resolution of events and errors for any Account using `pytypes_resolver`.
121+
122+
```python
123+
usdt = Account("0xdAC17F958D2ee523a2206206994597C13D831ec7")
124+
usdt.pytypes_resolver = IUSDT
125+
```
126+
127+
where `IUSDT` is a Solidity interface generated into pytypes.
128+
129+
This feature can be used to enforce user-friendly resolution of "external contracts" (e.g. forked contracts, low-level deployed contracts with init code) and in cases when implicit resolution doesn't work correctly.
130+
131+
`pytypes_resolver` must always be assigned to the Account that performs the event emit (LOGn instruction) or performs the revert. In a proxy-implementation setup, `pytypes_resolver` must be set on the implementation contract to correctly resolve events and errors originating from the code of the implementation contract. It may be set for the proxy as well if it defines its own events or errors.
132+
133+
This feature is currently only supported with `revm` testing chain.

wake/development/pytypes_generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ def generate_contract_template(
653653
self.add_str_to_types(
654654
1, f'_creation_code = "{compilation_info.evm.bytecode.object}"', 2
655655
)
656+
self.add_str_to_types(1, f'_fqn = "{fqn}"', 2)
656657

657658
if contract.kind == ContractKind.LIBRARY:
658659
lib_id = keccak.new(data=fqn.encode("utf-8"), digest_bits=256).digest()[:17]
@@ -2103,6 +2104,7 @@ def __init__(self):
21032104
self.__contract_reserved = {
21042105
"_abi",
21052106
"_creation_code",
2107+
"_fqn",
21062108
"_address",
21072109
"_chain",
21082110
"_label",

wake_rs/src/account.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,37 @@ impl Account {
430430
}
431431
}
432432

433+
#[setter]
434+
fn set_pytypes_resolver(&self, py: Python, value: Bound<PyAny>) -> PyResult<()> {
435+
if value.is_none() {
436+
match &self.chain {
437+
ChainWrapper::Native(chain) => {
438+
chain.borrow_mut(py).fqn_overrides.remove(&self.address.borrow(py).0);
439+
}
440+
ChainWrapper::Python(_) => {
441+
return Err(PyNotImplementedError::new_err(
442+
"Setting pytypes_resolver is currently not supported with non-revm chains",
443+
));
444+
}
445+
}
446+
} else {
447+
let fqn = value.getattr(intern!(py, "_fqn"))?.downcast_into::<PyString>()?;
448+
449+
match &self.chain {
450+
ChainWrapper::Native(chain) => {
451+
chain.borrow_mut(py).fqn_overrides.insert(self.address.borrow(py).0, fqn.into());
452+
}
453+
ChainWrapper::Python(_) => {
454+
return Err(PyNotImplementedError::new_err(
455+
"Setting pytypes_resolver is currently not supported with non-revm chains",
456+
));
457+
}
458+
}
459+
}
460+
461+
Ok(())
462+
}
463+
433464
#[classmethod]
434465
#[pyo3(signature = (private_key, chain=None))]
435466
fn from_key(

wake_rs/src/chain.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ pub struct Chain {
178178
latest_block_env: Option<BlockEnv>,
179179
snapshots: Vec<(CfgEnv, BlockEnv)>,
180180

181+
// address => fqn
182+
// overrides how to resolve fqn (and pytypes) for this address
183+
pub(crate) fqn_overrides: HashMap<RevmAddress, Py<PyString>>,
184+
181185
#[pyo3(get, set)]
182186
tx_callback: Option<Py<PyAny>>,
183187
}
@@ -215,6 +219,7 @@ impl Chain {
215219
default_access_list_account: None,
216220
latest_block_env: None,
217221
snapshots: vec![],
222+
fqn_overrides: HashMap::new(),
218223
tx_callback: None,
219224
},
220225
)?;
@@ -703,6 +708,7 @@ impl Chain {
703708
fn _disconnect(slf: Bound<Self>, py: Python) -> PyResult<()> {
704709
let mut borrowed = slf.borrow_mut();
705710
borrowed.connected = false;
711+
borrowed.fqn_overrides.clear();
706712

707713
if let Some(block_number) = borrowed.forked_block {
708714
let path = format!(

wake_rs/src/inspectors/fqn_inspector.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,30 @@ use revm::{
1313
};
1414

1515
pub enum EventMetadata {
16-
Create(Bytes),
16+
Create(CreateEventMetadata),
1717
Call(CallEventMetadata),
1818
}
1919

20+
pub struct CreateEventMetadata {
21+
pub init_code: Bytes,
22+
pub bytecode_address: Address,
23+
}
24+
2025
pub struct CallEventMetadata {
2126
pub metadata: Vec<u8>,
22-
pub bytecode_address: Option<Address>,
27+
pub bytecode_address: Address,
2328
}
2429

2530
pub enum ErrorMetadata {
26-
Create(Bytes),
31+
Create(CreateErrorMetadata),
2732
Call(CallErrorMetadata),
2833
}
2934

35+
pub struct CreateErrorMetadata {
36+
pub init_code: Bytes,
37+
pub bytecode_address: Address,
38+
}
39+
3040
pub struct CallErrorMetadata {
3141
pub metadata: Vec<u8>,
3242
pub bytecode_address: Address,
@@ -38,10 +48,9 @@ pub struct FqnInspector {
3848

3949
/// Stack of bytecode addresses.
4050
///
41-
/// Bytecode address is only used to resolve external events in the case of forking
42-
/// Since CREATE / EOFCREATE can only be performed with local (not forked) contracts,
43-
/// we can use None in create subcalls
44-
bytecode_addresses: Vec<Option<Address>>,
51+
/// Bytecode address is used to resolve external events in the case of forking
52+
/// and when pytypes overrides are used.
53+
bytecode_addresses: Vec<Address>,
4554

4655
/// Stack of init code.
4756
///
@@ -106,10 +115,15 @@ impl FqnInspector {
106115

107116
impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for FqnInspector {
108117
fn log(&mut self, interp: &mut Interpreter, _context: &mut CTX, log: Log) {
118+
let bytecode_address = self.bytecode_addresses.last().unwrap().clone();
119+
109120
if let Some(init_code) = self.init_code_stack.last().unwrap() {
110121
self.events_metadata.insert(
111122
log.clone(),
112-
EventMetadata::Create(init_code.clone()),
123+
EventMetadata::Create(CreateEventMetadata {
124+
init_code: init_code.clone(),
125+
bytecode_address,
126+
}),
113127
);
114128
} else {
115129
let bytecode = interp.bytecode.original_byte_slice();
@@ -119,15 +133,16 @@ impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for FqnInspector {
119133
log.clone(),
120134
EventMetadata::Call(CallEventMetadata {
121135
metadata: metadata.to_vec(),
122-
bytecode_address: self.bytecode_addresses.last().unwrap().clone(),
136+
bytecode_address,
123137
}),
124138
);
125139
}
126140
}
127141
}
128142

129-
fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
130-
self.bytecode_addresses.push(None);
143+
fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
144+
let nonce = context.journal().load_account(inputs.caller).ok()?.info.nonce;
145+
self.bytecode_addresses.push(inputs.created_address(nonce));
131146
self.init_code_stack.push(Some(inputs.init_code.clone()));
132147
None
133148
}
@@ -138,15 +153,18 @@ impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for FqnInspector {
138153
inputs: &CreateInputs,
139154
outcome: &mut CreateOutcome,
140155
) {
141-
self.bytecode_addresses.pop();
156+
let bytecode_address = self.bytecode_addresses.pop().unwrap();
142157
self.init_code_stack.pop();
143158

144159
if outcome.result.result == InstructionResult::Revert && outcome.result.output.len() >= 4 {
145160
let selector: [u8; 4] = (&outcome.result.output[..4]).try_into().unwrap();
146161

147162
self.errors_metadata
148163
.entry(selector)
149-
.or_insert(ErrorMetadata::Create(inputs.init_code.clone()));
164+
.or_insert(ErrorMetadata::Create(CreateErrorMetadata {
165+
init_code: inputs.init_code.clone(),
166+
bytecode_address,
167+
}));
150168
}
151169
}
152170

@@ -155,7 +173,6 @@ impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for FqnInspector {
155173
_context: &mut CTX,
156174
_inputs: &mut EOFCreateInputs,
157175
) -> Option<CreateOutcome> {
158-
self.bytecode_addresses.push(None);
159176
todo!();
160177
}
161178

@@ -165,12 +182,11 @@ impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for FqnInspector {
165182
_inputs: &EOFCreateInputs,
166183
_outcome: &mut CreateOutcome,
167184
) {
168-
self.bytecode_addresses.pop();
169-
self.init_code_stack.pop();
185+
todo!();
170186
}
171187

172188
fn call(&mut self, _context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
173-
self.bytecode_addresses.push(Some(inputs.bytecode_address));
189+
self.bytecode_addresses.push(inputs.bytecode_address);
174190
self.init_code_stack.push(None);
175191

176192
None

0 commit comments

Comments
 (0)