Skip to content

Commit c665b27

Browse files
authored
Add support for chain.id, block.timestamp, other builtin attributes (argotorg#208)
* Use enums for builtins * Add support for more 'msg', 'block', and 'chain' attributes * Use refs in evm_contracts tests * Add support for tx.origin, tx.gas_price * Make builtin obj code less noisy; err on nonexistent builtin methods * Add release note for builtin attributes
1 parent 7443ac2 commit c665b27

9 files changed

Lines changed: 597 additions & 397 deletions

File tree

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

analyzer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ tiny-keccak = { version = "2.0", features = ["keccak"] }
1414
hex = "0.4"
1515
ansi_term = "0.12.1"
1616
num-bigint = "0.3.1"
17+
strum = { version = "0.20.0", features = ["derive"] }

analyzer/src/builtins.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
1-
pub const SELF: &str = "self";
2-
pub const SENDER: &str = "sender";
3-
pub const MSG: &str = "msg";
4-
pub const CLONE: &str = "clone";
5-
pub const TO_MEM: &str = "to_mem";
1+
use strum::EnumString;
2+
3+
#[derive(Debug, PartialEq, EnumString)]
4+
#[strum(serialize_all = "snake_case")]
5+
pub enum Method {
6+
Clone,
7+
ToMem,
8+
Keccak256,
9+
AbiEncode,
10+
AbiEncodePacked,
11+
}
12+
13+
#[derive(Debug, PartialEq, EnumString)]
14+
#[strum(serialize_all = "lowercase")]
15+
pub enum Object {
16+
Block,
17+
Chain,
18+
Msg,
19+
Tx,
20+
#[strum(serialize = "self")]
21+
Self_,
22+
}
23+
24+
#[derive(Debug, PartialEq, EnumString)]
25+
#[strum(serialize_all = "snake_case")]
26+
pub enum BlockField {
27+
Coinbase,
28+
Difficulty,
29+
Number,
30+
Timestamp,
31+
}
32+
33+
#[derive(Debug, PartialEq, EnumString)]
34+
#[strum(serialize_all = "snake_case")]
35+
pub enum ChainField {
36+
Id,
37+
}
38+
39+
#[derive(Debug, PartialEq, EnumString)]
40+
#[strum(serialize_all = "snake_case")]
41+
pub enum MsgField {
42+
Data,
43+
Sender,
44+
Sig,
45+
Value,
46+
}
47+
48+
#[derive(Debug, PartialEq, EnumString)]
49+
#[strum(serialize_all = "snake_case")]
50+
pub enum TxField {
51+
GasPrice,
52+
Origin,
53+
}

analyzer/src/traversal/expressions.rs

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1+
use crate::builtins;
12
use crate::errors::SemanticError;
3+
use crate::namespace::operations;
24
use crate::namespace::scopes::{
35
BlockScope,
46
ContractFunctionDef,
57
Shared,
68
};
7-
use std::convert::TryFrom;
8-
9-
use crate::builtins;
10-
use crate::namespace::operations;
119
use crate::namespace::types::{
1210
Array,
1311
Base,
@@ -31,7 +29,9 @@ use crate::{
3129

3230
use fe_parser::ast as fe;
3331
use fe_parser::span::Spanned;
32+
use std::convert::TryFrom;
3433
use std::rc::Rc;
34+
use std::str::FromStr;
3535

3636
/// Gather context information for expressions and check for type errors.
3737
pub fn expr(
@@ -273,24 +273,48 @@ fn expr_attribute(
273273
exp: &Spanned<fe::Expr>,
274274
) -> Result<ExpressionAttributes, SemanticError> {
275275
if let fe::Expr::Attribute { value, attr } = &exp.node {
276-
return match expr_name_str(value)? {
277-
builtins::MSG => expr_attribute_msg(attr),
278-
builtins::SELF => expr_attribute_self(scope, attr),
279-
_ => Err(SemanticError::undefined_value()),
276+
use builtins::{
277+
BlockField,
278+
ChainField,
279+
MsgField,
280+
Object,
281+
TxField,
280282
};
281-
}
282283

283-
unreachable!()
284-
}
284+
let val = |t| Ok(ExpressionAttributes::new(Type::Base(t), Location::Value));
285+
let err = || Err(SemanticError::undefined_value());
285286

286-
fn expr_attribute_msg(attr: &Spanned<&str>) -> Result<ExpressionAttributes, SemanticError> {
287-
match attr.node {
288-
builtins::SENDER => Ok(ExpressionAttributes::new(
289-
Type::Base(Base::Address),
290-
Location::Value,
291-
)),
292-
_ => Err(SemanticError::undefined_value()),
287+
return match Object::from_str(expr_name_str(value)?) {
288+
Ok(Object::Self_) => expr_attribute_self(scope, attr),
289+
290+
Ok(Object::Block) => match BlockField::from_str(attr.node) {
291+
Ok(BlockField::Coinbase) => val(Base::Address),
292+
Ok(BlockField::Difficulty) => val(U256),
293+
Ok(BlockField::Number) => val(U256),
294+
Ok(BlockField::Timestamp) => val(U256),
295+
Err(_) => err(),
296+
},
297+
Ok(Object::Chain) => match ChainField::from_str(attr.node) {
298+
Ok(ChainField::Id) => val(U256),
299+
Err(_) => err(),
300+
},
301+
Ok(Object::Msg) => match MsgField::from_str(attr.node) {
302+
Ok(MsgField::Data) => todo!(),
303+
Ok(MsgField::Sender) => val(Base::Address),
304+
Ok(MsgField::Sig) => todo!(),
305+
Ok(MsgField::Value) => val(U256),
306+
Err(_) => err(),
307+
},
308+
Ok(Object::Tx) => match TxField::from_str(attr.node) {
309+
Ok(TxField::GasPrice) => val(U256),
310+
Ok(TxField::Origin) => val(Base::Address),
311+
Err(_) => err(),
312+
},
313+
Err(_) => err(),
314+
};
293315
}
316+
317+
unreachable!()
294318
}
295319

296320
fn expr_attribute_self(
@@ -526,10 +550,13 @@ fn expr_call_value_attribute(
526550
return Err(SemanticError::wrong_number_of_params());
527551
}
528552

529-
return match attr.node {
530-
builtins::CLONE => value_attributes.into_cloned(),
531-
builtins::TO_MEM => value_attributes.into_cloned_from_sto(),
532-
_ => Err(SemanticError::undefined_value()),
553+
use builtins::Method;
554+
return match Method::from_str(attr.node).map_err(|_| SemanticError::undefined_value())? {
555+
Method::Clone => value_attributes.into_cloned(),
556+
Method::ToMem => value_attributes.into_cloned_from_sto(),
557+
Method::Keccak256 => todo!(),
558+
Method::AbiEncode => todo!(),
559+
Method::AbiEncodePacked => todo!(),
533560
};
534561
}
535562

@@ -611,12 +638,21 @@ fn expr_attribute_call_type(
611638
exp: &Spanned<fe::Expr>,
612639
) -> Result<CallType, SemanticError> {
613640
if let fe::Expr::Attribute { value, attr } = &exp.node {
614-
return match value.node {
615-
fe::Expr::Name(builtins::SELF) => Ok(CallType::SelfAttribute {
616-
func_name: attr.node.to_string(),
617-
}),
618-
_ => Ok(CallType::ValueAttribute),
641+
if let fe::Expr::Name(name) = value.node {
642+
use builtins::Object;
643+
match Object::from_str(name) {
644+
Ok(Object::Block) | Ok(Object::Chain) | Ok(Object::Msg) | Ok(Object::Tx) => {
645+
return Err(SemanticError::undefined_value())
646+
}
647+
Ok(Object::Self_) => {
648+
return Ok(CallType::SelfAttribute {
649+
func_name: attr.node.to_string(),
650+
})
651+
}
652+
Err(_) => {}
653+
}
619654
};
655+
return Ok(CallType::ValueAttribute);
620656
}
621657

622658
unreachable!()

compiler/src/yul/mappers/expressions.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use fe_common::utils::keccak::get_full_signature;
1616
use fe_parser::ast as fe;
1717
use fe_parser::span::Spanned;
1818
use std::convert::TryFrom;
19+
use std::str::FromStr;
1920
use yultsur::*;
2021

2122
/// Builds a Yul expression from a Fe expression.
@@ -310,23 +311,45 @@ fn expr_attribute(
310311
exp: &Spanned<fe::Expr>,
311312
) -> Result<yul::Expression, CompileError> {
312313
if let fe::Expr::Attribute { value, attr } = &exp.node {
313-
return match expr_name_str(value)? {
314-
builtins::MSG => expr_attribute_msg(attr),
315-
builtins::SELF => expr_attribute_self(context, exp),
316-
_ => Err(CompileError::static_str("invalid attributes")),
314+
use builtins::{
315+
BlockField,
316+
ChainField,
317+
MsgField,
318+
Object,
319+
TxField,
320+
};
321+
return match Object::from_str(expr_name_str(value)?) {
322+
Ok(Object::Self_) => expr_attribute_self(context, exp),
323+
324+
Ok(Object::Block) => match BlockField::from_str(attr.node) {
325+
Ok(BlockField::Coinbase) => Ok(expression! { coinbase() }),
326+
Ok(BlockField::Difficulty) => Ok(expression! { difficulty() }),
327+
Ok(BlockField::Number) => Ok(expression! { number() }),
328+
Ok(BlockField::Timestamp) => Ok(expression! { timestamp() }),
329+
Err(_) => Err(CompileError::static_str("invalid `block` attribute name")),
330+
},
331+
Ok(Object::Chain) => match ChainField::from_str(attr.node) {
332+
Ok(ChainField::Id) => Ok(expression! { chainid() }),
333+
Err(_) => Err(CompileError::static_str("invalid `chain` attribute name")),
334+
},
335+
Ok(Object::Msg) => match MsgField::from_str(attr.node) {
336+
Ok(MsgField::Data) => todo!(),
337+
Ok(MsgField::Sender) => Ok(expression! { caller() }),
338+
Ok(MsgField::Sig) => todo!(),
339+
Ok(MsgField::Value) => Ok(expression! { callvalue() }),
340+
Err(_) => Err(CompileError::static_str("invalid `msg` attribute name")),
341+
},
342+
Ok(Object::Tx) => match TxField::from_str(attr.node) {
343+
Ok(TxField::GasPrice) => Ok(expression! { gasprice() }),
344+
Ok(TxField::Origin) => Ok(expression! { origin() }),
345+
Err(_) => Err(CompileError::static_str("invalid `msg` attribute name")),
346+
},
347+
Err(_) => Err(CompileError::static_str("invalid attributes")),
317348
};
318349
}
319-
320350
unreachable!()
321351
}
322352

323-
fn expr_attribute_msg(attr: &Spanned<&str>) -> Result<yul::Expression, CompileError> {
324-
match attr.node {
325-
builtins::SENDER => Ok(expression! { caller() }),
326-
_ => Err(CompileError::static_str("invalid msg attribute name")),
327-
}
328-
}
329-
330353
fn expr_attribute_self(
331354
context: &Context,
332355
exp: &Spanned<fe::Expr>,
@@ -489,17 +512,25 @@ mod tests {
489512
);
490513
}
491514

492-
#[test]
493-
fn msg_sender() {
494-
let mut harness = ContextHarness::new("msg.sender");
495-
harness.add_expression(
496-
"msg.sender",
497-
ExpressionAttributes::new(Type::Base(Base::Address), Location::Value),
498-
);
499-
500-
let result = map(&harness.context, "msg.sender");
501-
502-
assert_eq!(result, "caller()");
515+
#[rstest(
516+
expression,
517+
expected_yul,
518+
typ,
519+
case("block.coinbase", "coinbase()", Type::Base(Base::Address)),
520+
case("block.difficulty", "difficulty()", Type::Base(U256)),
521+
case("block.number", "number()", Type::Base(U256)),
522+
case("block.timestamp", "timestamp()", Type::Base(U256)),
523+
case("chain.id", "chainid()", Type::Base(U256)),
524+
case("msg.sender", "caller()", Type::Base(Base::Address)),
525+
case("msg.value", "callvalue()", Type::Base(U256)),
526+
case("tx.origin", "origin()", Type::Base(Base::Address)),
527+
case("tx.gas_price", "gasprice()", Type::Base(U256))
528+
)]
529+
fn builtin_attribute(expression: &str, expected_yul: &str, typ: Type) {
530+
let mut harness = ContextHarness::new(expression);
531+
harness.add_expression(expression, ExpressionAttributes::new(typ, Location::Value));
532+
let result = map(&harness.context, expression);
533+
assert_eq!(result, expected_yul);
503534
}
504535

505536
#[rstest(

0 commit comments

Comments
 (0)