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:
-
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.
-
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.
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).
The
ContextobjectWe propose to introduce a
Contextobject which gates access to features such as:The
Contextobject needs to be passed as a parameter to the function e.g. the example above would be rewritten as:Similar, creating a contract via
create/create2would also require to have access to theContextobject:As well does reading block chain information such as the current block number.
There are a few special rules about the context object:
The
Contextobject has a defined location in the parameter list. It either is the first parameter if the function does not takeself(e.g.pub fn foo(context:Context)) or it comes second if the function does takeself(e.g.pub fn foo(self, context:Context)). Any other index in the parameter list is a compile time error.The
Contextobject 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.Taking
mut ContextvsContextAs the eagle-eyed reader might have noticed the examples above either use
mut ContextorContext. This proposal can be implemented independent of having support formutin Fe but it would naturally benefit from havingmutsupport because all functionality that modifies the blockchain such as creating logs or contracts would require to obtain amut Contextreference whereas readonly access such asctx.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.
selfand therefore has no access to things that would make it impurefoo(val: u256)purefoo(self)viewfoo(mut self)payableornonpayablefoo(ctx: Context)viewfoo(ctx: mut Context)payableornonpayablefoo(self, ctx:Context)viewfoo(self, ctx: mut Context)viewfoo(mut self, ctx: Context)payableornonpayablefoo(mut self, ctx: mut Context)payableornonpayableWith 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.