Quellcode durchsuchen

Instantiate contract by passing in empty contract

Signed-off-by: Sean Young <sean@mess.org>
Sean Young vor 4 Jahren
Ursprung
Commit
f4b2cb47e6

+ 21 - 0
integration/solana/create_contract.sol

@@ -0,0 +1,21 @@
+
+contract creator {
+    child public c;
+
+    function create_child() public {
+        print("Going to create child");
+        c = new child();
+
+        c.say_hello();
+    }
+}
+
+contract child {
+    constructor() {
+        print("In child constructor");
+    }
+
+    function say_hello() pure public {
+        print("Hello there");
+    }
+}

+ 22 - 0
integration/solana/create_contract.spec.ts

@@ -0,0 +1,22 @@
+import { Keypair } from '@solana/web3.js';
+import expect from 'expect';
+import { establishConnection } from './index';
+
+describe('Deploy solang contract and test', () => {
+    it('create_contract', async function () {
+        this.timeout(50000);
+
+        let conn = await establishConnection();
+
+        let creator = await conn.loadProgram("bundle.so", "creator.abi");
+
+        // call the constructor
+        await creator.call_constructor(conn, 'creator', []);
+
+        console.log("now create child");
+
+        let child = await conn.createStorageAccount(creator.get_program_key(), 1024);
+
+        await creator.call_function(conn, "create_child", [], [child.publicKey, creator.get_program_key()]);
+    });
+});

+ 2 - 0
integration/solana/index.ts

@@ -92,6 +92,8 @@ class TestConnection {
             },
         );
 
+        console.log('Contract storage account', account.publicKey.toBase58());
+
         return account;
     }
 

+ 1 - 1
src/emit/mod.rs

@@ -3756,7 +3756,7 @@ pub trait TargetRuntime<'a> {
                             .map(|a| self.expression(bin, &a, &w.vars, function, ns))
                             .collect::<Vec<BasicValueEnum>>();
 
-                        let address = bin.builder.build_alloca(bin.address_type(ns), "address");
+                        let address = bin.build_alloca(function, bin.address_type(ns), "address");
 
                         let gas = self
                             .expression(bin, gas, &w.vars, function, ns)

+ 155 - 65
src/emit/solana.rs

@@ -3,7 +3,6 @@ use crate::parser::pt;
 use crate::sema::ast;
 use crate::Target;
 use std::collections::HashMap;
-use std::convert::TryInto;
 use std::str;
 
 use inkwell::module::Linkage;
@@ -12,7 +11,6 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, Unn
 use inkwell::{context::Context, types::BasicTypeEnum};
 use inkwell::{AddressSpace, IntPredicate, OptimizationLevel};
 use num_traits::ToPrimitive;
-use tiny_keccak::{Hasher, Keccak};
 
 use super::ethabiencoder;
 use super::loop_builder::LoopBuilder;
@@ -42,16 +40,9 @@ impl SolanaTarget {
         opt: OptimizationLevel,
         math_overflow_check: bool,
     ) -> Binary<'a> {
-        // We need a magic number for our binary. This is used to check if the binary storage
-        // account is initialized for the correct binary
-        let mut hasher = Keccak::v256();
-        let mut hash = [0u8; 32];
-        hasher.update(contract.name.as_bytes());
-        hasher.finalize(&mut hash);
-
         let mut target = SolanaTarget {
             abi: ethabiencoder::EthAbiDecoder { bswap: true },
-            magic: u32::from_le_bytes(hash[0..4].try_into().unwrap()),
+            magic: contract.selector(),
         };
 
         let mut binary = Binary::new(
@@ -75,7 +66,6 @@ impl SolanaTarget {
             ReturnCode::AbiEncodingInvalid,
             context.i64_type().const_int(2u64 << 32, false),
         );
-
         // externals
         target.declare_externals(&mut binary);
 
@@ -164,12 +154,7 @@ impl SolanaTarget {
                 }
 
                 // We need a magic number for our contract.
-                let mut hasher = Keccak::v256();
-                let mut hash = [0u8; 32];
-                hasher.update(contract.name.as_bytes());
-                hasher.finalize(&mut hash);
-
-                target.magic = u32::from_le_bytes(hash[0..4].try_into().unwrap());
+                target.magic = contract.selector();
 
                 target.emit_functions(&mut binary, contract, ns);
 
@@ -2329,38 +2314,15 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         }
     }
 
-    /// sabre has no keccak256 host function, so call our implementation
     fn keccak256_hash(
         &self,
-        binary: &Binary,
-        src: PointerValue,
-        length: IntValue,
-        dest: PointerValue,
+        _binary: &Binary,
+        _src: PointerValue,
+        _length: IntValue,
+        _dest: PointerValue,
         _ns: &ast::Namespace,
     ) {
-        binary.builder.build_call(
-            binary.module.get_function("keccak256").unwrap(),
-            &[
-                binary
-                    .builder
-                    .build_pointer_cast(
-                        src,
-                        binary.context.i8_type().ptr_type(AddressSpace::Generic),
-                        "src",
-                    )
-                    .into(),
-                length.into(),
-                binary
-                    .builder
-                    .build_pointer_cast(
-                        dest,
-                        binary.context.i8_type().ptr_type(AddressSpace::Generic),
-                        "dest",
-                    )
-                    .into(),
-            ],
-            "",
-        );
+        unreachable!();
     }
 
     fn return_empty_abi(&self, binary: &Binary) {
@@ -2587,22 +2549,148 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         );
     }
 
-    /// Create new binary
+    /// Create new contract
     fn create_contract<'b>(
         &mut self,
-        _binary: &Binary<'b>,
-        _function: FunctionValue,
-        _success: Option<&mut BasicValueEnum<'b>>,
-        _binary_no: usize,
-        _constructor_no: Option<usize>,
-        _address: PointerValue<'b>,
-        _args: &[BasicValueEnum],
+        binary: &Binary<'b>,
+        function: FunctionValue<'b>,
+        success: Option<&mut BasicValueEnum<'b>>,
+        contract_no: usize,
+        constructor_no: Option<usize>,
+        address: PointerValue<'b>,
+        args: &[BasicValueEnum<'b>],
         _gas: IntValue<'b>,
-        _value: Option<IntValue<'b>>,
+        value: Option<IntValue<'b>>,
         _salt: Option<IntValue<'b>>,
-        _ns: &ast::Namespace,
+        ns: &ast::Namespace,
     ) {
-        unimplemented!();
+        // abi encode the arguments. The
+        let mut tys = vec![ast::Type::Bytes(4)];
+
+        if let Some(function_no) = constructor_no {
+            for param in &ns.functions[function_no].params {
+                tys.push(param.ty.clone());
+            }
+        };
+
+        let packed = [binary
+            .context
+            .i32_type()
+            .const_int(ns.contracts[contract_no].selector().to_be() as u64, false)
+            .into()];
+
+        let encoder = ethabiencoder::EncoderBuilder::new(
+            binary, function, false, &packed, args, &tys, true, ns,
+        );
+
+        let length = encoder.encoded_length();
+        let address_length = binary
+            .context
+            .i32_type()
+            .const_int(ns.address_length as u64, false);
+
+        let malloc_length = binary
+            .builder
+            .build_int_add(length, address_length, "malloc_length");
+
+        // The format of the payload is:
+        // 32 bytes address (will be filled in by create_contract C function)
+        // 4 bytes contract selector/magic
+        // remainder: eth abi encoded constructor arguments
+        let payload = binary
+            .builder
+            .build_call(
+                binary.module.get_function("__malloc").unwrap(),
+                &[malloc_length.into()],
+                "",
+            )
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_pointer_value();
+
+        let enc = unsafe { binary.builder.build_gep(payload, &[address_length], "enc") };
+
+        encoder.finish(binary, function, enc, ns);
+
+        let value = if let Some(value) = value {
+            value
+        } else {
+            binary.context.i64_type().const_int(0, false)
+        };
+
+        let sol_params = function.get_last_param().unwrap().into_pointer_value();
+
+        let ret = binary
+            .builder
+            .build_call(
+                binary.module.get_function("create_contract").unwrap(),
+                &[
+                    payload.into(),
+                    malloc_length.into(),
+                    value.into(),
+                    sol_params.into(),
+                ],
+                "",
+            )
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+
+        binary.builder.build_call(
+            binary.module.get_function("__beNtoleN").unwrap(),
+            &[
+                binary
+                    .builder
+                    .build_pointer_cast(
+                        payload,
+                        binary.context.i8_type().ptr_type(AddressSpace::Generic),
+                        "",
+                    )
+                    .into(),
+                binary
+                    .builder
+                    .build_pointer_cast(
+                        address,
+                        binary.context.i8_type().ptr_type(AddressSpace::Generic),
+                        "",
+                    )
+                    .into(),
+                binary
+                    .context
+                    .i32_type()
+                    .const_int(ns.address_length as u64, false)
+                    .into(),
+            ],
+            "",
+        );
+
+        let is_success = binary.builder.build_int_compare(
+            IntPredicate::EQ,
+            ret,
+            binary.context.i64_type().const_zero(),
+            "success",
+        );
+
+        if let Some(success) = success {
+            // we're in a try statement. This means:
+            // do not abort execution; return success or not in success variable
+            *success = is_success.into();
+        } else {
+            let success_block = binary.context.append_basic_block(function, "success");
+            let bail_block = binary.context.append_basic_block(function, "bail");
+
+            binary
+                .builder
+                .build_conditional_branch(is_success, success_block, bail_block);
+
+            binary.builder.position_at_end(bail_block);
+
+            binary.builder.build_return(Some(&ret));
+
+            binary.builder.position_at_end(success_block);
+        }
     }
 
     /// Call external binary
@@ -2736,7 +2824,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         binary: &Binary<'b>,
         expr: &ast::Expression,
         _vartab: &HashMap<usize, Variable<'b>>,
-        _function: FunctionValue<'b>,
+        function: FunctionValue<'b>,
         ns: &ast::Namespace,
     ) -> BasicValueEnum<'b> {
         match expr {
@@ -2783,9 +2871,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                     )
                     .into_pointer_value();
 
-                let value = binary
-                    .builder
-                    .build_alloca(binary.address_type(ns), "self_address");
+                let value = binary.build_alloca(function, binary.address_type(ns), "self_address");
 
                 binary.builder.build_call(
                     binary.module.get_function("__beNtoleN").unwrap(),
@@ -2850,12 +2936,16 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 "hash",
             );
         } else {
-            let u8_ptr = binary.context.i8_type().ptr_type(AddressSpace::Generic);
             let u64_ty = binary.context.i64_type();
 
-            let sol_bytes = binary
-                .context
-                .struct_type(&[u8_ptr.into(), u64_ty.into()], false);
+            let sol_keccak256 = binary.module.get_function(fname).unwrap();
+
+            // The first argument is a SolBytes *, get the struct
+            let sol_bytes = sol_keccak256.get_type().get_param_types()[0]
+                .into_pointer_type()
+                .get_element_type()
+                .into_struct_type();
+
             let array = binary.builder.build_alloca(sol_bytes, "sol_bytes");
 
             binary.builder.build_store(
@@ -2874,7 +2964,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             );
 
             binary.builder.build_call(
-                binary.module.get_function(fname).unwrap(),
+                sol_keccak256,
                 &[
                     array.into(),
                     binary.context.i32_type().const_int(1, false).into(),

+ 12 - 0
src/sema/contracts.rs

@@ -4,6 +4,8 @@ use num_bigint::BigInt;
 use num_traits::Zero;
 use std::collections::HashMap;
 use std::collections::HashSet;
+use std::convert::TryInto;
+use tiny_keccak::{Hasher, Keccak};
 
 use super::expression::match_constructor_to_args;
 use super::functions;
@@ -88,6 +90,16 @@ impl ast::Contract {
 
         out
     }
+
+    /// Selector for this contract. This is used by Solana contract bundle
+    pub fn selector(&self) -> u32 {
+        let mut hasher = Keccak::v256();
+        let mut hash = [0u8; 32];
+        hasher.update(self.name.as_bytes());
+        hasher.finalize(&mut hash);
+
+        u32::from_le_bytes(hash[0..4].try_into().unwrap())
+    }
 }
 
 /// Resolve the following contract

+ 15 - 1
src/sema/expression.rs

@@ -6807,7 +6807,10 @@ fn parse_call_args(
                 if ns.target == Target::Solana {
                     diagnostics.push(Diagnostic::error(
                         arg.loc,
-                        format!("‘gas’ not permitted for external calls on {}", ns.target),
+                        format!(
+                            "‘gas’ not permitted for external calls or constructors on {}",
+                            ns.target
+                        ),
                     ));
                     return Err(());
                 }
@@ -6827,6 +6830,17 @@ fn parse_call_args(
                 res.gas = Box::new(cast(&arg.expr.loc(), expr, &ty, true, ns, diagnostics)?);
             }
             "salt" => {
+                if ns.target == Target::Solana {
+                    diagnostics.push(Diagnostic::error(
+                        arg.loc,
+                        format!(
+                            "‘salt’ not permitted for external calls or constructors on {}",
+                            ns.target
+                        ),
+                    ));
+                    return Err(());
+                }
+
                 if external_call {
                     diagnostics.push(Diagnostic::error(
                         arg.loc,

+ 2 - 0
stdlib/Makefile

@@ -11,3 +11,5 @@ all: $(SOLANA) $(WASM)
 
 $(SOLANA): TARGET_FLAGS=--target=bpfel -march=bpfel+solana
 $(WASM): TARGET_FLAGS=--target=wasm32
+
+bpf/solana.bc: solana.c solana_sdk.h

BIN
stdlib/bpf/solana.bc


+ 51 - 7
stdlib/solana.c

@@ -52,15 +52,15 @@ void *__malloc(uint32_t size)
     return sol_alloc_free_(size, NULL);
 }
 
+uint64_t sol_invoke_signed_c(
+    const SolInstruction *instruction,
+    const SolAccountInfo *account_infos,
+    int account_infos_len,
+    const SolSignerSeeds *signers_seeds,
+    int signers_seeds_len);
+
 uint64_t external_call(uint8_t *input, uint32_t input_len, SolParameters *params)
 {
-    uint64_t sol_invoke_signed_c(
-        const SolInstruction *instruction,
-        const SolAccountInfo *account_infos,
-        int account_infos_len,
-        const SolSignerSeeds *signers_seeds,
-        int signers_seeds_len);
-
     // The first 32 bytes of the input is the destination address
     const SolPubkey *dest = (const SolPubkey *)input;
 
@@ -100,6 +100,50 @@ uint64_t external_call(uint8_t *input, uint32_t input_len, SolParameters *params
     }
 }
 
+// This function creates a new address and calls its constructor.
+uint64_t create_contract(uint8_t *input, uint32_t input_len, uint64_t lamports, SolParameters *params)
+{
+    const SolAccountInfo *new_acc = NULL;
+
+    SolAccountMeta metas[10];
+    const SolInstruction instruction = {
+        .program_id = (SolPubkey *)params->program_id,
+        .accounts = metas,
+        .account_len = params->ka_num,
+        .data = input,
+        .data_len = input_len,
+    };
+
+    // A fresh account must be provided by the caller; find it
+    for (int account_no = 0; account_no < params->ka_num; account_no++)
+    {
+        const SolAccountInfo *acc = &params->ka[account_no];
+
+        uint64_t *data = (uint64_t *)acc->data;
+
+        if (!new_acc && !*data && SolPubkey_same(params->program_id, acc->owner))
+        {
+            new_acc = acc;
+            params->ka_last_called = new_acc;
+        }
+
+        metas[account_no].pubkey = acc->key;
+        metas[account_no].is_writable = acc->is_writable;
+        metas[account_no].is_signer = acc->is_signer;
+    }
+
+    if (!new_acc)
+    {
+        sol_log("create contract requires a new account");
+
+        return ERROR_NEW_ACCOUNT_NEEDED;
+    }
+
+    __memcpy8(input, new_acc->key->x, SIZE_PUBKEY / 8);
+
+    return sol_invoke_signed_c(&instruction, params->ka, params->ka_num, NULL, 0);
+}
+
 struct clock_layout
 {
     uint64_t slot;

+ 2 - 0
stdlib/solana_sdk.h

@@ -45,6 +45,8 @@
 #define MAX_SEED_LENGTH_EXCEEDED TO_BUILTIN(13)
 /** Provided seeds do not result in a valid address */
 #define INVALID_SEEDS TO_BUILTIN(14)
+/** Need more account */
+#define ERROR_NEW_ACCOUNT_NEEDED TO_BUILTIN(15)
 
 /**
  * Boolean type

+ 187 - 57
tests/solana.rs

@@ -5,7 +5,7 @@ use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
 use ethabi::Token;
 use libc::c_char;
 use rand::Rng;
-use serde::Serialize;
+use serde::{Deserialize, Serialize};
 use sha2::{Digest, Sha256};
 use solana_helpers::allocator_bump::Allocator;
 use solana_rbpf::{
@@ -16,7 +16,9 @@ use solana_rbpf::{
     vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, SyscallObject, SyscallRegistry},
 };
 use solang::{
-    compile,
+    abi::generate_abi,
+    codegen::{codegen, Options},
+    compile_many,
     file_cache::FileCache,
     sema::{ast, diagnostics},
     Target,
@@ -44,8 +46,13 @@ fn account_new() -> Account {
     a
 }
 
+struct AccountState {
+    data: Vec<u8>,
+    owner: Option<Account>,
+}
+
 struct VirtualMachine {
-    account_data: HashMap<Account, (Vec<u8>, Option<Account>)>,
+    account_data: HashMap<Account, AccountState>,
     programs: Vec<Contract>,
     stack: Vec<Contract>,
     printbuf: String,
@@ -55,7 +62,7 @@ struct VirtualMachine {
 #[derive(Clone)]
 struct Contract {
     program: Account,
-    abi: ethabi::Contract,
+    abi: Option<ethabi::Contract>,
     data: Account,
 }
 
@@ -68,43 +75,78 @@ struct ClockLayout {
     unix_timestamp: u64,
 }
 
+#[derive(Deserialize)]
+struct CreateAccount {
+    instruction: u32,
+    _lamports: u64,
+    space: u64,
+    program_id: Account,
+}
+
 fn build_solidity(src: &str) -> VirtualMachine {
     let mut cache = FileCache::new();
 
     cache.set_file_contents("test.sol", src.to_string());
 
-    let (res, ns) = compile(
-        "test.sol",
-        &mut cache,
-        inkwell::OptimizationLevel::Default,
-        Target::Solana,
-        false,
-    );
+    let mut ns = solang::parse_and_resolve("test.sol", &mut cache, Target::Solana);
+
+    // codegen all the contracts; some additional errors/warnings will be detected here
+    for contract_no in 0..ns.contracts.len() {
+        codegen(contract_no, &mut ns, &Options::default());
+    }
 
     diagnostics::print_messages(&mut cache, &ns, false);
 
-    for v in &res {
-        println!("contract size:{}", v.0.len());
-    }
+    let context = inkwell::context::Context::create();
 
-    assert_eq!(res.is_empty(), false);
+    let namespaces = vec![ns];
+
+    let binary = compile_many(
+        &context,
+        &namespaces,
+        "bundle.sol",
+        inkwell::OptimizationLevel::Default,
+        false,
+    );
+
+    let code = binary.code(true).expect("llvm code emit should work");
 
     let mut account_data = HashMap::new();
     let mut programs = Vec::new();
 
     // resolve
-    for (code, abi) in res {
+    let ns = &namespaces[0];
+
+    for contract_no in 0..ns.contracts.len() {
+        let (abi, _) = generate_abi(contract_no, &ns, &code, false);
+
         let program = account_new();
 
-        account_data.insert(program, (code.clone(), None));
+        account_data.insert(
+            program,
+            AccountState {
+                data: code.clone(),
+                owner: None,
+            },
+        );
 
         let abi = ethabi::Contract::load(abi.as_bytes()).unwrap();
 
         let data = account_new();
 
-        account_data.insert(data, ([0u8; 4096].to_vec(), Some(program)));
+        account_data.insert(
+            data,
+            AccountState {
+                data: [0u8; 4096].to_vec(),
+                owner: Some(program),
+            },
+        );
 
-        programs.push(Contract { program, abi, data });
+        programs.push(Contract {
+            program,
+            abi: Some(abi),
+            data,
+        });
     }
 
     // Add clock account
@@ -124,7 +166,10 @@ fn build_solidity(src: &str) -> VirtualMachine {
 
     account_data.insert(
         clock_account,
-        (bincode::serialize(&clock_layout).unwrap(), None),
+        AccountState {
+            data: bincode::serialize(&clock_layout).unwrap(),
+            owner: None,
+        },
     );
 
     let cur = programs.last().unwrap().clone();
@@ -143,7 +188,7 @@ const MAX_PERMITTED_DATA_INCREASE: usize = 10 * 1024;
 fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec<u8> {
     let mut v: Vec<u8> = Vec::new();
 
-    fn serialize_account(v: &mut Vec<u8>, key: &Account, acc: &(Vec<u8>, Option<Account>)) {
+    fn serialize_account(v: &mut Vec<u8>, key: &Account, acc: &AccountState) {
         // dup_info
         v.write_u8(0xff).unwrap();
         // signer
@@ -157,13 +202,13 @@ fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec<u8> {
         // key
         v.write_all(key).unwrap();
         // owner
-        v.write_all(&acc.1.unwrap_or([0u8; 32])).unwrap();
+        v.write_all(&acc.owner.unwrap_or([0u8; 32])).unwrap();
         // lamports
         v.write_u64::<LittleEndian>(0).unwrap();
 
         // account data
-        v.write_u64::<LittleEndian>(acc.0.len() as u64).unwrap();
-        v.write_all(&acc.0).unwrap();
+        v.write_u64::<LittleEndian>(acc.data.len() as u64).unwrap();
+        v.write_all(&acc.data).unwrap();
         v.write_all(&[0u8; MAX_PERMITTED_DATA_INCREASE]).unwrap();
 
         let padding = v.len() % 8;
@@ -190,16 +235,13 @@ fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec<u8> {
     v.write_all(input).unwrap();
 
     // program id
-    v.write_all(&[0u8; 32]).unwrap();
+    v.write_all(&vm.stack[0].program).unwrap();
 
     v
 }
 
 // We want to extract the account data
-fn deserialize_parameters(
-    input: &[u8],
-    accounts_data: &mut HashMap<Account, (Vec<u8>, Option<Account>)>,
-) {
+fn deserialize_parameters(input: &[u8], accounts_data: &mut HashMap<Account, AccountState>) {
     let mut start = 0;
 
     let ka_num = LittleEndian::read_u64(&input[start..]);
@@ -217,7 +259,45 @@ fn deserialize_parameters(
         let data = input[start..start + data_len].to_vec();
 
         if let Some(entry) = accounts_data.get_mut(&account) {
-            entry.0 = data;
+            entry.data = data;
+        }
+
+        start += data_len + MAX_PERMITTED_DATA_INCREASE;
+
+        let padding = start % 8;
+        if padding > 0 {
+            start += 8 - padding
+        }
+
+        start += size_of::<u64>();
+    }
+}
+
+// We want to extract the account data
+fn update_parameters(input: &[u8], accounts_data: &HashMap<Account, AccountState>) {
+    let mut start = 0;
+
+    let ka_num = LittleEndian::read_u64(&input[start..]);
+    start += size_of::<u64>();
+
+    for _ in 0..ka_num {
+        start += 8;
+
+        let account: Account = input[start..start + 32].try_into().unwrap();
+
+        start += 32 + 32 + 8;
+
+        let data_len = LittleEndian::read_u64(&input[start..]) as usize;
+        start += size_of::<u64>();
+
+        if let Some(entry) = accounts_data.get(&account) {
+            unsafe {
+                std::ptr::copy(
+                    entry.data.as_ptr(),
+                    input[start..].as_ptr() as *mut u8,
+                    data_len,
+                );
+            }
         }
 
         start += data_len + MAX_PERMITTED_DATA_INCREASE;
@@ -480,6 +560,12 @@ pub struct Instruction {
 #[derive(Debug, PartialEq, Clone)]
 pub struct Pubkey([u8; 32]);
 
+impl Pubkey {
+    fn is_system_instruction(&self) -> bool {
+        self.0 == [0u8; 32]
+    }
+}
+
 #[derive(Debug, PartialEq, Clone)]
 pub struct AccountMeta {
     /// An account's public key
@@ -559,7 +645,6 @@ fn translate_slice_inner<'a, T>(
 struct SyscallInvokeSignedC<'a> {
     context: Rc<RefCell<&'a mut VirtualMachine>>,
     input: &'a [u8],
-    calldata: &'a [u8],
 }
 
 impl<'a> SyscallInvokeSignedC<'a> {
@@ -613,35 +698,61 @@ impl<'a> SyscallObject<UserError> for SyscallInvokeSignedC<'a> {
             .translate_instruction(instruction_addr, memory_mapping)
             .expect("instruction not valid");
 
-        let data_id: Account = instruction.data[..32].try_into().unwrap();
-
         println!("instruction:{:?}", instruction);
 
         if let Ok(mut context) = self.context.try_borrow_mut() {
-            let p = context
-                .programs
-                .iter()
-                .find(|p| p.program == instruction.program_id.0 && p.data == data_id)
-                .unwrap()
-                .clone();
+            if instruction.program_id.is_system_instruction() {
+                let create_account: CreateAccount =
+                    bincode::deserialize(&instruction.data).unwrap();
 
-            context.stack.insert(0, p);
+                let address = &instruction.accounts[1].pubkey;
 
-            context.execute(&instruction.data);
+                assert_eq!(create_account.instruction, 0);
 
-            let parameter_bytes = serialize_parameters(&self.calldata, &context);
+                println!(
+                    "creating account {} with space {} owner {}",
+                    hex::encode(address.0),
+                    create_account.space,
+                    hex::encode(create_account.program_id)
+                );
 
-            assert_eq!(parameter_bytes.len(), self.input.len());
+                context.account_data.insert(
+                    address.0,
+                    AccountState {
+                        data: vec![0; create_account.space as usize],
+                        owner: Some(create_account.program_id),
+                    },
+                );
 
-            unsafe {
-                std::ptr::copy(
-                    parameter_bytes.as_ptr(),
-                    self.input.as_ptr() as *mut u8,
-                    parameter_bytes.len(),
+                context.programs.push(Contract {
+                    program: create_account.program_id,
+                    abi: None,
+                    data: address.0,
+                });
+            } else {
+                let data_id: Account = instruction.data[..32].try_into().unwrap();
+
+                println!(
+                    "calling {} program_id {}",
+                    hex::encode(data_id),
+                    hex::encode(instruction.program_id.0)
                 );
-            }
 
-            context.stack.remove(0);
+                let p = context
+                    .programs
+                    .iter()
+                    .find(|p| p.program == instruction.program_id.0 && p.data == data_id)
+                    .unwrap()
+                    .clone();
+
+                context.stack.insert(0, p);
+
+                context.execute(&instruction.data);
+
+                update_parameters(self.input, &context.account_data);
+
+                context.stack.remove(0);
+            }
         }
 
         *result = Ok(0)
@@ -659,7 +770,7 @@ impl VirtualMachine {
         let program = &self.stack[0];
 
         let mut executable = <dyn Executable<UserError, DefaultInstructionMeter>>::from_elf(
-            &self.account_data[&program.program].0,
+            &self.account_data[&program.program].data,
             None,
             Config::default(),
         )
@@ -729,7 +840,6 @@ impl VirtualMachine {
             Box::new(SyscallInvokeSignedC {
                 context: context.clone(),
                 input: &parameter_bytes,
-                calldata: &calldata,
             }),
             None,
         )
@@ -743,7 +853,7 @@ impl VirtualMachine {
 
         deserialize_parameters(&parameter_bytes, &mut elf.account_data);
 
-        let output = &elf.account_data[&elf.stack[0].data].0;
+        let output = &elf.account_data[&elf.stack[0].data].data;
 
         VirtualMachine::validate_heap(&output);
 
@@ -769,7 +879,7 @@ impl VirtualMachine {
         hasher.finalize(&mut hash);
         calldata.extend(&hash[0..4]);
 
-        if let Some(constructor) = &program.abi.constructor {
+        if let Some(constructor) = &program.abi.as_ref().unwrap().constructor {
             calldata.extend(&constructor.encode_input(vec![], args).unwrap());
         };
 
@@ -785,7 +895,7 @@ impl VirtualMachine {
 
         calldata.extend(&0u32.to_le_bytes());
 
-        match program.abi.functions[name][0].encode_input(args) {
+        match program.abi.as_ref().unwrap().functions[name][0].encode_input(args) {
             Ok(n) => calldata.extend(&n),
             Err(x) => panic!("{}", x),
         };
@@ -798,7 +908,7 @@ impl VirtualMachine {
 
         let program = &self.stack[0];
 
-        program.abi.functions[name][0]
+        program.abi.as_ref().unwrap().functions[name][0]
             .decode_output(&self.output)
             .unwrap()
     }
@@ -806,7 +916,7 @@ impl VirtualMachine {
     fn data(&self) -> &Vec<u8> {
         let program = &self.stack[0];
 
-        &self.account_data[&program.data].0
+        &self.account_data[&program.data].data
     }
 
     fn set_program(&mut self, no: usize) {
@@ -815,6 +925,26 @@ impl VirtualMachine {
         self.stack = vec![cur];
     }
 
+    fn create_empty_account(&mut self, size: usize) {
+        let account = account_new();
+
+        println!("new empty account {}", account.to_base58());
+
+        self.account_data.insert(
+            account,
+            AccountState {
+                data: vec![0u8; size],
+                owner: Some(self.stack[0].program),
+            },
+        );
+
+        self.programs.push(Contract {
+            program: self.stack[0].program,
+            abi: None,
+            data: account,
+        });
+    }
+
     fn validate_heap(data: &[u8]) {
         let mut prev_offset = 0;
         let return_len = LittleEndian::read_u32(&data[4..]) as usize;

+ 1 - 1
tests/solana_tests/call.rs

@@ -52,7 +52,7 @@ fn calltys() {
 
     assert_eq!(
         first_error(ns.diagnostics),
-        "‘gas’ not permitted for external calls on solana"
+        "‘gas’ not permitted for external calls or constructors on solana"
     );
 }
 

+ 162 - 0
tests/solana_tests/create_contract.rs

@@ -0,0 +1,162 @@
+use crate::{build_solidity, first_error, parse_and_resolve};
+use ethabi::Token;
+use solang::Target;
+
+#[test]
+fn simple_create_contract() {
+    let mut vm = build_solidity(
+        r#"
+        contract bar0 {
+            function test_other() public returns (bar1) {
+                bar1 x = new bar1("yo from bar0");
+
+                return x;
+            }
+
+            function call_bar1_at_address(bar1 a, string x) public {
+                a.say_hello(x);
+            }
+        }
+
+        contract bar1 {
+            constructor(string v) {
+                print("bar1 says: " + v);
+            }
+
+            function say_hello(string v) public {
+                print("Hello {}".format(v));
+            }
+        }"#,
+    );
+
+    vm.set_program(0);
+
+    vm.constructor("bar0", &[]);
+
+    vm.create_empty_account(8192);
+
+    let bar1 = vm.function("test_other", &[]);
+
+    assert_eq!(vm.printbuf, "bar1 says: yo from bar0");
+
+    vm.printbuf.truncate(0);
+
+    println!("next test, {:?}", bar1);
+
+    vm.function(
+        "call_bar1_at_address",
+        &[bar1[0].clone(), Token::String(String::from("xywoleh"))],
+    );
+
+    assert_eq!(vm.printbuf, "Hello xywoleh");
+}
+
+#[test]
+// 64424509440 = 15 << 32 (ERROR_NEW_ACCOUNT_NEEDED)
+#[should_panic(expected = "64424509440")]
+fn missing_contract() {
+    let mut vm = build_solidity(
+        r#"
+        contract bar0 {
+            function test_other() public returns (bar1) {
+                bar1 x = new bar1("yo from bar0");
+
+                return x;
+            }
+
+            function call_bar1_at_address(bar1 a, string x) public {
+                a.say_hello(x);
+            }
+        }
+
+        contract bar1 {
+            constructor(string v) {
+                print("bar1 says: " + v);
+            }
+
+            function say_hello(string v) public {
+                print("Hello {}".format(v));
+            }
+        }"#,
+    );
+
+    vm.set_program(0);
+
+    vm.constructor("bar0", &[]);
+
+    let _ = vm.function("test_other", &[]);
+}
+
+#[test]
+fn two_contracts() {
+    let mut vm = build_solidity(
+        r#"
+        contract bar0 {
+            function test_other() public returns (bar1) {
+                bar1 x = new bar1("yo from bar0");
+                bar1 y = new bar1("hi from bar0");
+
+                return x;
+            }
+        }
+
+        contract bar1 {
+            constructor(string v) {
+                print("bar1 says: " + v);
+            }
+        }"#,
+    );
+
+    vm.set_program(0);
+
+    vm.constructor("bar0", &[]);
+
+    vm.create_empty_account(8192);
+    vm.create_empty_account(8192);
+
+    let _bar1 = vm.function("test_other", &[]);
+
+    assert_eq!(
+        vm.printbuf,
+        "bar1 says: yo from bar0bar1 says: hi from bar0"
+    );
+
+    vm.printbuf.truncate(0);
+}
+
+#[test]
+fn syntax() {
+    let ns = parse_and_resolve(
+        r#"
+        contract y {
+            function f() public {
+                x a = new x{gas: 102}();
+            }
+        }
+        contract x {}
+    "#,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "‘gas’ not permitted for external calls or constructors on solana"
+    );
+
+    let ns = parse_and_resolve(
+        r#"
+        contract y {
+            function f() public {
+                x a = new x{salt: 102}();
+            }
+        }
+        contract x {}
+    "#,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "‘salt’ not permitted for external calls or constructors on solana"
+    );
+}

+ 1 - 0
tests/solana_tests/mod.rs

@@ -3,6 +3,7 @@ mod accessor;
 mod arrays;
 mod builtin;
 mod call;
+mod create_contract;
 mod destructure;
 mod hash;
 mod mappings;