Skip to content

Gate certain features behind a context object. #558

Description

@cburgdorf

What is wrong?

One of the main objectives of Fe is to be explicit and easy to audit. The EVM exposes certain features such as writing logs, creating contracts or obtaining the current block number and it should be easy to determine from the signature of a function whether the function has the capabilities to use such features or not.

This is currently not the case, e.g we might currently have a function such as the following which - from it's signature - looks pure when in reality it does modify the blockchain (by emitting a log).

pub def looks_pure_but_isnt():
  emit SomeEvent()

The Context object

We propose to introduce a Context object which gates access to features such as:

  • emitting logs
  • creating contracts
  • transferring Ether
  • reading message info
  • reading block info

The Context object needs to be passed as a parameter to the function e.g. the example above would be rewritten as:

pub fn emits_event(ctx: mut Context):
  ctx.emit(SomeEvent())

Similar, creating a contract via create/create2 would also require to have access to the Context object:

pub fn creates_contract(ctx: mut Context):
  ctx.create2(...)

As well does reading block chain information such as the current block number.

pub fn retrieves_blocknumber(ctx: Context):
  ctx.blocknumber()

There are a few special rules about the context object:

  1. The Context object has a defined location in the parameter list. It either is the first parameter if the function does not take self (e.g. pub fn foo(context:Context)) or it comes second if the function does take self (e.g. pub fn foo(self, context:Context)). Any other index in the parameter list is a compile time error.

  2. The Context object is automatically injected when a function is called externally but it has to be passed explicitly when the function is called from another Fe function e.g.

# The context object is automatically injected when this is called externally
pub fn amplified_block(ctx: Context) -> u256:
  # but it has to be passed along in this function call
  return retrieves_blocknumber(ctx) * 1000

fn retrieves_blocknumber(ctx: Context):
  return ctx.blocknumber()

Taking mut Context vs Context

As the eagle-eyed reader might have noticed the examples above either use mut Context or Context. This proposal can be implemented independent of having support for mut in Fe but it would naturally benefit from having mut support because all functionality that modifies the blockchain such as creating logs or contracts would require to obtain a mut Context reference whereas readonly access such as ctx.blocknumber() does not need require a mutable reference to the context.

ABI conformity

The proposed system works nicely with the existing function categories in the ABI but offers even tighter rules for added clarity.

Category Characteristics Fe Syntax ABI
Pure Can only operate on input arguments and not produce any information besides its return value. Can not take self and therefore has no access to things that would make it impure foo(val: u256) pure
Read Contract Reading information from the contract instance (broad definition includes reading constants from contract code) foo(self) view
Storage Writing Writing to contract storage (own or that of other contracts) foo(mut self) payable or nonpayable
Context Reading Reading contextual information from the blockchain (msg, block etc) foo(ctx: Context) view
Context Modifying Emitting logs, transferring ether, creating contracts foo(ctx: mut Context) payable or nonpayable
Read Contract & Context Reading information from the contract instance and Context foo(self, ctx:Context) view
Read Contract & write Context Reading information from the contract instance and modify Context foo(self, ctx: mut Context) view
Storage Writing & read Context Writing to contract storage and read from Context foo(mut self, ctx: Context) payable or nonpayable
Storage Writing & write Context Writing to contract storage and Context foo(mut self, ctx: mut Context) payable or nonpayable

With the proposed system Fe would have nine different categories that can be derived from the function signatures that map to four different ABI types.


This brain dump is based on the PR discussion in #527 which offers more detailed reasoning and is worth a read.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions