{"id":31285710,"url":"https://github.com/elser-lang/elser","last_synced_at":"2025-09-24T08:13:29.620Z","repository":{"id":300199857,"uuid":"1003684209","full_name":"elser-lang/elser","owner":"elser-lang","description":"Smart-contract oriented language with emphasis on explicitness for critical and mutative operations and enforcement of a structured approach to smart-contract building.","archived":false,"fork":false,"pushed_at":"2025-09-22T12:54:41.000Z","size":215,"stargazers_count":4,"open_issues_count":30,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-22T14:34:10.955Z","etag":null,"topics":["blockchain","clojure","dsl","ethereum","evm","language","lisp","programming-language","smart-contracts","solidity","vyper"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elser-lang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-17T14:08:04.000Z","updated_at":"2025-08-19T10:56:37.000Z","dependencies_parsed_at":"2025-06-20T11:45:05.989Z","dependency_job_id":null,"html_url":"https://github.com/elser-lang/elser","commit_stats":null,"previous_names":["elser-lang/elser"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/elser-lang/elser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elser-lang%2Felser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elser-lang%2Felser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elser-lang%2Felser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elser-lang%2Felser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elser-lang","download_url":"https://codeload.github.com/elser-lang/elser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elser-lang%2Felser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276714454,"owners_count":25691398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-24T02:00:09.776Z","response_time":97,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blockchain","clojure","dsl","ethereum","evm","language","lisp","programming-language","smart-contracts","solidity","vyper"],"created_at":"2025-09-24T08:13:28.495Z","updated_at":"2025-09-24T08:13:29.608Z","avatar_url":"https://github.com/elser-lang.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\nElser is a domain-specific language (DSL) for smart-contract development. It emphasizes explicitness for critical and mutative operations and enforces a structured approach to smart-contract building. It natively compiles to Yul and uses `solc` for optimization and final EVM bytecode generation.\n\n**ELSER** stands for ***E**xplicit **L**isp-like **S**mart-contract language with **E**nforced structure and **R**estrictness*.\n\n\u003e **NOTE:** Elser is in alpha and not yet suitable for production use.\n\n# Table of Contents\n- [Quickstart](#quickstart)\n- [Background](#background-and-motivation)\n- [Program Layout](#program-layout)\n  - [Storage Variables](#storage-variables)\n  - [Functions](#functions)\n  - [Constants](#constants)\n  - [Events](#events)\n  - [Types](#types)\n- [REPL](#repl)\n- [Tutorial](#tutorial)\n\n## Quickstart\n### Prerequisites\n- [Java 8+](https://www.java.com/en/download/manual.jsp) (required for Clojure)\n- [Clojure](https://clojure.org/guides/install_clojure)\n- [Leiningen](https://leiningen.org/#install)\n- [Solidity 0.8.18+](https://docs.soliditylang.org/en/latest/installing-solidity.html) (required to compile Yul to EVM bytecode)\n\n### Getting Started\n```sh\n## Clone the repo.\ngit clone https://github.com/elser-lang/elser\n\n## Build the JAR\nlein uberjar\n\n## 1. Launch (elser -\u003e Yul) REPL\njava -jar target/uberjar/elser-0.0.4-alpha-standalone.jar\n## 2. Check available commands.\njava -jar target/uberjar/elser-0.0.4-alpha-standalone.jar --help\n## 3. Compile elser program.\njava -jar target/uberjar/elser-0.0.4-alpha-standalone.jar -c examples/counter.els\n## 4. Generated bytecode \u0026 Yul will be saved in '/out' directory\nls out/\n```\nSyntax highlighting can be enabled in Emacs using [**`elser-mode`**](https://github.com/elser-lang/elser-mode).\n\n## Background and Motivation\nElser is a statically‑typed DSL designed around these core principles for safe, predictable smart‑contract development:\n\n### Restrictive \u0026 Uniform Syntax\nContracts must be unambiguous. Elser enforces program structure at the language-level and disallows implicit behavior.\n\n### Explicit Mutations \u0026 Control Flow\nEvery state‑modifying operation (e.g. storage reads/writes) and flow control construct must be spelled out in the source.\n\n### Explicit Permissions for Storage Access\nEvery function is required to contain a storage-permissions attribute that specifies allowed storage operations.\n\n## Program Layout\nElser programs require **fixed program layout**:\n- Namespace definition inside `(ns )` block (aka contract's name).\n- All storage variables are grouped inside `(storage ())` block.\n- All functions are grouped inside `(functions ())` block.\n- All constants are grouped inside `(constants ())` block.\n- All events are grouped inside `(events ())` block.\n\nThis structure makes it trivial to navigate code and integrate IDE features.\n```clj\n(ns \u003cyour-ns\u003e (:pragma \"0.8.30\")) ;; Solc version\n\n(constructor ())\n\n(events (\n  (def Event_0 ((arg_0 :type) … (arg_n :type)))\n))\n\n(constants\n  (:external ( (def NAME_0 (-\u003e (:type)) value) …)\n   :internal ( (def NAME_1 (-\u003e (:type)) value) …]))\n\n(storage\n  (:external ( (def var_0 (-\u003e (:type))) …)\n   :internal ( (def var_1 (-\u003e (:type))) …)))\n\n(functions\n  (:external\n    ( (defn x ((arg_0 [mut] :type) …) (@sto :w i :r j) (-\u003e ((ret_0 [mut] :type))) (body …))\n     … )\n   :internal\n    ( (defn y ((arg_1 [mut] :type) …) (@sto :w i :r j) (-\u003e ((ret_1 [mut] :type))) (body …))\n     … )\n))\n```\n- **`:external`** definitions become part of ABI.\n- **`:internal`** definitions are only visible/invokable within the namespace.\n\n### Storage Variables\nEvery storage variable is put either in `:external` variables list, or in `:internal` variables list.\n\nBy default, all storage variables are *mutable* (it might be changed in the future: https://github.com/elser-lang/elser/issues/20).\n\nDefinition syntax looks as follows:\n```clj\n(def owner (-\u003e (:addr)))\n(def balanceOf (-\u003e (map :addr :u256)))\n(def allowance (-\u003e (map :addr (map :addr :u256))))\n```\nIn Solidity, these variables would have been defined like this:\n```solidity\naddress owner;\nmapping(address =\u003e uin256) balanceOf;\nmapping(address =\u003e mapping(address =\u003e uint256)) allowance;\n```\n\n-------\n### Functions\nEvery function is put either in `:external` functions list, or in `:internal` functions list.\n\nDefinition syntax looks as follows:\n```clj\n(defn transfer ((to :addr) (amt :u256)) (@sto :w 1 :r 1) (-\u003e ((ok mut :bool))) (body))\n(defn approve ((spender :addr) (amt :u256)) (@sto :w 1 :r 1) (-\u003e ((ok mut :bool))) (body))\n```\nIn Solidity these functions would have been defined like this:\n```solidity\nfunction transfer(address to, uint256 amt) external returns (bool) {}\nfunction approve(address spender, uint256 amt) external returns (bool) {}\n```\n\n#### Function Parameters\nEvery function's parameter is immutable by default (e.g., `to` and `amt` can't be mutated), to mutate it inside the function, it's needed to add a special `mut` attribute `(amount mut :u256)`\n\nFor instance, if `transfer` function applies fee to the amount and overwrites it, we need to mark `amt` as mutable\n```clj\n(defn transfer ((to :addr) (amt mut :u256)) (@sto :w 1 :r 1) (-\u003e ((ok mut :bool)))\n  (...)\n  (set! amt (invoke! feeOnTransfer amt))\n  (...))\n```\n\n#### Function Permissions\nStorage-access attribute `(@sto :w 0 :r 0)` can't be omitted, and should always explicitly specify allowed operations for the function:\n- `(:w i)` - permission to write to storage.\n- `(:r j)` - permission to read from storage.\n\nWhere $i,j \\in$ {0,1,2,3}\n\nFunction $x$ can invoke function $y$\n$$\\iff x.w \\geq y.w \\land x.r \\geq y.r$$\n\nIn other words, $x$ must have at least as much write and read‑privilege as $y$.\n\n**Example of Multi-Level Permissions**\n\nIt can be useful to limit access to certain critical functions withtin a namespace. For example, we can have a `pausableWallet` namespace that will implement ERC20-storing wallet and will have pausable functionality.\n\nWe create two tiers of functionality:\n\n**1. Core operations (w ∈ {0,1}, r ∈ {0,1})**\n```clj\n(defn withdraw ((token :addr) (amt :u256)) (@sto :w 1 :r 1) (-\u003e ()) ...)\n(defn getBalance ((token :addr)) (@sto :w 0 :r 1) (-\u003e ((b mut :u256))) ...)\n```\n\n**2. Critical operations (w = 2, r = 1)**\n\nPushed to higher level of permissions:\n```clj\n(defn emergencyWithdraw ((token :addr)) @sto{:w 2 :r 1} (-\u003e ()) ...)\n(defn pause () (@sto :w 2 :r 1) (-\u003e ()) ...)\n```\nAttempting to call a level‑2 function from a level‑0 or level‑1 function results in a compile‑time error:\n```clj\n;; \u003e elser: invalid permissions: fn emergencyWithdraw | have {:r 1, :w 1} | want {:r 1, :w 2}\n(defn withdraw ((token :addr)) (@sto :w 1 :r 1) (-\u003e ())\n  (invoke! emergencyWithdraw token))\n```\n\nThe multi-level privilege relations can be represented via such diagram:\n```mermaid\nflowchart TD\n    A([\"emergencyWithdraw\"]) \u003c--\u003e B([\"pause\"])\n    A --\u003e n1([\"withdraw\"])\n    B --\u003e n1\n    n1 --x B \u0026 A\n    style A fill:#C8E6C9\n    style B fill:#C8E6C9\n    style n1 fill:#BBDEFB\n```\n\n#### Function Returns\nReturn parameters are also immutable by default, and should be explicitly marked as `mut` to map values to them. All functions should include return syntax, even if they don't return anything:\n\nE.g., `transfer` returns `:bool` on success.\n```clj\n(defn transfer ((to :addr) (amt :u256)) (@sto :w 1 :r 1) (-\u003e ((ok mut :bool)))\n  (...)\n  (-\u003e ok true)) ;; map TRUE to `ok`\n```\nWhile `withdraw` function from `WETH` contract doesn't return anything, but still requires to specify \"void\" return.\n```clj\n(defn withdraw ((wad :u256)) (@sto :w 1 :r 1) (-\u003e ()))\n```\n\n#### Function Body\nFunctions will contain execution logic - they can interact with other blocks of a namespace `[storage | constants | events]` and invoke function calls. \n\nFunction calls should be invoked via `(invoke! functionName args)` function, therefore every function call inside Elser program can be tracked via `invoke!` keywords.\n\nStorage can be accessed via `(sto read! var) | (sto write! var args)` function. Therefore, every storage interaction is easily trackable as well.\n```clj\n(functions\n (:external\n  (\n    ;; This function requires read access to storage, since it invokes `_checkOwner`\n    ;; that read from storage.\n    ;; And it requires write access, since invoked `_transferOwnership` will\n    ;; write to `owner` storage variable.\n   (defn transferOwnership ((newOwner :addr)) (@sto :w 1 :r 1) (-\u003e ())\n     (invoke! _checkOwner)\n     (assert (!= newOwner ADDRESS_ZERO))\n     (invoke! _transferOwnership newOwner))\n  )\n  :internal\n  (\n   ;; Throws if the sender is not the owner.\n   (defn _checkOwner () (@sto :w 0 :r 1) (-\u003e ())\n     (assert (= (caller) (sto read! owner))))\n\n   (defn _transferOwnership ((newOwner :addr)) (@sto :w 1 :r 1) (-\u003e ())\n     (let (oldOwner (sto read! owner))\n       (sto write! owner newOwner)\n       (emit! OwnershipTransferred oldOwner newOwner)))\n   )))\n```\n\n### Constants\nEvery constant is put either in `:external` constants list, or in `:internal` constants list.\n\nConstants are defined just like storage variables, but they also require to have a value assigned to them (FYI, types aren't checked there yet: https://github.com/elser-lang/elser/issues/9).\n```clj\n(def SUPPLY (-\u003e (:u256)) 10000000000000000000000000000)\n```\n\nTo access constant inside a function it's needed to refer it by its name:\n```clj\n(def setSupply () (@sto :w 1 :r 0) (-\u003e ())\n  (sto write! totalSupply SUPPLY))\n```\n\n### Events\nEvents are stored in one set with all events, and defined as:\n```clj\n(def OwnershipTransferred ((prevOwner :addr) (newOwner :addr)))\n```\nCurrently in generated `Yul` code events are emitted as `LOG0`, thus there are no `indexed` parameters (https://github.com/elser-lang/elser/issues/7).\n\n### Types\nThere are only 256-bits types to restrict variables packing, and prevent casting stuff back-and-forth during code generation.\n\nThe list of currently supported types:\n```clj\n:u256\n:bool\n:addr\n:b32\n(map ...)\n\n;; TODO:\n:i256\n(list [size] ...)\n(struct :type_0 ... :type_n)\n```\n\n## REPL\nRunning Elser without arguments will start REPL that provides `read -\u003e compile (to Yul) -\u003e print` pipeline.\n\n![repl](https://github.com/user-attachments/assets/c4dd87e3-822b-4626-b0c5-3f174ba0c142)\n\n\n## Tutorial\nLet's create basic `counter` contract.\n[An example Solidity program will look like this](https://solidity-by-example.org/first-app/):\n```solidity\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract Counter {\n    uint256 public count;\n\n    // Function to get the current count\n    function get() public view returns (uint256) {\n        return count;\n    }\n\n    // Function to increment count by 1\n    function inc() public {\n        count += 1;\n    }\n\n    // Function to decrement count by 1\n    function dec() public {\n        // This function will fail if count = 0\n        count -= 1;\n    }\n}\n```\n\n### 1. Creating Elser program\nFirstly, we'll create a `.els` file that will store all the code:\n```sh\ntouch counter.els\n```\n\n### 2. Defining Namespace and Storage\nNow, we'll define a namespace and will place storage variables inside `storage` block:\n```clj\n(ns counter (:pragma \"0.8.26\"))\n\n(storage\n  (:external\n    ( (def count (-\u003e (:u256))) ))) ; = uint256 public count;\n```\n\n### 3. Defining Functions\nNext, we'll store the main logic in functions:\n```clj\n(ns counter (:pragma \"0.8.30\"))\n\n(storage\n  (:external\n    ( (def count (-\u003e (:u256))) ))) ; = uint256 public count;\n\n(functions\n  ;; All required functions are public in Solidity, so we put them in :external block.\n  (:external\n    ( ;; This function needs to read storage so we set {:r 1}\n    (defn get () (@sto :w 0 :r 1) (-\u003e ((c mut :u256)))  ; function get() public view returns (uint256)\n        (-\u003e c (sto read! count))) ; return count;\n  \n     (defn inc () (@sto :w 1 :r 1) (-\u003e ()) ; function inc() public\n       (let (c (sto read! count)) ; store `count` in memory.\n         (sto write! count (+ c 1)))) ; count += 1;\n\n     (defn dec () (@sto :w 1 :r 1) (-\u003e ()) ; function dec() public\n       (let (c (sto read! count)) ; store `count` in memory.\n         (sto write! count (- c 1)))) ; count -= 1;\n  )))\n```\n\n### 4. Compiling Contract\nNow, when we have everything we can compile `counter.els`:\n```sh\njava -jar target/uberjar/elser-0.0.4-alpha-standalone.jar --compile counter.els\n```\nIt will produce `counter.yul \u0026 counter.bytecode` files in `/out` folder.\n\n### 5. Interacting with the Contract\n#### 5.1 Remix IDE\nThe simplest way to interact with the compiled contract is to use [Remix IDE](https://remix.ethereum.org/#optimize=true\u0026version=soljson-v0.4.24+commit.e67f0147.js\u0026lang=en\u0026runs=200\u0026evmVersion=null\u0026language=Solidity).\n\n##### - Copy `counter.yul` inside `/contracts` folder\n\u003cimg width=\"600\" alt=\"step0\" src=\"https://github.com/user-attachments/assets/8fa120c6-1e0f-401f-89c7-afca5cbe4bc6\" /\u003e\n\n##### - Set compiler version to the one specified in `:pragma`, and set language to `Yul`.\n\u003cimg width=\"200\" alt=\"step1\" src=\"https://github.com/user-attachments/assets/357911e7-c35a-4263-b95e-c5416ef8a954\" /\u003e\n\n##### - Compile and Deploy contract.\n##### - You can interact with it via `Low level interactions`:\n\u003cimg width=\"600\" alt=\"step2\" src=\"https://github.com/user-attachments/assets/8fe0111c-b220-4a84-88c4-b52d8e54d81a\" /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felser-lang%2Felser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felser-lang%2Felser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felser-lang%2Felser/lists"}