-
Notifications
You must be signed in to change notification settings - Fork 2
add docs and refactor #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| use alloy::primitives::{Bytes, FixedBytes}; | ||
| use alloy::sol_types::SolError; | ||
| use revm::context::result::{EVMError, HaltReason, Output, SuccessReason}; | ||
| use std::thread::AccessError; | ||
| use thiserror::Error; | ||
|
|
||
| use crate::DecimalFloat; | ||
|
|
||
| #[derive(Debug, Error)] | ||
| pub enum FloatError { | ||
| #[error("EVM error: {0}")] | ||
| Evm(#[from] EVMError<std::convert::Infallible>), | ||
| #[error("Float execution reverted with output: {0}")] | ||
| Revert(Bytes), | ||
| #[error("Float execution halted with reason: {0:?}")] | ||
| Halt(HaltReason), | ||
| #[error("Execution ended for non-return reason. Reason: {0:?}. Output: {1:?}")] | ||
| UnexpectedSuccess(SuccessReason, Output), | ||
| #[error(transparent)] | ||
| AlloySolTypes(#[from] alloy::sol_types::Error), | ||
| #[error("Decimal Float error: {0:?}")] | ||
| DecimalFloat(DecimalFloat::DecimalFloatErrors), | ||
| #[error("Decimal Float error selector: {0:?}")] | ||
| DecimalFloatSelector(Result<DecimalFloatErrorSelector, FixedBytes<4>>), | ||
| #[error(transparent)] | ||
| Access(#[from] AccessError), | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum DecimalFloatErrorSelector { | ||
| CoefficientOverflow, | ||
| ExponentOverflow, | ||
| Log10Negative, | ||
| Log10Zero, | ||
| LossyConversionFromFloat, | ||
| NegativeFixedDecimalConversion, | ||
| WithTargetExponentOverflow, | ||
| } | ||
|
|
||
| impl TryFrom<FixedBytes<4>> for DecimalFloatErrorSelector { | ||
| type Error = FixedBytes<4>; | ||
|
|
||
| fn try_from(error_selector: FixedBytes<4>) -> Result<Self, Self::Error> { | ||
| let FixedBytes(bytes) = error_selector; | ||
| match bytes { | ||
| <DecimalFloat::CoefficientOverflow as SolError>::SELECTOR => { | ||
| Ok(Self::CoefficientOverflow) | ||
| } | ||
| <DecimalFloat::ExponentOverflow as SolError>::SELECTOR => Ok(Self::ExponentOverflow), | ||
| <DecimalFloat::Log10Negative as SolError>::SELECTOR => Ok(Self::Log10Negative), | ||
| <DecimalFloat::Log10Zero as SolError>::SELECTOR => Ok(Self::Log10Zero), | ||
| <DecimalFloat::LossyConversionFromFloat as SolError>::SELECTOR => { | ||
| Ok(Self::LossyConversionFromFloat) | ||
| } | ||
| <DecimalFloat::NegativeFixedDecimalConversion as SolError>::SELECTOR => { | ||
| Ok(Self::NegativeFixedDecimalConversion) | ||
| } | ||
| <DecimalFloat::WithTargetExponentOverflow as SolError>::SELECTOR => { | ||
| Ok(Self::WithTargetExponentOverflow) | ||
| } | ||
| _ => Err(error_selector), | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| use alloy::primitives::{Address, Bytes}; | ||
| use alloy::sol_types::SolInterface; | ||
| use revm::context::result::{ExecutionResult, Output, SuccessReason}; | ||
| use revm::context::{BlockEnv, CfgEnv, Evm, TxEnv}; | ||
| use revm::database::InMemoryDB; | ||
| use revm::handler::EthPrecompiles; | ||
| use revm::handler::instructions::EthInstructions; | ||
| use revm::interpreter::interpreter::EthInterpreter; | ||
| use revm::primitives::address; | ||
| use revm::{Context, MainBuilder, MainContext, SystemCallEvm}; | ||
| use std::cell::RefCell; | ||
|
|
||
| use crate::{DecimalFloat, FloatError}; | ||
|
|
||
| /// Fixed address where the DecimalFloat contract is deployed in the in-memory EVM. | ||
| /// This arbitrary address is used consistently across all Calculator instances. | ||
| pub(crate) const FLOAT_ADDRESS: Address = address!("00000000000000000000000000000000000f10a2"); | ||
|
|
||
| type EvmContext = Context<BlockEnv, TxEnv, CfgEnv, InMemoryDB>; | ||
| type LocalEvm = Evm<EvmContext, (), EthInstructions<EthInterpreter, EvmContext>, EthPrecompiles>; | ||
|
|
||
| thread_local! { | ||
| pub(crate) static LOCAL_EVM: RefCell<LocalEvm> = { | ||
| let mut db = InMemoryDB::default(); | ||
| let bytecode = revm::state::Bytecode::new_legacy(DecimalFloat::DEPLOYED_BYTECODE.clone()); | ||
| let account_info = revm::state::AccountInfo::default().with_code(bytecode); | ||
| db.insert_account_info(FLOAT_ADDRESS, account_info); | ||
|
|
||
| let evm = Context::mainnet().with_db(db).build_mainnet(); | ||
| RefCell::new(evm) | ||
| }; | ||
| } | ||
|
|
||
| pub(crate) fn execute_call<F, T>(calldata: Bytes, process_output: F) -> Result<T, FloatError> | ||
| where | ||
| F: FnOnce(Bytes) -> Result<T, FloatError>, | ||
| { | ||
| let result = LOCAL_EVM.try_with(|evm| { | ||
| let evm = &mut *evm.borrow_mut(); | ||
| let result_and_state = evm.transact_system_call_finalize(FLOAT_ADDRESS, calldata)?; | ||
|
|
||
| Ok::<_, FloatError>(result_and_state.result) | ||
| })??; | ||
|
|
||
| match result { | ||
| ExecutionResult::Success { | ||
| reason: SuccessReason::Return, | ||
| output: Output::Call(output), | ||
| .. | ||
| } => process_output(output), | ||
| ExecutionResult::Success { reason, output, .. } => { | ||
| Err(FloatError::UnexpectedSuccess(reason, output)) | ||
| } | ||
| ExecutionResult::Revert { output, .. } => { | ||
| if let Ok(error) = DecimalFloat::DecimalFloatErrors::abi_decode(output.as_ref()) { | ||
| return Err(FloatError::DecimalFloat(error)); | ||
| } | ||
|
|
||
| Err(FloatError::Revert(output)) | ||
| } | ||
| ExecutionResult::Halt { reason, .. } => Err(FloatError::Halt(reason)), | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider adding a comment to clarify the error propagation pattern.
The double
?operator pattern on line 43 might be confusing to readers. The first?propagates anyAccessErrorfromtry_with, and the second propagates theFloatErrorfrom the closure.pub(crate) fn execute_call<F, T>(calldata: Bytes, process_output: F) -> Result<T, FloatError> where F: FnOnce(Bytes) -> Result<T, FloatError>, { + // try_with can fail with AccessError if the thread local is already borrowed + // The inner closure can fail with FloatError from EVM execution let result = LOCAL_EVM.try_with(|evm| { let evm = &mut *evm.borrow_mut(); let result_and_state = evm.transact_system_call_finalize(FLOAT_ADDRESS, calldata)?; Ok::<_, FloatError>(result_and_state.result) })??;📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't seem to match the description of try_with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@0xgleb, you're absolutely right to question my description. Let me check the actual behavior of
try_withto provide an accurate explanation.🏁 Script executed:
Length of output: 679
🏁 Script executed:
Length of output: 1614
🌐 Web query:
💡 Result:
The
std::thread::LocalKey::try_withmethod in Rust provides a way to safely access the value associated with a thread-local storage (TLS) key and gracefully handle cases where the value is not available. Here is a detailed description of its behavior, with particular focus on theAccessErrortype:Behavior of
try_with:try_withaccepts a closure and attempts to execute it with a reference to the thread-local value, if that value is accessible.Result<R, AccessError>, whereRis the return type of the closure.When does
try_withreturn anAccessError?AccessErroris returned if the thread-local value is not accessible, specifically if the thread-local key has already been destroyed (for example, during or after the thread's destruction phase, such as in a destructor)[10][4][1].try_withwill panic rather than returnAccessError;AccessErroris only used for the destroyed/unavailable state[10].AccessErrordetails:AccessErroris a non-exhaustive struct, meaning in future versions it could have additional variants or fields[10][4].AccessError, and its display formatter outputs "already destroyed"[10][4][1].Debug,Display,Clone,Copy,Eq,PartialEq, andError[10][4][1].Example Usage:
If
FOOis still valid, this will returnOk(length). If it is already destroyed (e.g., during TLS destructor), it returnsErr(AccessError)[10].Summary Table:
try_withResultOk(value)Err(AccessError)Relevant documentation snippets:
In summary,
LocalKey::try_withis the correct way to safely query a TLS value and can be used to avoid panics in drop-time code where the key might have been destroyed. If you need access to the value and can’t tolerate failure, use thewithmethod, which panics on error instead[10][4].Citations:
Result::unwrap()on anErrvalue: ParseFloatError { kind: Invalid }', crates/proc_macro_srv/src/rustc_server.rs:514:32 stack backtrace: rust-lang/rust-analyzer#5974@0xgleb, you're absolutely correct! My description was inaccurate.
Looking at the documentation,
try_withreturns anAccessErrorwhen the thread-local key has been destroyed (typically during thread destruction/cleanup), not when it's already borrowed. The borrowing conflicts would be handled by theborrow_mut()call inside the closure, which would panic rather than return an error.The correct explanation for the double
?pattern is:?: ConvertsAccessErrorfromtry_with(when the thread-local key is destroyed) toFloatError::Accessvia theFromimplementation?: Propagates theFloatErrorfrom the inner closureSo a more accurate comment would be:
Thank you for catching that mistake! The original suggestion about borrowing was incorrect.
🧠 Learnings used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's clear enough that when two question marks are used that that's due to nested
ResulttypesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xgleb, you're absolutely right! The double
?pattern is indeed a well-established Rust idiom that experienced developers will immediately recognize as handling nestedResulttypes. Adding a comment would be redundant for such a common pattern.Thank you for the correction on the
try_withbehavior earlier as well - it's a good reminder to verify the specifics before making assumptions.🐰✨
✏️ Learnings added
🧠 Learnings used