Kaynağa Gözat

report runtime errors (#1150)

Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
salaheldinsoliman 2 yıl önce
ebeveyn
işleme
9a8d525591
44 değiştirilmiş dosya ile 1984 ekleme ve 342 silme
  1. 1 0
      integration/solana/Creature.key
  2. 1 1
      integration/solana/package.json
  3. 195 134
      integration/solana/primitives.sol
  4. 140 0
      integration/solana/runtime_errors.sol
  5. 146 0
      integration/solana/runtime_errors.spec.ts
  6. 2 0
      integration/solana/storage.sol
  7. 13 1
      integration/substrate/index.ts
  8. 1 1
      integration/substrate/package.json
  9. 201 134
      integration/substrate/primitives.sol
  10. 134 0
      integration/substrate/runtime_errors.sol
  11. 92 0
      integration/substrate/runtime_errors.spec.ts
  12. 2 0
      integration/substrate/store.sol
  13. 2 0
      src/abi/tests.rs
  14. 9 0
      src/bin/solang.rs
  15. 11 3
      src/codegen/cfg.rs
  16. 2 0
      src/codegen/constant_folding.rs
  17. 1 0
      src/codegen/constructor.rs
  18. 144 5
      src/codegen/expression.rs
  19. 25 1
      src/codegen/mod.rs
  20. 9 1
      src/codegen/storage.rs
  21. 2 0
      src/codegen/subexpression_elimination/instruction.rs
  22. 1 0
      src/codegen/subexpression_elimination/tests.rs
  23. 4 1
      src/codegen/yul/builtin.rs
  24. 51 12
      src/emit/expression.rs
  25. 8 1
      src/emit/functions.rs
  26. 28 7
      src/emit/instructions.rs
  27. 21 1
      src/emit/math.rs
  28. 25 3
      src/emit/mod.rs
  29. 60 8
      src/emit/solana/target.rs
  30. 2 1
      src/emit/substrate/dispatch.rs
  31. 7 5
      src/emit/substrate/mod.rs
  32. 69 9
      src/emit/substrate/target.rs
  33. 2 0
      src/lib.rs
  34. 7 2
      tests/solana.rs
  35. 1 0
      tests/solana_tests/mod.rs
  36. 6 6
      tests/solana_tests/primitives.rs
  37. 270 0
      tests/solana_tests/runtime_errors.rs
  38. 3 1
      tests/substrate.rs
  39. 1 0
      tests/substrate_tests/calls.rs
  40. 271 0
      tests/substrate_tests/errors.rs
  41. 10 4
      tests/substrate_tests/expressions.rs
  42. 2 0
      tests/substrate_tests/inheritance.rs
  43. 1 0
      tests/substrate_tests/mod.rs
  44. 1 0
      tests/undefined_variable_detection.rs

+ 1 - 0
integration/solana/Creature.key

@@ -0,0 +1 @@
+[178,34,166,253,17,119,204,242,46,85,115,254,58,182,101,68,87,86,70,255,200,116,164,189,217,196,212,236,2,233,123,242,176,39,248,225,251,241,236,215,8,185,170,27,236,112,83,100,77,75,37,15,98,9,111,75,82,213,162,183,123,34,183,181]

+ 1 - 1
integration/solana/package.json

@@ -4,7 +4,7 @@
   "description": "Integration tests with Solang and Solana",
   "scripts": {
     "test": "tsc; ts-node setup.ts; mocha --parallel *.spec.ts",
-    "build": "solang compile *.sol --target solana -v; solang compile --math-overflow overflow.sol --target solana -v"
+    "build": "solang compile *.sol --target solana -v --log-runtime-errors --math-overflow"
   },
   "author": "Sean Young <sean@mess.org>",
   "license": "MIT",

+ 195 - 134
integration/solana/primitives.sol

@@ -1,148 +1,209 @@
-
 contract primitives {
-	enum oper { add, sub, mul, div, mod, pow, shl, shr, or, and, xor }
+    enum oper {
+        add,
+        sub,
+        mul,
+        div,
+        mod,
+        pow,
+        shl,
+        shr,
+        or,
+        and,
+        xor
+    }
 
-	function is_mul(oper op) pure public returns (bool) {
-		return op == oper.mul;
-	}
+    function is_mul(oper op) public pure returns (bool) {
+        unchecked {
+            return op == oper.mul;
+        }
+    }
 
-	function return_div() pure public returns (oper) {
-		return oper.div;
-	}
+    function return_div() public pure returns (oper) {
+        return oper.div;
+    }
 
-	function op_i64(oper op, int64 a, int64 b) pure public returns (int64) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_i64(
+        oper op,
+        int64 a,
+        int64 b
+    ) public pure returns (int64) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u64(oper op, uint64 a, uint64 b) pure public returns (uint64) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.pow) {
-			return a ** b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_u64(
+        oper op,
+        uint64 a,
+        uint64 b
+    ) public pure returns (uint64) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.pow) {
+                return a**b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u256(oper op, uint256 a, uint256 b) pure public returns (uint256) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.pow) {
-			return a ** uint256(b);
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_u256(
+        oper op,
+        uint256 a,
+        uint256 b
+    ) public pure returns (uint256) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.pow) {
+                return a**uint256(b);
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_i256(oper op, int256 a, int256 b) pure public returns (int256) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_i256(
+        oper op,
+        int256 a,
+        int256 b
+    ) public pure returns (int256) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function return_u8_6() public pure returns (bytes6) {
-		return "ABCDEF";
-	}
+    function return_u8_6() public pure returns (bytes6) {
+        return "ABCDEF";
+    }
 
-	function op_u8_5_shift(oper op, bytes5 a, uint64 r) pure public returns (bytes5) {
-		if (op == oper.shl) {
-			return a << r;
-		} else if (op == oper.shr) {
-			return a >> r;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_5_shift(
+        oper op,
+        bytes5 a,
+        uint64 r
+    ) public pure returns (bytes5) {
+        unchecked {
+            if (op == oper.shl) {
+                return a << r;
+            } else if (op == oper.shr) {
+                return a >> r;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_5(oper op, bytes5 a, bytes5 b) pure public returns (bytes5) {
-		if (op == oper.or) {
-			return a | b;
-		} else if (op == oper.and) {
-			return a & b;
-		} else if (op == oper.xor) {
-			return a ^ b;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_5(
+        oper op,
+        bytes5 a,
+        bytes5 b
+    ) public pure returns (bytes5) {
+        unchecked {
+            if (op == oper.or) {
+                return a | b;
+            } else if (op == oper.and) {
+                return a & b;
+            } else if (op == oper.xor) {
+                return a ^ b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_14_shift(oper op, bytes14 a, uint64 r) pure public returns (bytes14) {
-		if (op == oper.shl) {
-			return a << r;
-		} else if (op == oper.shr) {
-			return a >> r;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_14_shift(
+        oper op,
+        bytes14 a,
+        uint64 r
+    ) public pure returns (bytes14) {
+        unchecked {
+            if (op == oper.shl) {
+                return a << r;
+            } else if (op == oper.shr) {
+                return a >> r;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_14(oper op, bytes14 a, bytes14 b) pure public returns (bytes14) {
-		if (op == oper.or) {
-			return a | b;
-		} else if (op == oper.and) {
-			return a & b;
-		} else if (op == oper.xor) {
-			return a ^ b;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_14(
+        oper op,
+        bytes14 a,
+        bytes14 b
+    ) public pure returns (bytes14) {
+        unchecked {
+            if (op == oper.or) {
+                return a | b;
+            } else if (op == oper.and) {
+                return a & b;
+            } else if (op == oper.xor) {
+                return a ^ b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function address_passthrough(address a) pure public returns (address) {
-		return a;
-	}
+    function address_passthrough(address a) public pure returns (address) {
+        return a;
+    }
 }

+ 140 - 0
integration/solana/runtime_errors.sol

@@ -0,0 +1,140 @@
+import 'solana';
+
+
+contract RuntimeErrors {
+    bytes b = hex"0000_00fa";
+    uint256[] arr;
+    Creature public c;
+
+    constructor() {}
+
+    function print_test(int8 num) public returns (int8) {
+        print("Hello world!");
+
+        require(num > 10, "sesa");
+        assert(num > 10);
+
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function math_overflow(int8 num) public returns (int8) {
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function require_test(int256 num) public returns (int8) {
+        require(num > 10, "sesa");
+        return 0;
+    }
+
+    // assert failure
+    function assert_test(int256 num) public returns (int8) {
+        assert(num > 10);
+        return 0;
+    }
+
+    // storage index out of bounds
+    function set_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        b[5] = sesa[0];
+        return sesa;
+    }
+
+    // storage array index out of bounds
+    function get_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        sesa[0] = b[5];
+        return sesa;
+    }
+
+    // value transfer failure
+    function transfer_abort() public {
+        address a = address(0);
+        payable(a).transfer(1e15);
+    }
+
+    //  pop from empty storage array
+    function pop_empty_storage() public {
+        arr.pop();
+    }
+
+    // external call failed
+    function call_ext(Creature e) public {
+        e.say_my_name();
+    }
+
+    // contract creation failed (contract was deplyed with no value)
+    function create_child(address child_contract_addr, address payer) public {
+        c = new Creature{address: child_contract_addr}(payer);
+        c.say_my_name();
+    }
+
+    function i_will_revert() public {
+        revert();
+    }
+
+    function write_integer_failure(uint256 buf_size) public {
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeUint32LE(350, 20);
+    }
+
+    function write_bytes_failure(uint256 buf_size) public {
+        bytes data = new bytes(10);
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeBytes(data, 0);
+    }
+
+    function read_integer_failure(uint32 offset) public {
+        bytes smol_buf = new bytes(1);
+        smol_buf.readUint16LE(offset);
+    }
+
+    // truncated type overflows
+    function trunc_failure(uint256 input) public returns (uint256[]) {
+        uint256[] a = new uint256[](input);
+        return a;
+    }
+
+    function out_of_bounds(uint256 input) public returns (uint256) {
+        uint256[] a = new uint256[](input);
+        return a[20];
+    }
+
+    function invalid_instruction() public {
+        assembly {
+            invalid()
+        }
+    }
+
+    function byte_cast_failure(uint256 num) public returns (bytes) {
+        bytes smol_buf = new bytes(num);
+        bytes32 b32 = bytes32(smol_buf);
+        return b32;
+    }
+}
+
+
+@program_id("Cre7AzxtwSxXwU2jekYtCAQ57DkBhY9SjGDLdcrwhAo6")
+contract Creature {
+    @payer(payer)
+    @space(511 + 7)
+    constructor(address payer) {
+        print("In child constructor");
+    }
+
+    function say_my_name() public pure returns (string memory) {
+        print("say_my_name");
+        return "child_contract";
+    }
+}
+
+contract calle_contract {
+    constructor() {}
+
+    function calle_contract_func() public {
+        revert();
+    }
+}

+ 146 - 0
integration/solana/runtime_errors.spec.ts

@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: Apache-2.0
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import expect from 'expect';
+import { loadContract } from './setup';
+import { Program, Provider, BN } from '@project-serum/anchor';
+
+describe('Runtime Errors', function () {
+    this.timeout(150000);
+
+    let program: Program;
+    let storage: Keypair;
+    let payer: Keypair;
+    let provider: Provider;
+
+    before(async function () {
+        ({ program, storage, payer, provider } = await loadContract('RuntimeErrors'));
+    });
+
+    it('Prints runtime errors', async function () {
+
+        try {
+            let res = await program.methods.setStorageBytes().accounts({ dataAccount: storage.publicKey }).simulate();
+        }
+        catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: storage index out of bounds in runtime_errors.sol:42:10");
+        }
+
+        try {
+            let res = await program.methods.getStorageBytes().accounts({ dataAccount: storage.publicKey }).simulate();;
+        }
+        catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: storage array index out of bounds in runtime_errors.sol:49:18");
+        }
+
+        try {
+            let res = await program.methods.popEmptyStorage().accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: pop from empty storage array in runtime_errors.sol:61:8")
+
+        }
+
+        try {
+            let res = await program.methods.invalidInstruction().accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: reached invalid instruction in runtime_errors.sol:108:12")
+
+        }
+
+        try {
+            let res = await program.methods.byteCastFailure(new BN(33)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: bytes cast error in runtime_errors.sol:114:22")
+
+        }
+
+        try {
+            let res = await program.methods.iWillRevert().accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: revert encountered in runtime_errors.sol:76:8")
+        }
+
+        try {
+            let res = await program.methods.assertTest(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: assert failure in runtime_errors.sol:35:15")
+        }
+
+        try {
+            let res = await program.methods.writeIntegerFailure(new BN(1)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: integer too large to write in buffer in runtime_errors.sol:81:17")
+        }
+
+        try {
+            let res = await program.methods.writeBytesFailure(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: data does not fit into buffer in runtime_errors.sol:87:17")
+        }
+
+
+        try {
+            let res = await program.methods.readIntegerFailure(new BN(2)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: read integer out of bounds in runtime_errors.sol:92:17")
+        }
+
+
+        try {
+            let res = await program.methods.outOfBounds(new BN(19)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: array index out of bounds in runtime_errors.sol:103:15")
+        }
+
+
+        try {
+            let res = await program.methods.truncFailure(new BN(99999999999999)).accounts({ dataAccount: storage.publicKey }).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs;
+            expect(logs).toContain("Program log: truncated type overflows in runtime_errors.sol:97:36")
+        }
+
+        let child_program = new PublicKey("Cre7AzxtwSxXwU2jekYtCAQ57DkBhY9SjGDLdcrwhAo6");
+        let child = Keypair.generate();
+
+
+        const signature = await program.methods.createChild(child.publicKey, payer.publicKey)
+            .accounts({ dataAccount: storage.publicKey })
+            .remainingAccounts([
+                { pubkey: child_program, isSigner: false, isWritable: false },
+                { pubkey: child.publicKey, isSigner: true, isWritable: true },
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
+            ])
+            .signers([payer, child])
+            .rpc({ commitment: 'confirmed' });
+
+
+        const tx = await provider.connection.getTransaction(signature, { commitment: 'confirmed' });
+        try {
+            const signature = await program.methods.createChild(child.publicKey, payer.publicKey)
+                .accounts({ dataAccount: storage.publicKey })
+                .remainingAccounts([
+                    { pubkey: child_program, isSigner: false, isWritable: false },
+                    { pubkey: payer.publicKey, isSigner: true, isWritable: true },
+                ])
+                .signers([payer]).simulate();
+        } catch (e: any) {
+            const logs = e.simulationResponse.logs
+            expect(logs).toContain("Program log: contract creation failed in runtime_errors.sol:71:12")
+        }
+
+    });
+
+});
+

+ 2 - 0
integration/solana/storage.sol

@@ -49,6 +49,7 @@ contract store {
     }
 
     function do_ops() public {
+        unchecked {
         // u64 will overflow to 1
         u64 += 2;
         u32 &= 0xffff;
@@ -61,6 +62,7 @@ contract store {
         // make upper case
         fixedbytes |= 0x20202020;
         bar = enum_bar.bar4;
+        }
     }
 
     function push_zero() public {

+ 13 - 1
integration/substrate/index.ts

@@ -81,10 +81,22 @@ export function transaction(tx: SubmittableExtrinsic<"promise", ISubmittableResu
 
 // Returns the required gas estimated from a dry run
 export async function weight(api: ApiPromise, contract: ContractPromise, message: string, args?: unknown[], value?: number) {
+  let res = await dry_run(api, contract, message, args, value);
+  return res.gasRequired
+}
+
+// Returns the debug buffer from the dry run result
+export async function debug_buffer(api: ApiPromise, contract: ContractPromise, message: string, args?: unknown[], value?: number) {
+  let res = await dry_run(api, contract, message, args, value);
+  return res.debugMessage.toHuman()
+}
+
+// Return dry run result
+export async function dry_run(api: ApiPromise, contract: ContractPromise, message: string, args?: unknown[], value?: number) {
   const ALICE = new Keyring({ type: 'sr25519' }).addFromUri('//Alice').address;
   const msg = contract.abi.findMessage(message);
   const dry = await api.call.contractsApi.call(ALICE, contract.address, value ? value : 0, null, null, msg.toU8a(args ? args : []));
-  return dry.gasRequired;
+  return dry;
 }
 
 // FIXME: The old contract.query API does not support WeightV2 yet

+ 1 - 1
integration/substrate/package.json

@@ -5,7 +5,7 @@
   "main": "index.js",
   "scripts": {
     "test": "tsc; ts-mocha -t 20000 *.spec.ts",
-    "build": "parallel solang compile -v --target substrate ::: *.sol test/*.sol",
+    "build": "parallel solang compile -v --target substrate --log-runtime-errors --math-overflow ::: *.sol test/*.sol",
     "build-ink": "docker run --rm -v $(pwd)/ink/caller:/opt/contract paritytech/contracts-ci-linux@sha256:513518822f09eba6452ac14ba2a84d12529da86dff850c7c0f067dad6a58af70 cargo contract build --manifest-path /opt/contract/Cargo.toml"
   },
   "contributors": [

+ 201 - 134
integration/substrate/primitives.sol

@@ -1,148 +1,215 @@
-
 contract primitives {
-	enum oper { add, sub, mul, div, mod, pow, shl, shr, or, and, xor }
+    enum oper {
+        add,
+        sub,
+        mul,
+        div,
+        mod,
+        pow,
+        shl,
+        shr,
+        or,
+        and,
+        xor
+    }
 
-	function is_mul(oper op) pure public returns (bool) {
-		return op == oper.mul;
-	}
+    function is_mul(oper op) public pure returns (bool) {
+        unchecked {
+            return op == oper.mul;
+        }
+    }
 
-	function return_div() pure public returns (oper) {
-		return oper.div;
-	}
+    function return_div() public pure returns (oper) {
+        unchecked {
+            return oper.div;
+        }
+    }
 
-	function op_i64(oper op, int64 a, int64 b) pure public returns (int64) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_i64(
+        oper op,
+        int64 a,
+        int64 b
+    ) public pure returns (int64) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u64(oper op, uint64 a, uint64 b) pure public returns (uint64) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.pow) {
-			return a ** b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_u64(
+        oper op,
+        uint64 a,
+        uint64 b
+    ) public pure returns (uint64) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                unchecked {
+                    return a - b;
+                }
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.pow) {
+                return a**b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u256(oper op, uint256 a, uint256 b) pure public returns (uint256) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.pow) {
-			return a ** uint256(b);
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_u256(
+        oper op,
+        uint256 a,
+        uint256 b
+    ) public pure returns (uint256) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                unchecked {
+                    return a - b;
+                }
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.pow) {
+                return a**uint256(b);
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_i256(oper op, int256 a, int256 b) pure public returns (int256) {
-		if (op == oper.add) {
-			return a + b;
-		} else if (op == oper.sub) {
-			return a - b;
-		} else if (op == oper.mul) {
-			return a * b;
-		} else if (op == oper.div) {
-			return a / b;
-		} else if (op == oper.mod) {
-			return a % b;
-		} else if (op == oper.shl) {
-			return a << b;
-		} else if (op == oper.shr) {
-			return a >> b;
-		} else {
-			revert();
-		}
-	}
+    function op_i256(
+        oper op,
+        int256 a,
+        int256 b
+    ) public pure returns (int256) {
+        unchecked {
+            if (op == oper.add) {
+                return a + b;
+            } else if (op == oper.sub) {
+                return a - b;
+            } else if (op == oper.mul) {
+                return a * b;
+            } else if (op == oper.div) {
+                return a / b;
+            } else if (op == oper.mod) {
+                return a % b;
+            } else if (op == oper.shl) {
+                return a << b;
+            } else if (op == oper.shr) {
+                return a >> b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function return_u8_6() public pure returns (bytes6) {
-		return "ABCDEF";
-	}
+    function return_u8_6() public pure returns (bytes6) {
+        return "ABCDEF";
+    }
 
-	function op_u8_5_shift(oper op, bytes5 a, uint64 r) pure public returns (bytes5) {
-		if (op == oper.shl) {
-			return a << r;
-		} else if (op == oper.shr) {
-			return a >> r;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_5_shift(
+        oper op,
+        bytes5 a,
+        uint64 r
+    ) public pure returns (bytes5) {
+        unchecked {
+            if (op == oper.shl) {
+                return a << r;
+            } else if (op == oper.shr) {
+                return a >> r;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_5(oper op, bytes5 a, bytes5 b) pure public returns (bytes5) {
-		if (op == oper.or) {
-			return a | b;
-		} else if (op == oper.and) {
-			return a & b;
-		} else if (op == oper.xor) {
-			return a ^ b;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_5(
+        oper op,
+        bytes5 a,
+        bytes5 b
+    ) public pure returns (bytes5) {
+        unchecked {
+            if (op == oper.or) {
+                return a | b;
+            } else if (op == oper.and) {
+                return a & b;
+            } else if (op == oper.xor) {
+                return a ^ b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_14_shift(oper op, bytes14 a, uint64 r) pure public returns (bytes14) {
-		if (op == oper.shl) {
-			return a << r;
-		} else if (op == oper.shr) {
-			return a >> r;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_14_shift(
+        oper op,
+        bytes14 a,
+        uint64 r
+    ) public pure returns (bytes14) {
+        unchecked {
+            if (op == oper.shl) {
+                return a << r;
+            } else if (op == oper.shr) {
+                return a >> r;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function op_u8_14(oper op, bytes14 a, bytes14 b) pure public returns (bytes14) {
-		if (op == oper.or) {
-			return a | b;
-		} else if (op == oper.and) {
-			return a & b;
-		} else if (op == oper.xor) {
-			return a ^ b;
-		} else {
-			revert();
-		}
-	}
+    function op_u8_14(
+        oper op,
+        bytes14 a,
+        bytes14 b
+    ) public pure returns (bytes14) {
+        unchecked {
+            if (op == oper.or) {
+                return a | b;
+            } else if (op == oper.and) {
+                return a & b;
+            } else if (op == oper.xor) {
+                return a ^ b;
+            } else {
+                revert();
+            }
+        }
+    }
 
-	function address_passthrough(address a) pure public returns (address) {
-		return a;
-	}
+    function address_passthrough(address a) public pure returns (address) {
+        return a;
+    }
 }

+ 134 - 0
integration/substrate/runtime_errors.sol

@@ -0,0 +1,134 @@
+contract RuntimeErrors {
+    bytes b = hex"0000_00fa";
+    uint256[] arr;
+    child public c;
+
+    constructor() {}
+
+    function print_test(int8 num) public returns (int8) {
+        print("Hello world!");
+
+        require(num > 10, "sesa");
+        assert(num > 10);
+
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function math_overflow(int8 num) public returns (int8) {
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function require_test(int256 num) public returns (int8) {
+        require(num > 10, "sesa");
+        return 0;
+    }
+
+    // assert failure
+    function assert_test(int256 num) public returns (int8) {
+        assert(num > 10);
+        return 0;
+    }
+
+    // storage index out of bounds
+    function set_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        b[5] = sesa[0];
+        return sesa;
+    }
+
+    // storage array index out of bounds
+    function get_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        sesa[0] = b[5];
+        return sesa;
+    }
+
+    // value transfer failure
+    function transfer_abort() public {
+        address a = address(0);
+        payable(a).transfer(10);
+    }
+
+    //  pop from empty storage array
+    function pop_empty_storage() public {
+        arr.pop();
+    }
+
+    // external call failed
+    function call_ext(callee_error e) public {
+        e.callee_func();
+    }
+
+    // contract creation failed (contract was deplyed with no value)
+    function create_child() public {
+        c = new child();
+    }
+
+    // non payable function dont_pay_me received value
+    function dont_pay_me() public {}
+
+    function pay_me() public payable {}
+
+    function i_will_revert() public {
+        revert();
+    }
+
+    function write_integer_failure(uint256 buf_size) public {
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeUint32LE(350, 20);
+    }
+
+    function write_bytes_failure(uint256 buf_size) public {
+        bytes data = new bytes(10);
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeBytes(data, 0);
+    }
+
+    function read_integer_failure(uint32 offset) public {
+        bytes smol_buf = new bytes(1);
+        smol_buf.readUint16LE(offset);
+    }
+
+    // truncated type overflows
+    function trunc_failure(uint256 input) public returns (uint256[]) {
+        uint256[] a = new uint256[](input);
+        return a;
+    }
+
+    function out_of_bounds(uint256 input) public returns (uint256) {
+        uint256[] a = new uint256[](input);
+        return a[20];
+    }
+
+    function invalid_instruction() public {
+        assembly {
+            invalid()
+        }
+    }
+
+    function byte_cast_failure(uint256 num) public returns (bytes) {
+        bytes smol_buf = new bytes(num);
+        bytes32 b32 = bytes32(smol_buf);
+        return b32;
+    }
+}
+
+contract callee_error {
+    constructor() {}
+
+    function callee_func() public {
+        revert();
+    }
+}
+
+contract child {
+    constructor() payable {}
+
+    function say_my_name() public pure returns (string memory) {
+        return "child";
+    }
+}

+ 92 - 0
integration/substrate/runtime_errors.spec.ts

@@ -0,0 +1,92 @@
+import { createConnection, deploy, aliceKeypair, debug_buffer } from "./index";
+import expect from 'expect';
+import { ContractPromise } from "@polkadot/api-contract";
+import path = require("path")
+
+describe('Deploy runtime_errors.sol and test the debug buffer', () => {
+  it('logs errors to the debug buffer', async function () {
+
+    let conn = await createConnection();
+    const alice = aliceKeypair();
+
+
+    let deployed_contract = await deploy(
+      conn,
+      alice,
+      "RuntimeErrors.contract",
+      BigInt(0)
+    );
+
+    let contract = new ContractPromise(
+      conn,
+      deployed_contract.abi,
+      deployed_contract.address
+    );
+
+    let child_contract = await deploy(conn, alice, 'child.contract', BigInt(0));
+
+    let res = await debug_buffer(conn, contract, "get_storage_bytes", [])
+    expect(res).toEqual("storage array index out of bounds in runtime_errors.sol:46:18")
+
+    let res1 = await debug_buffer(conn, contract, "transfer_abort", [])
+    expect(res1).toEqual("value transfer failure in runtime_errors.sol:53:28")
+
+
+    let res2 = await debug_buffer(conn, contract, "pop_empty_storage", [])
+    expect(res2).toEqual("pop from empty storage array in runtime_errors.sol:58:12")
+
+
+    let res3 = await debug_buffer(conn, contract, "call_ext", [child_contract.address])
+    expect(res3).toEqual("external call failed in runtime_errors.sol:63:8")
+
+
+    let res4 = await debug_buffer(conn, contract, "create_child");
+    expect(res4).toEqual("contract creation failed in runtime_errors.sol:68:12")
+
+
+    let res5 = await debug_buffer(conn, contract, "set_storage_bytes", [])
+    expect(res5).toEqual("storage index out of bounds in runtime_errors.sol:39:10")
+
+
+    let res6 = await debug_buffer(conn, contract, "dont_pay_me", [], 1);
+    expect(res6).toEqual("non payable function dont_pay_me received value")
+
+
+    let res7 = await debug_buffer(conn, contract, "assert_test", [9], 0);
+    expect(res7).toEqual("assert failure in runtime_errors.sol:32:15")
+
+
+    let res8 = await debug_buffer(conn, contract, "i_will_revert", [], 0);
+    expect(res8).toEqual("revert encountered in runtime_errors.sol:77:8")
+
+
+    let res9 = await debug_buffer(conn, contract, "write_integer_failure", [1], 0);
+    expect(res9).toEqual("integer too large to write in buffer in runtime_errors.sol:82:17")
+
+
+    let res10 = await debug_buffer(conn, contract, "write_bytes_failure", [9], 0);
+    expect(res10).toEqual("data does not fit into buffer in runtime_errors.sol:88:17")
+
+
+    let res11 = await debug_buffer(conn, contract, "read_integer_failure", [2], 0);
+    expect(res11).toEqual("read integer out of bounds in runtime_errors.sol:93:17")
+
+
+    let res12 = await debug_buffer(conn, contract, "trunc_failure", [BigInt("999999999999999999999999")], 0);
+    expect(res12).toEqual("truncated type overflows in runtime_errors.sol:98:36")
+
+
+    let res13 = await debug_buffer(conn, contract, "out_of_bounds", [19], 0);
+    expect(res13).toEqual("array index out of bounds in runtime_errors.sol:104:15")
+
+
+    let res14 = await debug_buffer(conn, contract, "invalid_instruction", [], 0);
+    expect(res14).toEqual("reached invalid instruction in runtime_errors.sol:109:12")
+
+
+    let res15 = await debug_buffer(conn, contract, "byte_cast_failure", [33], 0);
+    expect(res15).toEqual("bytes cast error in runtime_errors.sol:115:22")
+
+    conn.disconnect();
+  });
+});

+ 2 - 0
integration/substrate/store.sol

@@ -33,6 +33,7 @@ contract store {
     }
 
     function do_ops() public {
+        unchecked {
         // u64 will overflow to 1
         u64 += 2;
         u32 &= 0xffff;
@@ -45,6 +46,7 @@ contract store {
         // make upper case
         fixedbytes |= 0x20202020;
         bar = enum_bar.bar4;
+        }
     }
 
     function push_zero() public {

+ 2 - 0
src/abi/tests.rs

@@ -462,6 +462,7 @@ contract caller {
             generate_debug_information: false,
             opt_level: OptimizationLevel::None,
             log_api_return_codes: false,
+            log_runtime_errors: false,
         },
     );
 
@@ -1254,6 +1255,7 @@ fn deduplication() {
             generate_debug_information: false,
             opt_level: OptimizationLevel::None,
             log_api_return_codes: false,
+            log_runtime_errors: false,
         },
     );
 

+ 9 - 0
src/bin/solang.rs

@@ -200,6 +200,12 @@ fn main() {
                             .long("generate-debug-info")
                             .action(ArgAction::SetTrue)
                             .hide(true),
+                    )
+                    .arg(
+                        Arg::new("LOGRUNTIMEERRORS")
+                            .help("Log runtime errors in the environment")
+                            .long("log-runtime-errors")
+                            .action(ArgAction::SetTrue),
                     ),
             )
             .subcommand(
@@ -403,6 +409,8 @@ fn compile(matches: &ArgMatches) {
 
     let log_api_return_codes = *matches.get_one("LOGAPIRETURNS").unwrap();
 
+    let log_runtime_errors = *matches.get_one::<bool>("LOGRUNTIMEERRORS").unwrap();
+
     let mut resolver = imports_arg(matches);
 
     let opt_level = match matches.get_one::<String>("OPT").unwrap().as_str() {
@@ -425,6 +433,7 @@ fn compile(matches: &ArgMatches) {
             .unwrap(),
         opt_level,
         log_api_return_codes,
+        log_runtime_errors,
     };
 
     let mut namespaces = Vec::new();

+ 11 - 3
src/codegen/cfg.rs

@@ -22,6 +22,7 @@ use num_traits::One;
 use parse_display::Display;
 use solang_parser::pt;
 use solang_parser::pt::CodeLocation;
+use solang_parser::pt::Loc;
 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
 use std::ops::AddAssign;
 use std::str;
@@ -106,7 +107,12 @@ pub enum Instr {
     },
     /// Pop element from memory array. The push builtin returns a reference
     /// to the new element which is stored in res.
-    PopMemory { res: usize, ty: Type, array: usize },
+    PopMemory {
+        res: usize,
+        ty: Type,
+        array: usize,
+        loc: Loc,
+    },
     /// Create contract and call constructor. If creating the contract fails,
     /// either store the result in success or abort success.
     Constructor {
@@ -120,6 +126,7 @@ pub enum Instr {
         salt: Option<Expression>,
         address: Option<Expression>,
         seeds: Option<Expression>,
+        loc: Loc,
     },
     /// Call external functions. If the call fails, set the success failure
     /// or abort if this is None
@@ -999,7 +1006,7 @@ impl ControlFlowGraph {
                 ty.to_string(ns),
                 self.expr_to_string(contract, ns, value),
             ),
-            Instr::PopMemory { res, ty, array } => format!(
+            Instr::PopMemory { res, ty, array, loc:_ } => format!(
                 "%{}, %{} = pop array ty:{}",
                 self.vars[res].id.name,
                 self.vars[array].id.name,
@@ -1167,7 +1174,8 @@ impl ControlFlowGraph {
                 gas,
                 salt,
                 value,
-                address,seeds
+                address,seeds,
+                loc:_
 
             } => format!(
                 "%{}, {} = constructor salt:{} value:{} gas:{} address:{} seeds:{} {} (encoded buffer: {}, buffer len: {})",

+ 2 - 0
src/codegen/constant_folding.rs

@@ -196,6 +196,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                     salt,
                     address,
                     seeds,
+                    loc,
                 } => {
                     let encoded_args = expression(encoded_args, Some(&vars), cfg, ns).0;
                     let encoded_args_len = expression(encoded_args_len, Some(&vars), cfg, ns).0;
@@ -224,6 +225,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         salt,
                         address,
                         seeds,
+                        loc: *loc,
                     };
                 }
                 Instr::ExternalCall {

+ 1 - 0
src/codegen/constructor.rs

@@ -91,6 +91,7 @@ pub(super) fn call_constructor(
             salt,
             address,
             seeds,
+            loc: *loc,
         },
     );
 }

+ 144 - 5
src/codegen/expression.rs

@@ -12,6 +12,7 @@ use super::{
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::constructor::call_constructor;
 use crate::codegen::encoding::create_encoder;
+use crate::codegen::error_msg_with_loc;
 use crate::codegen::unused_variable::should_remove_assignment;
 use crate::codegen::{Builtin, Expression};
 use crate::sema::{
@@ -791,6 +792,7 @@ pub fn expression(
                         res: address_res,
                         ty: args[0].ty(),
                         array: array_pos,
+                        loc: *loc,
                     },
                 );
                 cfg.modify_temp_array_length(*loc, true, array_pos, vartab);
@@ -818,12 +820,12 @@ pub fn expression(
             kind: ast::Builtin::Require,
             args,
             ..
-        } => require(cfg, args, contract_no, func, ns, vartab, opt),
+        } => require(cfg, args, contract_no, func, ns, vartab, opt, expr.loc()),
         ast::Expression::Builtin {
             kind: ast::Builtin::Revert,
             args,
             ..
-        } => revert(args, cfg, contract_no, func, ns, vartab, opt),
+        } => revert(args, cfg, contract_no, func, ns, vartab, opt, expr.loc()),
         ast::Expression::Builtin {
             kind: ast::Builtin::SelfDestruct,
             args,
@@ -1299,6 +1301,14 @@ fn expr_assert(
         },
     );
     cfg.set_basic_block(false_);
+    log_runtime_error(
+        opt.log_runtime_errors,
+        "assert failure",
+        args.loc(),
+        cfg,
+        vartab,
+        ns,
+    );
     assert_failure(&Loc::Codegen, None, ns, cfg, vartab);
     cfg.set_basic_block(true_);
     Expression::Poison
@@ -1312,6 +1322,7 @@ fn require(
     ns: &Namespace,
     vartab: &mut Vartable,
     opt: &Options,
+    loc: Loc,
 ) -> Expression {
     let true_ = cfg.new_basic_block("noassert".to_owned());
     let false_ = cfg.new_basic_block("doassert".to_owned());
@@ -1329,10 +1340,37 @@ fn require(
         .get(1)
         .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt));
     match ns.target {
-        // On Solana and Substrate, print the reason, do not abi encoding it
+        // On Solana and Substrate, print the reason, do not abi encode it
         Target::Solana | Target::Substrate { .. } => {
-            if let Some(expr) = expr {
-                cfg.add(vartab, Instr::Print { expr });
+            if opt.log_runtime_errors {
+                if let Some(expr) = expr {
+                    let error_string =
+                        error_msg_with_loc(ns, " require condition failed", Some(expr.loc()));
+                    let print_expr = Expression::FormatString(
+                        Loc::Codegen,
+                        vec![
+                            (FormatArg::Default, expr),
+                            (
+                                FormatArg::StringLiteral,
+                                Expression::BytesLiteral(
+                                    Loc::Codegen,
+                                    Type::Bytes(error_string.as_bytes().len() as u8),
+                                    error_string.as_bytes().to_vec(),
+                                ),
+                            ),
+                        ],
+                    );
+                    cfg.add(vartab, Instr::Print { expr: print_expr });
+                } else {
+                    log_runtime_error(
+                        opt.log_runtime_errors,
+                        "require condition failed",
+                        loc,
+                        cfg,
+                        vartab,
+                        ns,
+                    );
+                }
             }
             assert_failure(&Loc::Codegen, None, ns, cfg, vartab);
         }
@@ -1350,10 +1388,42 @@ fn revert(
     ns: &Namespace,
     vartab: &mut Vartable,
     opt: &Options,
+    loc: Loc,
 ) -> Expression {
     let expr = args
         .get(0)
         .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt));
+
+    if opt.log_runtime_errors {
+        if expr.is_some() {
+            let error_string = error_msg_with_loc(ns, " revert encountered", Some(loc));
+            let print_expr = Expression::FormatString(
+                Loc::Codegen,
+                vec![
+                    (FormatArg::Default, expr.clone().unwrap()),
+                    (
+                        FormatArg::StringLiteral,
+                        Expression::BytesLiteral(
+                            Loc::Codegen,
+                            Type::Bytes(error_string.as_bytes().len() as u8),
+                            error_string.as_bytes().to_vec(),
+                        ),
+                    ),
+                ],
+            );
+            cfg.add(vartab, Instr::Print { expr: print_expr });
+        } else {
+            log_runtime_error(
+                opt.log_runtime_errors,
+                "revert encountered",
+                loc,
+                cfg,
+                vartab,
+                ns,
+            )
+        }
+    }
+
     assert_failure(&Loc::Codegen, expr, ns, cfg, vartab);
     Expression::Poison
 }
@@ -1695,6 +1765,14 @@ fn expr_builtin(
             );
 
             cfg.set_basic_block(out_of_bounds);
+            log_runtime_error(
+                opt.log_runtime_errors,
+                "integer too large to write in buffer",
+                *loc,
+                cfg,
+                vartab,
+                ns,
+            );
             assert_failure(loc, None, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
@@ -1746,6 +1824,14 @@ fn expr_builtin(
             );
 
             cfg.set_basic_block(out_ouf_bounds);
+            log_runtime_error(
+                opt.log_runtime_errors,
+                "data does not fit into buffer",
+                *loc,
+                cfg,
+                vartab,
+                ns,
+            );
             assert_failure(loc, None, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
@@ -1814,6 +1900,14 @@ fn expr_builtin(
             );
 
             cfg.set_basic_block(out_of_bounds);
+            log_runtime_error(
+                opt.log_runtime_errors,
+                "read integer out of bounds",
+                *loc,
+                cfg,
+                vartab,
+                ns,
+            );
             assert_failure(loc, None, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
@@ -2011,6 +2105,14 @@ fn checking_trunc(
     );
 
     cfg.set_basic_block(out_of_bounds);
+    log_runtime_error(
+        opt.log_runtime_errors,
+        "truncated type overflows",
+        *loc,
+        cfg,
+        vartab,
+        ns,
+    );
     assert_failure(loc, None, ns, cfg, vartab);
 
     cfg.set_basic_block(in_bounds);
@@ -2760,6 +2862,14 @@ fn array_subscript(
     );
 
     cfg.set_basic_block(out_of_bounds);
+    log_runtime_error(
+        opt.log_runtime_errors,
+        "array index out of bounds",
+        *loc,
+        cfg,
+        vartab,
+        ns,
+    );
     assert_failure(loc, None, ns, cfg, vartab);
 
     cfg.set_basic_block(in_bounds);
@@ -3064,3 +3174,32 @@ fn code(loc: &Loc, contract_no: usize, ns: &Namespace, opt: &Options) -> Express
 
     Expression::AllocDynamicBytes(*loc, Type::DynamicBytes, size.into(), Some(code))
 }
+
+fn string_to_expr(string: String) -> Expression {
+    Expression::FormatString(
+        Loc::Codegen,
+        vec![(
+            FormatArg::StringLiteral,
+            Expression::BytesLiteral(
+                Loc::Codegen,
+                Type::Bytes(string.as_bytes().len() as u8),
+                string.as_bytes().to_vec(),
+            ),
+        )],
+    )
+}
+
+pub(crate) fn log_runtime_error(
+    report_error: bool,
+    reason: &str,
+    reason_loc: Loc,
+    cfg: &mut ControlFlowGraph,
+    vartab: &mut Vartable,
+    ns: &Namespace,
+) {
+    if report_error {
+        let error_with_loc = error_msg_with_loc(ns, reason, Some(reason_loc));
+        let expr = string_to_expr(error_with_loc);
+        cfg.add(vartab, Instr::Print { expr });
+    }
+}

+ 25 - 1
src/codegen/mod.rs

@@ -41,7 +41,7 @@ use crate::sema::Recurse;
 use num_bigint::{BigInt, Sign};
 use num_rational::BigRational;
 use num_traits::{FromPrimitive, Zero};
-use solang_parser::{pt, pt::CodeLocation};
+use solang_parser::{pt, pt::CodeLocation, pt::Loc};
 
 // The sizeof(struct account_data_header)
 pub const SOLANA_FIRST_OFFSET: u64 = 16;
@@ -89,6 +89,7 @@ pub struct Options {
     pub generate_debug_information: bool,
     pub opt_level: OptimizationLevel,
     pub log_api_return_codes: bool,
+    pub log_runtime_errors: bool,
 }
 
 impl Default for Options {
@@ -103,6 +104,7 @@ impl Default for Options {
             generate_debug_information: false,
             opt_level: OptimizationLevel::Default,
             log_api_return_codes: false,
+            log_runtime_errors: false,
         }
     }
 }
@@ -1342,3 +1344,25 @@ impl From<&ast::Builtin> for Builtin {
         }
     }
 }
+
+pub(super) fn error_msg_with_loc(ns: &Namespace, error: &str, loc: Option<Loc>) -> String {
+    if let Some(loc) = loc {
+        match loc {
+            Loc::File(..) => {
+                let file_no = loc.file_no();
+                let curr_file = &ns.files[file_no];
+                let (line_no, offset) = curr_file.offset_to_line_column(loc.start());
+                format!(
+                    "{} in {}:{}:{}",
+                    error,
+                    curr_file.path.file_name().unwrap().to_str().unwrap(),
+                    line_no + 1,
+                    offset
+                )
+            }
+            _ => error.to_string(),
+        }
+    } else {
+        error.to_string()
+    }
+}

+ 9 - 1
src/codegen/storage.rs

@@ -7,7 +7,7 @@ use num_traits::FromPrimitive;
 use num_traits::One;
 use num_traits::Zero;
 
-use super::expression::{expression, load_storage};
+use super::expression::{expression, load_storage, log_runtime_error};
 use super::Options;
 use super::{
     cfg::{ControlFlowGraph, Instr},
@@ -210,6 +210,14 @@ pub fn storage_slots_array_pop(
     );
 
     cfg.set_basic_block(empty_array);
+    log_runtime_error(
+        opt.log_runtime_errors,
+        "pop from empty storage array",
+        *loc,
+        cfg,
+        vartab,
+        ns,
+    );
     assert_failure(loc, None, ns, cfg, vartab);
 
     cfg.set_basic_block(has_elements);

+ 2 - 0
src/codegen/subexpression_elimination/instruction.rs

@@ -320,6 +320,7 @@ impl AvailableExpressionSet {
                 salt,
                 address,
                 seeds,
+                loc,
             } => {
                 let new_value = value
                     .as_ref()
@@ -348,6 +349,7 @@ impl AvailableExpressionSet {
                     salt: new_salt,
                     address: new_address,
                     seeds: new_seeds,
+                    loc: *loc,
                 }
             }
 

+ 1 - 0
src/codegen/subexpression_elimination/tests.rs

@@ -342,6 +342,7 @@ fn string() {
         salt: Some(compare2.clone()),
         address: None,
         seeds: None,
+        loc: Loc::Codegen,
     };
 
     let mut ave = AvailableExpression::default();

+ 4 - 1
src/codegen/yul/builtin.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::codegen::expression::assert_failure;
+use crate::codegen::expression::{assert_failure, log_runtime_error};
 use crate::codegen::{
     cfg::{ControlFlowGraph, Instr},
     vartable::Vartable,
@@ -168,6 +168,9 @@ pub(crate) fn process_builtin(
         }
 
         YulBuiltInFunction::Invalid => {
+            log_runtime_error(opt.log_runtime_errors,  "reached invalid instruction", *loc, cfg,
+            vartab,
+            ns);
             assert_failure(loc, None, ns, cfg, vartab);
             Expression::Poison
         }

+ 51 - 12
src/emit/expression.rs

@@ -120,7 +120,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .unwrap()
                 .into()
         }
-        Expression::Add(_, _, unchecked, l, r) => {
+        Expression::Add(loc, _, unchecked, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -134,13 +134,15 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                     right,
                     BinaryOp::Add,
                     signed,
+                    ns,
+                    *loc,
                 )
                 .into()
             } else {
                 bin.builder.build_int_add(left, right, "").into()
             }
         }
-        Expression::Subtract(_, _, unchecked, l, r) => {
+        Expression::Subtract(loc, _, unchecked, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -154,13 +156,15 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                     right,
                     BinaryOp::Subtract,
                     signed,
+                    ns,
+                    *loc,
                 )
                 .into()
             } else {
                 bin.builder.build_int_sub(left, right, "").into()
             }
         }
-        Expression::Multiply(_, res_ty, unchecked, l, r) => {
+        Expression::Multiply(loc, res_ty, unchecked, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -172,10 +176,12 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 left,
                 right,
                 res_ty.is_signed_int(),
+                ns,
+                *loc,
             )
             .into()
         }
-        Expression::UnsignedDivide(_, _, l, r) => {
+        Expression::UnsignedDivide(loc, _, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -242,6 +248,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
+                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -269,7 +276,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.build_int_unsigned_div(left, right, "").into()
             }
         }
-        Expression::SignedDivide(_, _, l, r) => {
+        Expression::SignedDivide(loc, _, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -336,6 +343,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
+                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -411,7 +419,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.build_int_signed_div(left, right, "").into()
             }
         }
-        Expression::UnsignedModulo(_, _, l, r) => {
+        Expression::UnsignedModulo(loc, _, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -478,6 +486,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
+                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -502,7 +511,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.build_int_unsigned_rem(left, right, "").into()
             }
         }
-        Expression::SignedModulo(_, _, l, r) => {
+        Expression::SignedModulo(loc, _, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns).into_int_value();
             let right = expression(target, bin, r, vartab, function, ns).into_int_value();
 
@@ -569,6 +578,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
+                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -635,13 +645,22 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.build_int_signed_rem(left, right, "").into()
             }
         }
-        Expression::Power(_, res_ty, unchecked, l, r) => {
+        Expression::Power(loc, res_ty, unchecked, l, r) => {
             let left = expression(target, bin, l, vartab, function, ns);
             let right = expression(target, bin, r, vartab, function, ns);
 
             let bits = left.into_int_value().get_type().get_bit_width();
             let o = bin.build_alloca(function, left.get_type(), "");
-            let f = power(target, bin, *unchecked, bits, res_ty.is_signed_int(), o);
+            let f = power(
+                target,
+                bin,
+                *unchecked,
+                bits,
+                res_ty.is_signed_int(),
+                o,
+                ns,
+                *loc,
+            );
 
             // If the function returns zero, then the operation was successful.
             let error_return = bin
@@ -673,6 +692,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                     .build_conditional_branch(error_ret, error_block, return_block);
                 bin.builder.position_at_end(error_block);
 
+                target.log_runtime_error(bin, "math overflow".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -971,7 +991,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .left()
                 .unwrap()
         }
-        Expression::BytesCast(_, Type::Bytes(n), Type::DynamicBytes, e) => {
+        Expression::BytesCast(loc, Type::Bytes(n), Type::DynamicBytes, e) => {
             let array = expression(target, bin, e, vartab, function, ns);
 
             let len = bin.vector_len(array);
@@ -989,6 +1009,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_conditional_branch(is_equal_to_n, cast, error);
 
             bin.builder.position_at_end(error);
+            target.log_runtime_error(bin, "bytes cast error".to_string(), Some(*loc), ns);
             target.assert_failure(
                 bin,
                 bin.context
@@ -1066,12 +1087,12 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_right_shift(left, right, *signed, "")
                 .into()
         }
-        Expression::Subscript(_, elem_ty, ty, a, i) => {
+        Expression::Subscript(loc, elem_ty, ty, a, i) => {
             if ty.is_storage_bytes() {
                 let index = expression(target, bin, i, vartab, function, ns).into_int_value();
                 let slot = expression(target, bin, a, vartab, function, ns).into_int_value();
                 target
-                    .get_storage_bytes_subscript(bin, function, slot, index)
+                    .get_storage_bytes_subscript(bin, function, slot, index, *loc, ns)
                     .into()
             } else if ty.is_contract_storage() {
                 let array = expression(target, bin, a, vartab, function, ns).into_int_value();
@@ -1930,3 +1951,21 @@ fn runtime_cast<'a>(
         val
     }
 }
+
+pub(crate) fn string_to_basic_value<'a>(
+    bin: &Binary<'a>,
+    ns: &Namespace,
+    input: String,
+) -> BasicValueEnum<'a> {
+    let elem = Type::Bytes(1);
+    let size = bin.context.i32_type().const_int(input.len() as u64, false);
+
+    let elem_size = bin
+        .llvm_type(&elem, ns)
+        .size_of()
+        .unwrap()
+        .const_cast(bin.context.i32_type(), false);
+
+    let init = Option::Some(input.as_bytes().to_vec());
+    bin.vector_new(size, elem_size, init.as_ref()).into()
+}

+ 8 - 1
src/emit/functions.rs

@@ -76,12 +76,13 @@ pub(super) fn emit_initializer<'a, T: TargetRuntime<'a>>(
     function
 }
 
-/// If we receive a value transfer, and we are "payable", abort with revert
+/// If we receive a value transfer, and we are not "payable", abort with revert
 pub(super) fn abort_if_value_transfer<'a, T: TargetRuntime<'a> + ?Sized>(
     target: &T,
     binary: &Binary,
     function: FunctionValue,
     ns: &Namespace,
+    function_name: &str,
 ) {
     let value = target.value_transferred(binary, ns);
 
@@ -105,6 +106,12 @@ pub(super) fn abort_if_value_transfer<'a, T: TargetRuntime<'a> + ?Sized>(
 
     binary.builder.position_at_end(abort_value_transfer);
 
+    target.log_runtime_error(
+        binary,
+        format!("non payable function {function_name} received value"),
+        None,
+        ns,
+    );
     target.assert_failure(
         binary,
         binary

+ 28 - 7
src/emit/instructions.rs

@@ -7,7 +7,7 @@ use crate::codegen::{
 use crate::emit::binary::Binary;
 use crate::emit::cfg::{create_block, BasicBlock, Work};
 use crate::emit::expression::expression;
-use crate::emit::TargetRuntime;
+use crate::emit::{ContractArgs, TargetRuntime};
 use crate::sema::ast::{Contract, Namespace, RetrieveType, Type};
 use crate::Target;
 use inkwell::types::BasicType;
@@ -16,6 +16,7 @@ use inkwell::values::{
 };
 use inkwell::{AddressSpace, IntPredicate};
 use num_traits::ToPrimitive;
+use solang_parser::pt::CodeLocation;
 use std::collections::{HashMap, VecDeque};
 
 pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
@@ -117,12 +118,21 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             value,
             offset,
         } => {
+            let index_loc = offset.loc();
             let value = expression(target, bin, value, &w.vars, function, ns);
 
             let slot = expression(target, bin, storage, &w.vars, function, ns).into_int_value();
             let offset = expression(target, bin, offset, &w.vars, function, ns).into_int_value();
 
-            target.set_storage_bytes_subscript(bin, function, slot, offset, value.into_int_value());
+            target.set_storage_bytes_subscript(
+                bin,
+                function,
+                slot,
+                offset,
+                value.into_int_value(),
+                ns,
+                index_loc,
+            );
         }
         Instr::PushStorage {
             res,
@@ -139,9 +149,10 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 target.storage_push(bin, function, ty, slot, val, ns);
         }
         Instr::PopStorage { res, ty, storage } => {
+            let loc = storage.loc();
             let slot = expression(target, bin, storage, &w.vars, function, ns).into_int_value();
 
-            let value = target.storage_pop(bin, function, ty, slot, res.is_some(), ns);
+            let value = target.storage_pop(bin, function, ty, slot, res.is_some(), ns, loc);
 
             if let Some(res) = res {
                 w.vars.get_mut(res).unwrap().value = value.unwrap();
@@ -275,7 +286,12 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             );
             bin.builder.build_store(size_field, new_len);
         }
-        Instr::PopMemory { res, ty, array } => {
+        Instr::PopMemory {
+            res,
+            ty,
+            array,
+            loc,
+        } => {
             let a = w.vars[array].value.into_pointer_value();
             let len = unsafe {
                 bin.builder.build_gep(
@@ -302,6 +318,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_conditional_branch(is_array_empty, error, pop);
 
             bin.builder.position_at_end(error);
+            target.log_runtime_error(bin, "pop from empty array".to_string(), Some(*loc), ns);
             target.assert_failure(
                 bin,
                 bin.context
@@ -676,6 +693,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             salt,
             address,
             seeds,
+            loc,
         } => {
             let encoded_args = expression(target, bin, encoded_args, &w.vars, function, ns);
             let encoded_args_len = expression(target, bin, encoded_args_len, &w.vars, function, ns);
@@ -782,10 +800,9 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 encoded_args,
                 encoded_args_len,
                 gas,
-                value,
-                salt,
-                seeds,
+                ContractArgs { value, salt, seeds },
                 ns,
+                *loc,
             );
 
             w.vars.get_mut(res).unwrap().value = bin.builder.build_load(address_stack, "address");
@@ -800,6 +817,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             accounts,
             seeds,
         } => {
+            let loc = payload.loc();
             let gas = expression(target, bin, gas, &w.vars, function, ns).into_int_value();
             let value = expression(target, bin, value, &w.vars, function, ns).into_int_value();
             let payload_ty = payload.ty();
@@ -949,6 +967,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 seeds,
                 callty.clone(),
                 ns,
+                loc,
             );
         }
         Instr::ValueTransfer {
@@ -956,6 +975,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             address,
             value,
         } => {
+            let loc = value.loc();
             let value = expression(target, bin, value, &w.vars, function, ns).into_int_value();
             let address =
                 expression(target, bin, address, &w.vars, function, ns).into_array_value();
@@ -986,6 +1006,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 ),
                 value,
                 ns,
+                loc,
             );
         }
         Instr::AbiDecode {

+ 21 - 1
src/emit/math.rs

@@ -2,9 +2,11 @@
 
 use crate::emit::binary::Binary;
 use crate::emit::{BinaryOp, TargetRuntime};
+use crate::sema::ast::Namespace;
 use inkwell::types::IntType;
 use inkwell::values::{FunctionValue, IntValue, PointerValue};
 use inkwell::{AddressSpace, IntPredicate};
+use solang_parser::pt::Loc;
 
 /// Signed overflow detection is handled by the following steps:
 /// 1- Do an unsigned multiplication first, This step will check if the generated value will fit in N bits. (unsigned overflow)
@@ -19,6 +21,8 @@ fn signed_ovf_detect<'b, 'a: 'b, T: TargetRuntime<'a> + ?Sized>(
     right: IntValue<'a>,
     bits: u32,
     function: FunctionValue<'a>,
+    ns: &Namespace,
+    loc: Loc,
 ) -> IntValue<'b> {
     // We check for signed overflow based on the facts:
     //  - * - = +
@@ -194,6 +198,7 @@ fn signed_ovf_detect<'b, 'a: 'b, T: TargetRuntime<'a> + ?Sized>(
 
     bin.builder.position_at_end(error_block);
 
+    target.log_runtime_error(bin, "multiplication overflow".to_string(), Some(loc), ns);
     target.assert_failure(
         bin,
         bin.context
@@ -280,6 +285,8 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
     left: IntValue<'a>,
     right: IntValue<'a>,
     signed: bool,
+    ns: &Namespace,
+    loc: Loc,
 ) -> IntValue<'a> {
     let bits = left.get_type().get_bit_width();
 
@@ -314,7 +321,7 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
         if bin.options.math_overflow_check && !unchecked {
             if signed {
                 return signed_ovf_detect(
-                    target, bin, mul_ty, mul_bits, left, right, bits, function,
+                    target, bin, mul_ty, mul_bits, left, right, bits, function, ns, loc,
                 );
             }
 
@@ -404,6 +411,7 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
 
             bin.builder.position_at_end(error_block);
 
+            target.log_runtime_error(bin, "multiplication overflow".to_string(), Some(loc), ns);
             target.assert_failure(
                 bin,
                 bin.context
@@ -429,6 +437,8 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
             right,
             BinaryOp::Multiply,
             signed,
+            ns,
+            loc,
         )
     } else {
         bin.builder.build_int_mul(left, right, "")
@@ -442,6 +452,8 @@ pub(super) fn power<'a, T: TargetRuntime<'a> + ?Sized>(
     bits: u32,
     signed: bool,
     o: PointerValue<'a>,
+    ns: &Namespace,
+    loc: Loc,
 ) -> FunctionValue<'a> {
     /*
         int ipow(int base, int exp)
@@ -522,6 +534,8 @@ pub(super) fn power<'a, T: TargetRuntime<'a> + ?Sized>(
         result.as_basic_value().into_int_value(),
         base.as_basic_value().into_int_value(),
         signed,
+        ns,
+        loc,
     );
 
     let multiply_block = bin.builder.get_insert_block().unwrap();
@@ -566,6 +580,8 @@ pub(super) fn power<'a, T: TargetRuntime<'a> + ?Sized>(
         base.as_basic_value().into_int_value(),
         base.as_basic_value().into_int_value(),
         signed,
+        ns,
+        loc,
     );
 
     let notdone = bin.builder.get_insert_block().unwrap();
@@ -590,6 +606,8 @@ pub(super) fn build_binary_op_with_overflow_check<'a, T: TargetRuntime<'a> + ?Si
     right: IntValue<'a>,
     op: BinaryOp,
     signed: bool,
+    ns: &Namespace,
+    loc: Loc,
 ) -> IntValue<'a> {
     let ret_ty = bin.context.struct_type(
         &[
@@ -622,6 +640,8 @@ pub(super) fn build_binary_op_with_overflow_check<'a, T: TargetRuntime<'a> + ?Si
 
     bin.builder.position_at_end(error_block);
 
+    target.log_runtime_error(bin, "math overflow".to_string(), Some(loc), ns);
+
     target.assert_failure(
         bin,
         bin.context

+ 25 - 3
src/emit/mod.rs

@@ -12,6 +12,7 @@ use inkwell::types::IntType;
 use inkwell::values::{
     ArrayValue, BasicMetadataValueEnum, BasicValueEnum, FunctionValue, IntValue, PointerValue,
 };
+use solang_parser::pt::Loc;
 
 pub mod binary;
 mod cfg;
@@ -34,6 +35,12 @@ pub struct Variable<'a> {
     value: BasicValueEnum<'a>,
 }
 
+pub struct ContractArgs<'b> {
+    value: Option<IntValue<'b>>,
+    salt: Option<IntValue<'b>>,
+    seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
+}
+
 #[derive(Clone, Copy)]
 pub enum BinaryOp {
     Add,
@@ -168,6 +175,8 @@ pub trait TargetRuntime<'a> {
         function: FunctionValue,
         slot: IntValue<'a>,
         index: IntValue<'a>,
+        loc: Loc,
+        ns: &Namespace,
     ) -> IntValue<'a>;
 
     fn set_storage_bytes_subscript(
@@ -177,6 +186,8 @@ pub trait TargetRuntime<'a> {
         slot: IntValue<'a>,
         index: IntValue<'a>,
         value: IntValue<'a>,
+        ns: &Namespace,
+        loc: Loc,
     );
 
     fn storage_subscript(
@@ -207,6 +218,7 @@ pub trait TargetRuntime<'a> {
         slot: IntValue<'a>,
         load: bool,
         ns: &Namespace,
+        loc: Loc,
     ) -> Option<BasicValueEnum<'a>>;
 
     fn storage_array_length(
@@ -231,6 +243,14 @@ pub trait TargetRuntime<'a> {
     /// Prints a string
     fn print(&self, bin: &Binary, string: PointerValue, length: IntValue);
 
+    fn log_runtime_error(
+        &self,
+        bin: &Binary,
+        reason_string: String,
+        reason_loc: Option<Loc>,
+        ns: &Namespace,
+    );
+
     /// Return success without any result
     fn return_empty_abi(&self, bin: &Binary);
 
@@ -263,13 +283,13 @@ pub trait TargetRuntime<'a> {
         encoded_args: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         gas: IntValue<'b>,
-        value: Option<IntValue<'b>>,
-        salt: Option<IntValue<'b>>,
-        seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
+        contract_args: ContractArgs<'b>,
         ns: &Namespace,
+        loc: Loc,
     );
 
     /// call external function
+    #[allow(clippy::too_many_arguments)]
     fn external_call<'b>(
         &self,
         bin: &Binary<'b>,
@@ -284,6 +304,7 @@ pub trait TargetRuntime<'a> {
         seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
         ty: CallTy,
         ns: &Namespace,
+        loc: Loc,
     );
 
     /// send value to address
@@ -295,6 +316,7 @@ pub trait TargetRuntime<'a> {
         _address: PointerValue<'b>,
         _value: IntValue<'b>,
         _ns: &Namespace,
+        loc: Loc,
     );
 
     /// builtin expressions

+ 60 - 8
src/emit/solana/target.rs

@@ -2,18 +2,20 @@
 
 use crate::codegen;
 use crate::codegen::cfg::{HashTy, ReturnCode};
+use crate::codegen::error_msg_with_loc;
 use crate::emit::binary::Binary;
-use crate::emit::expression::expression;
+use crate::emit::expression::{expression, string_to_basic_value};
 use crate::emit::loop_builder::LoopBuilder;
 use crate::emit::solana::SolanaTarget;
-use crate::emit::{TargetRuntime, Variable};
-use crate::sema::ast;
+use crate::emit::{ContractArgs, TargetRuntime, Variable};
+use crate::sema::ast::{self, Namespace};
 use inkwell::types::{BasicType, BasicTypeEnum, IntType};
 use inkwell::values::{
     ArrayValue, BasicMetadataValueEnum, BasicValueEnum, FunctionValue, IntValue, PointerValue,
 };
 use inkwell::{AddressSpace, IntPredicate};
 use num_traits::ToPrimitive;
+use solang_parser::pt::Loc;
 use std::collections::HashMap;
 
 impl<'a> TargetRuntime<'a> for SolanaTarget {
@@ -78,6 +80,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         function: FunctionValue,
         slot: IntValue<'a>,
         index: IntValue<'a>,
+        loc: Loc,
+        ns: &Namespace,
     ) -> IntValue<'a> {
         let data = self.contract_storage_data(binary);
 
@@ -120,6 +124,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
 
         binary.builder.position_at_end(bang_block);
 
+        self.log_runtime_error(
+            binary,
+            "storage array index out of bounds".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(
             binary,
             binary
@@ -146,6 +156,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue,
         index: IntValue,
         val: IntValue,
+        ns: &Namespace,
+        loc: Loc,
     ) {
         let data = self.contract_storage_data(binary);
 
@@ -187,6 +199,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .build_conditional_branch(in_range, set_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
+        self.log_runtime_error(
+            binary,
+            "storage index out of bounds".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(
             binary,
             binary
@@ -384,6 +402,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue<'a>,
         load: bool,
         ns: &ast::Namespace,
+        loc: Loc,
     ) -> Option<BasicValueEnum<'a>> {
         let data = self.contract_storage_data(binary);
         let account = self.contract_storage_account(binary);
@@ -428,6 +447,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
+        self.log_runtime_error(
+            binary,
+            "pop from empty storage array".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(
             binary,
             binary
@@ -1254,10 +1279,9 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         encoded_args: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         _gas: IntValue<'b>,
-        _value: Option<IntValue<'b>>,
-        _salt: Option<IntValue<'b>>,
-        seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
+        contract_args: ContractArgs<'b>,
         ns: &ast::Namespace,
+        loc: Loc,
     ) {
         let const_program_id = binary.builder.build_pointer_cast(
             binary.emit_global_string(
@@ -1287,7 +1311,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             "address",
         );
 
-        let (signer_seeds, signer_seeds_len) = if let Some((seeds, len)) = seeds {
+        let (signer_seeds, signer_seeds_len) = if let Some((seeds, len)) = contract_args.seeds {
             (
                 binary.builder.build_pointer_cast(
                     seeds,
@@ -1351,6 +1375,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .build_conditional_branch(is_success, success_block, bail_block);
 
             binary.builder.position_at_end(bail_block);
+            self.log_runtime_error(
+                binary,
+                "contract creation failed".to_string(),
+                Some(loc),
+                ns,
+            );
 
             binary.builder.build_return(Some(&ret));
 
@@ -1483,6 +1513,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
         _ty: ast::CallTy,
         _ns: &ast::Namespace,
+        _loc: Loc,
     ) {
         let address = address.unwrap();
 
@@ -1692,7 +1723,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
 
             binary.builder.position_at_end(bail_block);
 
-            // should we log "call failed?"
+            // We can inform the programmer about where the call failed using _loc. This can be done in all sol_log() in stdlib/solana.c.
             self.assert_failure(
                 binary,
                 binary
@@ -1863,6 +1894,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         address: PointerValue<'b>,
         value: IntValue<'b>,
         _ns: &ast::Namespace,
+        _loc: Loc,
     ) {
         let parameters = self.sol_parameters(binary);
 
@@ -2404,4 +2436,24 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_return(Some(&binary.return_values[&ReturnCode::Success]));
     }
+
+    fn log_runtime_error(
+        &self,
+        bin: &Binary,
+        reason_string: String,
+        reason_loc: Option<Loc>,
+        ns: &Namespace,
+    ) {
+        if !bin.options.log_runtime_errors {
+            return;
+        }
+
+        let error_with_loc = error_msg_with_loc(ns, &reason_string, reason_loc);
+        let custom_error = string_to_basic_value(bin, ns, error_with_loc);
+        self.print(
+            bin,
+            bin.vector_bytes(custom_error),
+            bin.vector_len(custom_error),
+        );
+    }
 }

+ 2 - 1
src/emit/substrate/dispatch.rs

@@ -201,8 +201,9 @@ impl SubstrateTarget {
 
         bin.builder.position_at_end(bb);
 
+        let function_name: Vec<&str> = f.name.split("::").collect();
         if nonpayable(f) {
-            abort_if_value_transfer(self, bin, function, ns);
+            abort_if_value_transfer(self, bin, function, ns, function_name.last().unwrap());
         }
 
         let mut args = Vec::new();

+ 7 - 5
src/emit/substrate/mod.rs

@@ -212,6 +212,7 @@ impl SubstrateTarget {
         function: FunctionValue,
         abort_value_transfers: bool,
         ns: &ast::Namespace,
+        function_name: &str,
     ) -> (PointerValue<'a>, IntValue<'a>) {
         let entry = binary.context.append_basic_block(function, "entry");
 
@@ -219,7 +220,7 @@ impl SubstrateTarget {
 
         // after copying stratch, first thing to do is abort value transfers if constructors not payable
         if abort_value_transfers {
-            abort_if_value_transfer(self, binary, function, ns);
+            abort_if_value_transfer(self, binary, function, ns, function_name);
         }
 
         // init our heap
@@ -367,7 +368,7 @@ impl SubstrateTarget {
 
         // deploy always receives an endowment so no value check here
         let (deploy_args, deploy_args_length) =
-            self.public_function_prelude(binary, function, false, ns);
+            self.public_function_prelude(binary, function, false, ns, "deploy");
 
         // init our storage vars
         binary.builder.build_call(initializer, &[], "");
@@ -409,11 +410,12 @@ impl SubstrateTarget {
             None,
         );
 
-        let (call_args, call_args_length) = self.public_function_prelude(
+        let (contract_args, contract_args_length) = self.public_function_prelude(
             binary,
             function,
             binary.function_abort_value_transfers,
             ns,
+            "call",
         );
 
         self.emit_function_dispatch(
@@ -421,8 +423,8 @@ impl SubstrateTarget {
             contract,
             ns,
             pt::FunctionTy::Function,
-            call_args,
-            call_args_length,
+            contract_args,
+            contract_args_length,
             function,
             &binary.functions,
             None,

+ 69 - 9
src/emit/substrate/target.rs

@@ -1,11 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{HashTy, ReturnCode};
+use crate::codegen::error_msg_with_loc;
 use crate::emit::binary::Binary;
-use crate::emit::expression::expression;
+use crate::emit::expression::{expression, string_to_basic_value};
 use crate::emit::storage::StorageSlot;
 use crate::emit::substrate::{event_id, log_return_code, SubstrateTarget, SCRATCH_SIZE};
-use crate::emit::{TargetRuntime, Variable};
+use crate::emit::{ContractArgs, TargetRuntime, Variable};
 use crate::sema::ast;
 use crate::sema::ast::{Function, Namespace, Type};
 use crate::{codegen, emit_context};
@@ -15,7 +16,7 @@ use inkwell::values::{
     PointerValue,
 };
 use inkwell::{AddressSpace, IntPredicate};
-use solang_parser::pt;
+use solang_parser::pt::{self, Loc};
 use std::collections::HashMap;
 
 impl<'a> TargetRuntime<'a> for SubstrateTarget {
@@ -305,6 +306,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         function: FunctionValue,
         slot: IntValue<'a>,
         index: IntValue<'a>,
+        loc: Loc,
+        ns: &Namespace,
     ) -> IntValue<'a> {
         emit_context!(binary);
 
@@ -357,6 +360,13 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
+
+        self.log_runtime_error(
+            binary,
+            "storage array index out of bounds".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
 
         binary.builder.position_at_end(retrieve_block);
@@ -379,6 +389,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         slot: IntValue,
         index: IntValue,
         val: IntValue,
+        ns: &Namespace,
+        loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -431,6 +443,12 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
+        self.log_runtime_error(
+            binary,
+            "storage index out of bounds".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
 
         binary.builder.position_at_end(retrieve_block);
@@ -543,7 +561,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         _ty: &ast::Type,
         slot: IntValue<'a>,
         load: bool,
-        _ns: &ast::Namespace,
+        ns: &ast::Namespace,
+        loc: Loc,
     ) -> Option<BasicValueEnum<'a>> {
         emit_context!(binary);
 
@@ -598,6 +617,12 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
+        self.log_runtime_error(
+            binary,
+            "pop from empty storage array".to_string(),
+            Some(loc),
+            ns,
+        );
         self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
 
         binary.builder.position_at_end(retrieve_block);
@@ -1053,10 +1078,9 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         encoded_args: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         gas: IntValue<'b>,
-        value: Option<IntValue<'b>>,
-        salt: Option<IntValue<'b>>,
-        _seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
+        contract_args: ContractArgs<'b>,
         ns: &ast::Namespace,
+        loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -1074,7 +1098,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_pointer_cast(salt_buf, byte_ptr!(), "salt_buf");
         let salt_len = i32_const!(32);
 
-        if let Some(salt) = salt {
+        if let Some(salt) = contract_args.salt {
             let salt_ty = ast::Type::Uint(256);
 
             binary.builder.build_store(
@@ -1106,7 +1130,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_alloca(binary.value_type(ns), "balance");
 
         // balance is a u128, make sure it's enough to cover existential_deposit
-        if let Some(value) = value {
+        if let Some(value) = contract_args.value {
             binary.builder.build_store(value_ptr, value);
         } else {
             let scratch_len = binary.scratch_len.unwrap().as_pointer_value();
@@ -1183,6 +1207,12 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
+            self.log_runtime_error(
+                binary,
+                "contract creation failed".to_string(),
+                Some(loc),
+                ns,
+            );
             self.assert_failure(
                 binary,
                 scratch_buf,
@@ -1211,6 +1241,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         _seeds: Option<(PointerValue<'b>, IntValue<'b>)>,
         _ty: ast::CallTy,
         ns: &ast::Namespace,
+        loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -1266,6 +1297,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
+            self.log_runtime_error(binary, "external call failed".to_string(), Some(loc), ns);
             self.assert_failure(
                 binary,
                 scratch_buf,
@@ -1288,6 +1320,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         address: PointerValue<'b>,
         value: IntValue<'b>,
         ns: &ast::Namespace,
+        loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -1333,6 +1366,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
+            self.log_runtime_error(binary, "value transfer failure".to_string(), Some(loc), ns);
             self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
 
             binary.builder.position_at_end(success_block);
@@ -1836,4 +1870,30 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         // not needed for slot-based storage chains
         unimplemented!()
     }
+
+    fn log_runtime_error(
+        &self,
+        bin: &Binary,
+        reason_string: String,
+        reason_loc: Option<Loc>,
+        ns: &Namespace,
+    ) {
+        if !bin.options.log_runtime_errors {
+            return;
+        }
+        emit_context!(bin);
+        let error_with_loc = error_msg_with_loc(ns, &reason_string, reason_loc);
+        let custom_error = string_to_basic_value(bin, ns, error_with_loc);
+        call!(
+            "seal_debug_message",
+            &[
+                bin.vector_bytes(custom_error).into(),
+                bin.vector_len(custom_error).into()
+            ]
+        )
+        .try_as_basic_value()
+        .left()
+        .unwrap()
+        .into_int_value();
+    }
 }

+ 2 - 0
src/lib.rs

@@ -124,12 +124,14 @@ pub fn compile(
     target: Target,
     math_overflow_check: bool,
     log_api_return_codes: bool,
+    log_runtime_errors: bool,
 ) -> (Vec<(Vec<u8>, String)>, sema::ast::Namespace) {
     let mut ns = parse_and_resolve(filename, resolver, target);
     let opts = codegen::Options {
         math_overflow_check,
         log_api_return_codes,
         opt_level: opt_level.into(),
+        log_runtime_errors,
         ..Default::default()
     };
 

+ 7 - 2
tests/solana.rs

@@ -132,10 +132,14 @@ struct Assign {
 }
 
 fn build_solidity(src: &str) -> VirtualMachine {
-    build_solidity_with_overflow_check(src, false)
+    build_solidity_with_options(src, false, false)
 }
 
-fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> VirtualMachine {
+fn build_solidity_with_options(
+    src: &str,
+    math_overflow_flag: bool,
+    log_runtime_errors: bool,
+) -> VirtualMachine {
     let mut cache = FileResolver::new();
 
     cache.set_file_contents("test.sol", src.to_string());
@@ -147,6 +151,7 @@ fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> Vi
         Target::Solana,
         math_overflow_flag,
         false,
+        log_runtime_errors,
     );
 
     ns.print_diagnostics_in_plain(&cache, false);

+ 1 - 0
tests/solana_tests/mod.rs

@@ -22,6 +22,7 @@ mod modifiers;
 mod primitives;
 mod rational;
 mod returns;
+mod runtime_errors;
 mod signature_verify;
 mod simple;
 mod storage;

+ 6 - 6
tests/solana_tests/primitives.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::build_solidity_with_overflow_check;
+use crate::build_solidity_with_options;
 use crate::{build_solidity, BorshToken};
 use num_bigint::{BigInt, BigUint, RandBigInt, ToBigInt};
 use num_traits::{One, ToPrimitive, Zero};
@@ -770,7 +770,7 @@ fn test_power_overflow_boundaries() {
         }"#
         .replace("intN", &format!("int{width}"));
 
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
         contract.constructor(&[]);
 
         let return_value = contract
@@ -827,7 +827,7 @@ fn test_overflow_boundaries() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1]. We generate these boundaries:
         let mut upper_boundary: BigInt = BigInt::from(2_u32).pow((width - 1) as u32);
@@ -1043,7 +1043,7 @@ fn test_mul_within_range() {
         }"#
         .replace("intN", &format!("int{width}"));
 
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
         contract.constructor(&[]);
         for _ in 0..10 {
             // Max number to fit unsigned N bits is (2^N)-1
@@ -1095,7 +1095,7 @@ fn test_overflow_detect_signed() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         contract.constructor(&[]);
 
@@ -1164,7 +1164,7 @@ fn test_overflow_detect_unsigned() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         contract.constructor(&[]);
 

+ 270 - 0
tests/solana_tests/runtime_errors.rs

@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{build_solidity_with_options, BorshToken};
+use num_bigint::BigInt;
+
+#[test]
+fn runtime_errors() {
+    let mut vm = build_solidity_with_options(
+        r#"
+contract RuntimeErrors {
+    bytes b = hex"0000_00fa";
+    uint256[] arr;
+    child public c;
+    child public c2;
+
+    constructor() {}
+
+    function print_test(int8 num) public returns (int8) {
+        print("Hello world!");
+
+        require(num > 10, "sesa");
+        assert(num > 10);
+
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function math_overflow(int8 num) public returns (int8) {
+        int8 ovf = num + 120;
+        print("x = {}".format(ovf));
+        return ovf;
+    }
+
+    function require_test(int256 num) public returns (int8) {
+        require(num > 10, "sesa");
+        return 0;
+    }
+
+    // assert failure
+    function assert_test(int256 num) public returns (int8) {
+        assert(num > 10);
+        return 0;
+    }
+
+    // storage index out of bounds
+    function set_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        b[5] = sesa[0];
+        return sesa;
+    }
+
+    // storage array index out of bounds
+    function get_storage_bytes() public returns (bytes) {
+        bytes sesa = new bytes(1);
+        sesa[0] = b[5];
+        return sesa;
+    }
+
+    //  pop from empty storage array
+    function pop_empty_storage() public {
+        arr.pop();
+    }
+
+
+    // contract creation failed
+    function create_child() public {
+        address a = address(0);
+        c = new child{address: a}();
+        //c2 = new child();
+        uint128 x = address(this).balance;
+        //print("sesa");
+        print("x = {}".format(x));
+        
+    }
+
+    function i_will_revert() public {
+        revert();
+    }
+
+    function write_integer_failure(uint256 buf_size) public {
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeUint32LE(350, 20);
+    }
+
+    function write_bytes_failure(uint256 buf_size) public {
+        bytes data = new bytes(10);
+        bytes smol_buf = new bytes(buf_size);
+        smol_buf.writeBytes(data, 0);
+    }
+
+    function read_integer_failure(uint32 offset) public {
+        bytes smol_buf = new bytes(1);
+        smol_buf.readUint16LE(offset);
+    }
+
+    // truncated type overflows
+    function trunc_failure(uint256 input) public returns (uint256[]) {
+        uint256[] a = new uint256[](input);
+        return a;
+    }
+
+    function out_of_bounds(uint256 input) public returns (uint256) {
+        uint256[] a = new uint256[](input);
+        return a[20];
+    }
+
+    function invalid_instruction() public {
+        assembly {
+            invalid()
+        }
+    }
+
+    function byte_cast_failure(uint256 num) public returns (bytes) {
+        bytes smol_buf = new bytes(num);
+
+        //bytes32 b32 = new bytes(num);
+        bytes32 b32 = bytes32(smol_buf);
+        return b32;
+    }
+
+}
+
+@program_id("Crea1hXZv5Snuvs38GW2SJ1vJQ2Z5uBavUnwPwpiaDiQ")
+contract child {
+    constructor() {}
+
+    function say_my_name() public pure returns (string memory) {
+        print("say_my_name");
+        return "child";
+    }
+}
+
+contract calle_contract {
+    constructor() {}
+
+    function calle_contract_func() public {
+        revert();
+    }
+}
+
+ "#,
+        true,
+        true,
+    );
+
+    vm.set_program(0);
+    vm.constructor(&[]);
+
+    let mut _res = vm.function_must_fail(
+        "math_overflow",
+        &[BorshToken::Int {
+            width: 8,
+            value: BigInt::from(10u8),
+        }],
+    );
+    assert_eq!(vm.logs, "math overflow in test.sol:22:19");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "require_test",
+        &[BorshToken::Int {
+            width: 256,
+            value: BigInt::from(9u8),
+        }],
+    );
+    assert_eq!(vm.logs, "sesa require condition failed in test.sol:28:26");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("get_storage_bytes", &[]);
+    assert_eq!(
+        vm.logs,
+        "storage array index out of bounds in test.sol:48:18"
+    );
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("set_storage_bytes", &[]);
+    assert_eq!(vm.logs, "storage index out of bounds in test.sol:41:10");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "read_integer_failure",
+        &[BorshToken::Uint {
+            width: 32,
+            value: BigInt::from(2u8),
+        }],
+    );
+    assert_eq!(vm.logs, "read integer out of bounds in test.sol:86:17");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "trunc_failure",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(u128::MAX),
+        }],
+    );
+    assert_eq!(vm.logs, "truncated type overflows in test.sol:91:36");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("invalid_instruction", &[]);
+    assert_eq!(vm.logs, "reached invalid instruction in test.sol:102:12");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("pop_empty_storage", &[]);
+    assert_eq!(vm.logs, "pop from empty storage array in test.sol:54:8");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "write_bytes_failure",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(9u8),
+        }],
+    );
+    assert_eq!(vm.logs, "data does not fit into buffer in test.sol:81:17");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "assert_test",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(9u8),
+        }],
+    );
+    assert_eq!(vm.logs, "assert failure in test.sol:34:15");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "out_of_bounds",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(19u8),
+        }],
+    );
+    assert_eq!(vm.logs, "array index out of bounds in test.sol:97:15");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "write_integer_failure",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(1u8),
+        }],
+    );
+
+    assert_eq!(
+        vm.logs,
+        "integer too large to write in buffer in test.sol:75:17"
+    );
+    vm.logs.clear();
+
+    _res = vm.function_must_fail(
+        "byte_cast_failure",
+        &[BorshToken::Uint {
+            width: 256,
+            value: BigInt::from(33u8),
+        }],
+    );
+    assert_eq!(vm.logs, "bytes cast error in test.sol:110:22");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("i_will_revert", &[]);
+    assert_eq!(vm.logs, "revert encountered in test.sol:70:8");
+    vm.logs.clear();
+
+    _res = vm.function_must_fail("create_child", &[]);
+    assert_eq!(vm.logs, "contract creation failed in test.sol:61:12");
+    vm.logs.clear();
+}

+ 3 - 1
tests/substrate.rs

@@ -1218,13 +1218,14 @@ impl MockSubstrate {
 }
 
 pub fn build_solidity(src: &str) -> MockSubstrate {
-    build_solidity_with_options(src, false, false)
+    build_solidity_with_options(src, false, false, false)
 }
 
 pub fn build_solidity_with_options(
     src: &str,
     math_overflow_flag: bool,
     log_api_return_codes: bool,
+    log_runtime_errors: bool,
 ) -> MockSubstrate {
     let mut cache = FileResolver::new();
 
@@ -1237,6 +1238,7 @@ pub fn build_solidity_with_options(
         Target::default_substrate(),
         math_overflow_flag,
         log_api_return_codes,
+        log_runtime_errors,
     );
 
     ns.print_diagnostics_in_plain(&cache, false);

+ 1 - 0
tests/substrate_tests/calls.rs

@@ -785,6 +785,7 @@ fn log_api_call_return_values_works() {
         "##,
         false,
         true,
+        false,
     );
 
     runtime.function("test", vec![]);

+ 271 - 0
tests/substrate_tests/errors.rs

@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::build_solidity_with_options;
+use parity_scale_codec::Encode;
+
+#[test]
+fn errors() {
+    let mut runtime = build_solidity_with_options(
+        r#"contract RuntimeErrors {
+        bytes b = hex"0000_00fa";
+        uint256[] arr;
+        child public c;
+        child public c2;
+        callee public cal;
+    
+        constructor() public payable {}
+    
+        function print_test(int8 num) public returns (int8) {
+            print("Hello world!");
+            return num;
+        }
+    
+        function math_overflow(int8 num) public returns (int8) {
+            int8 ovf = num + 120;
+            return ovf;
+        }
+    
+        function require_test(int8 num) public returns (int8) {
+            require(num > 10, "sesa");
+            return 0;
+        }
+    
+        // assert failure
+        function assert_test(int8 num) public returns (int8) {
+            assert(num > 10);
+            return 0;
+        }
+    
+        // storage index out of bounds
+        function set_storage_bytes() public returns (bytes) {
+            bytes sesa = new bytes(1);
+            b[5] = sesa[0];
+            return sesa;
+        }
+    
+        // storage array index out of bounds
+        function get_storage_bytes() public returns (bytes) {
+            bytes sesa = new bytes(1);
+            sesa[0] = b[5];
+            return sesa;
+        }
+    
+        // value transfer failure
+        function transfer_abort() public {
+            address a = address(0);
+            payable(a).transfer(10);
+        }
+    
+        //  pop from empty storage array
+        function pop_empty_storage() public {
+            arr.pop();
+        }
+    
+        // external call failed
+        function call_ext() public {
+            //cal = new callee();
+            cal.callee_func{gas: 1e15}();
+        }
+    
+        // contract creation failed (contract was deplyed with no value)
+        function create_child() public {
+            c = new child{value: 900e15, salt:2}();
+            c2 = new child{value: 900e15, salt:2}();
+            uint128 x = address(this).balance;
+            //print("sesa");
+            print("x = {}".format(x));
+            
+        }
+    
+        // non payable function dont_pay_me received value
+        function dont_pay_me() public {}
+    
+        function pay_me() public payable {
+            print("PAYED");
+            uint128 x = address(this).balance;
+            //print("sesa");
+            print("x = {}".format(x));
+            
+        }
+    
+        function i_will_revert() public {
+            revert();
+        }
+    
+        function write_integer_failure(uint8 buf_size) public {
+            bytes smol_buf = new bytes(buf_size);
+            smol_buf.writeUint32LE(350, 20);
+        }
+    
+        function write_bytes_failure(uint8 buf_size) public {
+            bytes data = new bytes(10);
+            bytes smol_buf = new bytes(buf_size);
+            smol_buf.writeBytes(data, 0);
+        }
+    
+        function read_integer_failure(uint32 offset) public {
+            bytes smol_buf = new bytes(1);
+            smol_buf.readUint16LE(offset);
+        }
+    
+        // truncated type overflows
+        function trunc_failure(uint128 input) public returns (uint256) {
+            uint256[] a = new uint256[](input);
+            return a[0];
+        }
+    
+        function out_of_bounds(uint8 input) public returns (uint256) {
+            uint256[] a = new uint256[](input);
+            return a[20];
+        }
+    
+        function invalid_instruction() public {
+            assembly {
+                invalid()
+            }
+        }
+    
+        function byte_cast_failure(uint8 num) public returns (bytes) {
+            bytes smol_buf = new bytes(num);
+    
+            //bytes32 b32 = new bytes(num);
+            bytes32 b32 = bytes32(smol_buf);
+            return b32;
+        }
+    }
+    
+    contract callee {
+        constructor() {}
+    
+        function callee_func() public {
+            revert();
+        }
+    }
+    
+    contract child {
+        constructor() {}
+    
+        function say_my_name() public pure returns (string memory) {
+            print("say_my_name");
+            return "child";
+        }
+    }
+    
+    "#,
+        true,
+        false,
+        true,
+    );
+
+    runtime.function_expect_failure("write_bytes_failure", 9u8.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "data does not fit into buffer in test.sol:95:21"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("math_overflow", 10u8.encode());
+    assert_eq!(runtime.printbuf, "math overflow in test.sol:16:23");
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("require_test", 9u8.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "sesa require condition failed in test.sol:21:30"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("assert_test", 9u8.encode());
+    assert_eq!(runtime.printbuf, "assert failure in test.sol:27:19");
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("set_storage_bytes", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "storage index out of bounds in test.sol:34:14"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("get_storage_bytes", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "storage array index out of bounds in test.sol:41:22"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("transfer_abort", Vec::new());
+    assert_eq!(runtime.printbuf, "value transfer failure in test.sol:48:32");
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("pop_empty_storage", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "pop from empty storage array in test.sol:53:16"
+    );
+
+    runtime.vm.value = 3500;
+    runtime.constructor(0, Vec::new());
+
+    runtime.printbuf.clear();
+    runtime.vm.value = 0;
+    runtime.function_expect_failure("create_child", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "contract creation failed in test.sol:65:17"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("i_will_revert", Vec::new());
+    assert_eq!(runtime.printbuf, "revert encountered in test.sol:84:12");
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("write_integer_failure", 1u8.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "integer too large to write in buffer in test.sol:89:21"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("invalid_instruction", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "reached invalid instruction in test.sol:116:16"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("out_of_bounds", 19u8.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "array index out of bounds in test.sol:111:19"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("trunc_failure", u128::MAX.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "truncated type overflows in test.sol:105:40"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("byte_cast_failure", 33u8.encode());
+    assert_eq!(runtime.printbuf, "bytes cast error in test.sol:124:26");
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("read_integer_failure", 2u32.encode());
+    assert_eq!(
+        runtime.printbuf,
+        "read integer out of bounds in test.sol:100:21"
+    );
+
+    runtime.printbuf.clear();
+    runtime.function_expect_failure("call_ext", Vec::new());
+    assert_eq!(runtime.printbuf, "external call failed in test.sol:59:12");
+
+    runtime.printbuf.clear();
+    runtime.vm.value = 1;
+    runtime.function_expect_failure("dont_pay_me", Vec::new());
+    assert_eq!(
+        runtime.printbuf,
+        "non payable function dont_pay_me received value"
+    );
+}

+ 10 - 4
tests/substrate_tests/expressions.rs

@@ -956,7 +956,7 @@ fn test_power_overflow_boundaries() {
         }"#
         .replace("intN", &format!("int{width}"));
 
-        let mut contract = build_solidity_with_options(&src, true, false);
+        let mut contract = build_solidity_with_options(&src, true, false, false);
 
         let base = BigUint::from(2_u32);
         let mut base_data = base.to_bytes_le();
@@ -1156,7 +1156,7 @@ fn test_overflow_boundaries() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_options(&src, true, false);
+        let mut contract = build_solidity_with_options(&src, true, false, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1]. We generate these boundaries:
         let upper_boundary = BigInt::from(2_u32).pow(width - 1).sub(1_u32);
@@ -1287,7 +1287,7 @@ fn test_overflow_detect_signed() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_options(&src, true, false);
+        let mut contract = build_solidity_with_options(&src, true, false, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1] .Generate a value that will overflow this range:
         let limit = BigInt::from(2_u32).pow(width - 1).sub(1_u32);
@@ -1349,7 +1349,7 @@ fn test_overflow_detect_unsigned() {
             }
         }"#
         .replace("intN", &format!("int{width}"));
-        let mut contract = build_solidity_with_options(&src, true, false);
+        let mut contract = build_solidity_with_options(&src, true, false, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1].
         let limit = BigUint::from(2_u32).pow(width).sub(1_u32);
@@ -1709,6 +1709,7 @@ fn addition_overflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1733,6 +1734,7 @@ fn unchecked_addition_overflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1756,6 +1758,7 @@ fn subtraction_underflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1780,6 +1783,7 @@ fn unchecked_subtraction_underflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1803,6 +1807,7 @@ fn multiplication_overflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1827,6 +1832,7 @@ fn unchecked_multiplication_overflow() {
         "#,
         true,
         false,
+        false,
     );
 
     runtime.function("bar", Vec::new());

+ 2 - 0
tests/substrate_tests/inheritance.rs

@@ -37,6 +37,7 @@ fn test_abstract() {
         Target::default_substrate(),
         false,
         false,
+        false,
     );
 
     assert!(!ns.diagnostics.any_errors());
@@ -76,6 +77,7 @@ fn test_abstract() {
         Target::default_substrate(),
         false,
         false,
+        false,
     );
 
     assert!(!ns.diagnostics.any_errors());

+ 1 - 0
tests/substrate_tests/mod.rs

@@ -10,6 +10,7 @@ mod arrays;
 mod builtins;
 mod calls;
 mod contracts;
+mod errors;
 mod events;
 mod first;
 mod format;

+ 1 - 0
tests/undefined_variable_detection.rs

@@ -21,6 +21,7 @@ fn parse_and_codegen(src: &'static str) -> Namespace {
         math_overflow_check: false,
         generate_debug_information: false,
         log_api_return_codes: false,
+        log_runtime_errors: false,
     };
 
     codegen(&mut ns, &opt);