diff --git a/Cargo.toml b/Cargo.toml
index 88bef034e..3415610a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -110,3 +110,7 @@ opcodes = { workspace = true, features = ["data"] }
 tracing.workspace = true
 zint.workspace = true
 hex.workspace = true
+
+# [features]
+# default = [ "evm" ]
+# evm = [  ]
diff --git a/codegen/src/asm.rs b/codegen/src/asm.rs
index 329f66407..0103011b1 100644
--- a/codegen/src/asm.rs
+++ b/codegen/src/asm.rs
@@ -46,7 +46,7 @@ impl Assembler {
             return Ok(());
         }
 
-        tracing::debug!(
+        tracing::trace!(
             "increment stack pointer {}.add({items}) -> {}",
             self.sp,
             self.sp + items
@@ -67,7 +67,7 @@ impl Assembler {
             return Ok(());
         }
 
-        tracing::debug!(
+        tracing::trace!(
             "decrement stack pointer {}.sub({items}) -> {}",
             self.sp,
             self.sp - items
diff --git a/codegen/src/codegen/constructor.rs b/codegen/src/codegen/constructor.rs
index a2428022c..a98d368e4 100644
--- a/codegen/src/codegen/constructor.rs
+++ b/codegen/src/codegen/constructor.rs
@@ -17,6 +17,7 @@ pub struct Constructor {
 impl Constructor {
     /// preset storage for the contract
     pub fn storage(&mut self, mapping: InitStorage) -> Result<()> {
+        tracing::debug!("Building storage in constructor ...");
         for (key, value) in mapping.into_iter() {
             self.masm.push(&value)?;
             self.masm.push(&key)?;
@@ -38,6 +39,11 @@ impl Constructor {
         let runtime_bytecode_offset =
             Self::runtime_bytcode_offset(init_code_len, runtime_bytecode_size.len());
 
+        tracing::trace!("length of bytecode: {:?}", runtime_bytecode_len);
+        tracing::trace!(
+            "length of bytecode in hex: {:?}",
+            hex::encode(&runtime_bytecode_size)
+        );
         let mut masm = self.masm.clone();
 
         // 1. copy runtime bytecode to memory
diff --git a/codegen/src/codegen/dispatcher.rs b/codegen/src/codegen/dispatcher.rs
index d103d84e2..ceae36c57 100644
--- a/codegen/src/codegen/dispatcher.rs
+++ b/codegen/src/codegen/dispatcher.rs
@@ -65,7 +65,7 @@ impl Dispatcher {
         self.abi.push(abi.clone());
 
         let selector_bytes = abi.selector();
-        tracing::trace!(
+        tracing::debug!(
             "Emitting selector {:?} for function: {}",
             selector_bytes,
             abi.signature(),
diff --git a/codegen/src/local.rs b/codegen/src/local.rs
index d2a1df71d..32e0989e5 100644
--- a/codegen/src/local.rs
+++ b/codegen/src/local.rs
@@ -87,11 +87,11 @@ impl Locals {
         let offset = if local.ty() == &LocalSlotType::Parameter {
             self.inner[..index].iter().fold(0, |acc, x| acc + x.align())
         } else {
-            panic!("This should never be reached");
-            // self.inner[..index]
-            //     .iter()
-            //     .filter(|x| x.ty() == &LocalSlotType::Variable)
-            //     .fold(0, |acc, x| acc + x.align())
+            // panic!("This should never be reached");
+            self.inner[..index]
+                .iter()
+                .filter(|x| x.ty() == &LocalSlotType::Variable)
+                .fold(0, |acc, x| acc + x.align())
         }
         .to_ls_bytes()
         .to_vec()
diff --git a/codegen/src/masm/mod.rs b/codegen/src/masm/mod.rs
index 6a7cf60cd..b8a1577e8 100644
--- a/codegen/src/masm/mod.rs
+++ b/codegen/src/masm/mod.rs
@@ -86,7 +86,7 @@ impl MacroAssembler {
 
     /// Place n bytes on stack.
     pub fn push(&mut self, bytes: &[u8]) -> Result<()> {
-        tracing::trace!("push bytes: 0x{:x?}", bytes);
+        tracing::trace!("push bytes: 0x{}", hex::encode(bytes));
 
         // TODO: support PUSH0 #247
         //
diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs
index 72e94f9e2..b50b5f9e4 100644
--- a/codegen/src/visitor/call.rs
+++ b/codegen/src/visitor/call.rs
@@ -111,6 +111,7 @@ impl Function {
             HostFunc::Evm(OpCode::LOG3) => self.log(3),
             HostFunc::Evm(OpCode::LOG4) => self.log(4),
             HostFunc::Evm(op) => self.masm.emit_op(op),
+            HostFunc::U256MAX => self.masm.push(&[255; 32]),
             HostFunc::Revert(count) => self.revert(count),
             HostFunc::NoOp | HostFunc::Label(_) => Ok(()),
             _ => {
diff --git a/codegen/src/visitor/control.rs b/codegen/src/visitor/control.rs
index f8a50987d..f151f1533 100644
--- a/codegen/src/visitor/control.rs
+++ b/codegen/src/visitor/control.rs
@@ -104,7 +104,8 @@ impl Function {
     ///
     /// Performs an unconditional branch.
     pub fn _br(&mut self, _depth: u32) -> Result<()> {
-        todo!()
+        // TODO: do sth here?
+        Ok(())
     }
 
     /// Performs a conditional branch if i32 is non-zero.
diff --git a/codegen/src/visitor/log.rs b/codegen/src/visitor/log.rs
index 801c95bd1..9bbb9e69f 100644
--- a/codegen/src/visitor/log.rs
+++ b/codegen/src/visitor/log.rs
@@ -33,9 +33,11 @@ impl Function {
         }
 
         let offset_len = (data[0] - 0x5f) as usize;
+        tracing::trace!("offset len: {offset_len}");
         let offset = {
             let mut bytes = [0; 4];
-            bytes[..offset_len].copy_from_slice(&data[1..(1 + offset_len)]);
+            bytes[(4 - offset_len)..].copy_from_slice(&data[1..(offset_len + 1)]);
+            bytes.reverse();
             i32::from_le_bytes(bytes)
         };
         tracing::debug!("log offset: {:?}", offset);
@@ -45,6 +47,7 @@ impl Function {
             return Err(Error::InvalidDataOffset(data[offset_len + 1].into()));
         }
         let size = {
+            // TODO: from ls bytes as offset
             let mut bytes = [0; 4];
             let size_bytes = &data[(offset_len + 2)..];
             bytes[..size_bytes.len()].copy_from_slice(size_bytes);
diff --git a/codegen/src/wasm/abi.rs b/codegen/src/wasm/abi.rs
index aa39b7822..8b1b93802 100644
--- a/codegen/src/wasm/abi.rs
+++ b/codegen/src/wasm/abi.rs
@@ -78,9 +78,6 @@ macro_rules! offset {
                     .rev()
                     .skip_while(|b| *b == 0)
                     .collect::<Vec<_>>()
-                    .into_iter()
-                    .rev()
-                    .collect::<Vec<_>>()
                     .into()
             }
         }
@@ -131,3 +128,9 @@ impl ToLSBytes for &[ValType] {
             .to_ls_bytes()
     }
 }
+
+#[test]
+fn test_usize_to_ls_bytes() {
+    assert_eq!(363usize.to_ls_bytes().to_vec(), vec![0x01, 0x6b]);
+    assert_eq!(255usize.to_ls_bytes().to_vec(), vec![0xff]);
+}
diff --git a/codegen/src/wasm/host.rs b/codegen/src/wasm/host.rs
index 9e9ec5dab..2fce8a2d4 100644
--- a/codegen/src/wasm/host.rs
+++ b/codegen/src/wasm/host.rs
@@ -18,6 +18,8 @@ pub enum HostFunc {
     EmitABI,
     /// check equal of two addresses
     AddressEq,
+    /// Push u256 max to stack
+    U256MAX,
     /// Revert messages with length of slots
     Revert(usize),
     /// Compiler labels
@@ -66,6 +68,10 @@ impl TryFrom<(&str, &str)> for HostFunc {
             })?)),
             ("zinkc", "emit_abi") => Ok(Self::EmitABI),
             ("zinkc", "address_eq") => Ok(Self::Evm(OpCode::EQ)),
+            ("zinkc", "u256_add") => Ok(Self::Evm(OpCode::ADD)),
+            ("zinkc", "u256_sub") => Ok(Self::Evm(OpCode::SUB)),
+            ("zinkc", "u256_lt") => Ok(Self::Evm(OpCode::LT)),
+            ("zinkc", "u256_max") => Ok(Self::U256MAX),
             ("zinkc", "label_reserve_mem_32") => Ok(Self::Label(CompilerLabel::ReserveMemory32)),
             ("zinkc", "label_reserve_mem_64") => Ok(Self::Label(CompilerLabel::ReserveMemory64)),
             _ => {
diff --git a/evm/abi/src/arg.rs b/evm/abi/src/arg.rs
index 609dba96d..d7700166e 100644
--- a/evm/abi/src/arg.rs
+++ b/evm/abi/src/arg.rs
@@ -37,6 +37,10 @@ pub enum Param {
     UInt32,
     /// A 64-bit unsigned integer.
     UInt64,
+    /// A 256-bit unsigned integer.
+    UInt256,
+    // /// A 256-bit unsigned integer.
+    // UInt256,
     /// A boolean type.
     Bool,
     /// An EVM address.
@@ -60,10 +64,11 @@ impl From<&str> for Param {
             "u16" | "uint16" => Param::UInt16,
             "u32" | "uint32" => Param::UInt32,
             "u64" | "uint64" => Param::UInt64,
+            "U256" | "u256" | "uint256" => Param::UInt256,
             "bool" => Param::Bool,
             "address" | "Address" => Param::Address,
             "Bytes" | "Vec<u8>" => Param::Bytes,
-            "String" => Param::String,
+            "String" | "String32" => Param::String,
             _ => Param::Unknown(s.to_string()),
         }
     }
@@ -88,6 +93,7 @@ impl AsRef<str> for Param {
             Param::UInt16 => "uint16",
             Param::UInt32 => "uint32",
             Param::UInt64 => "uint64",
+            Param::UInt256 => "uint256",
             Param::Address => "address",
             Param::Bool => "boolean",
             Param::Bytes => "bytes",
diff --git a/examples/constfn.rs b/examples/constfn.rs
new file mode 100644
index 000000000..87c369df1
--- /dev/null
+++ b/examples/constfn.rs
@@ -0,0 +1,26 @@
+//! Storage example.
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+
+/// set value to the storage.
+#[zink::external]
+pub fn decimals() -> i32 {
+    8
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
+
+#[test]
+fn value() -> anyhow::Result<()> {
+    use zint::{Bytes32, Contract};
+
+    let mut contract = Contract::search("constfn")?.compile()?;
+
+    let decimals = 8.to_bytes32().to_vec();
+    let info = contract.execute(&[b"decimals()".to_vec(), decimals.clone()])?;
+    assert_eq!(info.ret, decimals);
+    Ok(())
+}
diff --git a/examples/erc20.rs b/examples/erc20.rs
new file mode 100644
index 000000000..81edb0b1c
--- /dev/null
+++ b/examples/erc20.rs
@@ -0,0 +1,212 @@
+//! Constructor example.
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+
+use zink::{
+    primitives::{Address, String32, U256},
+    DoubleKeyMapping, Mapping, Storage,
+};
+
+#[zink::storage(String32)]
+pub struct Name;
+
+#[zink::storage(String32)]
+pub struct Symbol;
+
+#[zink::storage(U256)]
+pub struct TotalSupply;
+
+#[zink::storage(Address, U256)]
+pub struct Balances;
+
+#[zink::storage(Address, Address, U256)]
+pub struct Allowance;
+
+/// Get value from the storage.
+#[zink::external]
+pub fn init(name: String32, symbol: String32) {
+    Name::set(name);
+    Symbol::set(symbol);
+}
+
+/// Get value from the storage.
+#[zink::external]
+pub fn decimals() -> u32 {
+    8
+}
+
+#[zink::external]
+pub fn transfer(to: Address, value: U256) -> bool {
+    let owner = unsafe { zink::ffi::evm::caller() };
+    _transfer(owner, to, value);
+    true
+}
+
+#[zink::external]
+pub fn approve(spender: Address, value: U256) -> bool {
+    // TODO: wrap this in env
+    let owner = unsafe { zink::ffi::evm::caller() };
+    _approve(owner, spender, value, false);
+    true
+}
+
+#[zink::external]
+pub fn transfer_from(from: Address, to: Address, value: U256) -> bool {
+    let spender = unsafe { zink::ffi::evm::caller() };
+    _spend_allowance(from, spender, value);
+    _transfer(from, to, value);
+    true
+}
+
+fn _transfer(from: Address, to: Address, value: U256) {
+    if from.eq(Address::empty()) {
+        zink::revert!("Empty from address");
+    }
+
+    if to.eq(Address::empty()) {
+        zink::revert!("Empty to address");
+    }
+
+    _update(from, to, value)
+}
+
+fn _update(from: Address, to: Address, value: U256) {
+    if from.eq(Address::empty()) {
+        TotalSupply::set(TotalSupply::get().add(value));
+    } else {
+        let from_balance = Balances::get(from);
+        if from_balance.lt(value) {
+            zink::revert!("Insufficient balance");
+        }
+
+        Balances::set(from, from_balance.sub(value));
+    }
+
+    if to.eq(Address::empty()) {
+        TotalSupply::set(TotalSupply::get().sub(value));
+    } else {
+        TotalSupply::set(TotalSupply::get().add(value));
+    }
+}
+
+fn _mint(account: Address, value: U256) {
+    if account.eq(Address::empty()) {
+        zink::revert!("ERC20 invalid receiver");
+    }
+
+    _update(Address::empty(), account, value)
+}
+
+fn _burn(account: Address, value: U256) {
+    if account.eq(Address::empty()) {
+        zink::revert!("ERC20 invalid sender");
+    }
+
+    _update(account, Address::empty(), value)
+}
+
+fn _approve(owner: Address, spender: Address, value: U256, _emit_event: bool) {
+    if owner.eq(Address::empty()) {
+        zink::revert!("ERC20 Invalid approval");
+    }
+
+    if spender.eq(Address::empty()) {
+        zink::revert!("ERC20 Invalid spender");
+    }
+
+    Allowance::set(owner, spender, value);
+}
+
+fn _spend_allowance(owner: Address, spender: Address, value: U256) {
+    let current_allowance = Allowance::get(owner, spender);
+    if current_allowance.lt(U256::max()) {
+        if current_allowance.lt(value) {
+            zink::revert!("ERC20 Insufficient allowance");
+        }
+
+        _approve(owner, spender, current_allowance.sub(value), false)
+    }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
+
+// TODO:
+//
+// 1. nested function allocations
+// 2. memory slots for local variables
+#[ignore]
+#[test]
+fn deploy() -> anyhow::Result<()> {
+    use zint::{Bytes32, Contract, EVM};
+
+    let mut contract = Contract::search("erc20")?.compile()?;
+
+    let mut evm = EVM::default();
+    let name = "The Zink Language";
+    let symbol = "zink";
+
+    // 1. deploy
+    let info = evm.deploy(
+        &contract
+            .construct(
+                [
+                    (Name::STORAGE_KEY.to_bytes32().into(), name.to_vec().into()),
+                    (
+                        Symbol::STORAGE_KEY.to_bytes32().into(),
+                        symbol.to_vec().into(),
+                    ),
+                    (
+                        TotalSupply::STORAGE_KEY.to_bytes32().into(),
+                        vec![42].try_into()?,
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+            )?
+            .bytecode()?,
+    )?;
+    let address = info.address;
+
+    // 2. get name
+    let info = evm
+        .calldata(&contract.encode(&[b"name()".to_vec()])?)
+        .call(address)?;
+    assert_eq!(info.ret, name.to_bytes32());
+
+    // 3. get symbol
+    let info = evm
+        .calldata(&contract.encode(&[b"symbol()".to_vec()])?)
+        .call(address)?;
+    assert_eq!(info.ret, symbol.to_bytes32());
+
+    // 3. get total supply
+    let info = evm
+        .calldata(&contract.encode(&[b"total_supply()".to_vec()])?)
+        .call(address)?;
+    assert_eq!(info.ret, 42u64.to_bytes32());
+
+    // 4. check decimals
+    let info = evm
+        .calldata(&contract.encode(&[b"decimals()".to_vec()])?)
+        .call(address)?;
+    assert_eq!(info.ret, 8u64.to_bytes32());
+
+    // 5. check approve
+    let info = evm
+        .calldata(&contract.encode(&[
+            b"approve(address,uint256)".to_vec(),
+            [1; 20].to_vec(),
+            42.to_bytes32().to_vec(),
+        ])?)
+        .call(address)?;
+    println!("{info:?}");
+    println!("{:?}", String::from_utf8_lossy(&info.ret));
+    let storage_key = Allowance::storage_key(Address::empty(), Address([1; 20]));
+    let storage = evm.storage(contract.address, storage_key)?;
+    println!("{storage:?}");
+
+    Ok(())
+}
diff --git a/examples/storage.rs b/examples/storage.rs
index eb5974266..131129340 100644
--- a/examples/storage.rs
+++ b/examples/storage.rs
@@ -24,9 +24,9 @@ fn value() -> anyhow::Result<()> {
     use zint::{Bytes32, Contract, U256};
 
     let mut contract = Contract::search("storage")?.compile()?;
+    let value: i32 = 42;
 
     {
-        let value: i32 = 42;
         let info = contract.execute(&[b"set(int32)".to_vec(), value.to_bytes32().to_vec()])?;
         assert!(info.ret.is_empty());
         assert_eq!(
diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs
index 9974113bf..38b8e877d 100644
--- a/zink/codegen/src/lib.rs
+++ b/zink/codegen/src/lib.rs
@@ -10,6 +10,7 @@ mod event;
 mod revert;
 mod selector;
 mod storage;
+mod utils;
 
 /// Revert with the input message
 ///
diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs
index aa2723612..58b33a1ac 100644
--- a/zink/codegen/src/storage.rs
+++ b/zink/codegen/src/storage.rs
@@ -1,3 +1,4 @@
+use crate::utils::Bytes32;
 use heck::AsSnakeCase;
 use proc_macro::TokenStream;
 use proc_macro2::{Literal, Span, TokenTree};
@@ -45,8 +46,7 @@ impl Storage {
         let is = &self.target;
         let name = self.target.ident.clone();
         let slot = storage_slot(name.to_string());
-        let mut key = [0; 32];
-        key[28..].copy_from_slice(&slot.to_le_bytes());
+        let key = slot.to_bytes32();
 
         let keyl = Literal::byte_string(&key);
         let mut expanded = quote! {
@@ -96,7 +96,7 @@ impl Storage {
 
                     let mut seed = [0; 64];
                     seed[..32].copy_from_slice(&key.bytes32());
-                    seed[60..].copy_from_slice(&Self::STORAGE_SLOT.to_le_bytes());
+                    seed[32..].copy_from_slice(&Self::STORAGE_SLOT.bytes32());
                     zink::keccak256(&seed)
                 }
             }
@@ -138,7 +138,7 @@ impl Storage {
 
                     let mut seed = [0; 64];
                     seed[..32].copy_from_slice(&key1.bytes32());
-                    seed[60..].copy_from_slice(&Self::STORAGE_SLOT.to_le_bytes());
+                    seed[32..].copy_from_slice(&Self::STORAGE_SLOT.bytes32());
                     let skey1 = zink::keccak256(&seed);
                     seed[..32].copy_from_slice(&skey1);
                     seed[32..].copy_from_slice(&key2.bytes32());
diff --git a/zink/codegen/src/utils.rs b/zink/codegen/src/utils.rs
new file mode 100644
index 000000000..7ab578804
--- /dev/null
+++ b/zink/codegen/src/utils.rs
@@ -0,0 +1,121 @@
+//! Utils for bytes conversion.
+//!
+//! TODO: move this util to other library
+
+/// Trait for converting type to bytes32.
+pub trait Bytes32: Sized {
+    /// Convert type to the lowest significant bytes 32.
+    fn to_bytes32(&self) -> [u8; 32];
+
+    /// Convert type to vec of bytes.
+    fn to_vec(&self) -> Vec<u8> {
+        self.to_bytes32().to_vec()
+    }
+}
+
+/// Implement Bytes32 for types.
+macro_rules! impl_bytes32 {
+    ($($ty:ident),+) => {
+        $(
+            impl Bytes32 for $ty {
+                fn to_bytes32(&self) -> [u8; 32] {
+                    let mut bytes = [0u8; 32];
+                    let ls_bytes = {
+                        self.to_le_bytes()
+                            .into_iter()
+                            .rev()
+                            .skip_while(|b| *b == 0)
+                            .collect::<Vec<_>>()
+                            .into_iter()
+                            .rev()
+                            .collect::<Vec<_>>()
+                    };
+
+                    bytes[(32 - ls_bytes.len())..].copy_from_slice(&ls_bytes);
+                    bytes
+                }
+
+                fn to_vec(&self) -> Vec<u8> {
+                    self.to_le_bytes().to_vec()
+                }
+            }
+        )+
+    };
+}
+
+impl Bytes32 for Vec<u8> {
+    fn to_bytes32(&self) -> [u8; 32] {
+        let mut bytes = [0u8; 32];
+        bytes[(32 - self.len())..].copy_from_slice(self);
+        bytes
+    }
+
+    fn to_vec(&self) -> Vec<u8> {
+        self.clone()
+    }
+}
+
+impl Bytes32 for [u8; 20] {
+    fn to_bytes32(&self) -> [u8; 32] {
+        let mut bytes = [0u8; 32];
+        bytes[12..].copy_from_slice(self);
+        bytes
+    }
+}
+
+impl Bytes32 for [u8; 32] {
+    fn to_bytes32(&self) -> [u8; 32] {
+        *self
+    }
+
+    fn to_vec(&self) -> Vec<u8> {
+        self.as_ref().into()
+    }
+}
+
+impl Bytes32 for &[u8] {
+    fn to_bytes32(&self) -> [u8; 32] {
+        let mut bytes = [0u8; 32];
+        bytes[(32 - self.len())..].copy_from_slice(self);
+        bytes
+    }
+
+    fn to_vec(&self) -> Vec<u8> {
+        (*self).into()
+    }
+}
+
+impl Bytes32 for () {
+    fn to_bytes32(&self) -> [u8; 32] {
+        [0; 32]
+    }
+
+    fn to_vec(&self) -> Vec<u8> {
+        Default::default()
+    }
+}
+
+impl Bytes32 for &str {
+    fn to_bytes32(&self) -> [u8; 32] {
+        let mut bytes = [0u8; 32];
+        bytes[(32 - self.len())..].copy_from_slice(self.as_bytes());
+        bytes
+    }
+
+    fn to_vec(&self) -> Vec<u8> {
+        self.as_bytes().into()
+    }
+}
+
+impl Bytes32 for bool {
+    fn to_bytes32(&self) -> [u8; 32] {
+        let mut output = [0; 32];
+        if *self {
+            output[31] = 1;
+        }
+
+        output
+    }
+}
+
+impl_bytes32!(i8, u8, i16, u16, i32, u32, usize, i64, u64, i128, u128);
diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs
index fc8930e84..b308531a0 100644
--- a/zink/src/ffi/asm.rs
+++ b/zink/src/ffi/asm.rs
@@ -1,6 +1,6 @@
 //! Assembly FFI.
 
-use crate::primitives::Address;
+use crate::primitives::{Address, U256};
 
 #[link(wasm_import_module = "asm")]
 #[allow(improper_ctypes)]
@@ -32,6 +32,9 @@ extern "C" {
     /// Push address to stack
     pub fn push_address(address: Address);
 
+    /// Push u256 to stack
+    pub fn push_u256(u256: U256);
+
     /// Revert with message in 32 bytes
     pub fn revert1(message: &'static str);
 
@@ -70,4 +73,7 @@ extern "C" {
 
     /// Load address from storage
     pub fn sload_address() -> Address;
+
+    /// Load address from storage
+    pub fn sload_u256() -> U256;
 }
diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs
index 398ec523c..c832282b0 100644
--- a/zink/src/ffi/evm.rs
+++ b/zink/src/ffi/evm.rs
@@ -1,5 +1,7 @@
 //! EVM FFI.
 
+use crate::primitives::Address;
+
 #[link(wasm_import_module = "evm")]
 #[allow(improper_ctypes)]
 extern "C" {
@@ -100,7 +102,7 @@ extern "C" {
     pub fn push31(val: i32);
 
     /// Push 32 bytes to the stack.
-    pub fn push32(val: i32);
+    pub fn push32();
 
     /// Store a value in the storage
     pub fn sstore();
@@ -120,6 +122,9 @@ extern "C" {
     /// Compute Keccak-256 hash
     pub fn keccak256();
 
+    /// Get the current message sender
+    pub fn caller() -> Address;
+
     /// Append log record with no topics
     pub fn log0(name: &'static [u8]);
 
diff --git a/zink/src/ffi/mod.rs b/zink/src/ffi/mod.rs
index 2f41799fe..2b46443c5 100644
--- a/zink/src/ffi/mod.rs
+++ b/zink/src/ffi/mod.rs
@@ -1,6 +1,6 @@
 //! Zink FFI.
 
-use crate::primitives::Address;
+use crate::primitives::{Address, U256};
 
 pub mod asm;
 pub mod evm;
@@ -14,6 +14,18 @@ extern "C" {
     /// Equal operation for addresses
     pub fn address_eq(this: Address, other: Address) -> bool;
 
+    /// Equal operation for addresses
+    pub fn u256_add(this: U256, other: U256) -> U256;
+
+    /// Equal operation for addresses
+    pub fn u256_sub(this: U256, other: U256) -> U256;
+
+    /// Equal operation for addresses
+    pub fn u256_lt(this: U256, other: U256) -> bool;
+
+    /// Equal operation for addresses
+    pub fn u256_max() -> U256;
+
     /// Set up a label for reserving 32 bytes in memory
     pub fn label_reserve_mem_32();
 
diff --git a/zink/src/primitives/address.rs b/zink/src/primitives/address.rs
index be979b915..92ffb2649 100644
--- a/zink/src/primitives/address.rs
+++ b/zink/src/primitives/address.rs
@@ -5,10 +5,22 @@ use crate::{ffi, storage::StorageValue, Asm};
 #[derive(Clone, Copy)]
 pub struct Address(
     #[cfg(target_family = "wasm")] i32,
-    #[cfg(not(target_family = "wasm"))] [u8; 20],
+    #[cfg(not(target_family = "wasm"))] pub [u8; 20],
 );
 
 impl Address {
+    /// Returns empty address
+    #[cfg(not(target_family = "wasm"))]
+    pub const fn empty() -> Self {
+        Address([0; 20])
+    }
+
+    /// Returns empty address
+    #[cfg(target_family = "wasm")]
+    pub const fn empty() -> Self {
+        Address(0)
+    }
+
     /// if self equal to another
     ///
     /// NOTE: not using core::cmp because it uses registers in wasm
diff --git a/zink/src/primitives/mod.rs b/zink/src/primitives/mod.rs
index d0f41fb0b..67b4af318 100644
--- a/zink/src/primitives/mod.rs
+++ b/zink/src/primitives/mod.rs
@@ -1,5 +1,11 @@
 //! Zink primitive types
 
 mod address;
+mod u256;
 
 pub use address::Address;
+pub use u256::U256;
+
+pub type Bytes20 = Address;
+pub type Bytes32 = U256;
+pub type String32 = U256;
diff --git a/zink/src/primitives/u256.rs b/zink/src/primitives/u256.rs
new file mode 100644
index 000000000..65f42bb3f
--- /dev/null
+++ b/zink/src/primitives/u256.rs
@@ -0,0 +1,55 @@
+#![allow(clippy::should_implement_trait)]
+use crate::{ffi, storage::StorageValue, Asm};
+
+/// Account address
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct U256(
+    #[cfg(target_family = "wasm")] i32,
+    #[cfg(not(target_family = "wasm"))] [u8; 32],
+);
+
+impl U256 {
+    /// Returns empty address
+    #[cfg(not(target_family = "wasm"))]
+    pub const fn empty() -> Self {
+        U256([0; 32])
+    }
+
+    /// add another value
+    pub fn add(self, other: Self) -> Self {
+        unsafe { ffi::u256_add(self, other) }
+    }
+
+    /// add another value
+    pub fn lt(self, other: Self) -> bool {
+        unsafe { ffi::u256_lt(self, other) }
+    }
+
+    /// add another value
+    pub fn sub(self, other: Self) -> Self {
+        unsafe { ffi::u256_sub(self, other) }
+    }
+
+    /// max of u256
+    pub fn max() -> Self {
+        unsafe { ffi::u256_max() }
+    }
+}
+
+impl Asm for U256 {
+    fn push(self) {
+        unsafe { ffi::asm::push_u256(self) }
+    }
+
+    #[cfg(not(target_family = "wasm"))]
+    fn bytes32(&self) -> [u8; 32] {
+        self.0
+    }
+}
+
+impl StorageValue for U256 {
+    fn sload() -> Self {
+        unsafe { ffi::asm::sload_u256() }
+    }
+}
diff --git a/zink/src/storage/mod.rs b/zink/src/storage/mod.rs
index 8eaef9326..d7ecc018e 100644
--- a/zink/src/storage/mod.rs
+++ b/zink/src/storage/mod.rs
@@ -18,3 +18,9 @@ impl StorageValue for i32 {
         unsafe { ffi::asm::sload_i32() }
     }
 }
+
+impl StorageValue for u32 {
+    fn sload() -> Self {
+        unsafe { ffi::asm::sload_u32() }
+    }
+}
diff --git a/zint/src/contract.rs b/zint/src/contract.rs
index b53be3d03..e7eb6498e 100644
--- a/zint/src/contract.rs
+++ b/zint/src/contract.rs
@@ -43,6 +43,7 @@ impl Contract {
             .finish(self.artifact.runtime_bytecode.clone().into())
             .map(|v| v.to_vec())?;
 
+        tracing::debug!("runtime bytecode: {}", hex::encode(&bytecode));
         Ok(bytecode)
     }