Smart Contract Errors
Common errors when building, deploying, or calling Algorand smart contracts.
Table of Contents
Logic Eval Errors
Assert Failed
logic eval error: assert failed pc=123
Cause: An assert statement evaluated to false.
Debug with source maps (AlgoKit Utils):
// TypeScript - errors include source location automatically
try {
await appClient.send.myMethod({ args: { value: 0 } })
} catch (e) {
// Error includes: "assert failed at contracts/my_contract.py:45"
console.error(e)
}
# Python - LogicError includes source map info
from algokit_utils import LogicError
try:
app_client.send.my_method(value=0)
except LogicError as e:
print(e) # Shows: assert failed at contracts/my_contract.py:45
print(e.pc) # Program counter: 123
print(e.line) # Source line number
Common causes:
- Input validation failed (e.g.,
assert amount > 0) - Authorization check failed (e.g.,
assert Txn.sender == self.owner) - State precondition not met (e.g.,
assert self.is_initialized)
Fix: Check the assertion condition and ensure inputs satisfy it.
Opcode Budget Exceeded
logic eval error: dynamic cost budget exceeded
Cause: Contract exceeded the 700 opcode budget per app call.
Budget limits:
| Context | Budget |
|---|---|
| Single app call | 700 opcodes |
| Max pooled (16 app calls) | 11,200 opcodes |
| Logic signature | 20,000 opcodes |
Solutions:
- Pool budget with extra app calls:
# Add dummy app calls to increase budget
algorand.new_group()
.add_app_call_method_call(actual_call_params)
.add_app_call(AppCallParams(
sender=sender,
app_id=app_id,
args=[b"noop"] # Dummy call for budget
))
.send()
- Optimize expensive operations:
# EXPENSIVE - iteration over large data
for i in range(100):
process(data[i])
# CHEAPER - batch operations or use Box storage
box_data = Box(Bytes, key=b"data")
- Split across multiple calls:
# Instead of one large operation, split into phases
@arc4.abimethod
def process_phase1(self) -> None: ...
@arc4.abimethod
def process_phase2(self) -> None: ...
Invalid Program
logic eval error: invalid program
Cause: The TEAL program is malformed or uses unsupported opcodes.
Common causes:
- Compiling for wrong AVM version
- Using opcodes not supported on target network
- Corrupted approval/clear program bytes
Fix: Ensure compilation targets the correct AVM version.
Stack Underflow
logic eval error: stack underflow
Cause: Operation tried to pop from empty stack.
In Algorand Python: This usually indicates a bug in low-level operations. Check any op.* calls.
Byte/Int Type Mismatch
logic eval error: assert failed: wanted type uint64 but got []byte
Cause: Wrong type passed to an operation.
Common in Algorand Python:
# INCORRECT - String where UInt64 expected
assert payment.amount == "1000000"
# CORRECT - Use proper types
assert payment.amount == UInt64(1_000_000)
ABI Errors
Method Not Found
error: method "foo(uint64)void" not found
Cause: Calling a method that doesn't exist in the contract ABI.
Fix:
- Regenerate the typed client after contract changes
- Check the method signature matches exactly
- Verify the contract was deployed with the latest code
ABI Encoding Error
ABIEncodingError: value out of range for uint64
Cause: Value doesn't fit the ABI type.
Examples:
# INCORRECT - Negative value for uint64
arc4.UInt64(-1)
# INCORRECT - Value too large
arc4.UInt8(256) # Max is 255
# CORRECT - Use appropriate type
arc4.UInt64(0)
arc4.UInt16(256)
Return Value Decoding Error
error: could not decode return value
Cause: Method returned unexpected data format.
Common causes:
- Contract didn't log the return value properly
- Wrong return type in client
- Transaction failed before return
Fix: Check contract method has correct return annotation.
State Errors
Global State Full
logic eval error: store global state: failed
Cause: Exceeded declared global state schema.
Fix: Increase schema in contract deployment:
class MyContract(ARC4Contract):
# Declare more state slots in schema
@arc4.abimethod(create=True)
def create(self) -> None:
pass
Local State Not Opted In
logic eval error: application APPID not opted in
Cause: Account hasn't opted into the application.
Fix: Opt in before accessing local state:
algorand.send.app_call(AppCallParams(
sender=user_address,
app_id=app_id,
))
Box Not Found
logic eval error: box not found
Cause: Accessing a box that doesn't exist.
Fix: Create box before access or check existence:
# In contract - check if box exists
if self.my_box.exists:
value = self.my_box.value
else:
self.my_box.value = default_value
Box MBR Not Met
logic eval error: box create with insufficient funds
Cause: App account lacks funds for box minimum balance requirement.
MBR formula: 2500 + (400 * (key_length + value_length)) microAlgos per box
Fix: Fund the app account:
algorand.send.payment(PaymentParams(
sender=funder.address,
receiver=app_client.app_address,
amount=AlgoAmount(algo=1), # Cover box MBR
))
Inner Transaction Errors
Insufficient Balance for Inner Txn
logic eval error: insufficient balance
Cause: App account lacks funds for inner transaction amount.
Fix: Fund the app account before inner transactions:
# Fund app before calling method with inner transactions
algorand.send.payment(PaymentParams(
sender=deployer.address,
receiver=app_client.app_address,
amount=AlgoAmount(algo=5),
))
Inner Transaction Limit
logic eval error: too many inner transactions
Cause: Exceeded 256 inner transactions per group.
Fix: Split operations across multiple outer transactions.
App Not Opted Into Asset
logic eval error: asset ASSET_ID not opted in
Cause: Contract account isn't opted into the asset.
Fix: Add opt-in method to contract:
@arc4.abimethod
def opt_in_to_asset(self, asset: Asset) -> None:
itxn.AssetTransfer(
xfer_asset=asset,
asset_receiver=Global.current_application_address,
asset_amount=0,
fee=0
).submit()
Debugging Tips
Enable Debug Logging
// TypeScript
import { Config } from '@algorandfoundation/algokit-utils'
Config.configure({ debug: true })
# Python
import logging
logging.getLogger("algokit").setLevel(logging.DEBUG)
Get Transaction Trace
// Simulate to get execution trace
const result = await algorand.newGroup()
.addAppCallMethodCall(params)
.simulate({ execTraceConfig: { enable: true } })
console.log(result.simulateResponse.txnGroups[0].txnResults[0].execTrace)
Check Program Counter Location
When you see pc=123, use algokit to find the source:
algokit compile contracts/my_contract.py --output-sourcemap
Then map the PC to source using the generated .map file.