Prechádzať zdrojové kódy

Implement external function calls for Solana

No return data can be passed back yet, this will be implemented
in another commit.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 rokov pred
rodič
commit
c1db5ea540

+ 18 - 0
integration/solana/external_call.sol

@@ -0,0 +1,18 @@
+
+contract caller {
+    function do_call(callee e, int64 v) public {
+        e.set_x(v);
+    }
+}
+
+contract callee {
+    int64 x;
+
+    function set_x(int64 v) public {
+        x = v;
+    }
+
+    function get_x() public view returns (int64) {
+        return x;
+    }
+}

+ 24 - 4
integration/solana/index.ts

@@ -158,7 +158,7 @@ class Program {
         );
     }
 
-    async call_function(test: TestConnection, name: string, params: any[]): Promise<{ [key: string]: any }> {
+    async call_function(test: TestConnection, name: string, params: any[], pubkeys: PublicKey[] = []): Promise<{ [key: string]: any }> {
         let abi: AbiItem = JSON.parse(this.abi).find((e: AbiItem) => e.name == name);
 
         const input: string = Web3EthAbi.encodeFunctionCall(abi, params);
@@ -169,10 +169,17 @@ class Program {
 
         let debug = 'calling function ' + name + ' [' + params + ']';
 
+        let keys = [
+            { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true },
+            { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }];
+
+
+        for (let i = 0; i < pubkeys.length; i++) {
+            keys.push({ pubkey: pubkeys[i], isSigner: false, isWritable: true });
+        }
+
         const instruction = new TransactionInstruction({
-            keys: [
-                { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true },
-                { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }],
+            keys,
             programId: this.programId,
             data,
         });
@@ -222,4 +229,17 @@ class Program {
 
         return accountInfo!.data;
     }
+
+
+    all_keys(): PublicKey[] {
+        return [this.programId, this.contractStorageAccount.publicKey];
+    }
+
+    get_program_key(): PublicKey {
+        return this.programId;
+    }
+
+    get_storage_key(): PublicKey {
+        return this.contractStorageAccount.publicKey;
+    }
 }

+ 28 - 0
integration/solana/simple.spec.ts

@@ -532,4 +532,32 @@ describe('Deploy solang contract and test', () => {
 
         expect(res).toBe(JSON.stringify([false]));
     });
+
+    it('external_call', async function () {
+        this.timeout(50000);
+
+        let conn = await establishConnection();
+
+        let caller = await conn.loadProgram("caller.so", "caller.abi");
+        let callee = await conn.loadProgram("callee.so", "callee.abi");
+
+        // call the constructor
+        await caller.call_constructor(conn, []);
+        await callee.call_constructor(conn, []);
+
+        await callee.call_function(conn, "set_x", ["102"]);
+
+        let res = await callee.call_function(conn, "get_x", []);
+
+        expect(res["0"]).toBe("102");
+
+        let address = '0x' + callee.get_storage_key().toBuffer().toString('hex');
+        console.log("addres: " + address);
+
+        await caller.call_function(conn, "do_call", [address, "13123"], callee.all_keys());
+
+        res = await callee.call_function(conn, "get_x", []);
+
+        expect(res["0"]).toBe("13123");
+    });
 });

+ 49 - 12
src/codegen/expression.rs

@@ -1267,12 +1267,26 @@ pub fn emit_function_call(
 
             let success = vartab.temp_name("success", &Type::Bool);
 
+            let (payload, address) = if ns.target == Target::Solana {
+                (
+                    Expression::AbiEncode {
+                        loc: *loc,
+                        packed: vec![address, args],
+                        args: Vec::new(),
+                        tys: vec![Type::Address(false), Type::DynamicBytes],
+                    },
+                    None,
+                )
+            } else {
+                (args, Some(address))
+            };
+
             cfg.add(
                 vartab,
                 Instr::ExternalCall {
                     success: Some(success),
-                    address: Some(address),
-                    payload: args,
+                    address,
+                    payload,
                     value,
                     gas,
                     callty: ty.clone(),
@@ -1312,22 +1326,45 @@ pub fn emit_function_call(
 
                 tys.insert(0, Type::Bytes(4));
 
-                let payload = Expression::AbiEncode {
-                    loc: *loc,
-                    tys,
-                    packed: vec![Expression::NumberLiteral(
-                        *loc,
-                        Type::Bytes(4),
-                        BigInt::from(dest_func.selector()),
-                    )],
-                    args,
+                let (payload, address) = if ns.target == Target::Solana {
+                    tys.insert(0, Type::Address(false));
+                    (
+                        Expression::AbiEncode {
+                            loc: *loc,
+                            tys,
+                            packed: vec![
+                                address,
+                                Expression::NumberLiteral(
+                                    *loc,
+                                    Type::Bytes(4),
+                                    BigInt::from(dest_func.selector()),
+                                ),
+                            ],
+                            args,
+                        },
+                        None,
+                    )
+                } else {
+                    (
+                        Expression::AbiEncode {
+                            loc: *loc,
+                            tys,
+                            packed: vec![Expression::NumberLiteral(
+                                *loc,
+                                Type::Bytes(4),
+                                BigInt::from(dest_func.selector()),
+                            )],
+                            args,
+                        },
+                        Some(address),
+                    )
                 };
 
                 cfg.add(
                     vartab,
                     Instr::ExternalCall {
                         success: None,
-                        address: Some(address),
+                        address,
                         payload,
                         value,
                         gas,

+ 2 - 2
src/emit/ewasm.rs

@@ -1354,7 +1354,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
         success: Option<&mut BasicValueEnum<'b>>,
         payload: PointerValue<'b>,
         payload_len: IntValue<'b>,
-        address: PointerValue<'b>,
+        address: Option<PointerValue<'b>>,
         gas: IntValue<'b>,
         value: IntValue<'b>,
         callty: ast::CallTy,
@@ -1370,7 +1370,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
                 contract
                     .builder
                     .build_pointer_cast(
-                        address,
+                        address.unwrap(),
                         contract.context.i8_type().ptr_type(AddressSpace::Generic),
                         "",
                     )

+ 1 - 1
src/emit/generic.rs

@@ -610,7 +610,7 @@ impl<'a> TargetRuntime<'a> for GenericTarget {
         _success: Option<&mut BasicValueEnum<'b>>,
         _payload: PointerValue<'b>,
         _payload_len: IntValue<'b>,
-        _address: PointerValue<'b>,
+        _address: Option<PointerValue<'b>>,
         _gas: IntValue<'b>,
         _value: IntValue<'b>,
         _ty: ast::CallTy,

+ 42 - 20
src/emit/mod.rs

@@ -247,7 +247,7 @@ pub trait TargetRuntime<'a> {
         success: Option<&mut BasicValueEnum<'b>>,
         payload: PointerValue<'b>,
         payload_len: IntValue<'b>,
-        address: PointerValue<'b>,
+        address: Option<PointerValue<'b>>,
         gas: IntValue<'b>,
         value: IntValue<'b>,
         ty: ast::CallTy,
@@ -3862,26 +3862,32 @@ pub trait TargetRuntime<'a> {
                             .expression(contract, value, &w.vars, function)
                             .into_int_value();
                         let payload = self.expression(contract, payload, &w.vars, function);
-                        let address =
-                            self.expression(contract, address.as_ref().unwrap(), &w.vars, function);
 
-                        let addr = contract.builder.build_array_alloca(
-                            contract.context.i8_type(),
-                            contract
-                                .context
-                                .i32_type()
-                                .const_int(contract.ns.address_length as u64, false),
-                            "address",
-                        );
+                        let address = if let Some(address) = address {
+                            let address = self.expression(contract, address, &w.vars, function);
 
-                        contract.builder.build_store(
-                            contract.builder.build_pointer_cast(
-                                addr,
-                                address.get_type().ptr_type(AddressSpace::Generic),
+                            let addr = contract.builder.build_array_alloca(
+                                contract.context.i8_type(),
+                                contract
+                                    .context
+                                    .i32_type()
+                                    .const_int(contract.ns.address_length as u64, false),
                                 "address",
-                            ),
-                            address,
-                        );
+                            );
+
+                            contract.builder.build_store(
+                                contract.builder.build_pointer_cast(
+                                    addr,
+                                    address.get_type().ptr_type(AddressSpace::Generic),
+                                    "address",
+                                ),
+                                address,
+                            );
+
+                            Some(addr)
+                        } else {
+                            None
+                        };
 
                         let success = match success {
                             Some(n) => Some(&mut w.vars.get_mut(n).unwrap().value),
@@ -3894,7 +3900,7 @@ pub trait TargetRuntime<'a> {
                             success,
                             contract.vector_bytes(payload),
                             contract.vector_len(payload),
-                            addr,
+                            address,
                             gas,
                             value,
                             callty.clone(),
@@ -4349,7 +4355,23 @@ pub trait TargetRuntime<'a> {
         }
 
         if contract.ns.target == Target::Solana {
-            args.push(function.get_last_param().unwrap());
+            let params_ty = dest
+                .get_type()
+                .get_param_types()
+                .last()
+                .unwrap()
+                .into_pointer_type();
+
+            args.push(
+                contract
+                    .builder
+                    .build_pointer_cast(
+                        function.get_last_param().unwrap().into_pointer_value(),
+                        params_ty,
+                        "",
+                    )
+                    .into(),
+            );
         }
 
         let ret = contract

+ 1 - 1
src/emit/sabre.rs

@@ -698,7 +698,7 @@ impl<'a> TargetRuntime<'a> for SabreTarget {
         _success: Option<&mut BasicValueEnum<'b>>,
         _payload: PointerValue<'b>,
         _payload_len: IntValue<'b>,
-        _address: PointerValue<'b>,
+        _address: Option<PointerValue<'b>>,
         _gas: IntValue<'b>,
         _value: IntValue<'b>,
         _ty: ast::CallTy,

+ 87 - 12
src/emit/solana.rs

@@ -371,9 +371,16 @@ impl SolanaTarget {
             contract.context.i32_type().const_int(heap_offset, false),
         );
 
-        contract
-            .builder
-            .build_call(initializer, &[sol_params.into()], "");
+        let arg_ty = initializer.get_type().get_param_types()[0].into_pointer_type();
+
+        contract.builder.build_call(
+            initializer,
+            &[contract
+                .builder
+                .build_pointer_cast(sol_params, arg_ty, "")
+                .into()],
+            "",
+        );
 
         // There is only one possible constructor
         let ret = if let Some((cfg_no, cfg)) = contract
@@ -389,11 +396,24 @@ impl SolanaTarget {
             self.abi
                 .decode(contract, function, &mut args, input, input_len, &cfg.params);
 
-            args.push(sol_params.into());
+            let function = contract.functions[&cfg_no];
+            let params_ty = function
+                .get_type()
+                .get_param_types()
+                .last()
+                .unwrap()
+                .into_pointer_type();
+
+            args.push(
+                contract
+                    .builder
+                    .build_pointer_cast(sol_params, params_ty, "")
+                    .into(),
+            );
 
             contract
                 .builder
-                .build_call(contract.functions[&cfg_no], &args, "")
+                .build_call(function, &args, "")
                 .try_as_basic_value()
                 .left()
                 .unwrap()
@@ -1250,6 +1270,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .build_conditional_branch(in_range, get_block, bang_block);
 
         contract.builder.position_at_end(bang_block);
+
         self.assert_failure(
             contract,
             contract
@@ -2370,17 +2391,71 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     /// Call external contract
     fn external_call<'b>(
         &self,
-        _contract: &Contract<'b>,
-        _function: FunctionValue,
-        _success: Option<&mut BasicValueEnum<'b>>,
-        _payload: PointerValue<'b>,
-        _payload_len: IntValue<'b>,
-        _address: PointerValue<'b>,
+        contract: &Contract<'b>,
+        function: FunctionValue,
+        success: Option<&mut BasicValueEnum<'b>>,
+        payload: PointerValue<'b>,
+        payload_len: IntValue<'b>,
+        _address: Option<PointerValue<'b>>,
         _gas: IntValue<'b>,
         _value: IntValue<'b>,
         _ty: ast::CallTy,
     ) {
-        unimplemented!();
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap();
+
+        let ret = contract
+            .builder
+            .build_call(
+                contract.module.get_function("external_call").unwrap(),
+                &[payload.into(), payload_len.into(), parameters],
+                "",
+            )
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+
+        let is_success = contract.builder.build_int_compare(
+            IntPredicate::EQ,
+            ret,
+            contract.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 = contract.context.append_basic_block(function, "success");
+            let bail_block = contract.context.append_basic_block(function, "bail");
+
+            contract
+                .builder
+                .build_conditional_branch(is_success, success_block, bail_block);
+
+            contract.builder.position_at_end(bail_block);
+
+            // should we log "call failed?"
+            self.assert_failure(
+                contract,
+                contract
+                    .context
+                    .i8_type()
+                    .ptr_type(AddressSpace::Generic)
+                    .const_null(),
+                contract.context.i32_type().const_zero(),
+            );
+
+            contract.builder.position_at_end(success_block);
+        }
     }
 
     /// Get return buffer for external call

+ 2 - 2
src/emit/substrate.rs

@@ -3468,7 +3468,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         success: Option<&mut BasicValueEnum<'b>>,
         payload: PointerValue<'b>,
         payload_len: IntValue<'b>,
-        address: PointerValue<'b>,
+        address: Option<PointerValue<'b>>,
         gas: IntValue<'b>,
         value: IntValue<'b>,
         _ty: ast::CallTy,
@@ -3500,7 +3500,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_call(
                 contract.module.get_function("seal_call").unwrap(),
                 &[
-                    address.into(),
+                    address.unwrap().into(),
                     contract
                         .context
                         .i32_type()

+ 7 - 0
src/sema/expression.rs

@@ -6688,6 +6688,13 @@ fn parse_call_args(
                 )?));
             }
             "gas" => {
+                if ns.target == Target::Solana {
+                    diagnostics.push(Diagnostic::error(
+                        arg.loc,
+                        format!("‘gas’ not permitted for external calls on {}", ns.target),
+                    ));
+                    return Err(());
+                }
                 let ty = Type::Uint(64);
 
                 let expr = expression(

BIN
stdlib/bpf/solana.bc


+ 53 - 0
stdlib/solana.c

@@ -43,6 +43,59 @@ void *__malloc(uint32_t size)
     return sol_alloc_free_(size, NULL);
 }
 
+uint64_t external_call(uint8_t *input, uint32_t input_len, const 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;
+
+    for (int account_no = 1; account_no < params->ka_num; account_no++)
+    {
+        const SolAccountInfo *acc = &params->ka[account_no];
+
+        if (SolPubkey_same(dest, acc->key))
+        {
+            // found it
+            SolAccountMeta metas[3] = {
+                {
+                    .pubkey = params->ka[0].key,
+                    .is_writable = true,
+                    .is_signer = false,
+                },
+                {
+                    .pubkey = acc->key,
+                    .is_writable = true,
+                    .is_signer = false,
+                },
+                {
+                    .pubkey = acc->owner,
+                    .is_writable = false,
+                    .is_signer = false,
+                }};
+
+            SolInstruction instruction = {
+                .program_id = acc->owner,
+                .accounts = metas,
+                .account_len = SOL_ARRAY_SIZE(metas),
+                .data = input,
+                .data_len = input_len,
+            };
+
+            return sol_invoke_signed_c(&instruction, params->ka, params->ka_num, NULL, 0);
+        }
+    }
+
+    sol_log("call to account not in transaction");
+
+    return ERROR_INVALID_ACCOUNT_DATA;
+}
+
 struct account_data_header
 {
     uint32_t magic;

+ 361 - 70
tests/solana.rs

@@ -11,10 +11,19 @@ use solana_rbpf::{
     user_error::UserError,
     vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, Syscall, SyscallObject},
 };
-use solang::{compile, file_cache::FileCache, sema::diagnostics, Target};
+use solang::{
+    compile,
+    file_cache::FileCache,
+    sema::{ast, diagnostics},
+    Target,
+};
 use std::alloc::Layout;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::TryInto;
 use std::io::Write;
 use std::mem::{align_of, size_of};
+use std::rc::Rc;
 
 mod solana_tests;
 
@@ -30,7 +39,23 @@ fn account_new() -> Account {
     a
 }
 
-fn build_solidity(src: &str) -> Program {
+struct VirtualMachine {
+    account_data: HashMap<Account, (Vec<u8>, Option<Account>)>,
+    programs: Vec<Contract>,
+    stack: Vec<Contract>,
+    returndata: Account,
+    printbuf: String,
+    output: Vec<u8>,
+}
+
+#[derive(Clone)]
+struct Contract {
+    program: Account,
+    abi: ethabi::Contract,
+    data: Account,
+}
+
+fn build_solidity(src: &str) -> VirtualMachine {
     let mut cache = FileCache::new();
 
     cache.set_file_contents("test.sol", src.to_string());
@@ -51,27 +76,46 @@ fn build_solidity(src: &str) -> Program {
 
     assert_eq!(res.is_empty(), false);
 
+    let mut account_data = HashMap::new();
+    let mut programs = Vec::new();
+
     // resolve
-    let (code, abi) = res.last().unwrap().clone();
+    for (code, abi) in res {
+        let program = account_new();
+
+        account_data.insert(program, (code.clone(), None));
+
+        let abi = ethabi::Contract::load(abi.as_bytes()).unwrap();
+
+        let data = account_new();
+
+        account_data.insert(data, ([0u8; 4096].to_vec(), Some(program)));
+
+        programs.push(Contract { program, abi, data });
+    }
 
-    Program {
-        code,
-        abi: ethabi::Contract::load(abi.as_bytes()).unwrap(),
-        account: account_new(),
+    let returndata = account_new();
+
+    account_data.insert(returndata, ([0u8; 1024].to_vec(), None));
+
+    let cur = programs.last().unwrap().clone();
+
+    VirtualMachine {
+        account_data,
+        programs,
+        stack: vec![cur],
+        returndata,
         printbuf: String::new(),
         output: Vec::new(),
-        data: Vec::new(),
     }
 }
 
 const MAX_PERMITTED_DATA_INCREASE: usize = 10 * 1024;
 
-fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec<u8> {
+fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec<u8> {
     let mut v: Vec<u8> = Vec::new();
 
-    // ka_num
-    v.write_u64::<LittleEndian>(2).unwrap();
-    for account_no in 0..2 {
+    fn serialize_account(v: &mut Vec<u8>, key: &Account, acc: &(Vec<u8>, Option<Account>)) {
         // dup_info
         v.write_u8(0xff).unwrap();
         // signer
@@ -83,27 +127,15 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec<u8>
         // padding
         v.write_all(&[0u8; 4]).unwrap();
         // key
-        if account_no == 1 {
-            v.write_all(&account[..]).unwrap();
-        } else {
-            v.write_all(&account_new()).unwrap();
-        }
+        v.write_all(key).unwrap();
         // owner
-        v.write_all(&[0u8; 32]).unwrap();
+        v.write_all(&acc.1.unwrap_or([0u8; 32])).unwrap();
         // lamports
         v.write_u64::<LittleEndian>(0).unwrap();
 
         // account data
-        // data len
-        if account_no == 1 {
-            v.write_u64::<LittleEndian>(4096).unwrap();
-            let mut data = data.to_vec();
-            data.resize(4096, 0);
-            v.write_all(&data).unwrap();
-        } else {
-            v.write_u64::<LittleEndian>(4096).unwrap();
-            v.write_all(&[0u8; 4096]).unwrap();
-        }
+        v.write_u64::<LittleEndian>(acc.0.len() as u64).unwrap();
+        v.write_all(&acc.0).unwrap();
         v.write_all(&[0u8; MAX_PERMITTED_DATA_INCREASE]).unwrap();
 
         let padding = v.len() % 8;
@@ -116,6 +148,18 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec<u8>
         v.write_u64::<LittleEndian>(0).unwrap();
     }
 
+    // ka_num
+    v.write_u64::<LittleEndian>(vm.account_data.len() as u64)
+        .unwrap();
+
+    serialize_account(&mut v, &vm.returndata, &vm.account_data[&vm.returndata]);
+
+    for (key, data) in &vm.account_data {
+        if key != &vm.returndata {
+            serialize_account(&mut v, key, data);
+        }
+    }
+
     // calldata
     v.write_u64::<LittleEndian>(input.len() as u64).unwrap();
     v.write_all(input).unwrap();
@@ -127,21 +171,29 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec<u8>
 }
 
 // We want to extract the account data
-fn deserialize_parameters(input: &[u8]) -> Vec<Vec<u8>> {
+fn deserialize_parameters(
+    input: &[u8],
+    accounts_data: &mut HashMap<Account, (Vec<u8>, Option<Account>)>,
+) {
     let mut start = 0;
 
     let ka_num = LittleEndian::read_u64(&input[start..]);
     start += size_of::<u64>();
 
-    let mut res = Vec::new();
-
     for _ in 0..ka_num {
-        start += 8 + 32 + 32 + 8;
+        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>();
+        let data = input[start..start + data_len].to_vec();
 
-        res.push(input[start..start + data_len].to_vec());
+        if let Some(entry) = accounts_data.get_mut(&account) {
+            entry.0 = data;
+        }
 
         start += data_len + MAX_PERMITTED_DATA_INCREASE;
 
@@ -152,21 +204,10 @@ fn deserialize_parameters(input: &[u8]) -> Vec<Vec<u8>> {
 
         start += size_of::<u64>();
     }
-
-    res
-}
-
-struct Program {
-    code: Vec<u8>,
-    abi: ethabi::Contract,
-    account: Account,
-    printbuf: String,
-    data: Vec<u8>,
-    output: Vec<u8>,
 }
 
 struct Printer<'a> {
-    buf: &'a mut String,
+    context: Rc<RefCell<&'a mut VirtualMachine>>,
 }
 
 impl<'a> SyscallObject<UserError> for Printer<'a> {
@@ -194,7 +235,9 @@ impl<'a> SyscallObject<UserError> for Printer<'a> {
             ))
             .unwrap();
             println!("log: {}", message);
-            self.buf.push_str(message);
+            if let Ok(mut context) = self.context.try_borrow_mut() {
+                context.printbuf.push_str(message);
+            }
             Ok(0)
         }
     }
@@ -240,15 +283,220 @@ impl SyscallObject<UserError> for SyscallAllocFree {
     }
 }
 
-impl Program {
-    fn execute(&mut self, buf: &mut String, calldata: &[u8]) {
+/// Rust representation of C's SolInstruction
+#[derive(Debug)]
+struct SolInstruction {
+    program_id_addr: u64,
+    accounts_addr: u64,
+    accounts_len: usize,
+    data_addr: u64,
+    data_len: usize,
+}
+
+/// Rust representation of C's SolAccountMeta
+#[derive(Debug)]
+struct SolAccountMeta {
+    pubkey_addr: u64,
+    is_writable: bool,
+    is_signer: bool,
+}
+
+/// Rust representation of C's SolAccountInfo
+#[derive(Debug)]
+struct SolAccountInfo {
+    key_addr: u64,
+    lamports_addr: u64,
+    data_len: u64,
+    data_addr: u64,
+    owner_addr: u64,
+    rent_epoch: u64,
+    is_signer: bool,
+    is_writable: bool,
+    executable: bool,
+}
+
+/// Rust representation of C's SolSignerSeed
+#[derive(Debug)]
+struct SolSignerSeedC {
+    addr: u64,
+    len: u64,
+}
+
+/// Rust representation of C's SolSignerSeeds
+#[derive(Debug)]
+struct SolSignerSeedsC {
+    addr: u64,
+    len: u64,
+}
+
+#[derive(Debug)]
+pub struct Instruction {
+    /// Pubkey of the instruction processor that executes this instruction
+    pub program_id: Pubkey,
+    /// Metadata for what accounts should be passed to the instruction processor
+    pub accounts: Vec<AccountMeta>,
+    /// Opaque data passed to the instruction processor
+    pub data: Vec<u8>,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Pubkey([u8; 32]);
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct AccountMeta {
+    /// An account's public key
+    pub pubkey: Pubkey,
+    /// True if an Instruction requires a Transaction signature matching `pubkey`.
+    pub is_signer: bool,
+    /// True if the `pubkey` can be loaded as a read-write account.
+    pub is_writable: bool,
+}
+
+fn translate(
+    memory_mapping: &MemoryMapping,
+    access_type: AccessType,
+    vm_addr: u64,
+    len: u64,
+) -> Result<u64, EbpfError<UserError>> {
+    memory_mapping.map::<UserError>(access_type, vm_addr, len)
+}
+
+fn translate_type_inner<'a, T>(
+    memory_mapping: &MemoryMapping,
+    access_type: AccessType,
+    vm_addr: u64,
+) -> Result<&'a mut T, EbpfError<UserError>> {
+    unsafe {
+        translate(memory_mapping, access_type, vm_addr, size_of::<T>() as u64)
+            .map(|value| &mut *(value as *mut T))
+    }
+}
+fn translate_type<'a, T>(
+    memory_mapping: &MemoryMapping,
+    vm_addr: u64,
+) -> Result<&'a T, EbpfError<UserError>> {
+    translate_type_inner::<T>(memory_mapping, AccessType::Load, vm_addr).map(|value| &*value)
+}
+fn translate_slice<'a, T>(
+    memory_mapping: &MemoryMapping,
+    vm_addr: u64,
+    len: u64,
+) -> Result<&'a [T], EbpfError<UserError>> {
+    translate_slice_inner::<T>(memory_mapping, AccessType::Load, vm_addr, len).map(|value| &*value)
+}
+fn translate_slice_inner<'a, T>(
+    memory_mapping: &MemoryMapping,
+    access_type: AccessType,
+    vm_addr: u64,
+    len: u64,
+) -> Result<&'a mut [T], EbpfError<UserError>> {
+    if len == 0 {
+        Ok(&mut [])
+    } else {
+        match translate(
+            memory_mapping,
+            access_type,
+            vm_addr,
+            len.saturating_mul(size_of::<T>() as u64),
+        ) {
+            Ok(value) => {
+                Ok(unsafe { std::slice::from_raw_parts_mut(value as *mut T, len as usize) })
+            }
+            Err(e) => Err(e),
+        }
+    }
+}
+
+struct SyscallInvokeSignedC<'a> {
+    context: Rc<RefCell<&'a mut VirtualMachine>>,
+}
+
+impl<'a> SyscallInvokeSignedC<'a> {
+    fn translate_instruction(
+        &self,
+        addr: u64,
+        memory_mapping: &MemoryMapping,
+    ) -> Result<Instruction, EbpfError<UserError>> {
+        let ix_c = translate_type::<SolInstruction>(memory_mapping, addr)?;
+
+        let program_id = translate_type::<Pubkey>(memory_mapping, ix_c.program_id_addr)?;
+        let meta_cs = translate_slice::<SolAccountMeta>(
+            memory_mapping,
+            ix_c.accounts_addr,
+            ix_c.accounts_len as u64,
+        )?;
+        let data =
+            translate_slice::<u8>(memory_mapping, ix_c.data_addr, ix_c.data_len as u64)?.to_vec();
+        let accounts = meta_cs
+            .iter()
+            .map(|meta_c| {
+                let pubkey = translate_type::<Pubkey>(memory_mapping, meta_c.pubkey_addr)?;
+                Ok(AccountMeta {
+                    pubkey: pubkey.clone(),
+                    is_signer: meta_c.is_signer,
+                    is_writable: meta_c.is_writable,
+                })
+            })
+            .collect::<Result<Vec<AccountMeta>, EbpfError<UserError>>>()?;
+
+        Ok(Instruction {
+            program_id: program_id.clone(),
+            accounts,
+            data,
+        })
+    }
+}
+
+impl<'a> SyscallObject<UserError> for SyscallInvokeSignedC<'a> {
+    fn call(
+        &mut self,
+        instruction_addr: u64,
+        _account_infos_addr: u64,
+        _account_infos_len: u64,
+        _signers_seeds_addr: u64,
+        _signers_seeds_len: u64,
+        memory_mapping: &MemoryMapping,
+    ) -> Result<u64, EbpfError<UserError>> {
+        let instruction = self
+            .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();
+
+            context.stack.insert(0, p);
+
+            context.execute(&instruction.data);
+
+            context.stack.remove(0);
+        }
+
+        Ok(0)
+    }
+}
+
+impl VirtualMachine {
+    fn execute(&mut self, calldata: &[u8]) {
         println!("running bpf with calldata:{}", hex::encode(calldata));
 
-        let parameter_bytes = serialize_parameters(&calldata, &self.account, &self.data);
+        let parameter_bytes = serialize_parameters(&calldata, &self);
         let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
         let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START, true);
 
-        let executable = Executable::<UserError>::from_elf(&self.code, None).expect("should work");
+        let program = &self.stack[0];
+
+        let executable =
+            Executable::<UserError>::from_elf(&self.account_data[&program.program].0, None)
+                .expect("should work");
         let mut vm = EbpfVm::<UserError, DefaultInstructionMeter>::new(
             executable.as_ref(),
             Config::default(),
@@ -257,9 +505,23 @@ impl Program {
         )
         .unwrap();
 
-        vm.register_syscall_ex("sol_log_", Syscall::Object(Box::new(Printer { buf })))
-            .unwrap();
+        let context = Rc::new(RefCell::new(self));
+
+        vm.register_syscall_ex(
+            "sol_log_",
+            Syscall::Object(Box::new(Printer {
+                context: context.clone(),
+            })),
+        )
+        .unwrap();
 
+        vm.register_syscall_ex(
+            "sol_invoke_signed_c",
+            Syscall::Object(Box::new(SyscallInvokeSignedC {
+                context: context.clone(),
+            })),
+        )
+        .unwrap();
         vm.register_syscall_ex(
             "sol_alloc_free_",
             Syscall::Object(Box::new(SyscallAllocFree {
@@ -272,52 +534,81 @@ impl Program {
             .execute_program_interpreted(&mut DefaultInstructionMeter {})
             .unwrap();
 
-        let mut accounts = deserialize_parameters(&parameter_bytes);
+        let mut elf = context.try_borrow_mut().unwrap();
 
-        let output = accounts.remove(0);
-        let data = accounts.remove(0);
+        deserialize_parameters(&parameter_bytes, &mut elf.account_data);
+
+        let output = &elf.account_data[&elf.returndata].0;
 
         let len = LittleEndian::read_u64(&output);
-        self.output = output[8..len as usize + 8].to_vec();
-        self.data = data;
+        elf.output = output[8..len as usize + 8].to_vec();
 
-        println!("account: {}", hex::encode(&self.output));
+        println!("return: {}", hex::encode(&elf.output));
 
         assert_eq!(res, 0);
     }
 
     fn constructor(&mut self, args: &[Token]) {
-        let calldata = if let Some(constructor) = &self.abi.constructor {
+        let program = &self.stack[0];
+
+        let calldata = if let Some(constructor) = &program.abi.constructor {
             constructor
-                .encode_input(self.account.to_vec(), args)
+                .encode_input(program.data.to_vec(), args)
                 .unwrap()
         } else {
-            self.account.to_vec()
+            program.data.to_vec()
         };
 
-        let mut buf = String::new();
-        self.execute(&mut buf, &calldata);
-        self.printbuf = buf;
+        self.execute(&calldata);
     }
 
     fn function(&mut self, name: &str, args: &[Token]) -> Vec<Token> {
-        let mut calldata: Vec<u8> = self.account.to_vec();
+        let program = &self.stack[0];
 
-        match self.abi.functions[name][0].encode_input(args) {
+        let mut calldata: Vec<u8> = program.data.to_vec();
+
+        match program.abi.functions[name][0].encode_input(args) {
             Ok(n) => calldata.extend(&n),
             Err(x) => panic!("{}", x),
         };
 
         println!("input: {}", hex::encode(&calldata));
 
-        let mut buf = String::new();
-        self.execute(&mut buf, &calldata);
-        self.printbuf = buf;
+        self.execute(&calldata);
 
         println!("output: {}", hex::encode(&self.output));
 
-        self.abi.functions[name][0]
+        let program = &self.stack[0];
+
+        program.abi.functions[name][0]
             .decode_output(&self.output)
             .unwrap()
     }
+
+    fn data(&self) -> &Vec<u8> {
+        let program = &self.stack[0];
+
+        &self.account_data[&program.data].0
+    }
+
+    fn set_program(&mut self, no: usize) {
+        let cur = self.programs[no].clone();
+
+        self.stack = vec![cur];
+    }
+}
+
+pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
+    let mut cache = FileCache::new();
+
+    cache.set_file_contents("test.sol", src.to_string());
+
+    solang::parse_and_resolve("test.sol", &mut cache, target)
+}
+
+pub fn first_error(errors: Vec<ast::Diagnostic>) -> String {
+    match errors.iter().find(|m| m.level == ast::Level::Error) {
+        Some(m) => m.message.to_owned(),
+        None => panic!("no errors found"),
+    }
 }

+ 103 - 0
tests/solana_tests/call.rs

@@ -0,0 +1,103 @@
+use crate::{build_solidity, first_error, parse_and_resolve};
+use ethabi::Token;
+use solang::Target;
+
+#[test]
+fn calltys() {
+    let ns = parse_and_resolve(
+        r#"
+        contract main {
+            function test() public {
+                address x = address(0);
+
+                x.staticcall(hex"1222");
+            }
+        }"#,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "method ‘staticcall’ does not exist"
+    );
+
+    let ns = parse_and_resolve(
+        r#"
+        contract main {
+            function test() public {
+                address x = address(0);
+
+                x.delegatecall(hex"1222");
+            }
+        }"#,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "method ‘delegatecall’ does not exist"
+    );
+
+    let ns = parse_and_resolve(
+        r#"
+        contract main {
+            function test() public {
+                address x = address(0);
+
+                (bool success, bytes bs) = x.call{gas: 5}(hex"1222");
+            }
+        }"#,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "‘gas’ not permitted for external calls on solana"
+    );
+}
+
+#[test]
+fn two_contracts() {
+    let mut vm = build_solidity(
+        r#"
+        contract bar0 {
+            function test_bar(string v) public {
+                print("bar0 says: " + v);
+            }
+
+            function test_other(bar1 x) public {
+                x.test_bar("cross contract call");
+            }
+        }
+
+        contract bar1 {
+            function test_bar(string v) public {
+                print("bar1 says: " + v);
+            }
+        }"#,
+    );
+
+    vm.constructor(&[]);
+
+    vm.function("test_bar", &[Token::String(String::from("yo"))]);
+
+    assert_eq!(vm.printbuf, "bar1 says: yo");
+
+    vm.printbuf.truncate(0);
+
+    let bar1_account = vm.stack[0].data;
+
+    vm.set_program(0);
+
+    vm.constructor(&[]);
+
+    vm.function("test_bar", &[Token::String(String::from("uncle beau"))]);
+
+    assert_eq!(vm.printbuf, "bar0 says: uncle beau");
+
+    vm.printbuf.truncate(0);
+
+    vm.function("test_other", &[Token::FixedBytes(bar1_account.to_vec())]);
+
+    assert_eq!(vm.printbuf, "bar1 says: cross contract call");
+}

+ 1 - 0
tests/solana_tests/mod.rs

@@ -1,5 +1,6 @@
 mod abi;
 mod arrays;
+mod call;
 mod mappings;
 mod primitives;
 mod simple;

+ 5 - 3
tests/solana_tests/simple.rs

@@ -19,7 +19,7 @@ fn simple() {
 
     assert_eq!(vm.printbuf, "Hello from constructor");
 
-    vm.printbuf = String::new();
+    vm.printbuf.truncate(0);
 
     vm.function("test", &[]);
 
@@ -76,6 +76,8 @@ fn parameters() {
 
     assert_eq!(vm.printbuf, "x is 10");
 
+    vm.printbuf.truncate(0);
+
     vm.function(
         "test",
         &[
@@ -164,7 +166,7 @@ fn flipper() {
     vm.constructor(&[ethabi::Token::Bool(true)]);
 
     assert_eq!(
-        vm.data[0..9].to_vec(),
+        vm.data()[0..9].to_vec(),
         hex::decode("6fc90ec51000000001").unwrap()
     );
 
@@ -175,7 +177,7 @@ fn flipper() {
     vm.function("flip", &[]);
 
     assert_eq!(
-        vm.data[0..9].to_vec(),
+        vm.data()[0..9].to_vec(),
         hex::decode("6fc90ec51000000000").unwrap()
     );
 

+ 12 - 12
tests/solana_tests/storage.rs

@@ -21,7 +21,7 @@ fn string() {
     vm.constructor(&[]);
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![65, 177, 160, 100, 16, 0, 0, 0, 0, 0, 0, 0]
     );
 
@@ -32,11 +32,11 @@ fn string() {
     vm.function("set", &[Token::String(String::from("Hello, World!"))]);
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![65, 177, 160, 100, 16, 0, 0, 0, 32, 0, 0, 0]
     );
 
-    assert_eq!(vm.data[32..45].to_vec(), b"Hello, World!");
+    assert_eq!(vm.data()[32..45].to_vec(), b"Hello, World!");
 
     let returns = vm.function("get", &[]);
 
@@ -51,7 +51,7 @@ fn string() {
     assert_eq!(returns, vec![Token::String(String::from("Hallo, Werld!"))]);
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![65, 177, 160, 100, 16, 0, 0, 0, 32, 0, 0, 0]
     );
 
@@ -64,7 +64,7 @@ fn string() {
     assert_eq!(returns, vec![Token::String(String::from(""))]);
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![65, 177, 160, 100, 16, 0, 0, 0, 0, 0, 0, 0]
     );
 }
@@ -97,7 +97,7 @@ fn bytes() {
     vm.constructor(&[]);
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![11, 66, 182, 57, 16, 0, 0, 0, 0, 0, 0, 0]
     );
 
@@ -113,7 +113,7 @@ fn bytes() {
     );
 
     assert_eq!(
-        vm.data[0..12].to_vec(),
+        vm.data()[0..12].to_vec(),
         vec![11, 66, 182, 57, 16, 0, 0, 0, 32, 0, 0, 0]
     );
 
@@ -254,7 +254,7 @@ fn storage_alignment() {
     vm.constructor(&[]);
 
     assert_eq!(
-        vm.data[0..32].to_vec(),
+        vm.data()[0..32].to_vec(),
         vec![
             11, 66, 182, 57, 32, 0, 0, 0, 1, 0, 3, 2, 4, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 16, 15,
             14, 13, 12, 11, 10, 9
@@ -299,7 +299,7 @@ fn bytes_push_pop() {
 
     vm.function("push", &[Token::FixedBytes(vec![0x41])]);
 
-    println!("data:{}", hex::encode(&vm.data));
+    println!("data:{}", hex::encode(&vm.data()));
 
     let returns = vm.function("get_bs", &[]);
 
@@ -363,7 +363,7 @@ fn simple_struct() {
     vm.function("set_s2", &[]);
 
     assert_eq!(
-        vm.data[0..24].to_vec(),
+        vm.data()[0..24].to_vec(),
         vec![
             11, 66, 182, 57, 24, 0, 0, 0, 173, 222, 0, 0, 254, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0
         ]
@@ -436,7 +436,7 @@ fn struct_in_struct() {
     vm.function("set_s2", &[]);
 
     assert_eq!(
-        vm.data[0..44].to_vec(),
+        vm.data()[0..44].to_vec(),
         vec![
             11, 66, 182, 57, 40, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0, 254, 0, 0, 0, 102, 0, 0, 0,
             114, 97, 98, 111, 111, 102, 0, 0, 210, 2, 150, 73, 0, 0, 0, 0, 0, 0, 0, 0
@@ -517,7 +517,7 @@ fn string_in_struct() {
     vm.function("set_s2", &[]);
 
     assert_eq!(
-        vm.data[0..56].to_vec(),
+        vm.data()[0..56].to_vec(),
         vec![
             11, 66, 182, 57, 32, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0, 254, 48, 0, 0, 0, 0, 0, 0,
             210, 2, 150, 73, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 102, 111,