ソースを参照

Store return data in contract storage alongside contract state

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 年 前
コミット
48cf2e8aaf

+ 7 - 16
integration/solana/index.ts

@@ -94,7 +94,7 @@ class TestConnection {
         return account;
     }
 
-    async loadProgram(sopath: string, abipath: string, returnDataSize: number = 2048, contractStorageSize: number = 512): Promise<Program> {
+    async loadProgram(sopath: string, abipath: string, contractStorageSize: number = 2048): Promise<Program> {
         console.log(`Loading ${sopath} ...`)
 
         const data: Buffer = fs.readFileSync(sopath);
@@ -114,15 +114,14 @@ class TestConnection {
 
         console.log('Program loaded to account', programId.toBase58());
 
-        const returnDataAccount = await this.createStorageAccount(programId, returnDataSize);
         const contractStorageAccount = await this.createStorageAccount(programId, contractStorageSize);
 
-        return new Program(programId, returnDataAccount, contractStorageAccount, abi);
+        return new Program(programId, contractStorageAccount, abi);
     }
 }
 
 class Program {
-    constructor(private programId: PublicKey, private returnDataAccount: Account, private contractStorageAccount: Account, private abi: string) { }
+    constructor(private programId: PublicKey, private contractStorageAccount: Account, private abi: string) { }
 
     async call_constructor(test: TestConnection, params: string[]): Promise<void> {
         let abi: AbiItem | undefined = JSON.parse(this.abi).find((e: AbiItem) => e.type == "constructor");
@@ -140,7 +139,6 @@ class Program {
 
         const instruction = new TransactionInstruction({
             keys: [
-                { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true },
                 { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }],
             programId: this.programId,
             data,
@@ -170,7 +168,6 @@ 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 }];
 
 
@@ -196,11 +193,12 @@ class Program {
         );
 
         if (abi.outputs?.length) {
-            const accountInfo = await test.connection.getAccountInfo(this.returnDataAccount.publicKey);
+            const accountInfo = await test.connection.getAccountInfo(this.contractStorageAccount.publicKey);
 
-            let length = Number(accountInfo!.data.readBigInt64LE(0));
+            let length = Number(accountInfo!.data.readUInt32LE(4));
+            let offset = Number(accountInfo!.data.readUInt32LE(8));
 
-            let encoded = accountInfo!.data.slice(8, length + 8);
+            let encoded = accountInfo!.data.slice(offset, length + offset);
 
             let returns = Web3EthAbi.decodeParameters(abi.outputs!, encoded.toString('hex'));
 
@@ -224,13 +222,6 @@ class Program {
         return accountInfo!.data;
     }
 
-    async return_data(test: TestConnection, upto: number): Promise<Buffer> {
-        const accountInfo = await test.connection.getAccountInfo(this.returnDataAccount.publicKey);
-
-        return accountInfo!.data;
-    }
-
-
     all_keys(): PublicKey[] {
         return [this.programId, this.contractStorageAccount.publicKey];
     }

+ 5 - 33
integration/solana/simple.spec.ts

@@ -251,7 +251,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let prog = await conn.loadProgram("store.so", "store.abi");
+        let prog = await conn.loadProgram("store.so", "store.abi", 8192);
 
         // call the constructor
         await prog.call_constructor(conn, []);
@@ -400,7 +400,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 byes
-        let prog = await conn.loadProgram("store.so", "store.abi", 512, 100);
+        let prog = await conn.loadProgram("store.so", "store.abi", 100);
 
         await expect(prog.call_constructor(conn, []))
             .rejects
@@ -431,7 +431,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("store.so", "store.abi", 512, 180);
+        let prog = await conn.loadProgram("store.so", "store.abi", 180);
 
         await prog.call_constructor(conn, []);
 
@@ -448,7 +448,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("store.so", "store.abi", 512, 210);
+        let prog = await conn.loadProgram("store.so", "store.abi", 210);
 
         await prog.call_constructor(conn, []);
 
@@ -471,7 +471,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("arrays.so", "arrays.abi", 512, 4096);
+        let prog = await conn.loadProgram("arrays.so", "arrays.abi", 4096);
 
         await prog.call_constructor(conn, []);
 
@@ -532,32 +532,4 @@ 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");
-    });
 });

+ 109 - 103
src/emit/solana.rs

@@ -444,84 +444,6 @@ impl SolanaTarget {
         );
     }
 
-    // Returns the pointer to the length of the return buffer, and the buffer itself
-    fn return_buffer<'b>(
-        &self,
-        contract: &Contract<'b>,
-    ) -> (PointerValue<'b>, PointerValue<'b>, IntValue<'b>) {
-        let parameters = contract
-            .builder
-            .get_insert_block()
-            .unwrap()
-            .get_parent()
-            .unwrap()
-            .get_last_param()
-            .unwrap()
-            .into_pointer_value();
-
-        // the first field is the array of
-        // the first account passed in is the return buffer; 3 field of account is "data"
-        let data = contract
-            .builder
-            .build_load(
-                unsafe {
-                    contract.builder.build_gep(
-                        parameters,
-                        &[
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_int(3, false),
-                        ],
-                        "data",
-                    )
-                },
-                "data",
-            )
-            .into_pointer_value();
-
-        let length = contract
-            .builder
-            .build_load(
-                unsafe {
-                    contract.builder.build_gep(
-                        parameters,
-                        &[
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_zero(),
-                            contract.context.i32_type().const_int(2, false),
-                        ],
-                        "data_len",
-                    )
-                },
-                "data_len",
-            )
-            .into_int_value();
-
-        // First we have the 64 bit length field
-        let data_len_ptr = contract.builder.build_pointer_cast(
-            data,
-            contract.context.i64_type().ptr_type(AddressSpace::Generic),
-            "data_len_ptr",
-        );
-
-        // step over that field, and cast to u8* for the buffer itself
-        let data_ptr = contract.builder.build_pointer_cast(
-            unsafe {
-                contract.builder.build_gep(
-                    data_len_ptr,
-                    &[contract.context.i32_type().const_int(1, false)],
-                    "data_ptr",
-                )
-            },
-            contract.context.i8_type().ptr_type(AddressSpace::Generic),
-            "data_ptr",
-        );
-
-        (data_len_ptr, data_ptr, length)
-    }
-
     /// Free contract storage and zero out
     fn storage_free<'b>(
         &self,
@@ -2233,11 +2155,48 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     }
 
     fn return_empty_abi(&self, contract: &Contract) {
-        let (data_len_ptr, _, _) = self.return_buffer(contract);
+        let data = self.contract_storage_data(contract);
+
+        let header_ptr = contract.builder.build_pointer_cast(
+            data,
+            contract.context.i32_type().ptr_type(AddressSpace::Generic),
+            "header_ptr",
+        );
+
+        let data_len_ptr = unsafe {
+            contract.builder.build_gep(
+                header_ptr,
+                &[contract.context.i64_type().const_int(1, false)],
+                "data_len_ptr",
+            )
+        };
+
+        let data_ptr = unsafe {
+            contract.builder.build_gep(
+                header_ptr,
+                &[contract.context.i64_type().const_int(2, false)],
+                "data_ptr",
+            )
+        };
+
+        let offset = contract
+            .builder
+            .build_load(data_ptr, "offset")
+            .into_int_value();
+
+        contract.builder.build_call(
+            contract.module.get_function("account_data_free").unwrap(),
+            &[data.into(), offset.into()],
+            "",
+        );
 
         contract
             .builder
-            .build_store(data_len_ptr, contract.context.i64_type().const_zero());
+            .build_store(data_len_ptr, contract.context.i32_type().const_zero());
+
+        contract
+            .builder
+            .build_store(data_ptr, contract.context.i32_type().const_zero());
 
         // return 0 for success
         contract
@@ -2286,8 +2245,6 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     ) -> (PointerValue<'a>, IntValue<'a>) {
         debug_assert_eq!(args.len(), tys.len());
 
-        let (output_len, output, output_size) = self.return_buffer(contract);
-
         let mut tys = tys.to_vec();
 
         let packed = if let Some(selector) = selector {
@@ -2302,43 +2259,92 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
 
         let length = encoder.encoded_length();
 
-        let length64 =
-            contract
-                .builder
-                .build_int_z_extend(length, contract.context.i64_type(), "length64");
+        let data = self.contract_storage_data(contract);
+        let account = self.contract_storage_account(contract);
 
-        // check the return data account is big enough
-        let encoded_length_with_length = contract.builder.build_int_add(
-            length64,
-            contract.context.i64_type().const_int(8, false),
-            "encoded_length_with_length",
+        let header_ptr = contract.builder.build_pointer_cast(
+            data,
+            contract.context.i32_type().ptr_type(AddressSpace::Generic),
+            "header_ptr",
         );
 
-        // do bounds check on index
-        let is_enough_space = contract.builder.build_int_compare(
-            IntPredicate::UGE,
-            output_size,
-            encoded_length_with_length,
-            "is_enough_space",
+        let data_len_ptr = unsafe {
+            contract.builder.build_gep(
+                header_ptr,
+                &[contract.context.i64_type().const_int(1, false)],
+                "data_len_ptr",
+            )
+        };
+
+        let data_offset_ptr = unsafe {
+            contract.builder.build_gep(
+                header_ptr,
+                &[contract.context.i64_type().const_int(2, false)],
+                "data_offset_ptr",
+            )
+        };
+
+        let offset = contract
+            .builder
+            .build_load(data_offset_ptr, "offset")
+            .into_int_value();
+
+        let rc = contract
+            .builder
+            .build_call(
+                contract
+                    .module
+                    .get_function("account_data_realloc")
+                    .unwrap(),
+                &[
+                    account.into(),
+                    offset.into(),
+                    length.into(),
+                    data_offset_ptr.into(),
+                ],
+                "",
+            )
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+
+        let is_rc_zero = contract.builder.build_int_compare(
+            IntPredicate::EQ,
+            rc,
+            contract.context.i64_type().const_zero(),
+            "is_rc_zero",
         );
 
-        let not_enough = contract.context.append_basic_block(function, "not_enough");
-        let enough = contract.context.append_basic_block(function, "enough");
+        let rc_not_zero = contract.context.append_basic_block(function, "rc_not_zero");
+        let rc_zero = contract.context.append_basic_block(function, "rc_zero");
 
         contract
             .builder
-            .build_conditional_branch(is_enough_space, enough, not_enough);
+            .build_conditional_branch(is_rc_zero, rc_zero, rc_not_zero);
 
-        contract.builder.position_at_end(not_enough);
+        contract.builder.position_at_end(rc_not_zero);
 
         self.return_code(
             contract,
             contract.context.i64_type().const_int(5u64 << 32, false),
         );
 
-        contract.builder.position_at_end(enough);
+        contract.builder.position_at_end(rc_zero);
 
-        contract.builder.build_store(output_len, length64);
+        contract.builder.build_store(data_len_ptr, length);
+
+        let offset = contract
+            .builder
+            .build_load(data_offset_ptr, "offset")
+            .into_int_value();
+
+        // step over that field, and cast to u8* for the buffer itself
+        let output = contract.builder.build_pointer_cast(
+            unsafe { contract.builder.build_gep(data, &[offset], "data_ptr") },
+            contract.context.i8_type().ptr_type(AddressSpace::Generic),
+            "data_ptr",
+        );
 
         encoder.finish(contract, function, output);
 

BIN
stdlib/bpf/solana.bc


+ 2 - 3
stdlib/solana.c

@@ -19,8 +19,7 @@ entrypoint(const uint8_t *input)
 
     int account_no;
 
-    // the first account is the returndata account; ignore that one
-    for (account_no = 1; account_no < params.ka_num; account_no++)
+    for (account_no = 0; account_no < params.ka_num; account_no++)
     {
         if (SolPubkey_same(params.account_id, params.ka[account_no].key))
         {
@@ -55,7 +54,7 @@ uint64_t external_call(uint8_t *input, uint32_t input_len, const SolParameters *
     // 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++)
+    for (int account_no = 0; account_no < params->ka_num; account_no++)
     {
         const SolAccountInfo *acc = &params->ka[account_no];
 

+ 9 - 14
tests/solana.rs

@@ -43,7 +43,6 @@ struct VirtualMachine {
     account_data: HashMap<Account, (Vec<u8>, Option<Account>)>,
     programs: Vec<Contract>,
     stack: Vec<Contract>,
-    returndata: Account,
     printbuf: String,
     output: Vec<u8>,
 }
@@ -94,17 +93,12 @@ fn build_solidity(src: &str) -> VirtualMachine {
         programs.push(Contract { program, abi, data });
     }
 
-    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(),
     }
@@ -152,12 +146,8 @@ fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec<u8> {
     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);
-        }
+        serialize_account(&mut v, key, data);
     }
 
     // calldata
@@ -538,10 +528,11 @@ impl VirtualMachine {
 
         deserialize_parameters(&parameter_bytes, &mut elf.account_data);
 
-        let output = &elf.account_data[&elf.returndata].0;
+        let output = &elf.account_data[&elf.stack[0].data].0;
 
-        let len = LittleEndian::read_u64(&output);
-        elf.output = output[8..len as usize + 8].to_vec();
+        let len = LittleEndian::read_u32(&output[4..]) as usize;
+        let offset = LittleEndian::read_u32(&output[8..]) as usize;
+        elf.output = output[offset..offset + len].to_vec();
 
         println!("return: {}", hex::encode(&elf.output));
 
@@ -551,6 +542,8 @@ impl VirtualMachine {
     fn constructor(&mut self, args: &[Token]) {
         let program = &self.stack[0];
 
+        println!("constructor for {}", hex::encode(&program.data));
+
         let calldata = if let Some(constructor) = &program.abi.constructor {
             constructor
                 .encode_input(program.data.to_vec(), args)
@@ -565,6 +558,8 @@ impl VirtualMachine {
     fn function(&mut self, name: &str, args: &[Token]) -> Vec<Token> {
         let program = &self.stack[0];
 
+        println!("function for {}", hex::encode(&program.data));
+
         let mut calldata: Vec<u8> = program.data.to_vec();
 
         match program.abi.functions[name][0].encode_input(args) {

+ 5 - 5
tests/solana_tests/storage.rs

@@ -33,10 +33,10 @@ fn string() {
 
     assert_eq!(
         vm.data()[0..20].to_vec(),
-        vec![65, 177, 160, 100, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0]
+        vec![65, 177, 160, 100, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 120, 0, 0, 0]
     );
 
-    assert_eq!(vm.data()[40..53].to_vec(), b"Hello, World!");
+    assert_eq!(vm.data()[120..133].to_vec(), b"Hello, World!");
 
     let returns = vm.function("get", &[]);
 
@@ -52,7 +52,7 @@ fn string() {
 
     assert_eq!(
         vm.data()[0..20].to_vec(),
-        vec![65, 177, 160, 100, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0]
+        vec![65, 177, 160, 100, 96, 0, 0, 0, 152, 0, 0, 0, 24, 0, 0, 0, 120, 0, 0, 0]
     );
 
     // Try setting this to an empty string. This is also a special case where
@@ -65,7 +65,7 @@ fn string() {
 
     assert_eq!(
         vm.data()[0..20].to_vec(),
-        vec![65, 177, 160, 100, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0]
+        vec![65, 177, 160, 100, 64, 0, 0, 0, 40, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0]
     );
 }
 
@@ -114,7 +114,7 @@ fn bytes() {
 
     assert_eq!(
         vm.data()[0..20].to_vec(),
-        vec![11, 66, 182, 57, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0]
+        vec![11, 66, 182, 57, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 88, 0, 0, 0]
     );
 
     for (i, b) in b"The shoemaker always wears the worst shoes"