Ver código fonte

Push array of mappings on Solana

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 anos atrás
pai
commit
a5b39241f4

+ 36 - 9
src/codegen/cfg.rs

@@ -72,15 +72,16 @@ pub enum Instr {
         storage: Expression,
         offset: Expression,
     },
-    /// Push an element onto a bytes in storage
+    /// Push an element onto an array in storage
     PushStorage {
         res: usize,
-        value: Expression,
+        ty: Type,
+        value: Option<Expression>,
         storage: Expression,
     },
-    /// Pop an element from a bytes in storage
+    /// Pop an element from an array in storage
     PopStorage {
-        res: usize,
+        res: Option<usize>,
         ty: Type,
         storage: Expression,
     },
@@ -172,11 +173,16 @@ impl Instr {
                 expr.recurse(cx, f);
             }
 
-            Instr::SetStorage { value, storage, .. }
-            | Instr::PushStorage { value, storage, .. } => {
+            Instr::SetStorage { value, storage, .. } => {
                 value.recurse(cx, f);
                 storage.recurse(cx, f);
             }
+            Instr::PushStorage { value, storage, .. } => {
+                if let Some(value) = value {
+                    value.recurse(cx, f);
+                }
+                storage.recurse(cx, f);
+            }
 
             Instr::SetStorageBytes {
                 value,
@@ -840,17 +846,27 @@ impl ControlFlowGraph {
             ),
             Instr::PushStorage {
                 res,
+                ty,
                 storage,
                 value,
             } => {
                 format!(
-                    "%{} = push storage slot({}) = {}",
+                    "%{} = push storage ty:{} slot:{} = {}",
                     self.vars[res].id.name,
+                    ty.to_string(ns),
                     self.expr_to_string(contract, ns, storage),
-                    self.expr_to_string(contract, ns, value),
+                    if let Some(value) = value {
+                        self.expr_to_string(contract, ns, value)
+                    } else {
+                        String::from("empty")
+                    }
                 )
             }
-            Instr::PopStorage { res, ty, storage } => {
+            Instr::PopStorage {
+                res: Some(res),
+                ty,
+                storage,
+            } => {
                 format!(
                     "%{} = pop storage ty:{} slot({})",
                     self.vars[res].id.name,
@@ -858,6 +874,17 @@ impl ControlFlowGraph {
                     self.expr_to_string(contract, ns, storage),
                 )
             }
+            Instr::PopStorage {
+                res: None,
+                ty,
+                storage,
+            } => {
+                format!(
+                    "pop storage ty:{} slot({})",
+                    ty.to_string(ns),
+                    self.expr_to_string(contract, ns, storage),
+                )
+            }
             Instr::PushMemory {
                 res,
                 ty,

+ 5 - 1
src/codegen/constant_folding.rs

@@ -130,14 +130,18 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                 }
                 Instr::PushStorage {
                     res,
+                    ty,
                     storage,
                     value,
                 } => {
                     let (storage, _) = expression(storage, Some(&vars), &cur, cfg, ns);
-                    let (value, _) = expression(value, Some(&vars), &cur, cfg, ns);
+                    let value = value
+                        .as_ref()
+                        .map(|expr| expression(expr, Some(&vars), &cur, cfg, ns).0);
 
                     cfg.blocks[block_no].instr[instr_no] = Instr::PushStorage {
                         res: *res,
+                        ty: ty.clone(),
                         storage,
                         value,
                     };

+ 6 - 1
src/codegen/dead_storage.rs

@@ -243,7 +243,12 @@ fn instr_transfers(block_no: usize, block: &BasicBlock) -> Vec<Vec<Transfer>> {
                     expr: Some(storage.clone()),
                 }]
             }
-            Instr::PopStorage { res, storage, .. } | Instr::PushStorage { res, storage, .. } => {
+            Instr::PopStorage {
+                res: Some(res),
+                storage,
+                ..
+            }
+            | Instr::PushStorage { res, storage, .. } => {
                 vec![
                     Transfer::Kill { var_no: *res },
                     Transfer::Gen { def, var_no: *res },

+ 3 - 1
src/codegen/reaching_definitions.rs

@@ -130,7 +130,9 @@ fn instr_transfers(block_no: usize, block: &BasicBlock) -> Vec<Vec<Transfer>> {
             Instr::Set { res, .. } => set_var(&[*res]),
             Instr::Call { res, .. } => set_var(res),
             Instr::AbiDecode { res, .. } => set_var(res),
-            Instr::LoadStorage { res, .. } | Instr::PopStorage { res, .. } => set_var(&[*res]),
+            Instr::LoadStorage { res, .. } | Instr::PopStorage { res: Some(res), .. } => {
+                set_var(&[*res])
+            }
             Instr::PushMemory { array, res, .. } => {
                 let mut v = set_var(&[*res]);
                 v.push(Transfer::Mod { var_no: *array });

+ 1 - 1
src/codegen/statements.rs

@@ -1433,7 +1433,7 @@ impl Type {
                     ))
                 }
             }
-            _ => unreachable!(),
+            _ => None,
         }
     }
 }

+ 22 - 5
src/codegen/storage.rs

@@ -285,7 +285,7 @@ pub fn storage_slots_array_pop(
     Expression::Variable(*loc, elem_ty, res_pos)
 }
 
-/// Push() method on dynamic bytes in storage
+/// Push() method on array or bytes in storage
 pub fn array_push(
     loc: &pt::Loc,
     args: &[Expression],
@@ -301,9 +301,17 @@ pub fn array_push(
     let mut ty = args[0].ty().storage_array_elem();
 
     let value = if args.len() > 1 {
-        expression(&args[1], cfg, contract_no, func, ns, vartab, opt)
+        Some(expression(
+            &args[1],
+            cfg,
+            contract_no,
+            func,
+            ns,
+            vartab,
+            opt,
+        ))
     } else {
-        ty.deref_any().default(ns).unwrap()
+        ty.deref_any().default(ns)
     };
 
     if !ty.is_reference_type() {
@@ -316,6 +324,7 @@ pub fn array_push(
         vartab,
         Instr::PushStorage {
             res,
+            ty: ty.deref_any().clone(),
             storage,
             value,
         },
@@ -339,7 +348,11 @@ pub fn array_pop(
 
     let ty = args[0].ty().storage_array_elem().deref_into();
 
-    let res = vartab.temp_anonymous(&ty);
+    let res = if !ty.contains_mapping(ns) {
+        Some(vartab.temp_anonymous(&ty))
+    } else {
+        None
+    };
 
     cfg.add(
         vartab,
@@ -350,5 +363,9 @@ pub fn array_pop(
         },
     );
 
-    Expression::Variable(*loc, ty, res)
+    if let Some(res) = res {
+        Expression::Variable(*loc, ty, res)
+    } else {
+        Expression::Undefined(ty)
+    }
 }

+ 4 - 2
src/codegen/strength_reduce.rs

@@ -130,7 +130,9 @@ fn block_reduce(
                 *offset = expression_reduce(offset, &vars, ns);
             }
             Instr::PushStorage { storage, value, .. } => {
-                *value = expression_reduce(value, &vars, ns);
+                if let Some(value) = value {
+                    *value = expression_reduce(value, &vars, ns);
+                }
                 *storage = expression_reduce(storage, &vars, ns);
             }
             Instr::PopStorage { storage, .. } => {
@@ -773,7 +775,7 @@ fn transfer(instr: &Instr, vars: &mut Variables, ns: &Namespace) {
                 }
             }
         }
-        Instr::PopStorage { res, .. } => {
+        Instr::PopStorage { res: Some(res), .. } => {
             let mut set = HashSet::new();
 
             set.insert(Value::unknown(8));

+ 12 - 3
src/codegen/subexpression_elimination/instruction.rs

@@ -44,11 +44,16 @@ impl AvailableExpressionSet {
                 let _ = self.gen_expression(expr, ave, cst);
             }
 
-            Instr::SetStorage { value, storage, .. }
-            | Instr::PushStorage { value, storage, .. } => {
+            Instr::SetStorage { value, storage, .. } => {
                 let _ = self.gen_expression(value, ave, cst);
                 let _ = self.gen_expression(storage, ave, cst);
             }
+            Instr::PushStorage { value, storage, .. } => {
+                if let Some(value) = value {
+                    let _ = self.gen_expression(value, ave, cst);
+                }
+                let _ = self.gen_expression(storage, ave, cst);
+            }
 
             Instr::SetStorageBytes {
                 value,
@@ -222,11 +227,15 @@ impl AvailableExpressionSet {
 
             Instr::PushStorage {
                 res,
+                ty,
                 value,
                 storage,
             } => Instr::PushStorage {
                 res: *res,
-                value: self.regenerate_expression(value, ave, cst).1,
+                ty: ty.clone(),
+                value: value
+                    .as_ref()
+                    .map(|expr| self.regenerate_expression(expr, ave, cst).1),
                 storage: self.regenerate_expression(storage, ave, cst).1,
             },
 

+ 2 - 2
src/emit/ewasm.rs

@@ -876,7 +876,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
         _function: FunctionValue,
         _ty: &ast::Type,
         _slot: IntValue<'a>,
-        _val: BasicValueEnum<'a>,
+        _val: Option<BasicValueEnum<'a>>,
         _ns: &ast::Namespace,
     ) -> BasicValueEnum<'a> {
         unimplemented!();
@@ -888,7 +888,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
         _ty: &ast::Type,
         _slot: IntValue<'a>,
         _ns: &ast::Namespace,
-    ) -> BasicValueEnum<'a> {
+    ) -> Option<BasicValueEnum<'a>> {
         unimplemented!();
     }
 

+ 10 - 5
src/emit/mod.rs

@@ -193,7 +193,7 @@ pub trait TargetRuntime<'a> {
         function: FunctionValue<'a>,
         ty: &ast::Type,
         slot: IntValue<'a>,
-        val: BasicValueEnum<'a>,
+        val: Option<BasicValueEnum<'a>>,
         ns: &ast::Namespace,
     ) -> BasicValueEnum<'a>;
     fn storage_pop(
@@ -203,7 +203,7 @@ pub trait TargetRuntime<'a> {
         ty: &ast::Type,
         slot: IntValue<'a>,
         ns: &ast::Namespace,
-    ) -> BasicValueEnum<'a>;
+    ) -> Option<BasicValueEnum<'a>>;
     fn storage_array_length(
         &self,
         _bin: &Binary<'a>,
@@ -3249,16 +3249,19 @@ pub trait TargetRuntime<'a> {
                     }
                     Instr::PushStorage {
                         res,
+                        ty,
                         storage,
                         value,
                     } => {
-                        let val = self.expression(bin, value, &w.vars, function, ns);
+                        let val = value
+                            .as_ref()
+                            .map(|expr| self.expression(bin, expr, &w.vars, function, ns));
                         let slot = self
                             .expression(bin, storage, &w.vars, function, ns)
                             .into_int_value();
 
                         w.vars.get_mut(res).unwrap().value =
-                            self.storage_push(bin, function, &value.ty(), slot, val, ns);
+                            self.storage_push(bin, function, ty, slot, val, ns);
                     }
                     Instr::PopStorage { res, ty, storage } => {
                         let slot = self
@@ -3267,7 +3270,9 @@ pub trait TargetRuntime<'a> {
 
                         let value = self.storage_pop(bin, function, ty, slot, ns);
 
-                        w.vars.get_mut(res).unwrap().value = value;
+                        if let Some(res) = res {
+                            w.vars.get_mut(res).unwrap().value = value.unwrap();
+                        }
                     }
                     Instr::PushMemory {
                         res,

+ 14 - 8
src/emit/solana.rs

@@ -1642,7 +1642,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         function: FunctionValue<'a>,
         ty: &ast::Type,
         slot: IntValue<'a>,
-        val: BasicValueEnum<'a>,
+        val: Option<BasicValueEnum<'a>>,
         ns: &ast::Namespace,
     ) -> BasicValueEnum<'a> {
         let data = self.contract_storage_data(binary);
@@ -1675,7 +1675,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         let member_size = binary
             .context
             .i32_type()
-            .const_int(ty.size_of(ns).to_u64().unwrap(), false);
+            .const_int(ty.storage_slots(ns).to_u64().unwrap(), false);
         let new_length = binary
             .builder
             .build_int_add(length, member_size, "new_length");
@@ -1729,14 +1729,16 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             "",
         );
 
-        self.storage_store(binary, ty, &mut new_offset, val, function, ns);
+        if let Some(val) = val {
+            self.storage_store(binary, ty, &mut new_offset, val, function, ns);
+        }
 
         if ty.is_reference_type() {
             // Caller expects a reference to storage; note that storage_store() should not modify
             // new_offset even if the argument is mut
             new_offset.into()
         } else {
-            val
+            val.unwrap()
         }
     }
 
@@ -1747,7 +1749,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         ty: &ast::Type,
         slot: IntValue<'a>,
         ns: &ast::Namespace,
-    ) -> BasicValueEnum<'a> {
+    ) -> Option<BasicValueEnum<'a>> {
         let data = self.contract_storage_data(binary);
         let account = self.contract_storage_account(binary);
 
@@ -1804,7 +1806,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         let member_size = binary
             .context
             .i32_type()
-            .const_int(ty.size_of(ns).to_u64().unwrap(), false);
+            .const_int(ty.storage_slots(ns).to_u64().unwrap(), false);
 
         binary.builder.position_at_end(retrieve_block);
 
@@ -1814,7 +1816,11 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
 
         let mut new_offset = binary.builder.build_int_add(offset, new_length, "");
 
-        let val = self.storage_load(binary, ty, &mut new_offset, function, ns);
+        let val = if !ty.contains_mapping(ns) {
+            Some(self.storage_load(binary, ty, &mut new_offset, function, ns))
+        } else {
+            None
+        };
 
         // delete existing storage -- pointers need to be freed
         //self.storage_free(binary, ty, account, data, new_offset, function, false);
@@ -1862,7 +1868,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         let member_size = binary
             .context
             .i32_type()
-            .const_int(elem_ty.size_of(ns).to_u64().unwrap(), false);
+            .const_int(elem_ty.storage_slots(ns).to_u64().unwrap(), false);
 
         let length_bytes = binary
             .builder

+ 5 - 3
src/emit/substrate.rs

@@ -2561,9 +2561,11 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         _function: FunctionValue,
         _ty: &ast::Type,
         slot: IntValue<'a>,
-        val: BasicValueEnum<'a>,
+        val: Option<BasicValueEnum<'a>>,
         _ns: &ast::Namespace,
     ) -> BasicValueEnum<'a> {
+        let val = val.unwrap();
+
         let slot_ptr = binary.builder.build_alloca(slot.get_type(), "slot");
         binary.builder.build_store(slot_ptr, slot);
 
@@ -2669,7 +2671,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         _ty: &ast::Type,
         slot: IntValue<'a>,
         _ns: &ast::Namespace,
-    ) -> BasicValueEnum<'a> {
+    ) -> Option<BasicValueEnum<'a>> {
         let slot_ptr = binary.builder.build_alloca(slot.get_type(), "slot");
         binary.builder.build_store(slot_ptr, slot);
 
@@ -2789,7 +2791,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             "",
         );
 
-        val
+        Some(val)
     }
 
     /// Calculate length of storage dynamic bytes

+ 7 - 1
src/sema/expression.rs

@@ -6453,9 +6453,15 @@ fn method_call_pos_args(
                     let storage_elem = ty.storage_array_elem();
                     let elem_ty = storage_elem.deref_any();
 
+                    let return_ty = if elem_ty.contains_mapping(ns) {
+                        Type::Void
+                    } else {
+                        elem_ty.clone()
+                    };
+
                     return Ok(Expression::Builtin(
                         func.loc,
-                        vec![elem_ty.clone()],
+                        vec![return_ty],
                         Builtin::ArrayPop,
                         vec![var_expr],
                     ));

+ 52 - 9
src/sema/types.rs

@@ -766,6 +766,7 @@ impl Type {
             Type::Bool => BigInt::one(),
             Type::Contract(_) | Type::Address(_) => BigInt::from(ns.address_length),
             Type::Bytes(n) => BigInt::from(*n),
+            Type::Value => BigInt::from(ns.value_length),
             Type::Uint(n) | Type::Int(n) => BigInt::from(n / 8),
             Type::Rational => unreachable!(),
             Type::Array(ty, dims) => {
@@ -791,6 +792,7 @@ impl Type {
                 Type::Address(false).size_of(ns) + Type::Uint(32).size_of(ns)
             }
             Type::Mapping(_, _) => BigInt::zero(),
+            Type::Ref(ty) | Type::StorageRef(_, ty) => ty.size_of(ns),
             _ => unimplemented!(),
         }
     }
@@ -869,10 +871,44 @@ impl Type {
     /// be very large
     pub fn storage_slots(&self, ns: &Namespace) -> BigInt {
         if ns.target == Target::Solana {
-            if self.is_sparse_solana(ns) {
-                BigInt::from(SOLANA_BUCKET_SIZE) * ns.storage_type().storage_slots(ns)
-            } else {
-                self.size_of(ns)
+            match self {
+                Type::Enum(_) => BigInt::one(),
+                Type::Bool => BigInt::one(),
+                Type::Contract(_) | Type::Address(_) => BigInt::from(ns.address_length),
+                Type::Bytes(n) => BigInt::from(*n),
+                Type::Value => BigInt::from(ns.value_length),
+                Type::Uint(n) | Type::Int(n) => BigInt::from(n / 8),
+                Type::Rational => unreachable!(),
+                Type::Array(_, dims) if dims[0].is_none() => BigInt::from(4),
+                Type::Array(ty, dims) => {
+                    let pointer_size = BigInt::from(4);
+                    if self.is_sparse_solana(ns) {
+                        BigInt::from(SOLANA_BUCKET_SIZE) * BigInt::from(4)
+                    } else {
+                        ty.storage_slots(ns).mul(
+                            dims.iter()
+                                .map(|d| match d {
+                                    None => &pointer_size,
+                                    Some(d) => d,
+                                })
+                                .product::<BigInt>(),
+                        )
+                    }
+                }
+                Type::Struct(n) => ns.structs[*n]
+                    .offsets
+                    .last()
+                    .cloned()
+                    .unwrap_or_else(BigInt::zero),
+                Type::String | Type::DynamicBytes => BigInt::from(4),
+                Type::InternalFunction { .. } => BigInt::from(ns.target.ptr_size()),
+                Type::ExternalFunction { .. } => {
+                    // Address and selector
+                    BigInt::from(ns.address_length + 4)
+                }
+                Type::Mapping(_, _) => BigInt::from(SOLANA_BUCKET_SIZE) * BigInt::from(4),
+                Type::Ref(ty) | Type::StorageRef(_, ty) => ty.storage_slots(ns),
+                _ => unimplemented!(),
             }
         } else {
             match self {
@@ -1072,12 +1108,19 @@ impl Type {
     pub fn is_sparse_solana(&self, ns: &Namespace) -> bool {
         match self.deref_any() {
             Type::Mapping(_, _) => true,
+            Type::Array(_, dims) if dims[0].is_none() => false,
             Type::Array(ty, dims) => {
-                if let Some(len) = &dims[0] {
-                    ty.storage_slots(ns) * len >= BigInt::from(SOLANA_SPARSE_ARRAY_SIZE)
-                } else {
-                    false
-                }
+                let pointer_size = BigInt::from(4);
+                let len = ty.storage_slots(ns).mul(
+                    dims.iter()
+                        .map(|d| match d {
+                            None => &pointer_size,
+                            Some(d) => d,
+                        })
+                        .product::<BigInt>(),
+                );
+
+                len >= BigInt::from(SOLANA_SPARSE_ARRAY_SIZE)
             }
             _ => false,
         }

+ 1 - 1
tests/codegen_testcases/dead_storage.sol

@@ -108,7 +108,7 @@ contract deadstorage {
     }
 // CHECK: store storage slot(uint256 6)
 // CHECK: load storage slot(uint256 6)
-// CHECK: push storage slot(uint256 6)
+// CHECK: push storage ty:bytes1 slot:uint256 6
 // CHECK: load storage slot(uint256 6)
 // CHECK: store storage slot(uint256 6)
 

+ 1 - 1
tests/codegen_testcases/unused_variable_elimination.sol

@@ -229,7 +229,7 @@ bytes bar;
     function test20() public {
         bytes1 x = bar.push();
 // NOT-CHECK: ty:bytes1 %x
-// CHECK: push storage slot
+// CHECK: push storage ty:bytes1 slot
     }
 
 }

+ 118 - 0
tests/solana_tests/mappings.rs

@@ -475,3 +475,121 @@ fn massive_sparse_array() {
         ])]
     );
 }
+
+#[test]
+fn mapping_in_dynamic_array() {
+    let mut vm = build_solidity(
+        r#"
+        contract foo {
+            mapping (uint64 => uint64)[] public map;
+            int64 public number;
+
+            function set(uint64 array_no, uint64 index, uint64 val) public {
+                map[array_no][index] = val;
+            }
+
+            function rm(uint64 array_no, uint64 index) public {
+                delete map[array_no][index];
+            }
+
+            function push() public {
+                map.push();
+            }
+
+            function pop() public {
+                map.pop();
+            }
+
+            function setNumber(int64 x) public {
+                number = x;
+            }
+        }"#,
+    );
+
+    vm.constructor("foo", &[], 0);
+
+    vm.function("push", &[], &[], 0, None);
+    vm.function("push", &[], &[], 0, None);
+
+    for array_no in 0..2 {
+        for i in 0..10 {
+            vm.function(
+                "set",
+                &[
+                    Token::Uint(ethereum_types::U256::from(array_no)),
+                    Token::Uint(ethereum_types::U256::from(102 + i + array_no * 500)),
+                    Token::Uint(ethereum_types::U256::from(300331 + i)),
+                ],
+                &[],
+                0,
+                None,
+            );
+        }
+    }
+
+    for array_no in 0..2 {
+        for i in 0..10 {
+            let returns = vm.function(
+                "map",
+                &[
+                    Token::Uint(ethereum_types::U256::from(array_no)),
+                    Token::Uint(ethereum_types::U256::from(102 + i + array_no * 500)),
+                ],
+                &[],
+                0,
+                None,
+            );
+
+            assert_eq!(
+                returns,
+                vec![Token::Uint(ethereum_types::U256::from(300331 + i))]
+            );
+        }
+    }
+
+    let returns = vm.function(
+        "map",
+        &[
+            Token::Uint(ethereum_types::U256::from(0)),
+            Token::Uint(ethereum_types::U256::from(101)),
+        ],
+        &[],
+        0,
+        None,
+    );
+
+    assert_eq!(returns, vec![Token::Uint(ethereum_types::U256::from(0))]);
+
+    vm.function(
+        "rm",
+        &[
+            Token::Uint(ethereum_types::U256::from(0)),
+            Token::Uint(ethereum_types::U256::from(104)),
+        ],
+        &[],
+        0,
+        None,
+    );
+
+    for i in 0..10 {
+        let returns = vm.function(
+            "map",
+            &[
+                Token::Uint(ethereum_types::U256::from(0)),
+                Token::Uint(ethereum_types::U256::from(102 + i)),
+            ],
+            &[],
+            0,
+            None,
+        );
+
+        if 102 + i != 104 {
+            assert_eq!(
+                returns,
+                vec![Token::Uint(ethereum_types::U256::from(300331 + i))]
+            );
+        } else {
+            assert_eq!(returns, vec![Token::Uint(ethereum_types::U256::from(0))]);
+        }
+    }
+}