瀏覽代碼

Check that the account data is large enough

Add checks:
 - On constructor call
 - On returning abi encoded data to the first account
 - When allocating more data in account (e.g. storing string)
 - When reallocating more data in account (e.g. push() on storage bytes)

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 年之前
父節點
當前提交
4304504341
共有 12 個文件被更改,包括 349 次插入66 次删除
  1. 9 0
      docs/running.rst
  2. 3 3
      integration/solana/index.ts
  3. 73 0
      integration/solana/simple.spec.ts
  4. 4 4
      src/emit/ethabiencoder.rs
  5. 1 1
      src/emit/ewasm.rs
  6. 1 1
      src/emit/generic.rs
  7. 37 10
      src/emit/mod.rs
  8. 1 1
      src/emit/sabre.rs
  9. 191 28
      src/emit/solana.rs
  10. 1 1
      src/emit/substrate.rs
  11. 二進制
      stdlib/bpf/solana.bc
  12. 28 17
      stdlib/solana.c

+ 9 - 0
docs/running.rst

@@ -166,6 +166,15 @@ be used for different purposes. In Solang's case, each time the contract is exec
 The first account is for the `return data`, i.e. either the ABI encoded
 return values or the revert buffer. The second account is to hold the contract storage variables.
 
+The output of the compiler will tell you how large the second account needs to be. For the `flipper.sol` example,
+the output contains *"info: contract flipper uses exactly 9 bytes account data"*. This means the second account
+should be exactly 9 bytes; anything larger is wasted. If the output is
+*"info: contract store uses at least 168 bytes account data"* then some storage elements are dynamic, so the size
+depends on the data stored. For example there could be a ``string`` type, and storage depends on the length of
+the string. The minimum is 168 bytes, but storing any non-zero-length dynamic types will fail.
+
+If either account is too small, the transaction will fail with the error *account data too small for instruction*.
+
 Before any function on a smart contract can be used, the constructor must be first be called. This ensures that
 the constructor as declared in the solidity code is executed, and that the contract storage account is
 correctly initialized. To call the constructor, abi encode (using ethereum abi encoding) the constructor

+ 3 - 3
integration/solana/index.ts

@@ -94,7 +94,7 @@ class TestConnection {
         return account;
     }
 
-    async loadProgram(sopath: string, abipath: string): Promise<Program> {
+    async loadProgram(sopath: string, abipath: string, returnDataSize: number = 2048, contractStorageSize: number = 512): Promise<Program> {
         console.log(`Loading ${sopath} ...`)
 
         const data: Buffer = fs.readFileSync(sopath);
@@ -114,8 +114,8 @@ class TestConnection {
 
         console.log('Program loaded to account', programId.toBase58());
 
-        const returnDataAccount = await this.createStorageAccount(programId, 2048);
-        const contractStorageAccount = await this.createStorageAccount(programId, 512);
+        const returnDataAccount = await this.createStorageAccount(programId, returnDataSize);
+        const contractStorageAccount = await this.createStorageAccount(programId, contractStorageSize);
 
         return new Program(programId, returnDataAccount, contractStorageAccount, abi);
     }

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

@@ -391,4 +391,77 @@ describe('Deploy solang contract and test', () => {
             ]
         ]));
     });
+
+
+    it('account storage too small constructor', async function () {
+        this.timeout(50000);
+
+        let conn = await establishConnection();
+
+        // storage.sol needs 168 byes
+        let prog = await conn.loadProgram("store.so", "store.abi", 512, 100);
+
+        await expect(prog.call_constructor(conn, []))
+            .rejects
+            .toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
+    });
+
+    it('returndata too small', async function () {
+        this.timeout(50000);
+
+        let conn = await establishConnection();
+
+        // storage.sol needs 168 byes
+        let prog = await conn.loadProgram("store.so", "store.abi", 512);
+
+        await prog.call_constructor(conn, []);
+
+        await prog.call_function(conn, "set_foo1", []);
+
+        // get foo1
+        await expect(prog.call_function(conn, "get_both_foos", []))
+            .rejects
+            .toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
+    });
+
+    it('account storage too small dynamic alloc', async function () {
+        this.timeout(50000);
+
+        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);
+
+        await prog.call_constructor(conn, []);
+
+        // set a load of string which will overflow
+        await expect(prog.call_function(conn, "set_foo1", []))
+            .rejects
+            .toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
+    });
+
+
+    it('account storage too small dynamic realloc', async function () {
+        this.timeout(50000);
+
+        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);
+
+        await prog.call_constructor(conn, []);
+
+        async function push_until_bang() {
+            for (let i = 0; i < 100; i++) {
+                await prog.call_function(conn, "push", ["0x01"]);
+                console.log("pushed one byte");
+            }
+        }
+
+        // do realloc until failure
+        await expect(push_until_bang())
+            .rejects
+            .toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
+    });
 });
+

+ 4 - 4
src/emit/ethabiencoder.rs

@@ -5,7 +5,7 @@ use inkwell::AddressSpace;
 use inkwell::IntPredicate;
 use num_traits::ToPrimitive;
 
-use super::Contract;
+use super::{Contract, ReturnCode};
 
 pub struct EthAbiEncoder {
     pub bswap: bool,
@@ -1786,9 +1786,9 @@ impl EthAbiEncoder {
 
         contract.builder.position_at_end(bail_block);
 
-        contract
-            .builder
-            .build_return(Some(&contract.context.i32_type().const_int(3, false)));
+        contract.builder.build_return(Some(
+            &contract.return_values[&ReturnCode::AbiEncodingInvalid],
+        ));
 
         contract.builder.position_at_end(success_block);
     }

+ 1 - 1
src/emit/ewasm.rs

@@ -1123,7 +1123,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
     }
 
     // ewasm main cannot return any value
-    fn return_u32<'b>(&self, contract: &'b Contract, _ret: IntValue<'b>) {
+    fn return_code<'b>(&self, contract: &'b Contract, _ret: IntValue<'b>) {
         self.assert_failure(
             contract,
             contract

+ 1 - 1
src/emit/generic.rs

@@ -667,7 +667,7 @@ impl<'a> TargetRuntime<'a> for GenericTarget {
         panic!("generic cannot call other contracts");
     }
 
-    fn return_u32<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
+    fn return_code<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
         contract.builder.build_return(Some(&ret));
     }
 

+ 37 - 10
src/emit/mod.rs

@@ -200,7 +200,7 @@ pub trait TargetRuntime<'a> {
     fn return_empty_abi(&self, contract: &Contract);
 
     /// Return failure code
-    fn return_u32<'b>(&self, contract: &'b Contract, ret: IntValue<'b>);
+    fn return_code<'b>(&self, contract: &'b Contract, ret: IntValue<'b>);
 
     /// Return success with the ABI encoded result
     fn return_abi<'b>(&self, contract: &'b Contract, data: PointerValue<'b>, length: IntValue);
@@ -3028,7 +3028,7 @@ pub trait TargetRuntime<'a> {
                     Instr::Return { value } if value.is_empty() => {
                         contract
                             .builder
-                            .build_return(Some(&contract.context.i32_type().const_zero()));
+                            .build_return(Some(&contract.return_values[&ReturnCode::Success]));
                     }
                     Instr::Return { value } => {
                         let returns_offset = cfg.params.len();
@@ -3042,7 +3042,7 @@ pub trait TargetRuntime<'a> {
                         }
                         contract
                             .builder
-                            .build_return(Some(&contract.context.i32_type().const_zero()));
+                            .build_return(Some(&contract.return_values[&ReturnCode::Success]));
                     }
                     Instr::Set { res, expr, .. } => {
                         let value_ref = self.expression(contract, expr, &w.vars, function);
@@ -3557,7 +3557,7 @@ pub trait TargetRuntime<'a> {
                         let success = contract.builder.build_int_compare(
                             IntPredicate::EQ,
                             ret.into_int_value(),
-                            contract.context.i32_type().const_zero(),
+                            contract.return_values[&ReturnCode::Success],
                             "success",
                         );
 
@@ -4301,7 +4301,10 @@ pub trait TargetRuntime<'a> {
 
         if fallback.is_none() && receive.is_none() {
             // no need to check value transferred; we will abort either way
-            self.return_u32(contract, contract.context.i32_type().const_int(2, false));
+            self.return_code(
+                contract,
+                contract.return_values[&ReturnCode::FunctionSelectorInvalid],
+            );
 
             return;
         }
@@ -4337,7 +4340,7 @@ pub trait TargetRuntime<'a> {
                 self.return_empty_abi(contract);
             }
             None => {
-                self.return_u32(contract, contract.context.i32_type().const_int(2, false));
+                self.return_code(contract, contract.context.i32_type().const_int(2, false));
             }
         }
 
@@ -4352,7 +4355,7 @@ pub trait TargetRuntime<'a> {
                 self.return_empty_abi(contract);
             }
             None => {
-                self.return_u32(contract, contract.context.i32_type().const_int(2, false));
+                self.return_code(contract, contract.context.i32_type().const_int(2, false));
             }
         }
     }
@@ -4420,7 +4423,7 @@ pub trait TargetRuntime<'a> {
         let success = contract.builder.build_int_compare(
             IntPredicate::EQ,
             ret.into_int_value(),
-            contract.context.i32_type().const_zero(),
+            contract.return_values[&ReturnCode::Success],
             "success",
         );
 
@@ -4453,7 +4456,7 @@ pub trait TargetRuntime<'a> {
 
         contract.builder.position_at_end(bail_block);
 
-        self.return_u32(contract, ret.into_int_value());
+        self.return_code(contract, ret.into_int_value());
 
         cases.push((
             contract
@@ -5161,6 +5164,14 @@ pub struct Contract<'a> {
     scratch_len: Option<GlobalValue<'a>>,
     scratch: Option<GlobalValue<'a>>,
     accounts: Option<PointerValue<'a>>,
+    return_values: HashMap<ReturnCode, IntValue<'a>>,
+}
+
+#[derive(PartialEq, Eq, Hash)]
+enum ReturnCode {
+    Success,
+    FunctionSelectorInvalid,
+    AbiEncodingInvalid,
 }
 
 impl<'a> Contract<'a> {
@@ -5372,6 +5383,18 @@ impl<'a> Contract<'a> {
                 .const_zero(),
         );
 
+        let mut return_values = HashMap::new();
+
+        return_values.insert(ReturnCode::Success, context.i32_type().const_zero());
+        return_values.insert(
+            ReturnCode::FunctionSelectorInvalid,
+            context.i32_type().const_int(3, false),
+        );
+        return_values.insert(
+            ReturnCode::AbiEncodingInvalid,
+            context.i32_type().const_int(2, false),
+        );
+
         Contract {
             name: contract.name.to_owned(),
             module,
@@ -5394,6 +5417,7 @@ impl<'a> Contract<'a> {
             scratch: None,
             scratch_len: None,
             accounts: None,
+            return_values,
         }
     }
 
@@ -5696,7 +5720,10 @@ impl<'a> Contract<'a> {
             );
         }
 
-        self.context.i32_type().fn_type(&args, false)
+        // Solana return type should be 64 bit, 32 bit on wasm
+        self.return_values[&ReturnCode::Success]
+            .get_type()
+            .fn_type(&args, false)
     }
 
     pub fn upower(&self, bit: u32) -> FunctionValue<'a> {

+ 1 - 1
src/emit/sabre.rs

@@ -764,7 +764,7 @@ impl<'a> TargetRuntime<'a> for SabreTarget {
         panic!("Sabre cannot call other contracts");
     }
 
-    fn return_u32<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
+    fn return_code<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
         contract.builder.build_return(Some(&ret));
     }
 

+ 191 - 28
src/emit/solana.rs

@@ -12,7 +12,7 @@ use num_traits::ToPrimitive;
 use tiny_keccak::{Hasher, Keccak};
 
 use super::ethabiencoder;
-use super::{Contract, TargetRuntime, Variable};
+use super::{Contract, ReturnCode, TargetRuntime, Variable};
 
 pub struct SolanaTarget {
     abi: ethabiencoder::EthAbiEncoder,
@@ -54,6 +54,17 @@ impl SolanaTarget {
             None,
         );
 
+        con.return_values
+            .insert(ReturnCode::Success, context.i64_type().const_zero());
+        con.return_values.insert(
+            ReturnCode::FunctionSelectorInvalid,
+            context.i64_type().const_int(2u64 << 32, false),
+        );
+        con.return_values.insert(
+            ReturnCode::AbiEncodingInvalid,
+            context.i64_type().const_int(2u64 << 32, false),
+        );
+
         // externals
         target.declare_externals(&mut con);
 
@@ -160,15 +171,60 @@ impl SolanaTarget {
 
         contract.builder.position_at_end(badmagic_block);
 
-        contract
-            .builder
-            .build_return(Some(&contract.context.i32_type().const_int(7, false)));
+        contract.builder.build_return(Some(
+            &contract.context.i64_type().const_int(4u64 << 32, false),
+        ));
 
         contract.accounts = Some(accounts);
 
         // generate constructor code
         contract.builder.position_at_end(constructor_block);
 
+        // do we have enough contract data
+        let contract_data_len = contract
+            .builder
+            .build_load(
+                unsafe {
+                    contract.builder.build_gep(
+                        accounts,
+                        &[
+                            contract.context.i32_type().const_int(1, false),
+                            contract.context.i32_type().const_int(2, false),
+                        ],
+                        "contract_data_len_ptr",
+                    )
+                },
+                "contract_data_len",
+            )
+            .into_int_value();
+
+        let fixed_fields_size = contract.contract.fixed_layout_size.to_u64().unwrap();
+
+        let is_enough = contract.builder.build_int_compare(
+            IntPredicate::UGE,
+            contract_data_len,
+            contract
+                .context
+                .i64_type()
+                .const_int(fixed_fields_size, false),
+            "is_enough",
+        );
+
+        let not_enough = contract.context.append_basic_block(function, "not_enough");
+        let enough = contract.context.append_basic_block(function, "enough");
+
+        contract
+            .builder
+            .build_conditional_branch(is_enough, enough, not_enough);
+
+        contract.builder.position_at_end(not_enough);
+
+        contract.builder.build_return(Some(
+            &contract.context.i64_type().const_int(5u64 << 32, false),
+        ));
+
+        contract.builder.position_at_end(enough);
+
         // write our magic value to the contract
         contract.builder.build_store(
             magic_value_ptr,
@@ -187,8 +243,6 @@ impl SolanaTarget {
             )
         };
 
-        let fixed_fields_size = contract.contract.fixed_layout_size.to_u64().unwrap();
-
         // align heap to 8 bytes
         let heap_offset = (fixed_fields_size + 7) & !7;
 
@@ -225,7 +279,7 @@ impl SolanaTarget {
                 .unwrap()
         } else {
             // return 0 for success
-            contract.context.i32_type().const_int(0, false).into()
+            contract.context.i64_type().const_int(0, false).into()
         };
 
         contract.builder.build_return(Some(&ret));
@@ -253,7 +307,10 @@ 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>) {
+    fn return_buffer<'b>(
+        &self,
+        contract: &Contract<'b>,
+    ) -> (PointerValue<'b>, PointerValue<'b>, IntValue<'b>) {
         // the first account passed in is the return buffer; 3 field of account is "data"
         let data = contract
             .builder
@@ -272,6 +329,23 @@ impl SolanaTarget {
             )
             .into_pointer_value();
 
+        let length = contract
+            .builder
+            .build_load(
+                unsafe {
+                    contract.builder.build_gep(
+                        contract.accounts.unwrap(),
+                        &[
+                            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,
@@ -292,7 +366,7 @@ impl SolanaTarget {
             "data_ptr",
         );
 
-        (data_len_ptr, data_ptr)
+        (data_len_ptr, data_ptr, length)
     }
 }
 
@@ -603,7 +677,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     fn storage_bytes_push(
         &self,
         contract: &Contract,
-        _function: FunctionValue,
+        function: FunctionValue,
         slot: IntValue,
         val: IntValue,
     ) {
@@ -663,14 +737,19 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             "new_length",
         );
 
-        let new_offset = contract
+        let rc = contract
             .builder
             .build_call(
                 contract
                     .module
                     .get_function("account_data_realloc")
                     .unwrap(),
-                &[account.into(), offset.into(), new_length.into()],
+                &[
+                    account.into(),
+                    offset.into(),
+                    new_length.into(),
+                    offset_ptr.into(),
+                ],
                 "new_offset",
             )
             .try_as_basic_value()
@@ -678,7 +757,33 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .unwrap()
             .into_int_value();
 
-        contract.builder.build_store(offset_ptr, new_offset);
+        let is_rc_zero = contract.builder.build_int_compare(
+            IntPredicate::EQ,
+            rc,
+            contract.context.i64_type().const_zero(),
+            "is_rc_zero",
+        );
+
+        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_rc_zero, rc_zero, rc_not_zero);
+
+        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(rc_zero);
+
+        let new_offset = contract
+            .builder
+            .build_load(offset_ptr, "offset")
+            .into_int_value();
 
         let index = contract.builder.build_int_add(new_offset, length, "index");
         let member = unsafe { contract.builder.build_gep(data, &[index], "data") };
@@ -784,7 +889,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .module
                 .get_function("account_data_realloc")
                 .unwrap(),
-            &[account.into(), offset.into(), new_length.into()],
+            &[
+                account.into(),
+                offset.into(),
+                new_length.into(),
+                offset_ptr.into(),
+            ],
             "new_offset",
         );
 
@@ -1094,12 +1204,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 "free",
             );
 
-            // account_data_alloc will return 0 if the string is length 0
-            let new_offset = contract
+            // account_data_alloc will return offset = 0 if the string is length 0
+            let rc = contract
                 .builder
                 .build_call(
                     contract.module.get_function("account_data_alloc").unwrap(),
-                    &[account.into(), new_string_length.into()],
+                    &[account.into(), new_string_length.into(), offset_ptr.into()],
                     "alloc",
                 )
                 .try_as_basic_value()
@@ -1107,7 +1217,30 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .unwrap()
                 .into_int_value();
 
-            contract.builder.build_store(offset_ptr, new_offset);
+            let is_rc_zero = contract.builder.build_int_compare(
+                IntPredicate::EQ,
+                rc,
+                contract.context.i64_type().const_zero(),
+                "is_rc_zero",
+            );
+
+            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_rc_zero, rc_zero, rc_not_zero);
+
+            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(rc_zero);
+
+            let new_offset = contract.builder.build_load(offset_ptr, "new_offset");
 
             contract.builder.build_unconditional_branch(memcpy);
 
@@ -1117,7 +1250,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .builder
                 .build_phi(contract.context.i32_type(), "offset");
 
-            offset_phi.add_incoming(&[(&new_offset, realloc), (&offset, entry)]);
+            offset_phi.add_incoming(&[(&new_offset, rc_zero), (&offset, entry)]);
 
             let dest_string_data = unsafe {
                 contract.builder.build_gep(
@@ -1211,7 +1344,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     }
 
     fn return_empty_abi(&self, contract: &Contract) {
-        let (data_len_ptr, _) = self.return_buffer(contract);
+        let (data_len_ptr, _, _) = self.return_buffer(contract);
 
         contract
             .builder
@@ -1220,7 +1353,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         // return 0 for success
         contract
             .builder
-            .build_return(Some(&contract.context.i32_type().const_int(0, false)));
+            .build_return(Some(&contract.context.i64_type().const_int(0, false)));
     }
 
     fn return_abi<'b>(&self, contract: &'b Contract, _data: PointerValue<'b>, _length: IntValue) {
@@ -1229,16 +1362,16 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         // return 0 for success
         contract
             .builder
-            .build_return(Some(&contract.context.i32_type().const_int(0, false)));
+            .build_return(Some(&contract.context.i64_type().const_int(0, false)));
     }
 
     fn assert_failure<'b>(&self, contract: &'b Contract, _data: PointerValue, _length: IntValue) {
         // the reason code should be null (and already printed)
 
         // return 1 for failure
-        contract
-            .builder
-            .build_return(Some(&contract.context.i32_type().const_int(1, false)));
+        contract.builder.build_return(Some(
+            &contract.context.i64_type().const_int(1u64 << 32, false),
+        ));
     }
 
     /// ABI encode into a vector for abi.encode* style builtin functions
@@ -1263,7 +1396,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         args: &[BasicValueEnum<'a>],
         tys: &[ast::Type],
     ) -> (PointerValue<'a>, IntValue<'a>) {
-        let (output_len, mut output) = self.return_buffer(contract);
+        let (output_len, mut output, output_size) = self.return_buffer(contract);
 
         let (length, mut offset) = ethabiencoder::EthAbiEncoder::total_encoded_length(
             contract, selector, load, function, args, tys,
@@ -1274,7 +1407,37 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .builder
                 .build_int_z_extend(length, contract.context.i64_type(), "length64");
 
-        // FIXME ensure we have enough space for our return data
+        // 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",
+        );
+
+        // 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 not_enough = contract.context.append_basic_block(function, "not_enough");
+        let enough = contract.context.append_basic_block(function, "enough");
+
+        contract
+            .builder
+            .build_conditional_branch(is_enough_space, enough, not_enough);
+
+        contract.builder.position_at_end(not_enough);
+
+        self.return_code(
+            contract,
+            contract.context.i64_type().const_int(5u64 << 32, false),
+        );
+
+        contract.builder.position_at_end(enough);
+
         contract.builder.build_store(output_len, length64);
 
         if let Some(selector) = selector {
@@ -1400,7 +1563,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         unimplemented!();
     }
 
-    fn return_u32<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
+    fn return_code<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
         contract.builder.build_return(Some(&ret));
     }
 

+ 1 - 1
src/emit/substrate.rs

@@ -2553,7 +2553,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         contract.builder.build_unreachable();
     }
 
-    fn return_u32<'b>(&self, contract: &'b Contract, _ret: IntValue<'b>) {
+    fn return_code<'b>(&self, contract: &'b Contract, _ret: IntValue<'b>) {
         // we can't return specific errors
         self.assert_failure(
             contract,

二進制
stdlib/bpf/solana.bc


+ 28 - 17
stdlib/solana.c

@@ -4,7 +4,7 @@
 #include "stdlib.h"
 #include "solana_sdk.h"
 
-extern int solang_dispatch(const uint8_t *input, uint64_t input_len, SolAccountInfo *ka);
+extern uint64_t solang_dispatch(const uint8_t *input, uint64_t input_len, SolAccountInfo *ka);
 
 uint64_t
 entrypoint(const uint8_t *input)
@@ -46,13 +46,16 @@ struct chunk
 
 #define ROUND_UP(n, d) (((n) + (d)-1) & ~(d - 1))
 
-uint32_t account_data_alloc(SolAccountInfo *ai, uint32_t size)
+uint64_t account_data_alloc(SolAccountInfo *ai, uint32_t size, uint32_t *res)
 {
     void *data = ai->data;
     struct account_data_header *hdr = data;
 
     if (!size)
+    {
+        *res = 0;
         return 0;
+    }
 
     uint32_t offset = hdr->heap_offset;
 
@@ -70,12 +73,9 @@ uint32_t account_data_alloc(SolAccountInfo *ai, uint32_t size)
             {
                 offset += sizeof(struct chunk);
 
-                uint32_t length = ai->data_len - offset;
-
-                if (length < alloc_size)
+                if (offset + alloc_size + sizeof(struct chunk) >= ai->data_len)
                 {
-                    sol_log("account does not have enough storage");
-                    sol_panic();
+                    return ERROR_ACCOUNT_DATA_TOO_SMALL;
                 }
 
                 chunk->offset_next = offset + alloc_size;
@@ -90,7 +90,8 @@ uint32_t account_data_alloc(SolAccountInfo *ai, uint32_t size)
                 next->offset_next = 0;
                 next->allocated = false;
 
-                return offset;
+                *res = offset;
+                return 0;
             }
             else if (chunk->length < alloc_size)
             {
@@ -102,7 +103,8 @@ uint32_t account_data_alloc(SolAccountInfo *ai, uint32_t size)
                 chunk->allocated = true;
                 chunk->length = size;
 
-                return offset + sizeof(struct chunk);
+                *res = offset + sizeof(struct chunk);
+                return 0;
             }
             else
             {
@@ -128,7 +130,8 @@ uint32_t account_data_alloc(SolAccountInfo *ai, uint32_t size)
                     chunk->offset_prev = next_offset;
                 }
 
-                return offset + sizeof(struct chunk);
+                *res = offset + sizeof(struct chunk);
+                return 0;
             }
         }
 
@@ -222,7 +225,7 @@ void account_data_free(SolAccountInfo *ai, uint32_t offset)
     }
 }
 
-uint32_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size)
+uint64_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size, uint32_t *res)
 {
     if (!size)
     {
@@ -232,7 +235,7 @@ uint32_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size
 
     if (!offset)
     {
-        return account_data_alloc(ai, size);
+        return account_data_alloc(ai, size, res);
     }
 
     void *data = ai->data;
@@ -301,7 +304,8 @@ uint32_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size
             }
         }
 
-        return offset;
+        *res = offset;
+        return 0;
     }
 
     // 2. Can we use the next chunk to expand our chunk to fit
@@ -342,7 +346,8 @@ uint32_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size
                     next->offset_prev = offset_next;
                 }
 
-                return offset;
+                *res = offset;
+                return 0;
             }
         }
         else
@@ -359,17 +364,23 @@ uint32_t account_data_realloc(SolAccountInfo *ai, uint32_t offset, uint32_t size
                 next->allocated = false;
                 next->length = 0;
 
-                return offset;
+                *res = offset;
+                return 0;
             }
         }
     }
 
     uint32_t old_length = account_data_len(ai, offset);
-    uint32_t new_offset = account_data_alloc(ai, size);
+    uint32_t new_offset;
+    uint64_t rc = account_data_alloc(ai, size, &new_offset);
+    if (rc)
+        return rc;
+
     __memcpy(data + new_offset, data + offset, old_length);
     account_data_free(ai, offset);
 
-    return new_offset;
+    *res = new_offset;
+    return 0;
 }
 
 #ifdef TEST