Prechádzať zdrojové kódy

Implement push() on dynamic storage arrays with argument

Push with no argument not implemented yet.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 5 rokov pred
rodič
commit
3f7eb78d00

+ 1 - 1
clippy.toml

@@ -1,2 +1,2 @@
 too-many-arguments-threshold = 10
-cognitive-complexity-threshold = 40
+cognitive-complexity-threshold = 38

+ 137 - 43
src/resolver/expression.rs

@@ -21,6 +21,7 @@ use resolver;
 use resolver::address::to_hexstr_eip55;
 use resolver::cfg::{ControlFlowGraph, Instr, Storage, Vartable};
 use resolver::eval::eval_number_expression;
+use resolver::storage::array_offset;
 
 #[derive(PartialEq, Clone, Debug)]
 pub enum Expression {
@@ -2779,48 +2780,25 @@ fn array_subscript(
                 ));
             }
         }
-        // the index needs to be cast to i256 and multiplied by the number
-        // of slots for each element
-        // FIXME: if elem_size is power-of-2 then shift.
-        if elem_size == BigInt::one() {
-            Ok((
-                Expression::Add(
-                    *loc,
-                    Box::new(array_expr),
-                    Box::new(cast(
-                        &index.loc(),
-                        Expression::Variable(index.loc(), pos),
-                        &coerced_ty,
-                        &resolver::Type::Primitive(ast::PrimitiveType::Uint(256)),
-                        false,
-                        ns,
-                        errors,
-                    )?),
-                ),
-                elem_ty,
-            ))
-        } else {
-            Ok((
-                Expression::Add(
-                    *loc,
-                    Box::new(array_expr),
-                    Box::new(Expression::Multiply(
-                        *loc,
-                        Box::new(cast(
-                            &index.loc(),
-                            Expression::Variable(index.loc(), pos),
-                            &coerced_ty,
-                            &resolver::Type::Primitive(ast::PrimitiveType::Uint(256)),
-                            false,
-                            ns,
-                            errors,
-                        )?),
-                        Box::new(Expression::NumberLiteral(*loc, 256, elem_size)),
-                    )),
-                ),
-                elem_ty,
-            ))
-        }
+
+        Ok((
+            array_offset(
+                loc,
+                array_expr,
+                cast(
+                    &index.loc(),
+                    Expression::Variable(index.loc(), pos),
+                    &coerced_ty,
+                    &resolver::Type::Primitive(ast::PrimitiveType::Uint(256)),
+                    false,
+                    ns,
+                    errors,
+                )?,
+                elem_ty.clone(),
+                ns,
+            ),
+            elem_ty,
+        ))
     } else {
         match array_ty.deref() {
             resolver::Type::Primitive(ast::PrimitiveType::Bytes(array_length)) => {
@@ -3285,7 +3263,7 @@ fn named_struct_literal(
 /// Resolve a method call with positional arguments
 fn method_call(
     loc: &ast::Loc,
-    member: &ast::Expression,
+    var: &ast::Expression,
     func: &ast::Identifier,
     args: &[ast::Expression],
     cfg: &mut ControlFlowGraph,
@@ -3293,6 +3271,122 @@ fn method_call(
     vartab: &mut Option<&mut Vartable>,
     errors: &mut Vec<output::Output>,
 ) -> Result<(Expression, resolver::Type), ()> {
+    let (var_expr, var_ty) = expression(var, cfg, ns, vartab, errors)?;
+
+    if let resolver::Type::StorageRef(ty) = &var_ty {
+        if let resolver::Type::Array(_, dim) = ty.as_ref() {
+            if func.name == "push" {
+                if dim.last().unwrap().is_some() {
+                    errors.push(Output::error(
+                        func.loc,
+                        "method ‘push()’ not allowed on fixed length array".to_string(),
+                    ));
+                    return Err(());
+                }
+                if args.len() != 1 {
+                    errors.push(Output::error(
+                        func.loc,
+                        "method ‘push()’ takes 1 argument".to_string(),
+                    ));
+                    return Err(());
+                }
+                let (val_expr, val_ty) = expression(&args[0], cfg, ns, vartab, errors)?;
+                let elem_ty = ty.array_deref();
+
+                let tab = match vartab {
+                    &mut Some(ref mut tab) => tab,
+                    None => {
+                        errors.push(Output::error(
+                            *loc,
+                            format!("cannot call method ‘{}’ in constant expression", func.name),
+                        ));
+                        return Err(());
+                    }
+                };
+
+                // set array+length to val_expr
+                let slot_ty = resolver::Type::Primitive(ast::PrimitiveType::Uint(256));
+                let slot_pos = tab.temp_anonymous(&slot_ty);
+
+                cfg.add(
+                    tab,
+                    Instr::Set {
+                        res: slot_pos,
+                        expr: Expression::StorageLoad(
+                            *loc,
+                            slot_ty.clone(),
+                            Box::new(var_expr.clone()),
+                        ),
+                    },
+                );
+
+                let pos = tab.temp_anonymous(&elem_ty);
+
+                cfg.add(
+                    tab,
+                    Instr::Set {
+                        res: pos,
+                        expr: cast(
+                            &args[0].loc(),
+                            val_expr,
+                            &val_ty,
+                            &elem_ty.deref(),
+                            true,
+                            ns,
+                            errors,
+                        )?,
+                    },
+                );
+
+                cfg.add(
+                    tab,
+                    Instr::SetStorage {
+                        ty: elem_ty.clone(),
+                        local: pos,
+                        storage: array_offset(
+                            loc,
+                            Expression::Keccak256(*loc, Box::new(var_expr.clone())),
+                            Expression::Variable(*loc, slot_pos),
+                            elem_ty,
+                            ns,
+                        ),
+                    },
+                );
+
+                // increase length
+                let new_length = tab.temp_anonymous(&slot_ty);
+
+                cfg.add(
+                    tab,
+                    Instr::Set {
+                        res: new_length,
+                        expr: Expression::Add(
+                            *loc,
+                            Box::new(Expression::Variable(*loc, slot_pos)),
+                            Box::new(Expression::NumberLiteral(*loc, 256, BigInt::one())),
+                        ),
+                    },
+                );
+
+                cfg.add(
+                    tab,
+                    Instr::SetStorage {
+                        ty: slot_ty,
+                        local: new_length,
+                        storage: var_expr,
+                    },
+                );
+
+                return Ok((Expression::Poison, resolver::Type::Undef));
+            }
+        }
+    }
+
+    errors.push(Output::error(
+        func.loc,
+        format!("method ‘{}’ does not exist", func.name),
+    ));
+
     Err(())
 }
 

+ 1 - 0
src/resolver/mod.rs

@@ -17,6 +17,7 @@ pub mod cfg;
 mod eval;
 pub mod expression;
 mod functions;
+mod storage;
 mod structs;
 mod variables;
 

+ 52 - 0
src/resolver/storage.rs

@@ -0,0 +1,52 @@
+use num_bigint::BigInt;
+use num_traits::FromPrimitive;
+use num_traits::One;
+use num_traits::Zero;
+
+use super::expression::Expression;
+use parser::ast;
+use resolver;
+
+/// Given a storage slot which is the start of the array, calculate the
+/// offset of the array element. This function exists to avoid doing
+/// 256 bit multiply if possible.
+pub fn array_offset(
+    loc: &ast::Loc,
+    start: Expression,
+    index: Expression,
+    elem_ty: resolver::Type,
+    ns: &resolver::Contract,
+) -> Expression {
+    let elem_size = elem_ty.storage_slots(ns);
+
+    // the index needs to be cast to i256 and multiplied by the number
+    // of slots for each element
+    if elem_size == BigInt::one() {
+        Expression::Add(*loc, Box::new(start), Box::new(index))
+    } else if (elem_size.clone() & (elem_size.clone() - BigInt::one())) == BigInt::zero() {
+        // elem_size is power of 2
+        Expression::ShiftLeft(
+            *loc,
+            Box::new(start),
+            Box::new(Expression::ShiftLeft(
+                *loc,
+                Box::new(index),
+                Box::new(Expression::NumberLiteral(
+                    *loc,
+                    256,
+                    BigInt::from_usize(elem_size.bits()).unwrap(),
+                )),
+            )),
+        )
+    } else {
+        Expression::Add(
+            *loc,
+            Box::new(start),
+            Box::new(Expression::Multiply(
+                *loc,
+                Box::new(index),
+                Box::new(Expression::NumberLiteral(*loc, 256, elem_size)),
+            )),
+        )
+    }
+}

+ 53 - 0
tests/substrate_arrays/mod.rs

@@ -1011,3 +1011,56 @@ fn storage_dynamic_array_length() {
 
     runtime.function(&mut store, "test", Vec::new());
 }
+
+#[test]
+fn storage_dynamic_array_push() {
+    let (_, errors) = parse_and_resolve(
+        r#"
+        contract foo {
+            int32[] bar;
+
+            function test() public {
+                assert(bar.length == 0);
+                bar.push(102, 20);
+            }
+        }"#,
+        &Target::Substrate,
+    );
+
+    assert_eq!(first_error(errors), "method ‘push()’ takes 1 argument");
+
+    let (_, errors) = parse_and_resolve(
+        r#"
+        contract foo {
+            int32[4] bar;
+
+            function test() public {
+                bar.push(102);
+            }
+        }"#,
+        &Target::Substrate,
+    );
+
+    assert_eq!(
+        first_error(errors),
+        "method ‘push()’ not allowed on fixed length array"
+    );
+
+    let (runtime, mut store) = build_solidity(
+        r#"
+        pragma solidity 0;
+
+        contract foo {
+            int32[] bar;
+
+            function test() public {
+                assert(bar.length == 0);
+                bar.push(102);
+                assert(bar[0] == 102);
+                assert(bar.length == 1);
+            }
+        }"#,
+    );
+
+    runtime.function(&mut store, "test", Vec::new());
+}