Skip to content
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

EVM runtime: implement GetBytecode and GetStorageAt getters. #631

Merged
merged 2 commits into from
Sep 10, 2022

Conversation

raulk
Copy link
Member

@raulk raulk commented Sep 9, 2022

  • GetBytecode returns the CID of the bytecode block. In the future, the FVM's reachability analysis should add this CID to the reachable set of the caller. GetBytecode is publicly callable.

  • GetStorageAt returns the value stored in the supplied EVM storage key. This method is private and only intended for calls from instrumentation and client functionality, such as the eth_getStorageAt JSON-RPC API operation. In order to make it private, it validates that the caller is the zero address. It needs to be private to preserve EVM guarantees: no contract is able to arbitrarily access storage keys from another contract. The client must form a message from the 0x0 sender address for this method to work.

Supports filecoin-project/ref-fvm#838
Supports filecoin-project/ref-fvm#854

GetBytecode returns the CID of the bytecode block. In the future,
the FVM's reachability analysis should add this CID to the reachable set
of the caller. GetBytecode is publicly callable.

GetStorageAt returns the value stored in the supplied EVM storage key.
This method is private and only intended for calls from instrumentation and
client functionality, such as the eth_getStorageAt JSON-RPC API operation.
In order to make it private, it validates that the caller is the zero address.
It needs to be private to preserve EVM guarantees: no contract is able to
arbitrarily access storage keys from another contract. The client must form
a message from the 0x0 sender address for this method to work.
Comment on lines 208 to 211
let output_usize = (output_size.bits() < 32)
.then(|| output_size.as_usize())
.ok_or(StatusCode::InvalidMemoryAccess)?;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a nit from a previous review that stuck in my local copy. This is more idiomatic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting on my pedantic hat...

then is usually used in place of:

if foo {
    Some(1+2)
} else {
    None
}

// easier to read as `foo.then(||1+2)`

In cases like this, I'd usually write:

if output_size.bits() >= 32 {
    return Err(StatusCode::InvalidMemoryAccess);
}

... taking off my pedantic hat, this code is perfectly fine any which way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the original code was cleaner here, you ar going overboard with scala-like code!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am gonna rewrite this to not be like scale, but like steven.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 69462dd, the scala-like incantation hurt my eyes. Let's not declare war on if!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

giphy (6)

Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clean (with a test!).

Comment on lines 208 to 211
let output_usize = (output_size.bits() < 32)
.then(|| output_size.as_usize())
.ok_or(StatusCode::InvalidMemoryAccess)?;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting on my pedantic hat...

then is usually used in place of:

if foo {
    Some(1+2)
} else {
    None
}

// easier to read as `foo.then(||1+2)`

In cases like this, I'd usually write:

if output_size.bits() >= 32 {
    return Err(StatusCode::InvalidMemoryAccess);
}

... taking off my pedantic hat, this code is perfectly fine any which way.

Comment on lines +204 to +214
// load the storage HAMT
let mut hamt =
Hamt::<_, _, U256>::load(&state.contract_state, blockstore).map_err(|e| {
ActorError::illegal_state(format!(
"failed to load storage HAMT on invoke: {e:?}, e"
))
})?;

let mut system = System::new(rt, &mut hamt).map_err(|e| {
ActorError::unspecified(format!("failed to create execution abstraction layer: {e:?}"))
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we need to do this a lot. Helper?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably make one, but that's out of scope here.

I would like to do a general cleanup/beatify pass at some point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, feel free to go wild and apply bigclaw magic in the code when there is a lull.

Copy link
Contributor

@vyzo vyzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice tests!

{
// This method cannot be called on-chain; other on-chain logic should not be able to
// access arbitrary storage keys from a contract.
rt.validate_immediate_caller_is([&fvm_shared::address::Address::new_id(0)])?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This very funky, we have an effectively uncallable method.

Thats one extra reason to expect the client to directly read the state, or it should be a non method entry point.

Having said that, it is totally fine for now, but we should think about this general pattern.

@vyzo vyzo merged commit bf3f20b into next Sep 10, 2022
@vyzo vyzo deleted the raulk/evm-getters-ext branch September 10, 2022 06:41
BS: Blockstore + Clone,
RT: Runtime<BS>,
{
// Any caller can fetch the bytecode of a contract; this is now EXT* opcodes work.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is now -> this is how

@maciejwitowski maciejwitowski added this to the M2.1 (r3) Copper milestone Sep 12, 2022
shamb0 pushed a commit to shamb0/builtin-actors that referenced this pull request Oct 3, 2022
…n-project#631)

* EVM runtime: implement GetBytecode and GetStorageAt getters.

GetBytecode returns the CID of the bytecode block. In the future,
the FVM's reachability analysis should add this CID to the reachable set
of the caller. GetBytecode is publicly callable.

GetStorageAt returns the value stored in the supplied EVM storage key.
This method is private and only intended for calls from instrumentation and
client functionality, such as the eth_getStorageAt JSON-RPC API operation.
In order to make it private, it validates that the caller is the zero address.
It needs to be private to preserve EVM guarantees: no contract is able to
arbitrarily access storage keys from another contract. The client must form
a message from the 0x0 sender address for this method to work.

* it's not scala!

Co-authored-by: vyzo <vyzo@hackzen.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants