Bläddra i källkod

Enable passing in parameters

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 5 år sedan
förälder
incheckning
00ca6b02de
7 ändrade filer med 269 tillägg och 20 borttagningar
  1. 2 1
      src/emit/ethabiencoder.rs
  2. 15 2
      src/emit/mod.rs
  3. 240 3
      src/emit/solana.rs
  4. 1 1
      stdlib/Makefile
  5. BIN
      stdlib/solana.bc
  6. 2 2
      stdlib/solana.c
  7. 9 11
      tests/solana.rs

+ 2 - 1
src/emit/ethabiencoder.rs

@@ -881,7 +881,8 @@ impl EthAbiEncoder {
             | ast::Type::Address(_)
             | ast::Type::Int(_)
             | ast::Type::Uint(_)
-            | ast::Type::Bytes(_) => 32,
+            | ast::Type::Bytes(_)
+            | ast::Type::ExternalFunction { .. } => 32,
             // String and Dynamic bytes use 32 bytes for the offset into dynamic encoded
             ast::Type::String | ast::Type::DynamicBytes => 32,
             ast::Type::Enum(_) => 32,

+ 15 - 2
src/emit/mod.rs

@@ -4022,7 +4022,7 @@ pub trait TargetRuntime<'a> {
         let not_fallback = contract.builder.build_int_compare(
             IntPredicate::UGE,
             argslen,
-            contract.context.i32_type().const_int(4, false),
+            argslen.get_type().const_int(4, false),
             "",
         );
 
@@ -4055,7 +4055,7 @@ pub trait TargetRuntime<'a> {
 
         let argslen = contract.builder.build_int_sub(
             argslen,
-            contract.context.i32_type().const_int(4, false),
+            argslen.get_type().const_int(4, false),
             "argslen",
         );
 
@@ -5513,6 +5513,19 @@ impl<'a> Contract<'a> {
         )
     }
 
+    // Create the llvm intrinsic for bswap
+    pub fn llvm_bswap(&self, bit: u32) -> FunctionValue<'a> {
+        let name = format!("llvm.bswap.i{}", bit);
+        let ty = self.context.custom_width_int_type(bit);
+
+        if let Some(f) = self.module.get_function(&name) {
+            return f;
+        }
+
+        self.module
+            .add_function(&name, ty.fn_type(&[ty.into()], false), None)
+    }
+
     /// Return the llvm type for a variable holding the type, not the type itself
     fn llvm_var(&self, ty: &ast::Type) -> BasicTypeEnum<'a> {
         let llvm_ty = self.llvm_type(ty);

+ 240 - 3
src/emit/solana.rs

@@ -8,6 +8,7 @@ use inkwell::context::Context;
 use inkwell::types::IntType;
 use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, UnnamedAddress};
 use inkwell::AddressSpace;
+use inkwell::IntPredicate;
 use inkwell::OptimizationLevel;
 
 use super::ethabiencoder;
@@ -93,7 +94,7 @@ impl SolanaTarget {
             let mut args = Vec::new();
 
             // insert abi decode
-            self.abi.decode(
+            self.decode(
                 contract,
                 function,
                 &mut args,
@@ -143,6 +144,243 @@ impl SolanaTarget {
             |_| false,
         );
     }
+
+    /// abi decode the encoded data into the BasicValueEnums
+    pub fn decode<'a>(
+        &self,
+        contract: &Contract<'a>,
+        function: FunctionValue,
+        args: &mut Vec<BasicValueEnum<'a>>,
+        data: PointerValue<'a>,
+        data_length: IntValue<'a>,
+        spec: &[ast::Parameter],
+    ) {
+        let data = contract.builder.build_pointer_cast(
+            data,
+            contract.context.i8_type().ptr_type(AddressSpace::Generic),
+            "data",
+        );
+
+        let mut offset = contract.context.i64_type().const_zero();
+
+        for arg in spec {
+            args.push(self.decode_primitive(
+                contract,
+                function,
+                &arg.ty,
+                None,
+                &mut offset,
+                data,
+                data_length,
+            ));
+        }
+    }
+
+    // abi decode a single primitive
+    /// decode a single primitive which is always encoded in 32 bytes
+    fn decode_primitive<'a>(
+        &self,
+        contract: &Contract<'a>,
+        function: FunctionValue,
+        ty: &ast::Type,
+        to: Option<PointerValue<'a>>,
+        offset: &mut IntValue<'a>,
+        data: PointerValue<'a>,
+        length: IntValue,
+    ) -> BasicValueEnum<'a> {
+        // TODO: investigate whether we can use build_int_nuw_add() and avoid 64 bit conversions
+        let new_offset = contract.builder.build_int_add(
+            *offset,
+            contract.context.i64_type().const_int(32, false),
+            "next_offset",
+        );
+
+        self.check_overrun(contract, function, new_offset, length);
+
+        let data = unsafe { contract.builder.build_gep(data, &[*offset], "") };
+
+        *offset = new_offset;
+
+        let ty = if let ast::Type::Enum(n) = ty {
+            &contract.ns.enums[*n].ty
+        } else {
+            ty
+        };
+
+        match &ty {
+            ast::Type::Bool => {
+                // solidity checks all the 32 bytes for being non-zero; we will just look at the upper 8 bytes, else we would need four loads
+                // which is unneeded (hopefully)
+                // cast to 64 bit pointer
+                let bool_ptr = contract.builder.build_pointer_cast(
+                    data,
+                    contract.context.i64_type().ptr_type(AddressSpace::Generic),
+                    "",
+                );
+
+                let bool_ptr = unsafe {
+                    contract.builder.build_gep(
+                        bool_ptr,
+                        &[contract.context.i32_type().const_int(3, false)],
+                        "bool_ptr",
+                    )
+                };
+
+                let val = contract.builder.build_int_compare(
+                    IntPredicate::NE,
+                    contract
+                        .builder
+                        .build_load(bool_ptr, "abi_bool")
+                        .into_int_value(),
+                    contract.context.i64_type().const_zero(),
+                    "bool",
+                );
+                if let Some(p) = to {
+                    contract.builder.build_store(p, val);
+                }
+                val.into()
+            }
+            ast::Type::Uint(8) | ast::Type::Int(8) => {
+                let int8_ptr = unsafe {
+                    contract.builder.build_gep(
+                        data,
+                        &[contract.context.i32_type().const_int(31, false)],
+                        "uint8_ptr",
+                    )
+                };
+
+                let val = contract.builder.build_load(int8_ptr, "int8");
+
+                if let Some(p) = to {
+                    contract.builder.build_store(p, val);
+                }
+
+                val
+            }
+            ast::Type::Uint(n) | ast::Type::Int(n) if *n == 16 || *n == 32 || *n == 64 => {
+                // our value is big endian, 32 bytes. So, find the offset within the 32 bytes
+                // where our value starts
+                let int8_ptr = unsafe {
+                    contract.builder.build_gep(
+                        data,
+                        &[contract
+                            .context
+                            .i32_type()
+                            .const_int(32 - (*n as u64 / 8), false)],
+                        "uint8_ptr",
+                    )
+                };
+
+                let val = contract.builder.build_load(
+                    contract.builder.build_pointer_cast(
+                        int8_ptr,
+                        contract
+                            .context
+                            .custom_width_int_type(*n as u32)
+                            .ptr_type(AddressSpace::Generic),
+                        "",
+                    ),
+                    &format!("be{}", *n),
+                );
+
+                // now convert to le
+                let bswap = contract.llvm_bswap(*n as u32);
+
+                let val = contract
+                    .builder
+                    .build_call(bswap, &[val], "")
+                    .try_as_basic_value()
+                    .left()
+                    .unwrap()
+                    .into_int_value();
+
+                if let Some(p) = to {
+                    contract.builder.build_store(p, val);
+                }
+
+                val.into()
+            }
+            ast::Type::Uint(n) | ast::Type::Int(n) if *n < 64 => {
+                let uint64_ptr = contract.builder.build_pointer_cast(
+                    data,
+                    contract.context.i64_type().ptr_type(AddressSpace::Generic),
+                    "",
+                );
+
+                let uint64_ptr = unsafe {
+                    contract.builder.build_gep(
+                        uint64_ptr,
+                        &[contract.context.i32_type().const_int(3, false)],
+                        "uint64_ptr",
+                    )
+                };
+
+                let bswap = contract.llvm_bswap(64);
+
+                // load and bswap
+                let val = contract
+                    .builder
+                    .build_call(
+                        bswap,
+                        &[contract.builder.build_load(uint64_ptr, "uint64")],
+                        "",
+                    )
+                    .try_as_basic_value()
+                    .left()
+                    .unwrap()
+                    .into_int_value();
+
+                let val = contract.builder.build_right_shift(
+                    val,
+                    contract.context.i64_type().const_int(64 - *n as u64, false),
+                    ty.is_signed_int(),
+                    "",
+                );
+
+                let int_type = contract.context.custom_width_int_type(*n as u32);
+
+                let val = contract.builder.build_int_truncate(val, int_type, "");
+
+                val.into()
+            }
+            ast::Type::Bytes(1) => {
+                let val = contract.builder.build_load(data, "bytes1");
+
+                if let Some(p) = to {
+                    contract.builder.build_store(p, val);
+                }
+                val
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    /// Check that data has not overrun end
+    fn check_overrun(
+        &self,
+        contract: &Contract,
+        function: FunctionValue,
+        offset: IntValue,
+        end: IntValue,
+    ) {
+        let in_bounds = contract
+            .builder
+            .build_int_compare(IntPredicate::ULE, offset, end, "");
+
+        let success_block = contract.context.append_basic_block(function, "success");
+        let bail_block = contract.context.append_basic_block(function, "bail");
+        contract
+            .builder
+            .build_conditional_branch(in_bounds, success_block, bail_block);
+
+        contract.builder.position_at_end(bail_block);
+
+        contract
+            .builder
+            .build_return(Some(&contract.context.i32_type().const_int(3, false)));
+
+        contract.builder.position_at_end(success_block);
+    }
 }
 
 impl<'a> TargetRuntime<'a> for SolanaTarget {
@@ -435,8 +673,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         length: IntValue<'b>,
         spec: &[ast::Parameter],
     ) {
-        self.abi
-            .decode(contract, function, args, data, length, spec);
+        self.decode(contract, function, args, data, length, spec);
     }
 
     fn print(&self, contract: &Contract, string_ptr: PointerValue, string_len: IntValue) {

+ 1 - 1
stdlib/Makefile

@@ -1,5 +1,5 @@
 CC=clang
-CFLAGS=--target=$(TARGET) -emit-llvm -O3 -ffreestanding -fno-builtin -Wall
+CFLAGS=--target=$(TARGET) -emit-llvm -O3 -ffreestanding -fno-builtin -Wall -Wno-unused-function
 
 %.bc: %.c
 	$(CC) -c $(CFLAGS) $< -o $@

BIN
stdlib/solana.bc


+ 2 - 2
stdlib/solana.c

@@ -4,8 +4,8 @@
 
 #include "solana_sdk.h"
 
-extern int solang_constructor(const uint8_t *input, uint32_t input_len);
-extern int solang_function(const uint8_t *input, uint32_t input_len);
+extern int solang_constructor(const uint8_t *input, uint64_t input_len);
+extern int solang_function(const uint8_t *input, uint64_t input_len);
 
 uint64_t
 entrypoint(const uint8_t *input)

+ 9 - 11
tests/solana.rs

@@ -264,18 +264,17 @@ fn simple() {
     assert_eq!(vm.printbuf, "Hello from function");
 }
 
-/*
 #[test]
 fn basic() {
     let mut vm = build_solidity(
         r#"
         contract foo {
-            function test(uint32 x, uint32 y) public {
+            function test(uint32 x, uint64 y) public {
                 if (x == 10) {
                     print("x is 10");
                 }
-                if (x + 10 == y) {
-                    print("x plus 10 is y");
+                if (y == 102) {
+                    print("y is 102");
                 }
             }
         }"#,
@@ -284,21 +283,20 @@ fn basic() {
     vm.function(
         "test",
         &[
-            ethabi::Token::Int(ethereum_types::U256::from(10)),
-            ethabi::Token::Int(ethereum_types::U256::from(10)),
+            ethabi::Token::Uint(ethereum_types::U256::from(10)),
+            ethabi::Token::Uint(ethereum_types::U256::from(10)),
         ],
     );
 
-    assert_eq!(vm.printbuf, "Hello from function");
+    assert_eq!(vm.printbuf, "x is 10");
 
     vm.function(
         "test",
         &[
-            ethabi::Token::Int(ethereum_types::U256::from(10)),
-            ethabi::Token::Int(ethereum_types::U256::from(20)),
+            ethabi::Token::Uint(ethereum_types::U256::from(99)),
+            ethabi::Token::Uint(ethereum_types::U256::from(102)),
         ],
     );
 
-    assert_eq!(vm.printbuf, "Hello from function");
+    assert_eq!(vm.printbuf, "y is 102");
 }
-*/