Parcourir la source

Allow seeds to be passed into functions as string[] or string[][] (#1547)

This fixes this Solidity and similar constructs.

```solidity
import "solana-library/spl_token.sol";

contract c {

    // Invoke the token program to mint tokens to a token account, using a PDA as the mint authority
    function mintTo(address mint, address account, address authority, uint64 amount, bytes[] seeds) internal {
        // Prepare instruction data
        bytes instructionData = new bytes(9);
        instructionData[0] = uint8(7); // MintTo instruction index
        instructionData.writeUint64LE(amount, 1); // Amount to mint

        // Prepare accounts required by instruction
        AccountMeta[3] metas = [
            AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
            AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
            AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
        ];

        // Invoke the token program with prepared accounts and instruction data
        SplToken.tokenProgramId.call{accounts: metas, seeds: seeds}(instructionData);
    }
}
```

Fixes https://github.com/hyperledger/solang/issues/1433

Signed-off-by: Sean Young <sean@mess.org>
Sean Young il y a 2 ans
Parent
commit
5675a34547

+ 42 - 1
integration/solana/create_contract.sol

@@ -30,7 +30,7 @@ contract creator {
             AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false})
         ];
 
-        Child.new{accounts: metas}();        
+        Child.new{accounts: metas}();
         Child.use_metas();
     }
 
@@ -38,6 +38,17 @@ contract creator {
         MyCreature.new();
         MyCreature.say_my_name();
     }
+
+    function call_with_signer(address signer) view public {
+        for(uint32 i=0; i < tx.accounts.length; i++) {
+            if (tx.accounts[i].key == signer) {
+                require(tx.accounts[i].is_signer, "the signer must sign the transaction");
+                print("Signer found");
+                return;
+            }
+        }
+
+        revert("The signer account is missing");    }
 }
 
 @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
@@ -61,15 +72,31 @@ contract Child {
 
 @program_id("SeedHw4CsFsDEGu2AVwFM1toGXsbAJSKnb7kS8TrLxu")
 contract Seed1 {
+    bytes saved_seed;
+    bytes1 saved_bump;
 
     @payer(payer)
     constructor(@seed bytes seed, @bump bytes1 bump, @space uint64 space) {
         print("In Seed1 constructor");
+        saved_seed = seed;
+        saved_bump = bump;
     }
 
     function say_hello() pure public {
         print("Hello from Seed1");
     }
+
+    function sign(address creator_program_id) view public {
+        AccountMeta[1] metas = [
+            AccountMeta({pubkey: tx.accounts.dataAccount.key, is_signer: true, is_writable: false})
+        ];
+
+        creator.call_with_signer{
+            seeds: [ [ saved_seed, saved_bump ] ],
+            program_id: creator_program_id,
+            accounts: metas
+        }(tx.accounts.dataAccount.key);
+    }
 }
 
 @program_id("Seed23VDZ9HFCfKvFwmemB6dpi25n5XjZdP52B2RUmh")
@@ -91,6 +118,20 @@ contract Seed2 {
             print("I am PDA.");
         }
     }
+
+    function sign(address creator_program_id) view public {
+        bytes[2][1] seeds = [ [ "sunflower", my_seed ] ];
+
+        sign2(seeds, tx.accounts.dataAccount.key, creator_program_id);
+    }
+
+    function sign2(bytes[2][1] seeds, address child, address creator_program_id) view internal {
+        AccountMeta[1] metas = [
+            AccountMeta({pubkey: child, is_signer: true, is_writable: false})
+        ];
+
+        creator.call_with_signer{seeds: seeds, accounts: metas, program_id: creator_program_id}(child);
+    }
 }
 
 @program_id("8gTkAidfM82u3DGbKcZpHwL5p47KQA16MDb4WmrHdmF6")

+ 21 - 4
integration/solana/create_contract.spec.ts

@@ -10,12 +10,13 @@ describe('ChildContract', function () {
     this.timeout(150000);
 
     let program: Program;
-    let storage: Keypair
+    let storage: Keypair;
+    let program_key: PublicKey;
     let payer: Keypair;
     let provider: Provider;
 
     before(async function () {
-        ({ program, storage, payer, provider } = await loadContractAndCallConstructor('creator'));
+        ({ program, storage, payer, program_key, provider } = await loadContractAndCallConstructor('creator'));
     });
 
     it('Create Contract', async function () {
@@ -46,7 +47,7 @@ describe('ChildContract', function () {
         let seed_program = new PublicKey("SeedHw4CsFsDEGu2AVwFM1toGXsbAJSKnb7kS8TrLxu");
         let seed = Buffer.from("chai");
 
-        let [address, bump] = await PublicKey.findProgramAddress([seed], seed_program);
+        let [address, bump] = PublicKey.findProgramAddressSync([seed], seed_program);
 
         const signature = await program.methods.createSeed1(
             seed, Buffer.from([bump]), new BN(711))
@@ -68,13 +69,23 @@ describe('ChildContract', function () {
         const info = await provider.connection.getAccountInfo(address);
 
         expect(info?.data.length).toEqual(711);
+
+        const idl = JSON.parse(fs.readFileSync('Seed1.json', 'utf8'));
+
+        const seed1 = new Program(idl, seed_program, provider);
+
+        let res = await seed1.methods.sign(program_key)
+            .accounts({ dataAccount: address, creator_programId: program_key })
+            .simulate();
+
+        expect(res.raw.toString()).toContain('Signer found');
     });
 
     it('Creates Contract with seed2', async function () {
         let seed_program = new PublicKey("Seed23VDZ9HFCfKvFwmemB6dpi25n5XjZdP52B2RUmh");
         let bare_seed = Buffer.from("poppy");
 
-        let [address, bump] = await PublicKey.findProgramAddress([Buffer.from("sunflower"), bare_seed], seed_program);
+        let [address, bump] = PublicKey.findProgramAddressSync([Buffer.from("sunflower"), bare_seed], seed_program);
 
         let seed = Buffer.concat([bare_seed, Buffer.from([bump])]);
 
@@ -107,6 +118,12 @@ describe('ChildContract', function () {
             .simulate();
 
         expect(res.raw.toString()).toContain('I am PDA.');
+
+        res = await seed2.methods.sign(program_key)
+            .accounts({ dataAccount: address, creator_programId: program_key })
+            .simulate();
+
+        expect(res.raw.toString()).toContain('Signer found');
     });
 
     it('Create Contract with account metas vector', async function () {

+ 4 - 4
integration/solana/setup.ts

@@ -9,7 +9,7 @@ const endpoint: string = process.env.RPC_URL || "http://127.0.0.1:8899";
 export async function loadContractAndCallConstructor(name: string, args: any[] = [], space: number = 8192):
     Promise<{ program: Program, payer: Keypair, provider: AnchorProvider, storage: Keypair, program_key: PublicKey }> {
 
-    const {program, payer, provider, program_key} = await loadContract(name);
+    const { program, payer, provider, program_key } = await loadContract(name);
 
     const storage = Keypair.generate();
     await create_account(storage, program_key, space);
@@ -22,7 +22,7 @@ export async function loadContractAndCallConstructor(name: string, args: any[] =
 }
 
 export async function loadContract(name: string):
-    Promise<{program: Program, payer: Keypair, provider: AnchorProvider, program_key: PublicKey}> {
+    Promise<{ program: Program, payer: Keypair, provider: AnchorProvider, program_key: PublicKey }> {
     const idl = JSON.parse(fs.readFileSync(`${name}.json`, 'utf8'));
 
     const payer = loadKey('payer.key');
@@ -32,7 +32,7 @@ export async function loadContract(name: string):
     const provider = AnchorProvider.local(endpoint);
     const program_key = loadKey(`${name}.key`);
     const program = new Program(idl, program_key.publicKey, provider)
-    return {program, payer, provider, program_key: program_key.publicKey};
+    return { program, payer, provider, program_key: program_key.publicKey };
 }
 
 export async function create_account(account: Keypair, programId: PublicKey, space: number) {
@@ -50,7 +50,7 @@ export async function create_account(account: Keypair, programId: PublicKey, spa
             programId,
         }));
 
-    await provider.sendAndConfirm(transaction, [account]);
+    await provider.sendAndConfirm(transaction, [account], { commitment: 'confirmed' });
 }
 
 export function newConnectionAndPayer(): [Connection, Keypair] {

+ 94 - 25
src/emit/binary.rs

@@ -41,6 +41,94 @@ use solang_parser::pt;
 
 static LLVM_INIT: OnceCell<()> = OnceCell::new();
 
+#[macro_export]
+macro_rules! emit_context {
+    ($binary:expr) => {
+        #[allow(unused_macros)]
+        macro_rules! byte_ptr {
+            () => {
+                $binary.context.i8_type().ptr_type(AddressSpace::default())
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! i32_const {
+            ($val:expr) => {
+                $binary.context.i32_type().const_int($val, false)
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! i32_zero {
+            () => {
+                $binary.context.i32_type().const_zero()
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! i64_const {
+            ($val:expr) => {
+                $binary.context.i64_type().const_int($val, false)
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! i64_zero {
+            () => {
+                $binary.context.i64_type().const_zero()
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! call {
+            ($name:expr, $args:expr) => {
+                $binary
+                    .builder
+                    .build_call($binary.module.get_function($name).unwrap(), $args, "")
+            };
+            ($name:expr, $args:expr, $call_name:literal) => {
+                $binary.builder.build_call(
+                    $binary.module.get_function($name).unwrap(),
+                    $args,
+                    $call_name,
+                )
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! seal_get_storage {
+            ($key_ptr:expr, $key_len:expr, $value_ptr:expr, $value_len:expr) => {
+                call!("get_storage", &[$key_ptr, $key_len, $value_ptr, $value_len])
+                    .try_as_basic_value()
+                    .left()
+                    .unwrap()
+                    .into_int_value()
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! seal_set_storage {
+            ($key_ptr:expr, $key_len:expr, $value_ptr:expr, $value_len:expr) => {
+                call!("set_storage", &[$key_ptr, $key_len, $value_ptr, $value_len])
+                    .try_as_basic_value()
+                    .left()
+                    .unwrap()
+                    .into_int_value()
+            };
+        }
+
+        #[allow(unused_macros)]
+        macro_rules! scratch_buf {
+            () => {
+                (
+                    $binary.scratch.unwrap().as_pointer_value(),
+                    $binary.scratch_len.unwrap().as_pointer_value(),
+                )
+            };
+        }
+    };
+}
+
 pub struct Binary<'a> {
     pub name: String,
     pub module: Module<'a>,
@@ -805,33 +893,14 @@ impl<'a> Binary<'a> {
                     self.module.get_struct_type("struct.vector").unwrap().into()
                 }
                 Type::Array(base_ty, dims) => {
-                    let ty = self.llvm_field_ty(base_ty, ns);
-
-                    let mut dims = dims.iter();
-
-                    let mut aty = match dims.next().unwrap() {
-                        ArrayLength::Fixed(d) => ty.array_type(d.to_u32().unwrap()),
-                        ArrayLength::Dynamic => {
-                            return self.module.get_struct_type("struct.vector").unwrap().into()
-                        }
-                        ArrayLength::AnyFixed => {
-                            unreachable!()
-                        }
-                    };
-
-                    for dim in dims {
-                        match dim {
-                            ArrayLength::Fixed(d) => aty = aty.array_type(d.to_u32().unwrap()),
+                    dims.iter()
+                        .fold(self.llvm_field_ty(base_ty, ns), |aty, dim| match dim {
+                            ArrayLength::Fixed(d) => aty.array_type(d.to_u32().unwrap()).into(),
                             ArrayLength::Dynamic => {
-                                return self.module.get_struct_type("struct.vector").unwrap().into()
-                            }
-                            ArrayLength::AnyFixed => {
-                                unreachable!()
+                                self.module.get_struct_type("struct.vector").unwrap().into()
                             }
-                        }
-                    }
-
-                    BasicTypeEnum::ArrayType(aty)
+                            ArrayLength::AnyFixed => unreachable!(),
+                        })
                 }
                 Type::Struct(StructType::SolParameters) => self
                     .module

+ 240 - 4
src/emit/expression.rs

@@ -6,14 +6,16 @@ use crate::codegen::{Builtin, Expression};
 use crate::emit::binary::Binary;
 use crate::emit::math::{build_binary_op_with_overflow_check, multiply, power};
 use crate::emit::strings::{format_string, string_location};
-use crate::emit::{BinaryOp, TargetRuntime, Variable};
-use crate::sema::ast::{Namespace, RetrieveType, StructType, Type};
+use crate::emit::{loop_builder::LoopBuilder, BinaryOp, TargetRuntime, Variable};
+use crate::emit_context;
+use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type};
 use crate::Target;
 use inkwell::module::Linkage;
 use inkwell::types::{BasicType, StringRadix};
-use inkwell::values::{ArrayValue, BasicValueEnum, FunctionValue, IntValue};
+use inkwell::values::{ArrayValue, BasicValueEnum, FunctionValue, IntValue, PointerValue};
 use inkwell::{AddressSpace, IntPredicate};
 use num_bigint::Sign;
+use num_traits::ToPrimitive;
 use std::collections::HashMap;
 
 /// The expression function recursively emits code for expressions. The BasicEnumValue it
@@ -2014,7 +2016,7 @@ fn runtime_cast<'a>(
                 "int_to_ptr",
             )
             .into(),
-        (Type::DynamicBytes, Type::Slice(_)) => {
+        (Type::DynamicBytes | Type::String, Type::Slice(_)) => {
             let slice_ty = bin.llvm_type(to, ns);
             let slice = bin.build_alloca(function, slice_ty, "slice");
 
@@ -2120,3 +2122,237 @@ fn runtime_cast<'a>(
         _ => unreachable!(),
     }
 }
+
+/// Emit a codegen expression as a slice; the result is a pointer to the data and a length. This is
+/// needed for Solana syscalls that take slices, and will be useful for when we start supporting
+/// slices in Solidity (e.g. foo[2:3])
+pub(super) fn expression_to_slice<'a, T: TargetRuntime<'a> + ?Sized>(
+    target: &T,
+    bin: &Binary<'a>,
+    e: &Expression,
+    to: &Type,
+    vartab: &HashMap<usize, Variable<'a>>,
+    function: FunctionValue<'a>,
+    ns: &Namespace,
+) -> (PointerValue<'a>, IntValue<'a>) {
+    emit_context!(bin);
+
+    let Type::Slice(to_elem_ty) = to else {
+        unreachable!()
+    };
+
+    let llvm_to = bin.llvm_type(to, ns);
+
+    match e {
+        Expression::ArrayLiteral {
+            dimensions, values, ..
+        } => {
+            let length = dimensions[0];
+
+            let llvm_length = i32_const!(length.into());
+
+            let output = bin.build_array_alloca(function, llvm_to, llvm_length, "seeds");
+
+            for i in 0..length {
+                let (ptr, len) = expression_to_slice(
+                    target,
+                    bin,
+                    &values[i as usize],
+                    to_elem_ty,
+                    vartab,
+                    function,
+                    ns,
+                );
+
+                // SAFETY: llvm_to is an array of slices, so i is slice no and 0 is the data ptr
+                // of the slice struct. Since indexes are correct for type it is safe.
+                let output_ptr = unsafe {
+                    bin.builder.build_gep(
+                        llvm_to,
+                        output,
+                        &[i32_const!(i.into()), i32_zero!()],
+                        "output_ptr",
+                    )
+                };
+
+                bin.builder.build_store(output_ptr, ptr);
+
+                // SAFETY: llvm_to is an array of slices, so i is slice no and 1 is the len ptr
+                // of the slice struct. Since indexes are correct for type it is safe.
+                let output_len = unsafe {
+                    bin.builder.build_gep(
+                        llvm_to,
+                        output,
+                        &[i32_const!(i.into()), i32_const!(1)],
+                        "output_len",
+                    )
+                };
+
+                bin.builder.build_store(output_len, len);
+            }
+
+            (output, llvm_length)
+        }
+        Expression::AllocDynamicBytes {
+            initializer: Some(initializer),
+            ..
+        } => {
+            let ptr = bin.emit_global_string("slice_constant", initializer, true);
+            let len = i64_const!(initializer.len() as u64);
+
+            (ptr, len)
+        }
+        _ => {
+            let from = e.ty();
+
+            let val = expression(target, bin, e, vartab, function, ns);
+
+            basic_value_to_slice(bin, val, &from, to, function, ns)
+        }
+    }
+}
+
+/// Convert basic enum value to a slice. This function calls itself recursively
+/// for arrays (become slices of slices).
+fn basic_value_to_slice<'a>(
+    bin: &Binary<'a>,
+    val: BasicValueEnum<'a>,
+    from: &Type,
+    to: &Type,
+    function: FunctionValue<'a>,
+    ns: &Namespace,
+) -> (PointerValue<'a>, IntValue<'a>) {
+    emit_context!(bin);
+
+    match from {
+        Type::Slice(_) | Type::DynamicBytes | Type::String => {
+            let data = bin.vector_bytes(val);
+            let len = bin.vector_len(val);
+
+            (data, len)
+        }
+        Type::Address(_) => {
+            let address = call!("__malloc", &[i32_const!(ns.address_length as u64).into()])
+                .try_as_basic_value()
+                .left()
+                .unwrap()
+                .into_pointer_value();
+
+            bin.builder.build_store(address, val);
+
+            let len = i64_const!(ns.address_length as u64);
+
+            (address, len)
+        }
+        Type::Bytes(bytes_length) => {
+            let llvm_ty = bin.llvm_type(from, ns);
+            let src = bin.build_alloca(function, llvm_ty, "src");
+
+            bin.builder.build_store(src, val.into_int_value());
+
+            let bytes_length: u64 = (*bytes_length).into();
+
+            let dest = call!("__malloc", &[i32_const!(bytes_length).into()])
+                .try_as_basic_value()
+                .left()
+                .unwrap()
+                .into_pointer_value();
+
+            bin.builder.build_call(
+                bin.module.get_function("__leNtobeN").unwrap(),
+                &[src.into(), dest.into(), i32_const!(bytes_length).into()],
+                "",
+            );
+
+            let len = i64_const!(bytes_length);
+
+            (dest, len)
+        }
+        Type::Array(_, dims) => {
+            let to_elem = to.array_elem();
+
+            let to = bin.llvm_type(to, ns);
+
+            let length = match dims.last().unwrap() {
+                ArrayLength::Dynamic => bin.vector_len(val),
+                ArrayLength::Fixed(len) => i32_const!(len.to_u64().unwrap()),
+                _ => unreachable!(),
+            };
+
+            // FIXME: In Program Runtime v1, we can't do dynamic alloca. Remove the malloc once we move to
+            // program runtime v2
+            let size = bin.builder.build_int_mul(
+                bin.builder.build_int_truncate(
+                    bin.llvm_type(&Type::Slice(Type::Bytes(1).into()), ns)
+                        .size_of()
+                        .unwrap(),
+                    bin.context.i32_type(),
+                    "slice_size",
+                ),
+                length,
+                "size",
+            );
+
+            let output = call!("__malloc", &[size.into()])
+                .try_as_basic_value()
+                .left()
+                .unwrap()
+                .into_pointer_value();
+
+            // loop over seeds
+            let mut builder = LoopBuilder::new(bin, function);
+
+            let index = builder.over(bin, i32_zero!(), length);
+
+            // get value from array
+            let input_elem = bin.array_subscript(from, val.into_pointer_value(), index, ns);
+
+            let from_elem = from.array_elem();
+
+            // If the element is a fixed-length array, do not load it as it's stored in place and not
+            // as a pointer.
+            let load = if let Type::Array(_, dims) = &from_elem {
+                matches!(dims.last(), Some(ArrayLength::Dynamic))
+            } else {
+                true
+            };
+
+            let input_elem = if load {
+                bin.builder
+                    .build_load(bin.llvm_field_ty(&from_elem, ns), input_elem, "elem")
+            } else {
+                input_elem.into()
+            };
+
+            let (data, len) =
+                basic_value_to_slice(bin, input_elem, &from_elem, &to_elem, function, ns);
+
+            // SAFETY: to is an array of slices, so index is slice no and 0 is the data ptr
+            // of the slice struct. Since indexes are correct from type it is safe.
+            let output_data = unsafe {
+                bin.builder
+                    .build_gep(to, output, &[index, i32_zero!()], "output_data")
+            };
+
+            bin.builder.build_store(output_data, data);
+
+            // SAFETY: to is an array of slices, so index is slice no and 1 is the len ptr
+            // of the slice struct. Since indexes are correct from type it is safe.
+            let output_len = unsafe {
+                bin.builder
+                    .build_gep(to, output, &[index, i32_const!(1)], "output_len")
+            };
+
+            bin.builder.build_store(output_len, len);
+
+            builder.finish(bin);
+
+            let length = bin
+                .builder
+                .build_int_z_extend(length, bin.context.i64_type(), "length");
+
+            (output, length)
+        }
+        _ => unreachable!(),
+    }
+}

+ 11 - 61
src/emit/instructions.rs

@@ -20,6 +20,8 @@ use num_traits::ToPrimitive;
 use solang_parser::pt::CodeLocation;
 use std::collections::{HashMap, VecDeque};
 
+use super::expression::expression_to_slice;
+
 pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
     target: &mut T,
     ins: &Instr,
@@ -867,69 +869,17 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 (ptr, len)
             };
 
-            let seeds = if let Some(seeds) = seeds {
-                let len = seeds.ty().array_length().unwrap().to_u64().unwrap();
-                let seeds_ty = bin.llvm_type(
-                    &Type::Slice(Box::new(Type::Slice(Box::new(Type::Bytes(1))))),
-                    ns,
-                );
+            // sol_invoke_signed_c() takes of a slice of a slice of slice of bytes
+            // 1. A single seed value is a slice of bytes.
+            // 2. A signer for single address can have multiple seeds
+            // 3. A single call to sol_invoke_signed_c can sign for multiple addresses
+            let seeds_ty =
+                Type::Slice(Type::Slice(Type::Slice(Type::Bytes(1).into()).into()).into());
 
-                let output_seeds = bin.build_array_alloca(
-                    function,
-                    seeds_ty,
-                    bin.context.i64_type().const_int(len, false),
-                    "seeds",
-                );
+            let seeds = seeds.as_ref().map(|seeds| {
+                expression_to_slice(target, bin, seeds, &seeds_ty, &w.vars, function, ns)
+            });
 
-                if let Expression::ArrayLiteral { values, .. } = seeds {
-                    for i in 0..len {
-                        let val =
-                            expression(target, bin, &values[i as usize], &w.vars, function, ns);
-
-                        let seed_count = values[i as usize]
-                            .ty()
-                            .deref_any()
-                            .array_length()
-                            .unwrap()
-                            .to_u64()
-                            .unwrap();
-
-                        let dest = unsafe {
-                            bin.builder.build_gep(
-                                seeds_ty,
-                                output_seeds,
-                                &[
-                                    bin.context.i32_type().const_int(i, false),
-                                    bin.context.i32_type().const_zero(),
-                                ],
-                                "dest",
-                            )
-                        };
-
-                        bin.builder.build_store(dest, val);
-
-                        let dest = unsafe {
-                            bin.builder.build_gep(
-                                seeds_ty,
-                                output_seeds,
-                                &[
-                                    bin.context.i32_type().const_int(i, false),
-                                    bin.context.i32_type().const_int(1, false),
-                                ],
-                                "dest",
-                            )
-                        };
-
-                        let val = bin.context.i64_type().const_int(seed_count, false);
-
-                        bin.builder.build_store(dest, val);
-                    }
-                }
-
-                Some((output_seeds, bin.context.i64_type().const_int(len, false)))
-            } else {
-                None
-            };
             let flags = flags
                 .as_ref()
                 .map(|e| expression(target, bin, e, &w.vars, function, ns).into_int_value());

+ 1 - 74
src/emit/polkadot/mod.rs

@@ -12,6 +12,7 @@ use inkwell::AddressSpace;
 use crate::codegen::dispatch::polkadot::DispatchType;
 use crate::emit::functions::emit_functions;
 use crate::emit::{Binary, TargetRuntime};
+use crate::emit_context;
 
 mod storage;
 pub(super) mod target;
@@ -19,80 +20,6 @@ pub(super) mod target;
 // When using the seal api, we use our own scratch buffer.
 const SCRATCH_SIZE: u32 = 32 * 1024;
 
-#[macro_export]
-macro_rules! emit_context {
-    ($binary:expr) => {
-        #[allow(unused_macros)]
-        macro_rules! byte_ptr {
-            () => {
-                $binary.context.i8_type().ptr_type(AddressSpace::default())
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! i32_const {
-            ($val:expr) => {
-                $binary.context.i32_type().const_int($val, false)
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! i32_zero {
-            () => {
-                $binary.context.i32_type().const_zero()
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! call {
-            ($name:expr, $args:expr) => {
-                $binary
-                    .builder
-                    .build_call($binary.module.get_function($name).unwrap(), $args, "")
-            };
-            ($name:expr, $args:expr, $call_name:literal) => {
-                $binary.builder.build_call(
-                    $binary.module.get_function($name).unwrap(),
-                    $args,
-                    $call_name,
-                )
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! seal_get_storage {
-            ($key_ptr:expr, $key_len:expr, $value_ptr:expr, $value_len:expr) => {
-                call!("get_storage", &[$key_ptr, $key_len, $value_ptr, $value_len])
-                    .try_as_basic_value()
-                    .left()
-                    .unwrap()
-                    .into_int_value()
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! seal_set_storage {
-            ($key_ptr:expr, $key_len:expr, $value_ptr:expr, $value_len:expr) => {
-                call!("set_storage", &[$key_ptr, $key_len, $value_ptr, $value_len])
-                    .try_as_basic_value()
-                    .left()
-                    .unwrap()
-                    .into_int_value()
-            };
-        }
-
-        #[allow(unused_macros)]
-        macro_rules! scratch_buf {
-            () => {
-                (
-                    $binary.scratch.unwrap().as_pointer_value(),
-                    $binary.scratch_len.unwrap().as_pointer_value(),
-                )
-            };
-        }
-    };
-}
-
 pub struct PolkadotTarget;
 
 impl PolkadotTarget {

+ 6 - 2
src/sema/expression/function_call.rs

@@ -1963,7 +1963,11 @@ pub(super) fn parse_call_args(
                     return Err(());
                 }
 
-                let ty = Type::Slice(Box::new(Type::Slice(Box::new(Type::Bytes(1)))));
+                // sol_invoke_signed_c() takes of a slice of a slice of slice of bytes
+                // 1. A single seed value is a slice of bytes.
+                // 2. A signer for single address can have multiple seeds
+                // 3. A single call to sol_invoke_signed_c can sign for multiple addresses
+                let ty = Type::Slice(Type::Slice(Type::Slice(Type::Bytes(1).into()).into()).into());
 
                 let expr = expression(
                     &arg.expr,
@@ -1974,7 +1978,7 @@ pub(super) fn parse_call_args(
                     ResolveTo::Type(&ty),
                 )?;
 
-                res.seeds = Some(Box::new(expr));
+                res.seeds = Some(expr.cast(&expr.loc(), &ty, true, ns, diagnostics)?.into());
             }
             "program_id" => {
                 if ns.target != Target::Solana {

+ 23 - 14
src/sema/expression/literals.rs

@@ -662,25 +662,36 @@ pub(super) fn array_literal(
             let mut has_errors = false;
 
             for expr in exprs {
-                let expr = match expression(
+                let Type::Slice(elem) = slice.as_ref() else {
+                    unreachable!()
+                };
+
+                let Ok(expr) = expression(
                     expr,
                     context,
                     ns,
                     symtable,
                     diagnostics,
-                    ResolveTo::Type(&Type::Array(slice.clone(), vec![ArrayLength::Dynamic])),
-                ) {
-                    Ok(expr) => expr,
-                    Err(_) => {
-                        has_errors = true;
-                        continue;
-                    }
+                    ResolveTo::Type(&Type::Array(elem.clone(), vec![ArrayLength::Dynamic])),
+                ) else {
+                    has_errors = true;
+                    continue;
                 };
 
                 let ty = expr.ty();
 
-                if let Type::Array(elem, dims) = &ty {
-                    if elem != slice || dims.len() != 1 {
+                let deref_ty = ty.deref_any();
+
+                let Ok(expr) = expr.cast(&expr.loc(), deref_ty, true, ns, diagnostics) else {
+                    has_errors = true;
+                    continue;
+                };
+
+                if let Type::Array(elem, _) = deref_ty {
+                    if expr
+                        .cast(loc, slice, true, ns, &mut Diagnostics::default())
+                        .is_err()
+                    {
                         diagnostics.push(Diagnostic::error(
                             expr.loc(),
                             format!(
@@ -691,13 +702,11 @@ pub(super) fn array_literal(
                         ));
                         has_errors = true;
                     }
+                    used_variable(ns, &expr, symtable);
                 } else {
                     diagnostics.push(Diagnostic::error(
                         expr.loc(),
-                        format!(
-                            "type {} found where array of slices expected",
-                            ty.to_string(ns)
-                        ),
+                        format!("type {} found where array expected", ty.to_string(ns)),
                     ));
                     has_errors = true;
                 }

+ 26 - 3
src/sema/expression/mod.rs

@@ -1210,15 +1210,30 @@ impl Expression {
             {
                 Ok(self.clone())
             }
-            (Type::DynamicBytes | Type::Address(_) | Type::Bytes(_), Type::Slice(ty))
-                if ty.as_ref() == &Type::Bytes(1) =>
-            {
+            // bytes/address/bytesN -> slice bytes1
+            (_, Type::Slice(ty)) if can_cast_to_slice(from) && ty.as_ref() == &Type::Bytes(1) => {
                 Ok(Expression::Cast {
                     loc: *loc,
                     to: to.clone(),
                     expr: Box::new(self.clone()),
                 })
             }
+            // bytes[] -> slice slice bytes1
+            (Type::Array(from, dims), Type::Slice(to))
+                if dims.len() == 1
+                    && (from == to
+                        || (can_cast_to_slice(from)
+                            && to.slice_depth() == (1, &Type::Bytes(1)))) =>
+            {
+                Ok(self.clone())
+            }
+            // bytes[][] -> slice slice slice bytes1
+            (Type::Array(from, dims), Type::Slice(to))
+                if dims.len() == 2
+                    && (can_cast_to_slice(from) && to.slice_depth() == (2, &Type::Bytes(1))) =>
+            {
+                Ok(self.clone())
+            }
             (Type::FunctionSelector, Type::Bytes(n)) => {
                 let selector_length = ns.target.selector_length();
                 if *n == selector_length {
@@ -1280,6 +1295,14 @@ impl Expression {
     }
 }
 
+/// Can this type be cast to a bytes slice
+fn can_cast_to_slice(ty: &Type) -> bool {
+    matches!(
+        ty,
+        Type::Address(_) | Type::Bytes(_) | Type::DynamicBytes | Type::String
+    )
+}
+
 /// Resolve operator with the given arguments to an expression, if possible
 pub(super) fn user_defined_operator(
     loc: &pt::Loc,

+ 14 - 1
src/sema/types.rs

@@ -1299,7 +1299,7 @@ impl Type {
             Type::Unreachable => "unreachable".into(),
             // A slice of bytes1 is like bytes
             Type::Slice(ty) if **ty == Type::Bytes(1) => "bytes".into(),
-            Type::Slice(ty) => format!("{} slice", ty.to_string(ns)),
+            Type::Slice(ty) => format!("{}[]", ty.to_string(ns)),
             Type::Unresolved => "unresolved".into(),
             Type::BufferPointer => "buffer_pointer".into(),
             Type::FunctionSelector => "function_selector".into(),
@@ -1436,6 +1436,7 @@ impl Type {
             }
             Type::Array(ty, dim) if dim.len() == 1 => *ty.clone(),
             Type::DynamicBytes => Type::Bytes(1),
+            Type::Slice(ty) => *ty.clone(),
             _ => panic!("not an array"),
         }
     }
@@ -1671,6 +1672,18 @@ impl Type {
         }
     }
 
+    /// For a slice of slices, return the contained type and depth
+    /// slices
+    pub fn slice_depth(&self) -> (usize, &Type) {
+        if let Type::Slice(ty) = self {
+            let (depth, ty) = ty.slice_depth();
+
+            (depth + 1, ty)
+        } else {
+            (0, self)
+        }
+    }
+
     pub fn is_signed_int(&self, ns: &Namespace) -> bool {
         match self {
             Type::Int(_) => true,

+ 0 - 1
tests/contract_testcases/solana/annotations/constructor_seeds.sol

@@ -48,7 +48,6 @@ contract c4 {
 // ---- Expect: diagnostics ----
 // error: 1:14: address literal @#$! invalid character '@'
 // error: 7:15-16: 'a' not found
-// error: 8:31-36: conversion from string to bytes not possible
 // error: 11:15: address literal 102 invalid character '0'
 // error: 16:2-61: invalid parameter for annotation
 // error: 19:9-10: duplicate @space annotation for constructor

+ 0 - 1
tests/contract_testcases/solana/annotations/constructor_seeds_bad.sol

@@ -26,5 +26,4 @@ contract c1 {
 // 	note 10:2-11: previous @space
 // error: 16:14-19: duplicate @bump annotation for constructor
 // 	note 15:2-11: previous @bump
-// error: 19:31-36: conversion from string to bytes not possible
 // error: 19:66-71: implicit conversion to bytes1 from uint64 not allowed

+ 5 - 5
tests/contract_testcases/solana/bad_seeds_on_external_call.sol

@@ -25,11 +25,11 @@ contract c {
 }
 
 // ---- Expect: diagnostics ----
-// error: 13:4-7: type bytes1 found where array of slices expected
+// error: 13:4-7: type bytes1 found where array expected
 // error: 15:4-5: expected 'bytes[]', found integer
-// error: 16:4-5: type string found where array of slices expected
-// error: 17:4-13: type bytes2 found where array of slices expected
-// error: 18:4-5: type bytes found where array of slices expected
+// error: 16:4-5: type string found where array expected
+// error: 17:4-13: type bytes2 found where array expected
+// error: 18:4-5: type bytes found where array expected
 // error: 20:5-6: expected 'bytes', found integer
 // error: 21:5-10: conversion from struct AccountMeta[1] to bytes not possible
-// error: 22:4-15: type bytes found where array bytes expected
+// error: 22:4-15: type bytes found where array bytes[] expected

+ 580 - 0
tests/solana_tests/call.rs

@@ -733,3 +733,583 @@ fn pda() {
         .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
         .call();
 }
+
+#[test]
+fn pda_array() {
+    // now more dynamic
+    let mut vm = build_solidity(
+        r#"
+            import {AccountMeta} from 'solana';
+
+            contract pda {
+                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
+
+                function test(bytes[] dyn, address[] addr, bytes5[] b5) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    bytes3 foo = "foo";
+
+                    tokenProgramId.call{seeds: [ dyn, addr, b5 ], accounts: metas}(instr);
+                }
+            }"#,
+    );
+
+    let data_account = vm.initialize_data_account();
+    vm.function("new")
+        .accounts(vec![("dataAccount", data_account)])
+        .call();
+
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foobar"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardth",
+                    b"quinquagintaquadringentillionths"
+                ]
+            )
+        );
+        assert_eq!(
+            signers[2],
+            create_program_address(&vm.stack[0].id, &[b"tares", b"enoki"])
+        );
+    };
+
+    let token = Pubkey(
+        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+            .from_base58()
+            .unwrap()
+            .try_into()
+            .unwrap(),
+    );
+
+    vm.account_data.insert(token.0, AccountState::default());
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"foo".to_vec()),
+                BorshToken::Bytes(b"bar".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardth"),
+                BorshToken::Address(*b"quinquagintaquadringentillionths"),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::FixedBytes(b"tares".to_vec()),
+                BorshToken::FixedBytes(b"enoki".to_vec()),
+            ]),
+        ])
+        .call();
+}
+
+#[test]
+fn pda_array_of_array() {
+    // now more dynamic
+    let mut vm = build_solidity(
+        r#"
+            import {AccountMeta} from 'solana';
+
+            contract pda {
+                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
+
+                function test_bytes(bytes[][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+
+                function test_string(string[][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+
+                function test_bytes4(bytes4[][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+
+                function test_addr(address[][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+            }"#,
+    );
+
+    let data_account = vm.initialize_data_account();
+    vm.function("new")
+        .accounts(vec![("dataAccount", data_account)])
+        .call();
+
+    let token = Pubkey(
+        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+            .from_base58()
+            .unwrap()
+            .try_into()
+            .unwrap(),
+    );
+
+    vm.account_data.insert(token.0, AccountState::default());
+
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(signers.len(), 2);
+
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foobar"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[b"zemmiphobia", b"extemporaneousness", b"automysophobia"]
+            )
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_bytes")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"foo".to_vec()),
+                BorshToken::Bytes(b"bar".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"zemmiphobia".to_vec()),
+                BorshToken::Bytes(b"extemporaneousness".to_vec()),
+                BorshToken::Bytes(b"automysophobia".to_vec()),
+            ]),
+        ])])
+        .call();
+
+    // test string
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"Finifugal", b"Falsiloquence"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(&vm.stack[0].id, &[b"Obrotund"])
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_string")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"Finifugal".to_vec()),
+                BorshToken::Bytes(b"Falsiloquence".to_vec()),
+            ]),
+            BorshToken::Array(vec![BorshToken::Bytes(b"Obrotund".to_vec())]),
+        ])])
+        .call();
+
+    // test address
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardth",
+                    b"quinquagintaquadringentillionths"
+                ]
+            )
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardt1",
+                    b"quinquagintaquadringentilliardt2",
+                    b"quinquagintaquadringentilliardt3"
+                ]
+            )
+        );
+    };
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_addr")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardth"),
+                BorshToken::Address(*b"quinquagintaquadringentillionths"),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardt1"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt2"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt3"),
+            ]),
+        ])])
+        .call();
+
+    // test bytes4
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foofbarf"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(&vm.stack[0].id, &[b"drat", b"plop", b"dang"])
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_bytes4")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::Array(vec![
+                BorshToken::FixedBytes(b"foof".to_vec()),
+                BorshToken::FixedBytes(b"barf".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::FixedBytes(b"drat".to_vec()),
+                BorshToken::FixedBytes(b"plop".to_vec()),
+                BorshToken::FixedBytes(b"dang".to_vec()),
+            ]),
+        ])])
+        .call();
+}
+
+#[test]
+fn pda_array_of_array_fixed() {
+    // now more dynamic
+    let mut vm = build_solidity(
+        r#"
+            import {AccountMeta} from 'solana';
+
+            contract pda {
+                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
+
+                function test_bytes(bytes[2][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+
+                function test_string(string[1][2] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+
+                function test_addr(address[][2] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
+                }
+            }"#,
+    );
+
+    let data_account = vm.initialize_data_account();
+    vm.function("new")
+        .accounts(vec![("dataAccount", data_account)])
+        .call();
+
+    let token = Pubkey(
+        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+            .from_base58()
+            .unwrap()
+            .try_into()
+            .unwrap(),
+    );
+
+    vm.account_data.insert(token.0, AccountState::default());
+
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foobar"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(&vm.stack[0].id, &[b"zemmiphobia", b"extemporaneousness"])
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_bytes")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::FixedArray(vec![
+                BorshToken::Bytes(b"foo".to_vec()),
+                BorshToken::Bytes(b"bar".to_vec()),
+            ]),
+            BorshToken::FixedArray(vec![
+                BorshToken::Bytes(b"zemmiphobia".to_vec()),
+                BorshToken::Bytes(b"extemporaneousness".to_vec()),
+            ]),
+        ])])
+        .call();
+
+    // test string
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"Finifugal"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(&vm.stack[0].id, &[b"Obrotund"])
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_string")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::FixedArray(vec![
+            BorshToken::FixedArray(vec![BorshToken::Bytes(b"Finifugal".to_vec())]),
+            BorshToken::FixedArray(vec![BorshToken::Bytes(b"Obrotund".to_vec())]),
+        ])])
+        .call();
+
+    // // test address
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(
+            signers[0],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardth",
+                    b"quinquagintaquadringentillionths"
+                ]
+            )
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardt1",
+                    b"quinquagintaquadringentilliardt2",
+                    b"quinquagintaquadringentilliardt3"
+                ]
+            )
+        );
+    };
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_addr")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::FixedArray(vec![
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardth"),
+                BorshToken::Address(*b"quinquagintaquadringentillionths"),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardt1"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt2"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt3"),
+            ]),
+        ])])
+        .call();
+}
+
+#[test]
+fn pda_array_of_array_mixed() {
+    // now more dynamic
+    let mut vm = build_solidity(
+        r#"
+            import {AccountMeta} from 'solana';
+
+            contract pda {
+                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
+
+                function test(bytes[] dyn, address[] addr, bytes5[] b5, string f) public {
+                    bytes instr = new bytes(1);
+                    instr[0] = 0x95;
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+                    tokenProgramId.call{seeds: [ dyn, addr, b5, [f] ], accounts: metas}(instr);
+                }
+
+                function test_bytes2(bytes[][] seeds) public {
+                    bytes instr = new bytes(1);
+
+                    instr[0] = 0x95;
+
+                    AccountMeta[1] metas = [
+                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
+                    ];
+
+                    tokenProgramId.call{seeds: [seeds[0], seeds[1]], accounts: metas}(instr);
+                }
+            }"#,
+    );
+
+    let data_account = vm.initialize_data_account();
+    vm.function("new")
+        .accounts(vec![("dataAccount", data_account)])
+        .call();
+
+    let token = Pubkey(
+        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+            .from_base58()
+            .unwrap()
+            .try_into()
+            .unwrap(),
+    );
+
+    vm.account_data.insert(token.0, AccountState::default());
+
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(signers.len(), 4);
+
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foobar"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[
+                    b"quinquagintaquadringentilliardt1",
+                    b"quinquagintaquadringentilliardt2",
+                    b"quinquagintaquadringentilliardt3"
+                ]
+            )
+        );
+        assert_eq!(
+            signers[2],
+            create_program_address(&vm.stack[0].id, &[b"azure", b"squab", b"tares"])
+        );
+        assert_eq!(
+            signers[3],
+            create_program_address(&vm.stack[0].id, &[b"penultimatum"])
+        );
+    };
+
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"foo".to_vec()),
+                BorshToken::Bytes(b"bar".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Address(*b"quinquagintaquadringentilliardt1"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt2"),
+                BorshToken::Address(*b"quinquagintaquadringentilliardt3"),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::FixedBytes(b"azure".to_vec()),
+                BorshToken::FixedBytes(b"squab".to_vec()),
+                BorshToken::FixedBytes(b"tares".to_vec()),
+            ]),
+            BorshToken::String("penultimatum".into()),
+        ])
+        .call();
+
+    let test_args = |vm: &VirtualMachine, _instr: &Instruction, signers: &[Pubkey]| {
+        assert_eq!(signers.len(), 2);
+
+        assert_eq!(
+            signers[0],
+            create_program_address(&vm.stack[0].id, &[b"foobar"])
+        );
+        assert_eq!(
+            signers[1],
+            create_program_address(
+                &vm.stack[0].id,
+                &[b"zemmiphobia", b"extemporaneousness", b"automysophobia"]
+            )
+        );
+    };
+    vm.call_params_check.insert(token.clone(), test_args);
+
+    vm.function("test_bytes2")
+        .accounts(vec![("tokenProgram", token.0), ("systemProgram", [0; 32])])
+        .arguments(&[BorshToken::Array(vec![
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"foo".to_vec()),
+                BorshToken::Bytes(b"bar".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"zemmiphobia".to_vec()),
+                BorshToken::Bytes(b"extemporaneousness".to_vec()),
+                BorshToken::Bytes(b"automysophobia".to_vec()),
+            ]),
+            BorshToken::Array(vec![
+                BorshToken::Bytes(b"ignore".to_vec()),
+                BorshToken::Bytes(b"this".to_vec()),
+            ]),
+        ])])
+        .call();
+}