瀏覽代碼

Polkadot: Reverts return encoded error data (#1449)

This is a continuation of #1415. `require()`, `assert()` and `revert()`
now return error data, according to the [Ethereum Solidity
documentation](https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require).
Additionally, many reverts inserted by the compiler now return the
corresponding `Panic(uint256)` error data, to align Solang closer with
`solc`.

The error types known to the contract are added in the metadata
`lang_error` field. At the moment there are only `Error` and `Panic`
because we don't support custom errors yet.

Refactored revert-related code into a dedicated `codegen` module.
Refactored the `polkadot::errors` into distinct tests, made them less
brittle and added assertions for the execution output.

This works allows implementing a follow-up PR for bubbling up uncaught exceptions
(this is why it's already included that in the documentation).

---------

Signed-off-by: xermicus <cyrill@parity.io>
Cyrill Leutwiler 2 年之前
父節點
當前提交
bac5707bbf

+ 1 - 1
Cargo.toml

@@ -63,11 +63,11 @@ wasm-encoder = "0.29"
 toml = "0.7"
 wasm-opt = { version = "0.112.0", optional = true }
 contract-build = { version = "3.0.1", optional = true }
+primitive-types = { version = "0.12", features = ["codec"] }
 
 
 [dev-dependencies]
 num-derive = "0.4"
-primitive-types = { version = "0.12", features = ["codec"] }
 wasmi = "0.30"
 # rand version 0.7 is needed for ed25519_dalek::keypair::generate, used in solana_tests/signature_verify.rs
 rand_07 = { package = "rand", version = "0.7" }

+ 67 - 1
docs/targets/polkadot.rst

@@ -10,7 +10,6 @@ Solidity flavored for the Polkadot target has the following differences to Ether
 - Constructors can be named. Constructors with no name will be called ``new`` in the generated metadata.
 - There is no ``ecrecover()`` builtin function, or any other function to recover or verify cryptographic signatures at runtime
 - Only functions called via rpc may return values; when calling a function in a transaction, the return values cannot be accessed
-- An `assert()`, `require()`, or `revert()` executes the wasm unreachable instruction. The reason code is lost
 
 There is a solidity example which can be found in the
 `examples <https://github.com/hyperledger/solang/tree/main/examples>`_
@@ -62,3 +61,70 @@ The following example shows how call flags can be used:
 .. include:: ../examples/polkadot/call_flags.sol
   :code: solidity
 
+
+Reverts and error data decoding
+_______________________________
+
+When a contract reverts, the returned error data is what the
+`EVM would return <https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require>`_.
+``assert()``, ``require()``, or ``revert()`` will revert the contract execution, where the revert reason (if any) is 
+encoded as ``Error(string)`` and provided in the execution output. Solidity contracts can also revert with a ``Panic(uint256)`` 
+(please refer to the 
+`Ethereum Solidity language documentation <https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require>`_
+for more information about when ``Panic`` might be returned).
+Uncaught exceptions from calling and instantiating contracts or transferring funds will be bubbled 
+up back to the caller.
+
+The metadata contains all error variants that the contract `knows` about in the ``lang_error`` field.
+
+.. warning::
+
+    Never trust the error data.
+
+    Solidity contracts do bubble up uncaught errors. This can lead to situations where the 
+    contract reverts with error data unknown to the contracts. Examples of this include 
+    bubbling up custom error data from the callee or error data from an ``ink!`` contract.
+
+The 4 bytes selector of the error data can be seen as the enum discriminator or index. However, 
+because SCALE encoding does not allow index larger than 1 byte, the hex-encoded error selector 
+is provided as the path of the error variant type in the metadata.
+
+In the following example, the ``Panic`` variant of ``lang_error`` is of type ``10``, which looks like this:
+
+.. code-block:: json
+
+    {
+      "id": 10,
+      "type": {
+        "def": {
+          "composite": {
+            "fields": [
+              {
+                "type": 9
+              }
+            ]
+          }
+        },
+        "path": [
+          "0x4e487b71"
+        ]
+      }
+    }
+
+From this follows that error data matching the ``Panic`` selector of `0x4e487b71` can be decoded 
+according to type ``10`` (where the decoder must exclude the first 4 selector bytes).
+
+.. note::
+
+    Ethereum Solidity knows about ``Error``, ``Panic`` and 
+    `custom errors <https://docs.soliditylang.org/en/latest/abi-spec.html#errors>`_.
+    Solang does not yet support custom errors. For now, only ``Error`` (selector of `0x08c379a0`) 
+    and ``Panic`` (selector of `0x4e487b71`) are returned and occur in the metadata.
+
+The general process of decoding the output data of Solang Solidity contracts is as follows:
+
+1. The compiler of the contract must be Solang (check the ``compiler`` field in the contract metadata).
+2. If the revert flag is **not** set, the contract didn't revert and the output should be decoded as specified in the message spec.
+3. If the output length is smaller than 4 bytes, the error data can't be decoded (contracts may return empty error data, for example if ``revert()`` without arguments is used).
+4. If the first 4 bytes of the output do **not** match any of the selectors found in ``lang_error``, the error can't be decoded.
+5. **Skip** the selector (first 4 bytes) and decode the remaining data according to the matching type found in `lang_error`.

+ 4 - 0
integration/polkadot/asserts.spec.ts

@@ -31,6 +31,10 @@ describe('Deploy asserts contract and test', () => {
         expect(res1.result.asOk.flags.isRevert).toStrictEqual(true);
         expect(res1.result.asOk.data.toString()).toStrictEqual("0x08c379a0204920726566757365");
 
+        res1 = await query(conn, alice, contract, "testAssert");
+        expect(res1.result.asOk.flags.isRevert).toStrictEqual(true);
+        expect(res1.result.asOk.data.toString()).toStrictEqual("0x4e487b710100000000000000000000000000000000000000000000000000000000000000");
+
         let gasLimit = await weight(conn, contract, "testAssert");
         let tx = contract.tx.testAssert({ gasLimit });
 

+ 44 - 8
src/abi/polkadot.rs

@@ -52,12 +52,12 @@ fn primitive_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u3
 
 fn int_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
     let (signed, scalety) = match ty {
-        ast::Type::Uint(n) => ('u', n.next_power_of_two()),
-        ast::Type::Int(n) => ('i', n.next_power_of_two()),
+        ast::Type::Uint(n) => ("uint", n.next_power_of_two()),
+        ast::Type::Int(n) => ("int", n.next_power_of_two()),
         _ => unreachable!(),
     };
     let def = match (signed, scalety) {
-        ('u', n) => match n {
+        ("uint", n) => match n {
             8 => TypeDefPrimitive::U8,
             16 => TypeDefPrimitive::U16,
             32 => TypeDefPrimitive::U32,
@@ -66,25 +66,55 @@ fn int_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
             256 => TypeDefPrimitive::U256,
             _ => unreachable!(),
         },
-        ('i', n) => match n {
+        ("int", n) => match n {
             8 => TypeDefPrimitive::I8,
             16 => TypeDefPrimitive::I16,
             32 => TypeDefPrimitive::I32,
             64 => TypeDefPrimitive::I64,
             128 => TypeDefPrimitive::I128,
             256 => TypeDefPrimitive::I256,
-
             _ => unreachable!(),
         },
-        _ => {
-            unreachable!()
-        }
+        _ => unreachable!(),
     };
     let path = path!(format!("{signed}{scalety}"));
     let ty = Type::new(path, vec![], TypeDef::Primitive(def), Default::default());
     registry.register_type(ty)
 }
 
+/// Build the `lang_error` type of this contract, where `errors` is a list
+/// containing each error's name, selector and types. Returns a `TypeSpec`
+/// of `TypeDefVariant` with each error as a variant.
+fn lang_error(
+    ns: &ast::Namespace,
+    reg: &mut PortableRegistryBuilder,
+    errors: &[(&str, u32, Vec<ast::Type>)],
+) -> TypeSpec<PortableForm> {
+    let variants = errors.iter().enumerate().map(|(n, (name, selector, ty))| {
+        let struct_fields = ty
+            .iter()
+            .map(|ty| resolve_ast(ty, ns, reg).into())
+            .map(|field| Field::new(None, field, None, Default::default()))
+            .collect::<Vec<_>>();
+        let ty = Type::new(
+            path!(format!("0x{}", hex::encode(selector.to_be_bytes()))),
+            vec![],
+            TypeDef::Composite(TypeDefComposite::new(struct_fields)),
+            Default::default(),
+        );
+        Variant {
+            name: name.to_string(),
+            fields: vec![Field::new(None, reg.register_type(ty).into(), None, vec![])],
+            index: n.try_into().expect("we do not allow custome error types"),
+            docs: Default::default(),
+        }
+    });
+    let type_def = TypeDefVariant::new(variants);
+    let path = path!("SolidityError");
+    let id = reg.register_type(Type::new(path.clone(), vec![], type_def, vec![]));
+    TypeSpec::new(id.into(), path)
+}
+
 /// Given an `ast::Type`, find and register the `scale_info::Type` definition in the registry
 fn resolve_ast(ty: &ast::Type, ns: &ast::Namespace, registry: &mut PortableRegistryBuilder) -> u32 {
     match ty {
@@ -471,12 +501,18 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
         ))
         .done();
 
+    let error_definitions = &[
+        ("Error", 0x08c379a0, vec![ast::Type::String]),
+        ("Panic", 0x4e487b71, vec![ast::Type::Uint(256)]),
+    ];
+
     let spec = ContractSpec::new()
         .constructors(constructors)
         .messages(messages)
         .events(events)
         .docs(vec![render(&ns.contracts[contract_no].tags)])
         .environment(environment)
+        .lang_error(lang_error(ns, &mut registry, error_definitions))
         .done();
 
     InkProject::new_portable(storage, spec, registry.finish())

+ 1 - 1
src/codegen/dispatch/polkadot.rs

@@ -4,7 +4,7 @@ use crate::{
     codegen::{
         cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy, ReturnCode},
         encoding::{abi_decode, abi_encode},
-        expression::log_runtime_error,
+        revert::log_runtime_error,
         vartable::Vartable,
         Builtin, Expression, Options,
     },

+ 5 - 3
src/codegen/encoding/buffer_validator.rs

@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
-use crate::codegen::expression::assert_failure;
+use crate::codegen::revert::{assert_failure, PanicCode, SolidityError};
 use crate::codegen::vartable::Vartable;
 use crate::codegen::Expression;
 use crate::sema::ast::{Namespace, Type};
@@ -143,7 +143,8 @@ impl BufferValidator<'_> {
         cfg.set_basic_block(invalid);
 
         // TODO: This needs a proper error message
-        assert_failure(&Loc::Codegen, None, ns, cfg, vartab);
+        let error = SolidityError::Panic(PanicCode::Generic);
+        assert_failure(&Loc::Codegen, error, ns, cfg, vartab);
 
         cfg.set_basic_block(valid);
     }
@@ -222,7 +223,8 @@ impl BufferValidator<'_> {
 
         cfg.set_basic_block(out_of_bounds_block);
         // TODO: Add an error message here
-        assert_failure(&Loc::Codegen, None, ns, cfg, vartab);
+        let error = SolidityError::Panic(PanicCode::Generic);
+        assert_failure(&Loc::Codegen, error, ns, cfg, vartab);
         cfg.set_basic_block(inbounds_block);
     }
 

+ 10 - 3
src/codegen/encoding/mod.rs

@@ -10,7 +10,7 @@
 ///   Any such helper function should work fine regardless of the encoding scheme being used.
 mod borsh_encoding;
 mod buffer_validator;
-mod scale_encoding;
+pub(super) mod scale_encoding;
 
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::encoding::borsh_encoding::BorshEncoding;
@@ -180,7 +180,7 @@ fn calculate_size_args(
 /// However, this might be less suitable for schemas vastly different than SCALE or Borsh.
 /// In the worst case scenario, you need to provide your own implementation of `fn encode(..)`,
 /// which effectively means implementing the encoding logic for any given sema `Type` on your own.
-pub(super) trait AbiEncoding {
+pub(crate) trait AbiEncoding {
     /// The width (in bits) used in size hints for dynamic size types.
     fn size_width(
         &self,
@@ -1732,10 +1732,17 @@ pub(super) trait AbiEncoding {
 
     /// Returns if the we are packed encoding
     fn is_packed(&self) -> bool;
+
+    /// Encode constant data at compile time.
+    ///
+    /// Returns `None` if the data can not be encoded at compile time.
+    fn const_encode(&self, _args: &[Expression]) -> Option<Vec<u8>> {
+        None
+    }
 }
 
 /// This function should return the correct encoder, given the target
-pub(super) fn create_encoder(ns: &Namespace, packed: bool) -> Box<dyn AbiEncoding> {
+pub(crate) fn create_encoder(ns: &Namespace, packed: bool) -> Box<dyn AbiEncoding> {
     match &ns.target {
         Target::Solana => Box::new(BorshEncoding::new(packed)),
         // Solana utilizes Borsh encoding and Polkadot, SCALE encoding.

+ 112 - 0
src/codegen/encoding/scale_encoding.rs

@@ -6,6 +6,8 @@ use crate::codegen::vartable::Vartable;
 use crate::codegen::{Builtin, Expression};
 use crate::sema::ast::StructType;
 use crate::sema::ast::{Namespace, Type, Type::Uint};
+use parity_scale_codec::Encode;
+use primitive_types::U256;
 use solang_parser::pt::Loc::Codegen;
 use std::collections::HashMap;
 
@@ -578,4 +580,114 @@ impl AbiEncoding for ScaleEncoding {
     fn is_packed(&self) -> bool {
         self.packed_encoder
     }
+
+    /// TODO: This is used and tested for error data (Error and Panic) only.
+    fn const_encode(&self, args: &[Expression]) -> Option<Vec<u8>> {
+        let mut result = vec![];
+        for arg in args {
+            match arg {
+                Expression::AllocDynamicBytes {
+                    initializer: Some(data),
+                    ty: Type::String | Type::DynamicBytes,
+                    ..
+                } => result.extend_from_slice(&data.encode()),
+                Expression::AllocDynamicBytes {
+                    initializer: Some(data),
+                    ty: Type::Slice(inner),
+                    ..
+                } if matches!(**inner, Type::Bytes(1)) => result.extend_from_slice(data),
+                Expression::NumberLiteral {
+                    ty: Type::Bytes(4),
+                    value,
+                    ..
+                } => {
+                    let bytes = value.to_bytes_be().1;
+                    if bytes.len() < 4 {
+                        let mut buf = Vec::new();
+                        buf.resize(4 - bytes.len(), 0);
+                        result.extend_from_slice(&buf);
+                    }
+                    result.extend_from_slice(&bytes[..]);
+                }
+                Expression::NumberLiteral {
+                    ty: Type::Uint(256),
+                    value,
+                    ..
+                } => {
+                    let bytes = value.to_bytes_be().1;
+                    result.extend_from_slice(&U256::from_big_endian(&bytes).encode()[..]);
+                }
+                _ => return None,
+            }
+        }
+        result.into()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use num_bigint::{BigInt, Sign};
+    use parity_scale_codec::Encode;
+    use primitive_types::U256;
+
+    use crate::{
+        codegen::{
+            encoding::{scale_encoding::ScaleEncoding, AbiEncoding},
+            Expression,
+        },
+        sema::ast::Type,
+    };
+
+    #[test]
+    fn const_encode_dynamic_bytes() {
+        let data = vec![0x41, 0x41];
+        let encoder = ScaleEncoding::new(false);
+        let expr = Expression::AllocDynamicBytes {
+            loc: Default::default(),
+            ty: Type::DynamicBytes,
+            size: Expression::Poison.into(),
+            initializer: data.clone().into(),
+        };
+        let encoded = encoder.const_encode(&[expr]).unwrap();
+        assert_eq!(encoded, data.encode());
+    }
+
+    #[test]
+    fn const_encode_uint() {
+        let encoder = ScaleEncoding::new(false);
+        for value in [U256::MAX, U256::zero(), U256::one()] {
+            let mut bytes = [0u8; 32].to_vec();
+            value.to_big_endian(&mut bytes);
+            let data = BigInt::from_bytes_be(Sign::Plus, &bytes);
+            let expr = Expression::NumberLiteral {
+                loc: Default::default(),
+                ty: Type::Uint(256),
+                value: data,
+            };
+            let encoded = encoder.const_encode(&[expr]).unwrap();
+            assert_eq!(encoded, value.encode());
+        }
+    }
+
+    #[test]
+    fn const_encode_bytes4() {
+        let encoder = ScaleEncoding::new(false);
+        for value in [
+            [0x00, 0x00, 0xff, 0xff],
+            [0x00, 0xff, 0xff, 0x00],
+            [0xff, 0xff, 0x00, 0x00],
+            [0xff, 0xff, 0xff, 0xff],
+            [0x00, 0x00, 0x00, 0x00],
+            [0xde, 0xad, 0xbe, 0xef],
+            [0x01, 0x00, 0x00, 0x00],
+            [0x00, 0x00, 0x00, 0x01],
+        ] {
+            let expr = Expression::NumberLiteral {
+                ty: Type::Bytes(4),
+                value: BigInt::from_bytes_be(Sign::Plus, &value),
+                loc: Default::default(),
+            };
+            assert_eq!(&encoder.const_encode(&[expr]).unwrap(), &value.encode());
+        }
+    }
 }

+ 13 - 180
src/codegen/expression.rs

@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use super::encoding::{abi_decode, abi_encode};
+use super::revert::{
+    assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError,
+};
 use super::storage::{
     array_offset, array_pop, array_push, storage_slots_array_pop, storage_slots_array_push,
 };
@@ -11,7 +14,6 @@ use super::{
 };
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::constructor::call_constructor;
-use crate::codegen::error_msg_with_loc;
 use crate::codegen::unused_variable::should_remove_assignment;
 use crate::codegen::{Builtin, Expression};
 use crate::sema::{
@@ -24,7 +26,6 @@ use crate::sema::{
     eval::{eval_const_number, eval_const_rational},
     expression::integers::bigint_to_expression,
     expression::ResolveTo,
-    file::PathDisplay,
 };
 use crate::Target;
 use num_bigint::BigInt;
@@ -1527,117 +1528,6 @@ fn and(
     }
 }
 
-fn expr_assert(
-    cfg: &mut ControlFlowGraph,
-    args: &ast::Expression,
-    contract_no: usize,
-    func: Option<&Function>,
-    ns: &Namespace,
-    vartab: &mut Vartable,
-    opt: &Options,
-) -> Expression {
-    let true_ = cfg.new_basic_block("noassert".to_owned());
-    let false_ = cfg.new_basic_block("doassert".to_owned());
-    let cond = expression(args, cfg, contract_no, func, ns, vartab, opt);
-    cfg.add(
-        vartab,
-        Instr::BranchCond {
-            cond,
-            true_block: true_,
-            false_block: false_,
-        },
-    );
-    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
-}
-
-fn require(
-    cfg: &mut ControlFlowGraph,
-    args: &[ast::Expression],
-    contract_no: usize,
-    func: Option<&Function>,
-    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());
-    let cond = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
-    cfg.add(
-        vartab,
-        Instr::BranchCond {
-            cond,
-            true_block: true_,
-            false_block: false_,
-        },
-    );
-    cfg.set_basic_block(false_);
-    let expr = args
-        .get(1)
-        .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt));
-    match ns.target {
-        // On Solana and Polkadot, print the reason, do not abi encode it
-        Target::Solana | Target::Polkadot { .. } => {
-            if opt.log_runtime_errors {
-                if let Some(expr) = expr {
-                    let prefix = b"runtime_error: ";
-                    let error_string = format!(
-                        " require condition failed in {},\n",
-                        ns.loc_to_string(PathDisplay::Filename, &expr.loc())
-                    );
-                    let print_expr = Expression::FormatString {
-                        loc: Loc::Codegen,
-                        args: vec![
-                            (
-                                FormatArg::StringLiteral,
-                                Expression::BytesLiteral {
-                                    loc: Loc::Codegen,
-                                    ty: Type::Bytes(prefix.len() as u8),
-                                    value: prefix.to_vec(),
-                                },
-                            ),
-                            (FormatArg::Default, expr),
-                            (
-                                FormatArg::StringLiteral,
-                                Expression::BytesLiteral {
-                                    loc: Loc::Codegen,
-                                    ty: Type::Bytes(error_string.as_bytes().len() as u8),
-                                    value: 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);
-        }
-        _ => assert_failure(&Loc::Codegen, expr, ns, cfg, vartab),
-    }
-    cfg.set_basic_block(true_);
-    Expression::Poison
-}
-
 fn self_destruct(
     args: &[ast::Expression],
     cfg: &mut ControlFlowGraph,
@@ -2011,7 +1901,8 @@ fn expr_builtin(
                 vartab,
                 ns,
             );
-            assert_failure(loc, None, ns, cfg, vartab);
+            let error = SolidityError::Panic(PanicCode::ArrayIndexOob);
+            assert_failure(loc, error, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
 
@@ -2071,7 +1962,8 @@ fn expr_builtin(
                 vartab,
                 ns,
             );
-            assert_failure(loc, None, ns, cfg, vartab);
+            let error = SolidityError::Panic(PanicCode::ArrayIndexOob);
+            assert_failure(loc, error, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
             let advanced_ptr = Expression::AdvancePointer {
@@ -2148,7 +2040,8 @@ fn expr_builtin(
                 vartab,
                 ns,
             );
-            assert_failure(loc, None, ns, cfg, vartab);
+            let error = SolidityError::Panic(PanicCode::ArrayIndexOob);
+            assert_failure(loc, error, ns, cfg, vartab);
 
             cfg.set_basic_block(in_bounds);
 
@@ -2412,7 +2305,8 @@ fn checking_trunc(
         vartab,
         ns,
     );
-    assert_failure(loc, None, ns, cfg, vartab);
+    let error = SolidityError::Panic(PanicCode::MathOverflow);
+    assert_failure(loc, error, ns, cfg, vartab);
 
     cfg.set_basic_block(in_bounds);
 
@@ -3276,7 +3170,8 @@ fn array_subscript(
         vartab,
         ns,
     );
-    assert_failure(loc, None, ns, cfg, vartab);
+    let error = SolidityError::Panic(PanicCode::ArrayIndexOob);
+    assert_failure(loc, error, ns, cfg, vartab);
 
     cfg.set_basic_block(in_bounds);
 
@@ -3603,39 +3498,6 @@ fn array_literal_to_memory_array(
     }
 }
 
-/// This function encodes the arguments for the assert-failure instruction
-/// and inserts it in the CFG.
-pub(super) fn assert_failure(
-    loc: &Loc,
-    arg: Option<Expression>,
-    ns: &Namespace,
-    cfg: &mut ControlFlowGraph,
-    vartab: &mut Vartable,
-) {
-    // On Solana, returning the encoded arguments has no effect
-    if arg.is_none() || ns.target == Target::Solana {
-        cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
-        return;
-    }
-
-    let selector = 0x08c3_79a0u32;
-    let selector = Expression::NumberLiteral {
-        loc: Loc::Codegen,
-        ty: Type::Bytes(4),
-        value: BigInt::from(selector),
-    };
-    let args = vec![selector, arg.unwrap()];
-
-    let (encoded_buffer, _) = abi_encode(loc, args, ns, vartab, cfg, false);
-
-    cfg.add(
-        vartab,
-        Instr::AssertFailure {
-            encoded_args: Some(encoded_buffer),
-        },
-    )
-}
-
 /// Generate the binary code for a contract
 #[cfg(feature = "llvm")]
 fn code(loc: &Loc, contract_no: usize, ns: &Namespace, opt: &Options) -> Expression {
@@ -3675,35 +3537,6 @@ fn code(loc: &Loc, _contract_no: usize, _ns: &Namespace, _opt: &Options) -> Expr
     }
 }
 
-fn string_to_expr(string: String) -> Expression {
-    Expression::FormatString {
-        loc: Loc::Codegen,
-        args: vec![(
-            FormatArg::StringLiteral,
-            Expression::BytesLiteral {
-                loc: Loc::Codegen,
-                ty: Type::Bytes(string.as_bytes().len() as u8),
-                value: 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.to_string(), Some(reason_loc));
-        let expr = string_to_expr(error_with_loc);
-        cfg.add(vartab, Instr::Print { expr });
-    }
-}
-
 fn add_prefix_and_delimiter_to_print(mut expr: Expression) -> Expression {
     let prefix = b"print: ";
     let delimiter = b",\n";

+ 4 - 16
src/codegen/mod.rs

@@ -6,11 +6,12 @@ mod constant_folding;
 mod constructor;
 mod dead_storage;
 pub(crate) mod dispatch;
-mod encoding;
+pub(crate) mod encoding;
 mod events;
 mod expression;
 mod external_functions;
 mod reaching_definitions;
+pub mod revert;
 mod solana_accounts;
 mod solana_deploy;
 mod statements;
@@ -34,10 +35,7 @@ use self::{
 use crate::sema::ast::{
     FormatArg, Function, Layout, Namespace, RetrieveType, StringLocation, Type,
 };
-use crate::{
-    sema::{ast, file::PathDisplay},
-    Target,
-};
+use crate::{sema::ast, Target};
 use std::cmp::Ordering;
 
 use crate::codegen::cfg::ASTFunction;
@@ -49,7 +47,7 @@ use contract_build::OptimizationPasses;
 use num_bigint::{BigInt, Sign};
 use num_rational::BigRational;
 use num_traits::{FromPrimitive, Zero};
-use solang_parser::{pt, pt::CodeLocation, pt::Loc};
+use solang_parser::{pt, pt::CodeLocation};
 
 // The sizeof(struct account_data_header)
 pub const SOLANA_FIRST_OFFSET: u64 = 16;
@@ -1831,13 +1829,3 @@ impl From<&ast::Builtin> for Builtin {
         }
     }
 }
-
-pub(super) fn error_msg_with_loc(ns: &Namespace, error: String, loc: Option<Loc>) -> String {
-    match &loc {
-        Some(loc @ Loc::File(..)) => {
-            let loc_from_file = ns.loc_to_string(PathDisplay::Filename, loc);
-            format!("runtime_error: {error} in {loc_from_file},\n")
-        }
-        _ => error + ",\n",
-    }
-}

+ 410 - 0
src/codegen/revert.rs

@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Releated to code that ultimately compiles to the target
+//! equivalent instruction of EVM revert (0xfd).
+
+use super::encoding::{abi_encode, create_encoder};
+use super::expression::expression;
+use super::Options;
+use super::{
+    cfg::{ControlFlowGraph, Instr},
+    vartable::Vartable,
+};
+
+use crate::codegen::Expression;
+use crate::sema::{
+    ast,
+    ast::{FormatArg, Function, Namespace, Type},
+    file::PathDisplay,
+};
+use crate::Target;
+use parse_display::Display;
+use solang_parser::pt::{CodeLocation, Loc, Loc::Codegen};
+
+/// Corresponds to the error types from the Solidity language.
+///
+/// Marked as non-exhaustive because Solidity may add more variants in the future.
+#[non_exhaustive]
+#[derive(Debug, PartialEq, Clone)]
+pub enum SolidityError {
+    /// Reverts with "empty error data"; stems from `revert()` or `require()` without string arguments.
+    Empty,
+    /// The `Error(string)` selector
+    String(Expression),
+    /// The `Panic(uint256)` selector
+    Panic(PanicCode),
+}
+
+impl SolidityError {
+    /// Return the selector expression of the error.
+    pub fn selector_expression(&self) -> Expression {
+        let selector = match self {
+            Self::Empty => unreachable!("empty return data has no selector"),
+            Self::String(_) => self.selector().into(),
+            Self::Panic(_) => self.selector().into(),
+        };
+
+        Expression::NumberLiteral {
+            loc: Codegen,
+            ty: Type::Bytes(4),
+            value: selector,
+        }
+    }
+
+    /// Return the selector of the error.
+    pub fn selector(&self) -> u32 {
+        match self {
+            Self::Empty => unreachable!("empty return data has no selector"),
+            Self::String(_) => 0x08c379a0u32,
+            Self::Panic(_) => 0x4e487b71u32,
+        }
+    }
+
+    /// ABI encode the selector and any error data.
+    ///
+    /// Returns `None` if the data can't be ABI encoded.
+    pub(super) fn abi_encode(
+        &self,
+        loc: &Loc,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> Option<Expression> {
+        match self {
+            Self::Empty => None,
+            Self::String(data) => {
+                let args = vec![self.selector_expression(), data.clone()];
+                create_encoder(ns, false)
+                    .const_encode(&args)
+                    .map(|bytes| {
+                        let size = Expression::NumberLiteral {
+                            loc: Codegen,
+                            ty: Type::Uint(32),
+                            value: bytes.len().into(),
+                        };
+                        Expression::AllocDynamicBytes {
+                            loc: Codegen,
+                            ty: Type::Slice(Type::Bytes(1).into()),
+                            size: size.into(),
+                            initializer: bytes.into(),
+                        }
+                    })
+                    .or_else(|| abi_encode(loc, args, ns, vartab, cfg, false).0.into())
+            }
+            Self::Panic(code) => {
+                let code = Expression::NumberLiteral {
+                    loc: Codegen,
+                    ty: Type::Uint(256),
+                    value: (*code as u8).into(),
+                };
+                create_encoder(ns, false)
+                    .const_encode(&[self.selector_expression(), code])
+                    .map(|bytes| {
+                        let size = Expression::NumberLiteral {
+                            loc: Codegen,
+                            ty: Type::Uint(32),
+                            value: bytes.len().into(),
+                        };
+                        Expression::AllocDynamicBytes {
+                            loc: Codegen,
+                            ty: Type::Slice(Type::Bytes(1).into()),
+                            size: size.into(),
+                            initializer: bytes.into(),
+                        }
+                    })
+            }
+        }
+    }
+}
+
+/// Solidity `Panic` Codes. Source:
+/// https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require
+///
+/// FIXME: Currently, not all panic variants are wired up yet in Solang:
+/// * EnumCastOob
+/// * StorageBytesEncodingIncorrect
+/// * OutOfMemory
+///
+/// Tracking issue: <https://github.com/hyperledger/solang/issues/1477>
+#[derive(Display, Debug, PartialEq, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u8)]
+pub enum PanicCode {
+    Generic = 0x00,
+    Assertion = 0x01,
+    MathOverflow = 0x11,
+    DivisionByZero = 0x12,
+    EnumCastOob = 0x21,
+    StorageBytesEncodingIncorrect = 0x22,
+    EmptyArrayPop = 0x31,
+    ArrayIndexOob = 0x32,
+    OutOfMemory = 0x41,
+    InternalFunctionUninitialized = 0x51,
+}
+
+/// This function encodes the arguments for the assert-failure instruction
+/// and inserts it in the CFG.
+pub(super) fn assert_failure(
+    loc: &Loc,
+    error: SolidityError,
+    ns: &Namespace,
+    cfg: &mut ControlFlowGraph,
+    vartab: &mut Vartable,
+) {
+    // On Solana, returning the encoded arguments has no effect
+    if ns.target == Target::Solana {
+        cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
+        return;
+    }
+
+    let encoded_args = error.abi_encode(loc, ns, vartab, cfg);
+    cfg.add(vartab, Instr::AssertFailure { encoded_args })
+}
+
+pub(super) fn expr_assert(
+    cfg: &mut ControlFlowGraph,
+    args: &ast::Expression,
+    contract_no: usize,
+    func: Option<&Function>,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    opt: &Options,
+) -> Expression {
+    let true_ = cfg.new_basic_block("noassert".to_owned());
+    let false_ = cfg.new_basic_block("doassert".to_owned());
+    let cond = expression(args, cfg, contract_no, func, ns, vartab, opt);
+    cfg.add(
+        vartab,
+        Instr::BranchCond {
+            cond,
+            true_block: true_,
+            false_block: false_,
+        },
+    );
+    cfg.set_basic_block(false_);
+    log_runtime_error(
+        opt.log_runtime_errors,
+        "assert failure",
+        args.loc(),
+        cfg,
+        vartab,
+        ns,
+    );
+    let error = SolidityError::Panic(PanicCode::Assertion);
+    assert_failure(&Codegen, error, ns, cfg, vartab);
+    cfg.set_basic_block(true_);
+    Expression::Poison
+}
+
+pub(super) fn require(
+    cfg: &mut ControlFlowGraph,
+    args: &[ast::Expression],
+    contract_no: usize,
+    func: Option<&Function>,
+    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());
+    let cond = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
+    cfg.add(
+        vartab,
+        Instr::BranchCond {
+            cond,
+            true_block: true_,
+            false_block: false_,
+        },
+    );
+    cfg.set_basic_block(false_);
+    let expr = args
+        .get(1)
+        .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt));
+
+    // On Solana and Polkadot, print the reason
+    if opt.log_runtime_errors && (ns.target == Target::Solana || ns.target.is_polkadot()) {
+        if let Some(expr) = expr.clone() {
+            let prefix = b"runtime_error: ";
+            let error_string = format!(
+                " require condition failed in {},\n",
+                ns.loc_to_string(PathDisplay::Filename, &expr.loc())
+            );
+            let print_expr = Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![
+                    (
+                        FormatArg::StringLiteral,
+                        Expression::BytesLiteral {
+                            loc: Loc::Codegen,
+                            ty: Type::Bytes(prefix.len() as u8),
+                            value: prefix.to_vec(),
+                        },
+                    ),
+                    (FormatArg::Default, expr),
+                    (
+                        FormatArg::StringLiteral,
+                        Expression::BytesLiteral {
+                            loc: Loc::Codegen,
+                            ty: Type::Bytes(error_string.as_bytes().len() as u8),
+                            value: 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,
+            );
+        }
+    }
+
+    let error = expr
+        .map(SolidityError::String)
+        .unwrap_or(SolidityError::Empty);
+    assert_failure(&Codegen, error, ns, cfg, vartab);
+
+    cfg.set_basic_block(true_);
+    Expression::Poison
+}
+
+pub(super) fn revert(
+    args: &[ast::Expression],
+    cfg: &mut ControlFlowGraph,
+    contract_no: usize,
+    func: Option<&Function>,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    opt: &Options,
+    loc: &Loc,
+) {
+    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 prefix = b"runtime_error: ";
+            let error_string = format!(
+                " revert encountered in {},\n",
+                ns.loc_to_string(PathDisplay::Filename, loc)
+            );
+            let print_expr = Expression::FormatString {
+                loc: Codegen,
+                args: vec![
+                    (
+                        FormatArg::StringLiteral,
+                        Expression::BytesLiteral {
+                            loc: Codegen,
+                            ty: Type::Bytes(prefix.len() as u8),
+                            value: prefix.to_vec(),
+                        },
+                    ),
+                    (FormatArg::Default, expr.clone().unwrap()),
+                    (
+                        FormatArg::StringLiteral,
+                        Expression::BytesLiteral {
+                            loc: Codegen,
+                            ty: Type::Bytes(error_string.as_bytes().len() as u8),
+                            value: 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,
+            )
+        }
+    }
+
+    let error = expr
+        .map(SolidityError::String)
+        .unwrap_or(SolidityError::Empty);
+    assert_failure(&Codegen, error, ns, cfg, vartab);
+}
+
+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.to_string(), Some(reason_loc));
+        let expr = string_to_expr(error_with_loc);
+        cfg.add(vartab, Instr::Print { expr });
+    }
+}
+
+pub(crate) fn error_msg_with_loc(ns: &Namespace, error: String, loc: Option<Loc>) -> String {
+    match &loc {
+        Some(loc @ Loc::File(..)) => {
+            let loc_from_file = ns.loc_to_string(PathDisplay::Filename, loc);
+            format!("runtime_error: {error} in {loc_from_file},\n")
+        }
+        _ => error + ",\n",
+    }
+}
+
+fn string_to_expr(string: String) -> Expression {
+    Expression::FormatString {
+        loc: Loc::Codegen,
+        args: vec![(
+            FormatArg::StringLiteral,
+            Expression::BytesLiteral {
+                loc: Loc::Codegen,
+                ty: Type::Bytes(string.as_bytes().len() as u8),
+                value: string.as_bytes().to_vec(),
+            },
+        )],
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::codegen::{
+        revert::{PanicCode, SolidityError},
+        Expression,
+    };
+
+    #[test]
+    fn panic_code_as_byte() {
+        assert_eq!(0x00, PanicCode::Generic as u8);
+        assert_eq!(0x01, PanicCode::Assertion as u8);
+        assert_eq!(0x11, PanicCode::MathOverflow as u8);
+        assert_eq!(0x12, PanicCode::DivisionByZero as u8);
+        assert_eq!(0x21, PanicCode::EnumCastOob as u8);
+        assert_eq!(0x22, PanicCode::StorageBytesEncodingIncorrect as u8);
+        assert_eq!(0x31, PanicCode::EmptyArrayPop as u8);
+        assert_eq!(0x32, PanicCode::ArrayIndexOob as u8);
+        assert_eq!(0x41, PanicCode::OutOfMemory as u8);
+        assert_eq!(0x51, PanicCode::InternalFunctionUninitialized as u8);
+    }
+
+    #[test]
+    fn function_selector_expression() {
+        assert_eq!(
+            0x08c379a0u32, // Keccak256('Error(string)')[:4]
+            SolidityError::String(Expression::Poison).selector(),
+        );
+        assert_eq!(
+            0x4e487b71u32, // Keccak256('Panic(uint256)')[:4]
+            SolidityError::Panic(PanicCode::Generic).selector(),
+        );
+    }
+}

+ 6 - 69
src/codegen/statements.rs

@@ -3,9 +3,7 @@
 use num_bigint::BigInt;
 
 use super::encoding::{abi_decode, abi_encode};
-use super::expression::{
-    assert_failure, assign_single, default_gas, emit_function_call, expression, log_runtime_error,
-};
+use super::expression::{assign_single, default_gas, emit_function_call, expression};
 use super::Options;
 use super::{
     cfg::{ControlFlowGraph, Instr},
@@ -13,19 +11,17 @@ use super::{
 };
 use crate::codegen::constructor::call_constructor;
 use crate::codegen::events::new_event_emitter;
+use crate::codegen::revert::revert;
 use crate::codegen::unused_variable::{
     should_remove_assignment, should_remove_variable, SideEffectsCheckParameters,
 };
 use crate::codegen::yul::inline_assembly_cfg;
 use crate::codegen::Expression;
-use crate::sema::Recurse;
-use crate::sema::{
-    ast::{
-        self, ArrayLength, CallTy, DestructureField, FormatArg, Function, Namespace, RetrieveType,
-        Statement, TryCatch, Type, Type::Uint,
-    },
-    file::PathDisplay,
+use crate::sema::ast::{
+    self, ArrayLength, CallTy, DestructureField, Function, Namespace, RetrieveType, Statement,
+    TryCatch, Type, Type::Uint,
 };
+use crate::sema::Recurse;
 use num_traits::Zero;
 use solang_parser::pt::{self, CodeLocation, Loc::Codegen};
 
@@ -607,65 +603,6 @@ pub(crate) fn statement(
     }
 }
 
-fn revert(
-    args: &[ast::Expression],
-    cfg: &mut ControlFlowGraph,
-    contract_no: usize,
-    func: Option<&Function>,
-    ns: &Namespace,
-    vartab: &mut Vartable,
-    opt: &Options,
-    loc: &pt::Loc,
-) {
-    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 prefix = b"runtime_error: ";
-            let error_string = format!(
-                " revert encountered in {},\n",
-                ns.loc_to_string(PathDisplay::Filename, loc)
-            );
-            let print_expr = Expression::FormatString {
-                loc: Codegen,
-                args: vec![
-                    (
-                        FormatArg::StringLiteral,
-                        Expression::BytesLiteral {
-                            loc: Codegen,
-                            ty: Type::Bytes(prefix.len() as u8),
-                            value: prefix.to_vec(),
-                        },
-                    ),
-                    (FormatArg::Default, expr.clone().unwrap()),
-                    (
-                        FormatArg::StringLiteral,
-                        Expression::BytesLiteral {
-                            loc: Codegen,
-                            ty: Type::Bytes(error_string.as_bytes().len() as u8),
-                            value: 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(&Codegen, expr, ns, cfg, vartab);
-}
-
 /// Generate if-then-no-else
 fn if_then(
     cond: &ast::Expression,

+ 6 - 3
src/codegen/storage.rs

@@ -7,13 +7,15 @@ use num_traits::FromPrimitive;
 use num_traits::One;
 use num_traits::Zero;
 
-use super::expression::{expression, load_storage, log_runtime_error};
+use super::expression::{expression, load_storage};
+use super::revert::PanicCode;
+use super::revert::SolidityError;
 use super::Options;
 use super::{
     cfg::{ControlFlowGraph, Instr},
     vartable::Vartable,
 };
-use crate::codegen::expression::assert_failure;
+use crate::codegen::revert::{assert_failure, log_runtime_error};
 use crate::sema::ast::{Function, Namespace, RetrieveType, Type};
 use solang_parser::pt;
 
@@ -252,7 +254,8 @@ pub fn storage_slots_array_pop(
         vartab,
         ns,
     );
-    assert_failure(loc, None, ns, cfg, vartab);
+    let error = SolidityError::Panic(PanicCode::EmptyArrayPop);
+    assert_failure(loc, error, ns, cfg, vartab);
 
     cfg.set_basic_block(has_elements);
     let new_length = vartab.temp_anonymous(&slot_ty);

+ 2 - 2
src/codegen/yul/builtin.rs

@@ -3,7 +3,7 @@
 use crate::{
     codegen::{
         cfg::{ControlFlowGraph, Instr},
-        expression::{assert_failure, log_runtime_error},
+        revert::{assert_failure, log_runtime_error, PanicCode, SolidityError},
         vartable::Vartable,
         yul::expression::expression,
         {Builtin, Expression, Options},
@@ -198,7 +198,7 @@ pub(crate) fn process_builtin(
             log_runtime_error(opt.log_runtime_errors,  "reached invalid instruction", *loc, cfg,
             vartab,
             ns);
-            assert_failure(loc, None, ns, cfg, vartab);
+            assert_failure(loc, SolidityError::Panic(PanicCode::Generic), ns, cfg, vartab);
             Expression::Poison
         }
 

+ 36 - 1
src/emit/binary.rs

@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
+use crate::codegen::encoding::create_encoder;
+use crate::codegen::revert::{error_msg_with_loc, PanicCode, SolidityError};
+use crate::codegen::Expression;
 use crate::sema::ast::{ArrayLength, Contract, Namespace, StructType, Type};
 use std::cell::RefCell;
 use std::path::Path;
@@ -13,7 +16,7 @@ use tempfile::tempdir;
 #[cfg(feature = "wasm_opt")]
 use wasm_opt::OptimizationOptions;
 
-use crate::codegen::{cfg::ReturnCode, error_msg_with_loc, Options};
+use crate::codegen::{cfg::ReturnCode, Options};
 use crate::emit::{polkadot, TargetRuntime};
 use crate::emit::{solana, BinaryOp, Generate};
 use crate::linker::link;
@@ -1074,6 +1077,38 @@ impl<'a> Binary<'a> {
                 .const_int(error_with_loc.len() as u64, false),
         );
     }
+
+    /// Emit encoded error data of "Panic(uint256)" as interned global string.
+    ///
+    /// On Solana, because reverts do not return data, a nil ptr is returned.
+    pub(super) fn panic_data_const(
+        &self,
+        ns: &Namespace,
+        code: PanicCode,
+    ) -> (PointerValue<'a>, IntValue<'a>) {
+        if ns.target == Target::Solana {
+            return (
+                self.context
+                    .i8_type()
+                    .ptr_type(AddressSpace::default())
+                    .const_null(),
+                self.context.i32_type().const_zero(),
+            );
+        }
+
+        let expr = Expression::NumberLiteral {
+            loc: pt::Loc::Codegen,
+            ty: Type::Uint(256),
+            value: (code as u8).into(),
+        };
+        let bytes = create_encoder(ns, false)
+            .const_encode(&[SolidityError::Panic(code).selector_expression(), expr])
+            .unwrap();
+        (
+            self.emit_global_string(&code.to_string(), &bytes, true),
+            self.context.i32_type().const_int(bytes.len() as u64, false),
+        )
+    }
 }
 
 /// Return the stdlib as parsed llvm module. The solidity standard library is hardcoded into

+ 33 - 69
src/emit/expression.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{HashTy, ReturnCode};
+use crate::codegen::revert::PanicCode;
 use crate::codegen::{Builtin, Expression};
 use crate::emit::binary::Binary;
 use crate::emit::math::{build_binary_op_with_overflow_check, multiply, power};
@@ -277,14 +278,9 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
                 // throw division by zero error should be an assert
                 bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
-                target.assert_failure(
-                    bin,
-                    bin.context
-                        .i8_type()
-                        .ptr_type(AddressSpace::default())
-                        .const_null(),
-                    bin.context.i32_type().const_zero(),
-                );
+                let (revert_out, revert_out_len) =
+                    bin.panic_data_const(ns, PanicCode::DivisionByZero);
+                target.assert_failure(bin, revert_out, revert_out_len);
 
                 bin.builder.position_at_end(success_block);
 
@@ -374,14 +370,9 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
                 // throw division by zero error should be an assert
                 bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
-                target.assert_failure(
-                    bin,
-                    bin.context
-                        .i8_type()
-                        .ptr_type(AddressSpace::default())
-                        .const_null(),
-                    bin.context.i32_type().const_zero(),
-                );
+                let (revert_out, revert_out_len) =
+                    bin.panic_data_const(ns, PanicCode::DivisionByZero);
+                target.assert_failure(bin, revert_out, revert_out_len);
 
                 bin.builder.position_at_end(success_block);
 
@@ -519,14 +510,9 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
                 // throw division by zero error should be an assert
                 bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
-                target.assert_failure(
-                    bin,
-                    bin.context
-                        .i8_type()
-                        .ptr_type(AddressSpace::default())
-                        .const_null(),
-                    bin.context.i32_type().const_zero(),
-                );
+                let (revert_out, revert_out_len) =
+                    bin.panic_data_const(ns, PanicCode::DivisionByZero);
+                target.assert_failure(bin, revert_out, revert_out_len);
 
                 bin.builder.position_at_end(success_block);
 
@@ -613,14 +599,9 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
                 // throw division by zero error should be an assert
                 bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
-                target.assert_failure(
-                    bin,
-                    bin.context
-                        .i8_type()
-                        .ptr_type(AddressSpace::default())
-                        .const_null(),
-                    bin.context.i32_type().const_zero(),
-                );
+                let (revert_out, revert_out_len) =
+                    bin.panic_data_const(ns, PanicCode::DivisionByZero);
+                target.assert_failure(bin, revert_out, revert_out_len);
 
                 bin.builder.position_at_end(success_block);
 
@@ -713,39 +694,28 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
             // Load the result pointer
             let res = bin.builder.build_load(left.get_type(), o, "");
 
-            if *overflowing || ns.target.is_polkadot() {
-                // In Polkadot, overflow case will hit an unreachable expression, so no additional checks are needed.
-                res
-            } else {
-                // In Solana, a return other than zero will abort execution. We need to check if power() returned a zero or not.
-                let error_block = bin.context.append_basic_block(function, "error");
-                let return_block = bin.context.append_basic_block(function, "return_block");
-
-                let error_ret = bin.builder.build_int_compare(
-                    IntPredicate::NE,
-                    error_return.into_int_value(),
-                    error_return.get_type().const_zero().into_int_value(),
-                    "",
-                );
+            // A return other than zero will abort execution. We need to check if power() returned a zero or not.
+            let error_block = bin.context.append_basic_block(function, "error");
+            let return_block = bin.context.append_basic_block(function, "return_block");
 
-                bin.builder
-                    .build_conditional_branch(error_ret, error_block, return_block);
-                bin.builder.position_at_end(error_block);
+            let error_ret = bin.builder.build_int_compare(
+                IntPredicate::NE,
+                error_return.into_int_value(),
+                error_return.get_type().const_zero().into_int_value(),
+                "",
+            );
 
-                bin.log_runtime_error(target, "math overflow".to_string(), Some(*loc), ns);
-                target.assert_failure(
-                    bin,
-                    bin.context
-                        .i8_type()
-                        .ptr_type(AddressSpace::default())
-                        .const_null(),
-                    bin.context.i32_type().const_zero(),
-                );
+            bin.builder
+                .build_conditional_branch(error_ret, error_block, return_block);
+            bin.builder.position_at_end(error_block);
 
-                bin.builder.position_at_end(return_block);
+            bin.log_runtime_error(target, "math overflow".to_string(), Some(*loc), ns);
+            let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::MathOverflow);
+            target.assert_failure(bin, revert_out, revert_out_len);
 
-                res
-            }
+            bin.builder.position_at_end(return_block);
+
+            res
         }
         Expression::Equal { left, right, .. } => {
             if left.ty().is_address() {
@@ -1135,14 +1105,8 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
             bin.builder.position_at_end(error);
             bin.log_runtime_error(target, "bytes cast error".to_string(), Some(*loc), ns);
-            target.assert_failure(
-                bin,
-                bin.context
-                    .i8_type()
-                    .ptr_type(AddressSpace::default())
-                    .const_null(),
-                bin.context.i32_type().const_zero(),
-            );
+            let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::Generic);
+            target.assert_failure(bin, revert_out, revert_out_len);
 
             bin.builder.position_at_end(cast);
             let bytes_ptr = bin.vector_bytes(array);

+ 28 - 8
src/emit/instructions.rs

@@ -2,6 +2,7 @@
 
 use crate::codegen::{
     cfg::{ControlFlowGraph, Instr, InternalCallTy, ReturnCode},
+    revert::PanicCode,
     Expression,
 };
 use crate::emit::binary::Binary;
@@ -292,14 +293,8 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
 
             bin.builder.position_at_end(error);
             bin.log_runtime_error(target, "pop from empty array".to_string(), Some(*loc), ns);
-            target.assert_failure(
-                bin,
-                bin.context
-                    .i8_type()
-                    .ptr_type(AddressSpace::default())
-                    .const_null(),
-                bin.context.i32_type().const_zero(),
-            );
+            let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::EmptyArrayPop);
+            target.assert_failure(bin, revert_out, revert_out_len);
 
             bin.builder.position_at_end(pop);
             let llvm_ty = bin.llvm_type(ty, ns);
@@ -601,6 +596,31 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             let callable =
                 expression(target, bin, call_expr, &w.vars, function, ns).into_pointer_value();
 
+            let ptr_ok = bin.context.append_basic_block(function, "fn_ptr_ok");
+            let ptr_nil_block = bin.context.append_basic_block(function, "fn_ptr_nil");
+            let nil_ptr = bin
+                .context
+                .i8_type()
+                .ptr_type(AddressSpace::default())
+                .const_null();
+            let is_ptr_nil =
+                bin.builder
+                    .build_int_compare(IntPredicate::EQ, nil_ptr, callable, "check_nil_ptr");
+            bin.builder
+                .build_conditional_branch(is_ptr_nil, ptr_nil_block, ptr_ok);
+
+            bin.builder.position_at_end(ptr_nil_block);
+            bin.log_runtime_error(
+                target,
+                "internal function uninitialized".to_string(),
+                None,
+                ns,
+            );
+            let (revert_out, revert_out_len) =
+                bin.panic_data_const(ns, PanicCode::InternalFunctionUninitialized);
+            target.assert_failure(bin, revert_out, revert_out_len);
+
+            bin.builder.position_at_end(ptr_ok);
             let ret = bin
                 .builder
                 .build_indirect_call(llvm_func, callable, &parms, "")

+ 9 - 26
src/emit/math.rs

@@ -1,11 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0
 
+use crate::codegen::revert::PanicCode;
 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 inkwell::IntPredicate;
 use solang_parser::pt::Loc;
 
 /// Signed overflow detection is handled by the following steps:
@@ -181,14 +182,8 @@ fn signed_ovf_detect<'a, T: TargetRuntime<'a> + ?Sized>(
     bin.builder.position_at_end(error_block);
 
     bin.log_runtime_error(target, "multiplication overflow".to_string(), Some(loc), ns);
-    target.assert_failure(
-        bin,
-        bin.context
-            .i8_type()
-            .ptr_type(AddressSpace::default())
-            .const_null(),
-        bin.context.i32_type().const_zero(),
-    );
+    let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::MathOverflow);
+    target.assert_failure(bin, revert_out, revert_out_len);
 
     bin.builder.position_at_end(return_block);
 
@@ -359,14 +354,9 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
             bin.builder.position_at_end(error_block);
 
             bin.log_runtime_error(target, "multiplication overflow".to_string(), Some(loc), ns);
-            target.assert_failure(
-                bin,
-                bin.context
-                    .i8_type()
-                    .ptr_type(AddressSpace::default())
-                    .const_null(),
-                bin.context.i32_type().const_zero(),
-            );
+
+            let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::MathOverflow);
+            target.assert_failure(bin, revert_out, revert_out_len);
 
             bin.builder.position_at_end(return_block);
 
@@ -588,15 +578,8 @@ pub(super) fn build_binary_op_with_overflow_check<'a, T: TargetRuntime<'a> + ?Si
     bin.builder.position_at_end(error_block);
 
     bin.log_runtime_error(target, "math overflow".to_string(), Some(loc), ns);
-
-    target.assert_failure(
-        bin,
-        bin.context
-            .i8_type()
-            .ptr_type(AddressSpace::default())
-            .const_null(),
-        bin.context.i32_type().const_zero(),
-    );
+    let (revert_out, revert_out_len) = bin.panic_data_const(ns, PanicCode::MathOverflow);
+    target.assert_failure(bin, revert_out, revert_out_len);
 
     bin.builder.position_at_end(success_block);
 

+ 7 - 3
src/emit/polkadot/target.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{HashTy, ReturnCode};
+use crate::codegen::revert::PanicCode;
 use crate::emit::binary::Binary;
 use crate::emit::expression::expression;
 use crate::emit::polkadot::{log_return_code, PolkadotTarget, SCRATCH_SIZE};
@@ -357,7 +358,8 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
             Some(loc),
             ns,
         );
-        self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
+        let (revert_out, revert_out_len) = binary.panic_data_const(ns, PanicCode::ArrayIndexOob);
+        self.assert_failure(binary, revert_out, revert_out_len);
 
         binary.builder.position_at_end(retrieve_block);
 
@@ -445,7 +447,8 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
             Some(loc),
             ns,
         );
-        self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
+        let (revert_out, revert_out_len) = binary.panic_data_const(ns, PanicCode::ArrayIndexOob);
+        self.assert_failure(binary, revert_out, revert_out_len);
 
         binary.builder.position_at_end(retrieve_block);
 
@@ -625,7 +628,8 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
             Some(loc),
             ns,
         );
-        self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
+        let (revert_out, revert_out_len) = binary.panic_data_const(ns, PanicCode::EmptyArrayPop);
+        self.assert_failure(binary, revert_out, revert_out_len);
 
         binary.builder.position_at_end(retrieve_block);
 

+ 68 - 1
tests/polkadot_tests/abi.rs

@@ -3,9 +3,17 @@
 use crate::{build_wasm, load_abi};
 use ink_metadata::{InkProject, TypeSpec};
 use once_cell::sync::Lazy;
-use scale_info::form::PortableForm;
+use scale_info::{
+    form::PortableForm, Path, TypeDef, TypeDefComposite, TypeDefPrimitive, TypeDefVariant,
+};
 use std::sync::Mutex;
 
+macro_rules! path {
+    ($( $segments:expr ),*) => {
+        Path::from_segments_unchecked([$($segments),*].iter().map(ToString::to_string))
+    }
+}
+
 /// Partially mimicking the ink! "mother" integration test.
 static MOTHER: Lazy<Mutex<(InkProject, InkProject)>> = Lazy::new(|| {
     let src = r#"
@@ -106,3 +114,62 @@ fn inherited_externally_callable_functions() {
     assert_eq!(messages.len(), 1);
     assert_eq!(messages[0].label(), "supportsInterface");
 }
+
+/// Ensure that the correct selector and data type for Error(String) and
+/// Panic(uint256) is present in the metadata.
+#[test]
+fn error_and_panic_in_lang_error() {
+    let src = r##"
+    contract Foo { uint public foo; }
+    "##;
+    let abi = load_abi(&build_wasm(src, false, false)[0].1);
+
+    // Find them in lang_error
+    let (error_ty_id, panic_ty_id) = match &abi
+        .registry()
+        .resolve(abi.spec().lang_error().ty().id)
+        .unwrap()
+        .type_def
+    {
+        TypeDef::<PortableForm>::Variant(TypeDefVariant::<PortableForm> { variants }) => {
+            let error = variants.iter().find(|v| v.name == "Error").unwrap();
+            let panic = variants.iter().find(|v| v.name == "Panic").unwrap();
+            (error.fields[0].ty.id, panic.fields[0].ty.id)
+        }
+        _ => panic!("unexpected lang_err type def"),
+    };
+
+    // Asserts for Error
+    let error_ty = abi.registry().resolve(error_ty_id).unwrap();
+    let error_ty_id = match &error_ty.type_def {
+        TypeDef::<PortableForm>::Composite(TypeDefComposite::<PortableForm> { fields }) => {
+            assert_eq!(error_ty.path, path!("0x08c379a0"));
+            fields[0].ty.id
+        }
+        _ => panic!("expected Error(string) type"),
+    };
+    let error_ty = abi.registry().resolve(error_ty_id).unwrap();
+    match &error_ty.type_def {
+        TypeDef::<PortableForm>::Primitive(TypeDefPrimitive::Str) => {
+            assert_eq!(error_ty.path, path!("string"))
+        }
+        _ => panic!("expected Error(string) type"),
+    };
+
+    // Asserts for Panic
+    let panic_ty = abi.registry().resolve(panic_ty_id).unwrap();
+    let panic_ty_id = match &panic_ty.type_def {
+        TypeDef::<PortableForm>::Composite(TypeDefComposite::<PortableForm> { fields }) => {
+            assert_eq!(panic_ty.path, path!("0x4e487b71"));
+            fields[0].ty.id
+        }
+        _ => panic!("expected Panic(uint256) type"),
+    };
+    let panic_ty = abi.registry().resolve(panic_ty_id).unwrap();
+    match &panic_ty.type_def {
+        TypeDef::<PortableForm>::Primitive(TypeDefPrimitive::U256) => {
+            assert_eq!(panic_ty.path, path!("uint256"))
+        }
+        _ => panic!("expected Panic(uint256) type"),
+    };
+}

+ 15 - 15
tests/polkadot_tests/calls.rs

@@ -61,26 +61,26 @@ fn revert() {
 
 #[test]
 fn require() {
-    let mut runtime = build_solidity(
-        r#"
-        contract c {
-            function test1() public {
-                require(false, "Program testing can be used to show the presence of bugs, but never to show their absence!");
-            }
-
-            function test2() public {
-                require(true, "Program testing can be used to show the presence of bugs, but never to show their absence!");
-            }
-        }"#,
+    let msg = "Program testing can be used to show the presence of bugs, but never to show their absence!".to_string();
+    let src = format!(
+        r#"contract c {{
+            function test1() public pure {{
+                require(false, "{}");
+            }}
+
+            function test2() public pure {{
+                require(true, "{}");
+            }}
+        }}"#,
+        &msg, &msg
     );
+    let mut runtime = build_solidity(&src);
 
     runtime.function_expect_failure("test1", Vec::new());
-
-    // The reason is lost
-    assert_eq!(runtime.output().len(), 0);
+    let selector = 0x08c379a0u32.to_be_bytes();
+    assert_eq!(runtime.output(), (selector, msg).encode());
 
     runtime.function("test2", Vec::new());
-
     assert_eq!(runtime.output().len(), 0);
 }
 

+ 461 - 203
tests/polkadot_tests/errors.rs

@@ -1,306 +1,564 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::build_solidity_with_options;
-use parity_scale_codec::Encode;
+use crate::build_solidity;
+use parity_scale_codec::{Decode, Encode};
+use primitive_types::U256;
+use solang::codegen::{
+    revert::{PanicCode, PanicCode::*, SolidityError},
+    Expression,
+};
+
+#[derive(Encode, Decode)]
+struct PanicData {
+    selector: [u8; 4],
+    data: U256,
+}
 
-#[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;
+impl From<PanicCode> for PanicData {
+    fn from(value: PanicCode) -> Self {
+        Self {
+            selector: SolidityError::Panic(value).selector().to_be_bytes(),
+            data: U256::from(value as u8),
+        }
+    }
+}
 
-        constructor() public payable {}
+#[derive(Encode, Decode)]
+struct ErrorData {
+    selector: [u8; 4],
+    msg: String,
+}
+
+impl From<String> for ErrorData {
+    fn from(msg: String) -> Self {
+        Self {
+            selector: SolidityError::String(Expression::Poison)
+                .selector()
+                .to_be_bytes(),
+            msg,
+        }
+    }
+}
 
-        function print_test(int8 num) public returns (int8) {
-            print("Hello world!");
-            return num;
+#[test]
+fn constructor_buf_too_small() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function write_bytes_failure(uint8 buf_size) public pure {
+            bytes data = new bytes(10);
+            bytes smol_buf = new bytes(buf_size);
+            smol_buf.writeBytes(data, 0);
         }
+    }"#,
+    );
+
+    runtime.function_expect_failure("write_bytes_failure", 9u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: data does not fit into buffer in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
 
-        function math_overflow(int8 num) public returns (int8) {
+#[test]
+fn math_overflow() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function math_overflow(int8 num) public pure returns (int8) {
             int8 ovf = num + 120;
             return ovf;
         }
+    }"#,
+    );
+
+    runtime.function_expect_failure("math_overflow", 10u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: math overflow in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(MathOverflow).encode());
+}
 
-        function require_test(int8 num) public returns (int8) {
+#[test]
+fn require() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function require_test(int8 num) public pure returns (int8) {
             require(num > 10, "sesa");
             return 0;
         }
+    }"#,
+    );
+
+    runtime.function_expect_failure("require_test", 9u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: sesa require condition failed in test.sol"));
+    assert_eq!(
+        runtime.output(),
+        ErrorData::from("sesa".to_string()).encode()
+    );
+}
+
+#[test]
+fn require_without_message() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function require_test(int8 num) public pure returns (int8) {
+            require(num > 10);
+            return 0;
+        }
+    }"#,
+    );
+
+    runtime.function_expect_failure("require_test", 9u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: require condition failed in test.sol"));
+    assert!(runtime.output().is_empty());
+}
 
-        // assert failure
+#[test]
+fn assert() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
         function assert_test(int8 num) public returns (int8) {
             assert(num > 10);
             return 0;
         }
+    }"#,
+    );
 
-        // storage index out of bounds
+    runtime.function_expect_failure("assert_test", 9u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: assert failure in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(Assertion).encode());
+}
+
+#[test]
+fn set_storage_bytes_oob() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        bytes public b = hex"0000_00fa";
         function set_storage_bytes() public returns (bytes) {
             bytes sesa = new bytes(1);
             b[5] = sesa[0];
             return sesa;
         }
+    }"#,
+    );
+
+    runtime.constructor(0, vec![]);
+    runtime.function_expect_failure("set_storage_bytes", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: storage index out of bounds in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
 
-        // storage array index out of bounds
+#[test]
+fn get_storage_bytes_oob() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        bytes public b = hex"0000_00fa";
         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);
-        }
+    runtime.constructor(0, vec![]);
+    runtime.function_expect_failure("get_storage_bytes", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: storage array index out of bounds in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
+
+#[test]
+fn transfer_fails() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+            function transfer_abort() public {
+                address a = address(0);
+                payable(a).transfer(10);
+            }
+    }"#,
+    );
+
+    runtime.function_expect_failure("transfer_abort", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: value transfer failure in test.sol"));
+    assert!(runtime.output().is_empty());
+}
 
-        //  pop from empty storage array
+#[test]
+fn empty_storage_array_pop() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        uint256[] public arr;
         function pop_empty_storage() public {
             arr.pop();
         }
+    }"#,
+    );
 
-        // external call failed
-        function call_ext() public {
-            //cal = new callee();
-            cal.callee_func{gas: 1e15}();
-        }
+    runtime.constructor(0, vec![]);
+    runtime.function_expect_failure("pop_empty_storage", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: pop from empty storage array in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(EmptyArrayPop).encode());
+}
 
-        // contract creation failed (contract was deplyed with no value)
+#[test]
+fn contract_instantiatoin_fail() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        child public c;
+        child public c2;
+        constructor() payable {}
         function create_child() public {
-            c = new child{value: 900e15, salt: hex"02"}();
-            c2 = new child{value: 900e15, salt: hex"02"}();
-            uint128 x = address(this).balance;
-            //print("sesa");
-            print("x = {}".format(x));
+                c = new child{value: 900e15, salt: hex"02"}();
+                c2 = new child{value: 900e15, salt: hex"02"}();
+                uint128 x = address(this).balance;
+                print("x = {}".format(x));
+        }
+    }
 
+    contract child {
+        function say_my_name() public pure returns (string memory) {
+            print("say_my_name");
+            return "child";
         }
+    }"#,
+    );
 
-        // non payable function dont_pay_me received value
-        function dont_pay_me() public {}
+    runtime.set_transferred_value(3500);
+    runtime.constructor(0, Vec::new());
 
-        function pay_me() public payable {
-            print("PAYED");
-            uint128 x = address(this).balance;
-            //print("sesa");
-            print("x = {}".format(x));
+    runtime.set_transferred_value(0);
+    runtime.function_expect_failure("create_child", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: contract creation failed in test.sol:6"));
+    assert!(runtime.output().is_empty());
+}
 
-        }
+#[test]
+fn revert() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+            function i_will_revert() public pure {
+                revert();
+            }
+            function revert_dyn(string s) public pure {
+                revert(s);
+            }
+            function revert_static() public pure {
+                revert("hi");
+            }
+    }"#,
+    );
 
-        function i_will_revert() public {
-            revert();
-        }
+    runtime.function_expect_failure("i_will_revert", Vec::new());
+    assert!(runtime.debug_buffer().contains("runtime_error: revert"));
+    assert!(runtime.output().is_empty());
+
+    let msg = "hello \"\n\0world!".to_string();
+    runtime.function_expect_failure("revert_dyn", msg.encode());
+    assert!(runtime.debug_buffer().contains("revert encountered"));
+    assert!(runtime.debug_buffer().contains(&msg));
+    assert_eq!(runtime.output(), ErrorData::from(msg).encode());
+
+    runtime.function_expect_failure("revert_static", Vec::new());
+    assert!(runtime.debug_buffer().contains("runtime_error: hi revert"));
+    assert_eq!(runtime.output(), ErrorData::from("hi".to_string()).encode());
+}
 
+#[test]
+fn int_too_large_for_bytes() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
         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];
-        }
+    runtime.function_expect_failure("write_integer_failure", 1u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: integer too large to write in buffer in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
 
-        function invalid_instruction() public {
+#[test]
+fn invalid_instruction() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function invalid_instruction() public pure {
             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";
-        }
-    }
-    "#,
-        false,
-        true,
-    );
-
-    runtime.constructor(0, vec![]);
-    runtime.function_expect_failure("write_bytes_failure", 9u8.encode());
-
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: data does not fit into buffer in test.sol:95:22-32,\n"
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("math_overflow", 10u8.encode());
+    runtime.function_expect_failure("invalid_instruction", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: reached invalid instruction in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(Generic).encode());
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: math overflow in test.sol:16:24-33,\n"
+#[test]
+fn array_index_oob() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function out_of_bounds(uint8 input) public pure returns (uint256) {
+            uint256[] a = new uint256[](input);
+            return a[20];
+        } 
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("require_test", 9u8.encode());
+    runtime.function_expect_failure("out_of_bounds", 19u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: array index out of bounds in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: sesa require condition failed in test.sol:21:31-37,\n"
+#[test]
+fn truncated_type_overflow() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function trunc_failure(uint128 input) public returns (uint256) {
+            uint256[] a = new uint256[](input);
+            return a[0];
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("assert_test", 9u8.encode());
+    runtime.function_expect_failure("trunc_failure", u128::MAX.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: truncated type overflows in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(MathOverflow).encode());
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: assert failure in test.sol:27:20-28,\n"
+#[test]
+fn byte_cast_fail() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function byte_cast_failure(uint8 num) public pure returns (bytes) {
+            bytes smol_buf = new bytes(num);
+            bytes32 b32 = bytes32(smol_buf);
+            return b32;
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("set_storage_bytes", Vec::new());
-
+    runtime.function_expect_failure("byte_cast_failure", 33u8.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: bytes cast error in test.sol"));
     assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: storage index out of bounds in test.sol:34:15-16,\n"
-    );
-
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("get_storage_bytes", Vec::new());
+        runtime.output(),
+        PanicData::from(PanicCode::Generic).encode()
+    )
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: storage array index out of bounds in test.sol:41:23-27,\n"
+#[test]
+fn int_read_oob() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function read_integer_failure(uint32 offset) public {
+            bytes smol_buf = new bytes(1);
+            smol_buf.readUint16LE(offset);
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("transfer_abort", Vec::new());
-
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: value transfer failure in test.sol:48:33-35,\n"
-    );
+    runtime.function_expect_failure("read_integer_failure", 2u32.encode());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: read integer out of bounds in test.sol"));
+    assert_eq!(runtime.output(), PanicData::from(ArrayIndexOob).encode());
+}
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("pop_empty_storage", Vec::new());
+#[test]
+fn external_call() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        callee public cal;
+        constructor() payable {}
+        function call_ext() public {
+            cal = new callee();
+            cal.callee_func{gas: 1e15}();
+        }
+    }
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: pop from empty storage array in test.sol:53:17-20,\n"
+    contract callee {
+        function callee_func() public {
+            revert();
+        }
+    }"#,
     );
 
     runtime.set_transferred_value(3500);
-    runtime.constructor(0, Vec::new());
+    runtime.constructor(0, vec![]);
+    runtime.function_expect_failure("call_ext", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: external call failed in test.sol"));
+    assert!(runtime.output().is_empty());
+}
 
-    runtime.debug_buffer().clear();
-    runtime.set_transferred_value(0);
-    runtime.function_expect_failure("create_child", Vec::new());
+#[test]
+fn non_payable_function_with_value() {
+    let mut runtime =
+        build_solidity(r#"contract RuntimeErrors { function dont_pay_me() public {} }"#);
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: contract creation failed in test.sol:64:17-58,\n"
-    );
+    runtime.set_transferred_value(1);
+    runtime.function_expect_failure("dont_pay_me", Vec::new());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: non payable function dont_pay_me received value"));
+    assert!(runtime.output().is_empty());
+}
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("i_will_revert", Vec::new());
+#[test]
+fn multiplication_overflow_big_u256() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+            function pow(uint256 bar) public pure returns(uint256) {
+                return bar ** 2;
+            }
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: revert encountered in test.sol:84:13-21,\n"
+            function mul(uint256 bar) public pure returns(uint256) {
+                return bar * 2;
+            }
+        }"#,
     );
+    let expected_debug_output = "runtime_error: multiplication overflow";
+    let expected_output = PanicData::from(MathOverflow).encode();
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("write_integer_failure", 1u8.encode());
+    runtime.function_expect_failure("pow", U256::MAX.encode());
+    assert!(runtime.debug_buffer().contains(expected_debug_output));
+    assert_eq!(runtime.output(), expected_output);
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: integer too large to write in buffer in test.sol:89:22-35,\n"
-    );
+    runtime.function_expect_failure("mul", U256::MAX.encode());
+    assert!(runtime.debug_buffer().contains(expected_debug_output));
+    assert_eq!(runtime.output(), expected_output)
+}
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("invalid_instruction", Vec::new());
+#[test]
+fn multiplication_overflow_u8() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+            function pow(uint8 bar) public pure returns(uint8) {
+                return bar ** 2;
+            }
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: reached invalid instruction in test.sol:116:17-26,\n"
+            function mul(uint8 bar) public pure returns(uint8) {
+                return bar * 2;
+            }
+        }"#,
     );
+    let expected_debug_output = "runtime_error: math overflow";
+    let expected_output = PanicData::from(MathOverflow).encode();
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("out_of_bounds", 19u8.encode());
+    runtime.function_expect_failure("pow", u8::MAX.encode());
+    assert!(runtime.debug_buffer().contains(expected_debug_output));
+    assert_eq!(runtime.output(), expected_output);
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: array index out of bounds in test.sol:111:20-25,\n"
-    );
-
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("trunc_failure", u128::MAX.encode());
+    runtime.function_expect_failure("mul", u8::MAX.encode());
+    assert!(runtime.debug_buffer().contains(expected_debug_output));
+    assert_eq!(runtime.output(), expected_output)
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: truncated type overflows in test.sol:105:41-46,\n"
+#[test]
+fn empty_array_pop() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function pop_empty_array() public pure returns(uint256[] arr) {
+            arr.pop();
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("byte_cast_failure", 33u8.encode());
+    runtime.function_expect_failure("pop_empty_array", vec![]);
+    assert!(runtime.debug_buffer().contains("pop from empty array"));
+    assert_eq!(runtime.output(), PanicData::from(EmptyArrayPop).encode());
+}
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: bytes cast error in test.sol:124:27-44,\n"
+#[test]
+fn uint256_div_by_zero() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function div_by_zero(uint256 div) public pure returns(uint256 ret) {
+            ret = ret / div;
+        }
+        function mod_zero(uint256 div) public pure returns(uint256 ret) {
+            ret = ret % div;
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("read_integer_failure", 2u32.encode());
+    runtime.function_expect_failure("div_by_zero", U256::zero().encode());
+    assert!(runtime.debug_buffer().contains("division by zero"));
+    assert_eq!(runtime.output(), PanicData::from(DivisionByZero).encode());
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: read integer out of bounds in test.sol:100:22-34,\n"
+    runtime.function_expect_failure("mod_zero", U256::zero().encode());
+    assert!(runtime.debug_buffer().contains("division by zero"));
+    assert_eq!(runtime.output(), PanicData::from(DivisionByZero).encode());
+}
+
+#[test]
+fn int256_div_by_zero() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function div_by_zero(int256 div) public pure returns(int256 ret) {
+            ret = ret / div;
+        }
+        function mod_zero(int256 div) public pure returns(int256 ret) {
+            ret = ret % div;
+        }
+    }"#,
     );
 
-    runtime.debug_buffer().clear();
-    runtime.function_expect_failure("call_ext", Vec::new());
+    runtime.function_expect_failure("div_by_zero", U256::zero().encode());
+    assert!(runtime.debug_buffer().contains("division by zero"));
+    assert_eq!(runtime.output(), PanicData::from(DivisionByZero).encode());
 
-    assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: external call failed in test.sol:59:13-41,\n"
-    );
+    runtime.function_expect_failure("mod_zero", U256::zero().encode());
+    assert!(runtime.debug_buffer().contains("division by zero"));
+    assert_eq!(runtime.output(), PanicData::from(DivisionByZero).encode());
+}
 
-    runtime.debug_buffer().clear();
-    runtime.set_transferred_value(1);
-    runtime.function_expect_failure("dont_pay_me", Vec::new());
+#[test]
+fn internal_dyn_call_on_nil() {
+    let mut runtime = build_solidity(
+        r#"contract RuntimeErrors {
+        function() internal x;
+        function f() public returns (uint r) {
+            x();
+            return 2;
+        } 
+    }"#,
+    );
 
+    runtime.function_expect_failure("f", vec![]);
+    assert!(runtime
+        .debug_buffer()
+        .contains("internal function uninitialized"));
     assert_eq!(
-        runtime.debug_buffer(),
-        "runtime_error: non payable function dont_pay_me received value,\n"
+        runtime.output(),
+        PanicData::from(InternalFunctionUninitialized).encode()
     );
 }