├── .gitattributes ├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── README.org ├── dasy ├── README.org ├── __init__.py ├── builtin │ ├── functions.py │ └── macros.hy ├── compiler.py ├── main.py └── parser │ ├── __init__.py │ ├── builtins.hy │ ├── comparisons.py │ ├── core.py │ ├── macros.py │ ├── nodes.py │ ├── ops.py │ ├── output.py │ ├── parse.py │ ├── stmt.py │ └── utils.hy ├── dasybyexample.md ├── dasybyexample.org ├── docs.org ├── examples ├── ERC20.dasy ├── ERC20.vy ├── Transient.vy ├── constants.dasy ├── constructor.dasy ├── default_function.dasy ├── delegate_call.dasy ├── dynamic_arrays.dasy ├── enum.dasy ├── error.dasy ├── event.dasy ├── for_loop.dasy ├── function_visibility.dasy ├── functions.dasy ├── hashing.dasy ├── hello_world.dasy ├── if_else.dasy ├── immutable.dasy ├── infix_macro.dasy ├── interface.dasy ├── mutable_hello.dasy ├── nonreentrant.dasy ├── nonreentrant2.dasy ├── nonreentrantenforcer.dasy ├── nonreentrantenforcer.vy ├── payable.dasy ├── private_public_state.dasy ├── raw_call.dasy ├── reference_types.dasy ├── selfdestruct.dasy ├── send_ether.dasy ├── simple_auction.dasy ├── test_delegate_call.dasy ├── test_interface.dasy ├── test_interface.vy ├── transient_nonreentrant.dasy ├── unsafe_ops.dasy ├── value_types.dasy ├── venom.dasy ├── venom_comp.vy └── view_pure.dasy ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py ├── parser └── test_utils.py └── test_dasy.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.dasy linguist-language=Clojure 2 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up Python 3.10 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: "3.10" 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pytest vyper titanoboa black hy 30 | pip install . --upgrade 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - uses: psf/black@stable 33 | - name: Test with pytest 34 | run: | 35 | pytest 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dasy/__pycache__/ 2 | __pycache__ 3 | .pytest_cache 4 | *.pyc 5 | /dist/ 6 | *DS_STORE* 7 | .hypothesis 8 | poetry.lock 9 | .build 10 | frenv 11 | .benchmarks 12 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: Dasy 2 | #+EXPORT_FILE_NAME: index 3 | #+SETUPFILE: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup 4 | 5 | #+begin_quote 6 | The Dasypeltis, gansi, is considered an egg-eating snake. Their diet consists of all forms of eggs considering they have no teeth in which to eat living prey with. 7 | #+end_quote 8 | 9 | Dasy is an experimental smart contract programming language in the lisp family. It is implemented by compiling to Vyper and benefits from the extensive optimizations and excellent performance of Vyper. 10 | 11 | Learn more in the [[file:docs.org][documentation]] 12 | 13 | * Examples 14 | [[https://dasy-by-example.github.io][More examples at Dasy By Example]] 15 | #+begin_src clojure 16 | (defvars :public 17 | myMap (hash-map :address :uint256) 18 | nums (dyn-arr :uint256 3) 19 | owner :address) 20 | 21 | (defn __init__ [] :external 22 | (set self/owner msg/sender) 23 | (set-at self/myMap msg/sender 10) 24 | (do ;; wrap statements in do 25 | (.append self/nums 11))) 26 | 27 | (defn getOwnerNum [] :uint256 :external 28 | (get-at self/myMap self/owner)) 29 | #+end_src 30 | 31 | * Installation 32 | ** For use as a library 33 | #+begin_src bash 34 | pip install git+https://github.com/dasylang/dasy.git 35 | #+end_src 36 | ** For use as an executable via =pipx= 37 | #+begin_src bash 38 | pipx install git+https://github.com/dasylang/dasy.git 39 | #+end_src 40 | ** [[https://github.com/dasylang/ape-dasy][Ape Plugin]] 41 | #+begin_src bash 42 | pip install ape-dasy 43 | #+end_src 44 | ** [[https://github.com/dasylang/foundry-dasy][Foundry plugin]] 45 | * Motivation 46 | ** Macros 47 | There are a lot of opportunities for macros in smart contracts. They can also be used to prototype features before implementing them at a lower level in the vyper compiler. 48 | 49 | macros are written in Hy, a pythonic lisp. They allow us to transform our code at compile time, allowing the developer to tailor the language itself to their needs. 50 | 51 | =cond= and =condp= are examples of useful macros that help make your code shorter, yet easier to understand. 52 | #+begin_src clojure 53 | (defn useIf [:uint256 x] :uint256 :external 54 | (if (<= x 10) 55 | (return 1) 56 | (if (<= x 20) 57 | (return 2) 58 | (return 3)))) 59 | 60 | ;; cond macro helps prevent deeply nested ifs 61 | (defn useCond [:uint256 x] :uint256 :external 62 | (cond 63 | (<= x 10) (return 1) 64 | (<= x 20) (return 2) 65 | :else (return 3))) 66 | 67 | ;; condp saves you from repeating the same operation 68 | (defn useCondp [:uint256 x] :uint256 :external 69 | (condp <= x 70 | 10 (return 1) 71 | 20 (return 2) 72 | :else (return 3))) 73 | #+end_src 74 | 75 | ** For fun 76 | -------------------------------------------------------------------------------- /dasy/README.org: -------------------------------------------------------------------------------- 1 | #+title: Dasy Compiler 2 | * Compilation Flow 3 | The compilation process kicks off by obtaining a string of source code. Once this is done, the source code string should be passed to [[file:compiler.py::def compile(src: str) -> CompilerData:][compiler.compile()]] 4 | 5 | Code is parsed into a Vyper AST, which is then used to create a CompilerData object. This object contains deployment and runtime bytecode, ABI, and other metadata. 6 | -------------------------------------------------------------------------------- /dasy/__init__.py: -------------------------------------------------------------------------------- 1 | import hy 2 | from hy import read, read_many 3 | from dasy.compiler import compile, compile_file 4 | from dasy.main import main 5 | from dasy.parser.output import get_external_interface 6 | from .parser import parse 7 | from .parser.parse import parse_src, parse_node 8 | 9 | __version__ = "0.1.29" 10 | -------------------------------------------------------------------------------- /dasy/builtin/functions.py: -------------------------------------------------------------------------------- 1 | # for handling venom 2 | from vyper.ast import Call, Expr 3 | from vyper.ir.s_expressions import parse_s_exp 4 | from vyper.codegen.ir_node import IRnode 5 | from vyper.builtins.functions import ( 6 | STMT_DISPATCH_TABLE, 7 | DISPATCH_TABLE, 8 | BuiltinFunction, 9 | ) 10 | from vyper.compiler import phases 11 | 12 | from dasy import parser 13 | from dasy.parser.utils import get_ir_type 14 | 15 | from hy import repr 16 | 17 | 18 | def parse_ir(expr): 19 | # check for optional return type annotation as second element 20 | ret_type = None 21 | ir_expr = None 22 | if len(expr) == 3: 23 | ret_type = get_ir_type(expr[1].name) 24 | ir_expr = expr[2] 25 | elif len(expr) == 2: 26 | ir_expr = expr[1] 27 | ir = IRnode.from_list((parse_s_exp(repr(ir_expr)[1:]))[0], typ=ret_type) 28 | 29 | # generate some vyper code to patch in. 30 | IDENTIFIER = f"__DASY_VENOM_BUILTIN_{parser.next_nodeid()}__" 31 | insert_code = f"{IDENTIFIER}()" 32 | 33 | # dynamically generate a class on the fly 34 | class generated_builtin(BuiltinFunction): 35 | _id = IDENTIFIER 36 | _inputs = () 37 | _return_type = ret_type 38 | 39 | def fetch_call_return(self, node): 40 | return self._return_type 41 | 42 | def infer_arg_types(self, node): 43 | return [] 44 | 45 | def build_IR(self, expr, context): 46 | return ir 47 | 48 | if ret_type is not None: 49 | DISPATCH_TABLE[IDENTIFIER] = generated_builtin() 50 | gend_ast = phases.generate_ast(insert_code, 0, "") 51 | return gend_ast[1].body[0].value 52 | 53 | STMT_DISPATCH_TABLE[IDENTIFIER] = generated_builtin() 54 | return phases.generate_ast(insert_code, 0, "")[1].body[0] 55 | 56 | 57 | def parse_vyper(expr): 58 | return phases.generate_ast(str(expr[1]), 0, "")[1].body[0] 59 | 60 | 61 | def wrap_calls(nodes): 62 | new_nodes = [] 63 | for call_node in nodes: 64 | if isinstance(call_node, Call): 65 | expr_node = parser.build_node(Expr, value=call_node) 66 | new_nodes.append(expr_node) 67 | else: 68 | new_nodes.append(call_node) 69 | return new_nodes 70 | 71 | 72 | def parse_splice(expr): 73 | return_val = wrap_calls([parser.parse_node(n) for n in expr[1:]]) 74 | return return_val 75 | -------------------------------------------------------------------------------- /dasy/builtin/macros.hy: -------------------------------------------------------------------------------- 1 | ;; Dasy macros are syntax transformations that run at compile time 2 | ;; 3 | ;;they can make writing verbose code much more convenient. Dasy has some warts from being built around Vyper, and macros help patch over these. 4 | ;;they can also be used to implement new language features 5 | 6 | ;; some convenient type methods 7 | 8 | (defmacro hash-map [key-type val-type] 9 | "(hash-map :address :string) -> (subscript HashMap '(:address :string)). 10 | The vyper equivalent is HashMap[address, string]" 11 | `(subscript HashMap (tuple ~key-type ~val-type))) 12 | 13 | (defmacro dyn-array [type length] 14 | "(hash-map :address 5) -> (subscript DynArray '(:address 5)). 15 | The vyper equivalent is DynArray[address, 5]" 16 | `(subscript DynArray (tuple ~type ~length))) 17 | 18 | (defmacro string [length] `(subscript String ~length)) 19 | 20 | (defmacro bytes [length] `(subscript Bytes ~length)) 21 | 22 | ;; Field Access Macros 23 | 24 | (defmacro set-in [obj field new-val] 25 | "(set-in person age 12) -> (set (. person age) 12). 26 | The vyper equivalent is: person.age = 12" 27 | `(set (. ~obj ~field) ~new-val)) 28 | 29 | (defmacro set-self [#* keys] 30 | (lfor k keys 31 | `(set (. self ~k) ~k))) 32 | 33 | (defmacro get-at [obj #* keys] 34 | "(get-at person age) -> (subscript person age). 35 | The vyper equivalent is: person[age]" 36 | (let [body `(subscript ~obj ~(get keys 0))] 37 | (for [k (cut keys 1 None)] 38 | (setv body `(subscript ~body ~k))) 39 | body)) 40 | 41 | (defmacro get-at! [obj keys] 42 | (let [body `(subscript ~obj ~(get keys 0))] 43 | (for [k (cut keys 1 None)] 44 | (setv body `(subscript ~body ~k))) 45 | body)) 46 | 47 | (defmacro set-at [obj #* keys] 48 | (let [body `(subscript ~obj ~(get keys 0))] 49 | (for [k (cut keys 1 -1)] 50 | (setv body `(subscript ~body ~k))) 51 | `(set ~body ~(get keys -1)))) 52 | 53 | (defmacro set-at! [obj keys val] 54 | (let [body `(subscript ~obj ~(get keys 0))] 55 | (for [k (cut keys 1 None)] 56 | (setv body `(subscript ~body ~k))) 57 | `(set ~body ~val))) 58 | 59 | 60 | ;; Syntax Sugar macros 61 | (defmacro doto [ obj #*cmds] 62 | `(splice ~@(lfor c cmds 63 | `(~(get c 0) ~obj ~@(cut c 1 None))))) 64 | 65 | (defmacro condp [op obj #*body] 66 | `(cond 67 | ~@(lfor i (range 0 (len body)) 68 | (if (= 0 (% i 2)) 69 | (if (= :else (get body i)) 70 | (get body i) 71 | `(~op ~obj ~(get body i))) 72 | (get body i))))) 73 | 74 | (defmacro inc [target] 75 | `(+= ~target 1)) 76 | 77 | (defmacro dec [target] 78 | `(-= ~target 1)) 79 | 80 | 81 | ;; Compiler extension macros 82 | 83 | (defmacro interface! [filename] 84 | (import dasy) 85 | (import os) 86 | (let [path (+ (.getcwd os) "/" filename) 87 | data (.compile-file dasy path) 88 | interface-str (.get-external-interface dasy data)] 89 | (.read dasy interface-str))) 90 | 91 | (defmacro include! [filename] 92 | (import dasy os) 93 | (let [path (+ (.getcwd os) "/" filename) 94 | stream (open path) 95 | forms []] 96 | (while True 97 | (try 98 | (.append forms (.read dasy stream)) 99 | (except [EOFError] (break)))) 100 | `(splice ~@forms))) 101 | 102 | ;; -> 103 | (defmacro arrow [args #*body] 104 | ;; TODO: Get rid of this dynamic import 105 | (import hy.models [Expression]) 106 | (let [[first #*rest] body 107 | body (if (isinstance first Expression) 108 | `(~(get first 0) ~args ~@(cut first 1 None)) 109 | `(~first ~args))] 110 | (for [exp rest] 111 | (setv body (if (isinstance exp Expression) 112 | `(~(get exp 0) ~body ~@(cut exp 1 None)) 113 | `(~exp ~body)))) 114 | body)) 115 | 116 | ;; ->> 117 | (defmacro arroww [args #*body] 118 | (import hy.models [Expression]) 119 | (let [[first #*rest] body 120 | body (if (isinstance first Expression) 121 | `(~(get first 0) ~@(cut first 1 None) ~args) 122 | `(~first ~args))] 123 | (for [exp rest] 124 | (setv body (if (isinstance exp Expression) 125 | `(~(get exp 0) ~@(cut exp 1 None) ~body) 126 | `(~exp ~body)))) 127 | body)) 128 | -------------------------------------------------------------------------------- /dasy/compiler.py: -------------------------------------------------------------------------------- 1 | from vyper.compiler.phases import CompilerData as VyperCompilerData 2 | from pathlib import Path 3 | from vyper.compiler.output import ( 4 | build_abi_output, 5 | build_asm_output, 6 | build_bytecode_runtime_output, 7 | build_external_interface_output, 8 | build_interface_output, 9 | build_ir_output, 10 | build_ir_runtime_output, 11 | build_layout_output, 12 | build_opcodes_output, 13 | ) 14 | from vyper.compiler.settings import Settings 15 | from dasy.parser import parse_src 16 | from dasy.parser.utils import filename_to_contract_name 17 | from vyper.evm.opcodes import anchor_evm_version 18 | 19 | 20 | class CompilerData(VyperCompilerData): 21 | def __init__(self, *args, **kwargs): 22 | VyperCompilerData.__init__(self, *args, **kwargs) 23 | 24 | @property 25 | def runtime_bytecode(self): 26 | runtime_bytecode = build_bytecode_runtime_output(self) 27 | self.__dict__["runtime_bytecode"] = runtime_bytecode 28 | return runtime_bytecode 29 | 30 | @property 31 | def abi(self): 32 | abi = build_abi_output(self) 33 | self.__dict__["abi"] = abi 34 | return abi 35 | 36 | @property 37 | def interface(self): 38 | interface = build_interface_output(self) 39 | self.__dict__["interface"] = interface 40 | return interface 41 | 42 | @property 43 | def ir(self): 44 | ir = build_ir_output(self) 45 | self.__dict__["ir"] = ir 46 | return ir 47 | 48 | @property 49 | def runtime_ir(self): 50 | ir = build_ir_runtime_output(self) 51 | self.__dict__["runtime_ir"] = ir 52 | return ir 53 | 54 | @property 55 | def asm(self): 56 | asm = build_asm_output(self) 57 | self.__dict__["asm"] = asm 58 | return asm 59 | 60 | @property 61 | def opcodes(self): 62 | return build_opcodes_output(self) 63 | 64 | @property 65 | def runtime_opcodes(self): 66 | return build_opcodes_output(self) 67 | 68 | @property 69 | def external_interface(self): 70 | return build_external_interface_output(self) 71 | 72 | @property 73 | def layout(self): 74 | return build_layout_output(self) 75 | 76 | 77 | def generate_compiler_data(src: str, name="DasyContract") -> CompilerData: 78 | (ast, settings) = parse_src(src) 79 | settings = Settings(**settings) 80 | version = settings.evm_version or "paris" 81 | with anchor_evm_version(version): 82 | data = CompilerData( 83 | "", 84 | ast.name or name, 85 | None, 86 | source_id=0, 87 | settings=settings, 88 | ) 89 | # data.settings = settings 90 | data.vyper_module = ast 91 | _ = data.bytecode 92 | return data 93 | 94 | 95 | def compile(src: str, name="DasyContract", include_abi=True) -> CompilerData: 96 | data = generate_compiler_data(src, name) 97 | return data 98 | 99 | 100 | def compile_file(filepath: str) -> CompilerData: 101 | path = Path(filepath) 102 | name = path.stem 103 | # name = ''.join(x.capitalize() for x in name.split('_')) 104 | with path.open() as f: 105 | src = f.read() 106 | if filepath.endswith(".vy"): 107 | return CompilerData(src, contract_name=filename_to_contract_name(filepath)) 108 | return compile(src, name=name) 109 | 110 | 111 | def generate_abi(src: str) -> list: 112 | return compile(src).abi 113 | -------------------------------------------------------------------------------- /dasy/main.py: -------------------------------------------------------------------------------- 1 | from dasy import compiler 2 | from vyper.compiler import OUTPUT_FORMATS as VYPER_OUTPUT_FORMATS 3 | import argparse 4 | import sys 5 | 6 | from dasy.parser.output import get_external_interface 7 | 8 | format_help = """Format to print, one or more of: 9 | bytecode (default) - Deployable bytecode 10 | bytecode_runtime - Bytecode at runtime 11 | abi - ABI in JSON format 12 | abi_python - ABI in python format 13 | source_map - Vyper source map 14 | method_identifiers - Dictionary of method signature to method identifier 15 | userdoc - Natspec user documentation 16 | devdoc - Natspec developer documentation 17 | combined_json - All of the above format options combined as single JSON output 18 | layout - Storage layout of a Vyper contract 19 | ast - AST in JSON format 20 | external_interface - External (Dasy) interface of a contract, used for outside contract calls 21 | vyper_interface - External (Vyper) interface of a contract, used for outside contract calls 22 | opcodes - List of opcodes as a string 23 | opcodes_runtime - List of runtime opcodes as a string 24 | ir - Intermediate representation in list format 25 | ir_json - Intermediate representation in JSON format 26 | hex-ir - Output IR and assembly constants in hex instead of decimal 27 | no-optimize - Do not optimize (don't use this for production code) 28 | """ 29 | 30 | OUTPUT_FORMATS = VYPER_OUTPUT_FORMATS.copy() 31 | 32 | OUTPUT_FORMATS["vyper_interface"] = OUTPUT_FORMATS["external_interface"] 33 | OUTPUT_FORMATS["external_interface"] = get_external_interface 34 | 35 | 36 | def main(): 37 | parser = argparse.ArgumentParser( 38 | prog="dasy", 39 | description="Lispy Smart Contract Language for the EVM", 40 | formatter_class=argparse.RawTextHelpFormatter, 41 | ) 42 | parser.add_argument("filename", type=str, nargs="?", default="") 43 | parser.add_argument("-f", help=format_help, default="bytecode", dest="format") 44 | 45 | src = "" 46 | 47 | args = parser.parse_args() 48 | 49 | if args.filename != "": 50 | with open(args.filename, "r") as f: 51 | src = f.read() 52 | if args.filename.endswith(".vy"): 53 | data = compiler.CompilerData( 54 | src, contract_name=args.filename.split("/")[-1].split(".")[0] 55 | ) 56 | else: 57 | data = compiler.compile(src, name=args.filename.split(".")[0]) 58 | else: 59 | for line in sys.stdin: 60 | src += line 61 | data = compiler.compile(src, name="StdIn") 62 | 63 | translate_map = { 64 | "abi_python": "abi", 65 | "json": "abi", 66 | "ast": "ast_dict", 67 | "ir_json": "ir_dict", 68 | "interface": "external_interface", 69 | } 70 | output_format = translate_map.get(args.format, args.format) 71 | if output_format in OUTPUT_FORMATS: 72 | print(OUTPUT_FORMATS[output_format](data)) 73 | else: 74 | raise Exception( 75 | f"Unrecognized Output Format {args.format}. Must be one of {OUTPUT_FORMATS.keys()}" 76 | ) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /dasy/parser/__init__.py: -------------------------------------------------------------------------------- 1 | import hy 2 | import os 3 | from .parse import parse_src, parse_node 4 | from . import output, builtins 5 | from pathlib import Path 6 | from .utils import next_node_id_maker, build_node, next_nodeid 7 | 8 | 9 | def reset_nodeid_counter(): 10 | builtins.next_nodeid = next_node_id_maker() 11 | 12 | 13 | def install_builtin_macros(): 14 | macro_file = Path(os.path.dirname(__file__)).parent / "builtin" / "macros.hy" 15 | with macro_file.open() as f: 16 | code = f.read() 17 | for expr in hy.read_many(code): 18 | parse_node(expr) 19 | 20 | 21 | install_builtin_macros() 22 | -------------------------------------------------------------------------------- /dasy/parser/builtins.hy: -------------------------------------------------------------------------------- 1 | (import vyper.ast.nodes * 2 | .utils [build-node]) 3 | 4 | (require 5 | hyrule.control [case]) 6 | 7 | (defn parse-builtin [node] 8 | (case (str node) 9 | "+" (build-node Add) 10 | "-" (build-node Sub) 11 | "*" (build-node Mult) 12 | "**" (build-node Pow) 13 | "%" (build-node Mod) 14 | "^" (build-node BitXor) 15 | "|" (build-node BitOr) 16 | "&" (build-node BitAnd) 17 | "~" (build-node Invert) 18 | "/" (build-node Div) 19 | "<" (build-node Lt :-pretty "<" :-description "less than") 20 | ">" (build-node Gt :-pretty ">" :-description "greater than") 21 | "<=" (build-node LtE :-pretty "<=" :-description "less than equal") 22 | ">=" (build-node GtE :-pretty ">=" :-description "greater than equal") 23 | "==" (build-node Eq :-pretty "==" :-description "equal") 24 | "!=" (build-node NotEq :-pretty "!=" :-description "not equal") 25 | "in" (build-node In :-pretty "in" :-description "membership") 26 | "notin" (build-node NotIn :-pretty "not in" :-description "exclusion") 27 | "not" (build-node Not :-pretty "not" :-description "negation") 28 | "usub" (build-node USub :-pretty "-" :-description "unary subtraction") 29 | "and" (build-node And :-pretty "and" :-description "boolean and") 30 | "or" (build-node Or :-pretty "or" :-description "boolean or"))) 31 | -------------------------------------------------------------------------------- /dasy/parser/comparisons.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | from hy import models 3 | from dasy import parser 4 | import vyper.ast.nodes as vy_nodes 5 | from hy.models import Expression, Symbol 6 | 7 | COMP_FUNCS = ["<", "<=", ">", ">=", "==", "!="] 8 | 9 | 10 | def chain_comps(chain_expr: Expression) -> Expression: 11 | """ 12 | Creates a new expression chaining comparisons. 13 | """ 14 | new_node = models.Expression() 15 | new_expr: List[Union[Symbol, Expression]] = [models.Symbol("and")] 16 | for vals in zip(chain_expr[1:], chain_expr[2:]): 17 | new_expr.append(models.Expression((chain_expr[0], vals[0], vals[1]))) 18 | new_node += tuple(new_expr) 19 | return new_node 20 | 21 | 22 | def parse_comparison(comparison_expr: Expression) -> vy_nodes.Compare: 23 | """ 24 | Parses a comparison expression, chaining comparisons if necessary. 25 | """ 26 | assert ( 27 | str(comparison_expr[0]) in COMP_FUNCS 28 | ), f"Invalid comparison operator {comparison_expr[0]}" 29 | 30 | # Always apply chain comps for consistency 31 | chained_expr = chain_comps(comparison_expr) 32 | left = parser.parse_node(chained_expr[1]) 33 | right = parser.parse_node(chained_expr[2]) 34 | op = parser.parse_node(chained_expr[0]) 35 | return parser.build_node(vy_nodes.Compare, left=left, ops=[op], comparators=[right]) 36 | -------------------------------------------------------------------------------- /dasy/parser/core.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | import dasy 3 | import vyper.ast.nodes as vy_nodes 4 | from .utils import build_node, next_nodeid, pairwise 5 | from hy import models 6 | 7 | from .utils import has_return, process_body 8 | 9 | 10 | def parse_attribute(expr): 11 | """Parses an attribute and builds a node.""" 12 | if len(expr) < 2: 13 | raise ValueError("Expression too short to parse attribute.") 14 | _, obj, attr = expr 15 | attr_node = build_node( 16 | vy_nodes.Attribute, attr=str(attr), value=dasy.parser.parse_node(obj) 17 | ) 18 | return attr_node 19 | 20 | 21 | def parse_tuple(tuple_tree): 22 | elements = [] 23 | if tuple_tree[0] == models.Symbol("quote"): 24 | elements = tuple_tree[1] 25 | elif tuple_tree[0] == models.Symbol("tuple"): 26 | elements = tuple_tree[1:] 27 | else: 28 | raise Exception("Invalid tuple declaration") 29 | return build_node( 30 | vy_nodes.Tuple, elements=[dasy.parser.parse_node(e) for e in elements] 31 | ) 32 | 33 | 34 | def parse_args_list(args_list) -> list[vy_nodes.arg]: 35 | if len(args_list) == 0: 36 | return [] 37 | results = [] 38 | current_type = args_list[0] 39 | assert isinstance(current_type, models.Keyword) or isinstance( 40 | current_type, models.Expression 41 | ) 42 | # get annotation and name 43 | for arg in args_list[1:]: 44 | # check if we hit a new type 45 | if isinstance(arg, (models.Keyword, models.Expression)): 46 | current_type = arg 47 | continue 48 | # get annotation and name 49 | if isinstance(current_type, models.Keyword): 50 | # built-in types like :uint256 51 | annotation_node = build_node( 52 | vy_nodes.Name, id=str(current_type.name), parent=None 53 | ) 54 | elif isinstance(current_type, models.Expression): 55 | # user-defined types like Foo 56 | annotation_node = dasy.parse.parse_node(current_type) 57 | else: 58 | raise Exception("Invalid type annotation") 59 | arg_node = build_node( 60 | vy_nodes.arg, arg=str(arg), parent=None, annotation=annotation_node 61 | ) 62 | results.append(arg_node) 63 | return results 64 | 65 | 66 | def parse_fn_args(fn_tree): 67 | args_node, *rest = fn_tree[2:] 68 | args_list = parse_args_list(args_node) 69 | args = build_node(vy_nodes.arguments, args=args_list, defaults=list()) 70 | return args, rest 71 | 72 | 73 | def parse_fn_decorators(decs): 74 | if isinstance(decs, models.Keyword): 75 | return [build_node(vy_nodes.Name, id=str(decs.name))] 76 | elif isinstance(decs, models.List): 77 | return [dasy.parse.parse_node(d) for d in decs] 78 | return [] 79 | 80 | 81 | def parse_fn_body(body, wrap=False): 82 | fn_body = [dasy.parse.parse_node(body_node) for body_node in body[:-1]] 83 | if wrap and not has_return(body[-1]): 84 | value_node = dasy.parse.parse_node(body[-1]) 85 | implicit_return_node = build_node(vy_nodes.Return, value=value_node) 86 | fn_body.append(implicit_return_node) 87 | else: 88 | fn_body.append(dasy.parse.parse_node(body[-1])) 89 | return process_body(fn_body) 90 | 91 | 92 | def _fn_tree_has_return_type(fn_tree): 93 | # has return type 94 | # (defn name [args] :uint256 :external ...) 95 | # (defn name [args] :uint256 [:external :view] ...) 96 | fn_args = fn_tree[1:] 97 | fn_args_len = len(fn_args) 98 | return ( 99 | fn_args_len > 3 100 | and isinstance(fn_args[0], models.Symbol) 101 | and isinstance(fn_args[1], models.List) 102 | and isinstance(fn_args[2], (models.Keyword, models.Expression, models.Symbol)) 103 | and isinstance(fn_args[3], (models.Keyword, models.List)) 104 | ) 105 | 106 | 107 | def _fn_tree_has_no_return_type(fn_tree): 108 | # no return type 109 | # (defn name [args] ...) 110 | fn_args = fn_tree[1:] 111 | fn_args_len = len(fn_args) 112 | return ( 113 | fn_args_len > 2 114 | and isinstance(fn_args[0], models.Symbol) 115 | and isinstance(fn_args[1], models.List) 116 | and isinstance(fn_args[2], (models.Keyword, models.List)) 117 | ) 118 | 119 | 120 | def _fn_is_constructor(fn_tree): 121 | return isinstance(fn_tree[1], models.Symbol) and str(fn_tree[1]) == "__init__" 122 | 123 | 124 | def parse_defn(fn_tree): 125 | fn_node_id = ( 126 | next_nodeid() 127 | ) # we want our fn node to have a lower id than its child node 128 | assert isinstance(fn_tree, models.Expression) 129 | assert fn_tree[0] == models.Symbol("defn") 130 | return_type = None 131 | name = str(fn_tree[1]) 132 | args = None 133 | decorators = [] 134 | 135 | fn_args = fn_tree[1:] 136 | args, rest = parse_fn_args(fn_tree) 137 | 138 | if _fn_is_constructor(fn_tree): 139 | decorators = [build_node(vy_nodes.Name, id="external")] 140 | fn_body = parse_fn_body(rest[1:]) 141 | elif _fn_tree_has_return_type(fn_tree): 142 | decorators = parse_fn_decorators(fn_args[3]) 143 | fn_body = parse_fn_body(rest[2:], wrap=True) 144 | return_type = dasy.parse.parse_node(fn_args[2]) 145 | elif _fn_tree_has_no_return_type(fn_tree): 146 | decorators = parse_fn_decorators(fn_args[2]) 147 | fn_body = parse_fn_body(rest[1:]) 148 | else: 149 | raise Exception(f"Invalid fn form {fn_tree}") 150 | 151 | fn_node = build_node( 152 | vy_nodes.FunctionDef, 153 | args=args, 154 | returns=return_type, 155 | decorator_list=decorators, 156 | pos=None, 157 | body=fn_body, 158 | name=name, 159 | node_id=fn_node_id, 160 | ) 161 | 162 | return fn_node 163 | 164 | 165 | def parse_declaration(var, typ, value=None, attrs: Set[str] = set()): 166 | target = dasy.parse.parse_node(var) 167 | annotation_attrs = {"public": False, "immutable": False, "constant": False} 168 | if attrs is not None: 169 | for attr in attrs: 170 | annotation_attrs[attr] = True 171 | 172 | annotation = None 173 | 174 | match typ: 175 | case [models.Symbol(e), _] if str(e) in annotation_attrs.keys(): 176 | annotation = dasy.parse.parse_node(typ) 177 | annotation_attrs[str(e)] = True 178 | case models.Expression() | models.Keyword(): 179 | for attr in attrs: 180 | typ = models.Expression((models.Symbol(attr), typ)) 181 | annotation = dasy.parse.parse_node(typ) 182 | case models.Symbol(): 183 | for attr in attrs: 184 | typ = models.Expression((models.Symbol(attr), typ)) 185 | annotation = dasy.parse.parse_node(typ) 186 | case _: 187 | raise Exception(f"Invalid declaration type {typ}") 188 | 189 | if annotation is None: 190 | raise Exception("No valid annotation was found") 191 | 192 | vdecl_node = build_node( 193 | vy_nodes.VariableDecl, 194 | target=target, 195 | annotation=annotation, 196 | value=value, 197 | **annotation_attrs, 198 | ) 199 | return vdecl_node 200 | 201 | 202 | def parse_defvars(expr): 203 | if isinstance(expr[1], models.Keyword): 204 | attrs = {expr[1].name} 205 | return [ 206 | parse_declaration(var, typ, attrs=attrs) for var, typ in pairwise(expr[2:]) 207 | ] 208 | return [parse_declaration(var, typ) for var, typ in pairwise(expr[1:])] 209 | 210 | 211 | def create_annotated_node(node_class, var, typ, value=None): 212 | target = dasy.parse.parse_node(var) 213 | if not isinstance(typ, (models.Expression, models.Keyword, models.Symbol)): 214 | raise Exception(f"Invalid declaration type {typ}") 215 | annotation = dasy.parse.parse_node(typ) 216 | node = build_node(node_class, target=target, annotation=annotation, value=value) 217 | return node 218 | 219 | 220 | def parse_variabledecl(expr) -> vy_nodes.VariableDecl: 221 | return create_annotated_node( 222 | vy_nodes.VariableDecl, 223 | expr[1], 224 | expr[2], 225 | value=dasy.parse.parse_node(expr[3]) if len(expr) == 4 else None, 226 | ) 227 | 228 | 229 | def parse_annassign(expr) -> vy_nodes.AnnAssign: 230 | return create_annotated_node( 231 | vy_nodes.AnnAssign, 232 | expr[1], 233 | expr[2], 234 | value=dasy.parse.parse_node(expr[3]) if len(expr) == 4 else None, 235 | ) 236 | 237 | 238 | def parse_structbody(expr): 239 | return [ 240 | create_annotated_node(vy_nodes.AnnAssign, var, typ) 241 | for var, typ in pairwise(expr[2:]) 242 | ] 243 | 244 | 245 | def parse_defcontract(expr): 246 | mod_node = vy_nodes.Module( 247 | body=[], 248 | name=str(expr[1]), 249 | doc_string="", 250 | ast_type="Module", 251 | node_id=next_nodeid(), 252 | ) 253 | expr_body = [] 254 | match expr[1:]: 255 | case (_, vars, *body) if isinstance(vars, models.List): 256 | # # contract has state 257 | for var, typ in pairwise(vars): 258 | mod_node.add_to_body(parse_declaration(var, typ)) 259 | expr_body = body 260 | case (_, *body): 261 | # no contract state 262 | expr_body = body 263 | case _: 264 | raise Exception(f"Invalid defcontract form: {expr}") 265 | for node in expr_body: 266 | mod_node.add_to_body(dasy.parse.parse_node(node)) 267 | 268 | return mod_node 269 | 270 | 271 | def parse_defstruct(expr): 272 | struct_node = build_node( 273 | vy_nodes.StructDef, name=str(expr[1]), body=parse_structbody(expr) 274 | ) 275 | return struct_node 276 | 277 | 278 | def parse_definterface(expr): 279 | name = str(expr[1]) 280 | body = [] 281 | for f in expr[2:]: 282 | rets = None if len(f) == 4 else dasy.parse.parse_node(f[3]) 283 | 284 | args_list = parse_args_list(f[2]) 285 | args_node = build_node(vy_nodes.arguments, args=args_list, defaults=list()) 286 | 287 | # in an interface, the body is a single expr node with the visibility 288 | visibility_node = dasy.parse.parse_node(f[-1]) 289 | body_node = build_node(vy_nodes.Expr, value=visibility_node) 290 | 291 | fn_node = build_node( 292 | vy_nodes.FunctionDef, 293 | args=args_node, 294 | returns=rets, 295 | decorator_list=[], 296 | pos=None, 297 | body=[body_node], 298 | name=str(f[1]), 299 | ) 300 | body.append(fn_node) 301 | 302 | interface_node = build_node(vy_nodes.InterfaceDef, body=body, name=name) 303 | return interface_node 304 | 305 | 306 | def parse_defevent(expr): 307 | return build_node(vy_nodes.EventDef, name=str(expr[1]), body=parse_structbody(expr)) 308 | 309 | 310 | def parse_enumbody(expr): 311 | return [build_node(vy_nodes.Expr, value=dasy.parse.parse_node(x)) for x in expr[2:]] 312 | 313 | 314 | def parse_defenum(expr): 315 | return build_node(vy_nodes.EnumDef, name=str(expr[1]), body=parse_enumbody(expr)) 316 | 317 | 318 | def parse_do(expr): 319 | calls = [dasy.parse.parse_node(x) for x in expr[1:]] 320 | exprs = [build_node(vy_nodes.Expr, value=call_node) for call_node in calls] 321 | return exprs 322 | 323 | 324 | def parse_subscript(expr): 325 | """(subscript value slice)""" 326 | index_value_node = dasy.parse.parse_node(expr[2]) 327 | index_node = build_node(vy_nodes.Index, value=index_value_node) 328 | value_node = dasy.parse.parse_node(expr[1]) 329 | subscript_node = build_node(vy_nodes.Subscript, slice=index_node, value=value_node) 330 | return subscript_node 331 | -------------------------------------------------------------------------------- /dasy/parser/macros.py: -------------------------------------------------------------------------------- 1 | import dasy 2 | import hy 3 | 4 | MACROS = ["cond"] 5 | 6 | 7 | def is_macro(cmd_str): 8 | return cmd_str in MACROS 9 | 10 | 11 | def macroexpand(code_str): 12 | return hy.macroexpand(hy.read(code_str)) 13 | 14 | 15 | def handle_macro(expr): 16 | new_node = hy.macroexpand(expr) 17 | return dasy.parser.parse_node(new_node) 18 | 19 | 20 | def parse_defmacro(expr): 21 | hy.eval(expr) 22 | MACROS.append(str(expr[1])) 23 | return None 24 | -------------------------------------------------------------------------------- /dasy/parser/nodes.py: -------------------------------------------------------------------------------- 1 | from vyper.ast import nodes as vy_nodes 2 | from vyper.ast.nodes import ( 3 | Break, 4 | Pass, 5 | Continue, 6 | Log, 7 | Raise, 8 | Return, 9 | AugAssign, 10 | Assert, 11 | Index, 12 | ) 13 | from hy import models 14 | from dasy import parser 15 | from .utils import process_body, build_node 16 | 17 | 18 | def parse_for(expr): 19 | # (for [x xs] (.append self/nums x)) 20 | # (for [target iter] *body) 21 | target, iter_ = expr[1] 22 | target_node = parser.parse_node(target) 23 | iter_node = parser.parse_node(iter_) 24 | body_nodes = [parser.parse_node(b) for b in expr[2:]] 25 | body = process_body(body_nodes) 26 | for_node = build_node(vy_nodes.For, body=body, iter=iter_node, target=target_node) 27 | return for_node 28 | 29 | 30 | def parse_if(expr): 31 | # used for base case in cond expansion 32 | if expr[1] == models.Keyword("else"): 33 | if expr[3] == models.Symbol("None"): 34 | return parser.parse_node(expr[2]) 35 | 36 | body_nodes = [parser.parse_node(expr[2])] 37 | body = process_body(body_nodes) 38 | else_nodes = [parser.parse_node(expr[3])] if len(expr) == 4 else [] 39 | else_ = process_body(else_nodes) 40 | test = parser.parse_node(expr[1]) 41 | 42 | # if-expressions always have: 43 | # - one node in body 44 | # - one node in else 45 | # - both nodes are ExprNodes 46 | # in theory we could also verify that both ExprNodes are of the same type 47 | # but the Vyper compiler will catch that anyway 48 | if ( 49 | len(body) == 1 50 | and len(else_) == 1 51 | and isinstance(body[0], vy_nodes.ExprNode) 52 | and isinstance(else_[0], vy_nodes.ExprNode) 53 | ): 54 | body = body[0] 55 | else_ = else_[0] 56 | if_node = build_node(vy_nodes.IfExp, test=test, body=body, orelse=else_) 57 | else: 58 | if_node = build_node(vy_nodes.If, test=test, body=body, orelse=else_) 59 | return if_node 60 | 61 | 62 | def parse_assign(expr): 63 | # needs some slight massaging due to the way targets/target is treated 64 | # the Assign class has a target slot, but it uses the first value in the 65 | # targets arg to fill it instead of using the target kwarg 66 | args = [parser.parse_node(arg) for arg in expr[1:]] 67 | return build_node(vy_nodes.Assign, *args, targets=[args[0]]) 68 | 69 | 70 | def parse_expr(expr, nodes): 71 | return [parser.parse_node(node) for node in expr[1 : nodes + 1]] 72 | 73 | 74 | handlers = { 75 | node_type.__name__.lower(): lambda expr, node_type=node_type: build_node( 76 | node_type, *parse_expr(expr, 2) 77 | ) 78 | for node_type in [ 79 | Break, 80 | Pass, 81 | Continue, 82 | Log, 83 | Raise, 84 | Return, 85 | AugAssign, 86 | Assert, 87 | Index, 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /dasy/parser/ops.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | from hy import models 3 | from dasy import parser 4 | from vyper.ast.nodes import BinOp, Compare, UnaryOp, BoolOp 5 | from .builtins import build_node 6 | 7 | BIN_FUNCS = {"+", "-", "/", "*", "**", "%"} 8 | COMP_FUNCS = {"<", "<=", ">", ">=", "==", "!=", "in", "notin"} 9 | UNARY_OPS = {"not", "usub"} 10 | BOOL_OPS = {"and", "or"} 11 | 12 | 13 | def is_op(cmd_str): 14 | return cmd_str in BIN_FUNCS | COMP_FUNCS | UNARY_OPS | BOOL_OPS 15 | 16 | 17 | def parse_op(expr, alias=None): 18 | cmd_str = alias or str(expr[0]) 19 | if cmd_str in BIN_FUNCS: 20 | return parse_binop(expr) 21 | if cmd_str in COMP_FUNCS: 22 | return parse_comparison(expr) 23 | if cmd_str in UNARY_OPS: 24 | return parse_unary(expr) 25 | if cmd_str in BOOL_OPS: 26 | return parse_boolop(expr) 27 | 28 | 29 | def chain_comps(expr): 30 | new_node = models.Expression() 31 | new_expr: List[Union[models.Symbol, models.Expression]] = [models.Symbol("and")] 32 | for vals in zip(expr[1:], expr[2:]): 33 | new_expr.append(models.Expression((expr[0], vals[0], vals[1]))) 34 | new_node += tuple(new_expr) 35 | return new_node 36 | 37 | 38 | def parse_comparison(comp_tree): 39 | if ( 40 | len(comp_tree[1:]) > 2 41 | ): # comparing more than 2 things; chain comps for (< 2 3 4 ) 42 | return parser.parse_node(chain_comps(comp_tree)) 43 | left = parser.parse_node(comp_tree[1]) 44 | right = parser.parse_node(comp_tree[2]) 45 | op = parser.parse_node(comp_tree[0]) 46 | return build_node(Compare, left=left, ops=[op], comparators=[right]) 47 | 48 | 49 | def parse_unary(expr): 50 | operand = parser.parse_node(expr[1]) 51 | op = parser.parse_node(expr[0]) 52 | return build_node(UnaryOp, operand=operand, op=op) 53 | 54 | 55 | def parse_boolop(expr): 56 | op = parser.parse_node(expr[0]) 57 | values = [parser.parse_node(e) for e in expr[1:]] 58 | return build_node(BoolOp, op=op, values=values) 59 | 60 | 61 | def chain_binops(expr): 62 | if len(expr) == 3: 63 | return expr 64 | else: 65 | new_node = models.Expression() 66 | tmp_expr = tuple([expr[0], *expr[2:]]) 67 | tmp_node = models.Expression() 68 | tmp_node += tmp_expr 69 | subtree = chain_binops(tmp_node) 70 | new_node += tuple([expr[0], expr[1], subtree]) 71 | return new_node 72 | 73 | 74 | def parse_binop(binop_tree): 75 | if len(binop_tree) > 3: 76 | return parser.parse_node(chain_binops(binop_tree)) 77 | left = parser.parse_node(binop_tree[1]) 78 | right = parser.parse_node(binop_tree[2]) 79 | op = parser.parse_node(binop_tree[0]) 80 | return build_node(BinOp, left=left, right=right, op=op) 81 | -------------------------------------------------------------------------------- /dasy/parser/output.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | from vyper.compiler import CompilerData 4 | from vyper.semantics.types.function import ContractFunctionT, FunctionVisibility 5 | 6 | 7 | def convert_type(vyper_type: str) -> str: 8 | vyper_type = str(vyper_type) 9 | if "[" in vyper_type: 10 | base = re.search(r"[A-Za-z]+", vyper_type).group() 11 | size = re.search(r"\d+", vyper_type).group() 12 | if base in ["String", "Bytes"]: 13 | return f"({base.lower()} {size})" 14 | else: 15 | return f"(array {base.lower()} {size})" 16 | return f":{vyper_type}" 17 | 18 | 19 | def get_external_interface(compiler_data: CompilerData) -> str: 20 | interface = compiler_data.vyper_module_folded._metadata["type"] 21 | stem = Path(compiler_data.contract_name).stem 22 | # capitalize words separated by '_' 23 | # ex: test_interface.vy -> TestInterface 24 | contract_name = ( 25 | "".join([x.capitalize() for x in stem.split("_")]) if "_" in stem else str(stem) 26 | ) 27 | 28 | out = ";; External Interface\n" 29 | funcs = [] 30 | for func in [ 31 | func for func in interface.members.values() if type(func) == ContractFunctionT 32 | ]: 33 | if func.visibility == FunctionVisibility.INTERNAL or func.name == "__init__": 34 | continue 35 | args = "" 36 | cur_type = "" 37 | for arg in func.arguments: 38 | if str(arg.typ) != cur_type: 39 | args += convert_type(arg.typ) + " " 40 | cur_type = str(arg.typ) 41 | args += f"{arg.name} " 42 | args = "[" + args[:-1] + "]" # remove trailing space 43 | return_type = "" 44 | if func.return_type is not None: 45 | return_type = convert_type(func.return_type) 46 | mutability = func.mutability.value 47 | func_str = f"(defn {func.name} {args} {return_type} :{mutability})" 48 | funcs.append(func_str) 49 | body = "\n ".join(funcs) 50 | out = f"{out}(definterface {contract_name}\n {body})" 51 | return out 52 | -------------------------------------------------------------------------------- /dasy/parser/parse.py: -------------------------------------------------------------------------------- 1 | import ast as py_ast 2 | 3 | from typing import Union 4 | 5 | from dasy.parser.macros import handle_macro, is_macro 6 | 7 | 8 | import hy 9 | import vyper.ast.nodes as vy_nodes 10 | from hy import models 11 | 12 | from .builtins import parse_builtin, build_node 13 | from .ops import BIN_FUNCS, BOOL_OPS, COMP_FUNCS, UNARY_OPS, is_op, parse_op 14 | from .utils import add_src_map 15 | 16 | # namespaces with expression handlers 17 | from . import nodes, core, macros 18 | from dasy.builtin import functions 19 | 20 | BUILTIN_FUNCS = BIN_FUNCS | COMP_FUNCS | UNARY_OPS | BOOL_OPS | {"in", "notin"} 21 | 22 | NAME_CONSTS = ["True", "False"] 23 | 24 | CONSTS = {} 25 | 26 | ALIASES = { 27 | ".": "attribute", 28 | "quote": "tuple", 29 | "array": "subscript", 30 | "defvar": "annassign", 31 | "setv": "assign", 32 | "set": "assign", 33 | "+!": "unsafe_add", 34 | "-!": "unsafe_sub", 35 | "*!": "unsafe_mul", 36 | "/!": "unsafe_div", 37 | "def": "annassign", 38 | "->": "arrow", 39 | "->>": "arroww", 40 | } 41 | 42 | SRC = "" 43 | 44 | 45 | def convert_annassign(ast): 46 | # top-level AnnAssign nodes should be replaced with a VariableDecl 47 | is_public = False 48 | is_immutable = False 49 | is_constant = False 50 | if isinstance(ast.annotation, vy_nodes.Call): 51 | match ast.annotation.func: 52 | case "public": 53 | is_public = True 54 | case "immutable": 55 | is_immutable = True 56 | case "constant": 57 | is_constant = True 58 | new_node = build_node( 59 | vy_nodes.VariableDecl, 60 | target=ast.target, 61 | annotation=ast.annotation, 62 | value=ast.value, 63 | is_constant=is_constant, 64 | is_public=is_public, 65 | is_immutable=is_immutable, 66 | ) 67 | for child in ast.get_children(): 68 | new_node._children.add(child) 69 | child._parent = new_node 70 | return new_node 71 | 72 | 73 | DONT_REPLACE = ("tuple",) 74 | 75 | 76 | def parse_expr(expr): 77 | cmd_str = ALIASES.get(str(expr[0]), str(expr[0])) 78 | 79 | if cmd_str != str(expr[0]) and cmd_str not in DONT_REPLACE: 80 | expr = models.Expression((models.Symbol(cmd_str), *expr[1:])) 81 | 82 | if is_op(cmd_str): 83 | return parse_op(expr, cmd_str) 84 | 85 | if cmd_str in nodes.handlers: 86 | return nodes.handlers[cmd_str](expr) 87 | 88 | node_fn = f"parse_{cmd_str}" 89 | 90 | for ns in [nodes, core, macros, functions]: 91 | if hasattr(ns, node_fn): 92 | return getattr(ns, node_fn)(expr) 93 | 94 | if is_macro(cmd_str): 95 | return handle_macro(expr) 96 | 97 | if cmd_str.startswith("."): 98 | inner_node = models.Expression((models.Symbol("."), expr[1], (cmd_str[1:]))) 99 | outer_node = models.Expression((inner_node, *expr[2:])) 100 | return parse_node(outer_node) 101 | 102 | match cmd_str: 103 | case "defconst": 104 | CONSTS[str(expr[1])] = expr[2] 105 | return None 106 | case "defimmutable" | "defimm": 107 | CONSTS[str(expr[1])] = None 108 | return None 109 | case "+=" | "-=" | "*=" | "/=": 110 | return parse_augop(expr) 111 | case _: 112 | return parse_call(expr) 113 | 114 | 115 | def parse_augop(expr): 116 | op = models.Symbol(str(expr[0])[:1]) 117 | target, value = expr[1:] 118 | parsed_code = build_node( 119 | vy_nodes.AugAssign, 120 | op=parse_node(op), 121 | target=parse_node(target), 122 | value=parse_node(value), 123 | ) 124 | return parsed_code 125 | 126 | 127 | def parse_call(expr, wrap_expr=False): 128 | match expr: 129 | case (fn_name, *args): 130 | args_list = [] 131 | kw_args = [] 132 | i = 0 133 | while i < len(args): 134 | cur_arg = args[i] 135 | if ( 136 | isinstance(cur_arg, models.Keyword) 137 | and len(args) > (i + 1) 138 | and not isinstance(args[i + 1], models.Keyword) 139 | ): 140 | # TODO: remove this ugly hack and properly check against builtin types 141 | # or reconsider whether we should be using keywords for builtin types at all 142 | val_arg = args[i + 1] 143 | val_node = parse_node(val_arg) 144 | kw_node = build_node( 145 | vy_nodes.keyword, arg=str(cur_arg)[1:], value=val_node 146 | ) 147 | kw_args.append(kw_node) 148 | i += 2 149 | else: 150 | val_node = parse_node(args[i]) 151 | args_list.append(val_node) 152 | i += 1 153 | func_node = parse_node(fn_name) 154 | call_node = build_node( 155 | vy_nodes.Call, func=func_node, args=args_list, keywords=kw_args 156 | ) 157 | if wrap_expr: 158 | expr_node = build_node(vy_nodes.Expr, value=call_node) 159 | return expr_node 160 | return call_node 161 | 162 | 163 | def parse_node( 164 | node: Union[ 165 | models.Expression, 166 | models.Integer, 167 | models.String, 168 | models.Symbol, 169 | models.Keyword, 170 | models.Bytes, 171 | models.List, 172 | ], 173 | ): 174 | """ 175 | This function converts a node into its corresponding AST node based on its type. 176 | :param node: A node of the parsed model 177 | :return: Corresponding AST node, if the node type is supported. Raises exception otherwise. 178 | """ 179 | # Initialize ast_node to None 180 | ast_node = None 181 | 182 | # dispatch on type of parsed model 183 | match node: 184 | case models.Expression(node): 185 | # check for pragma and set settings 186 | if node[0] == models.Symbol("pragma"): 187 | if node[1] == models.Keyword("evm-version"): 188 | return {"evm_version": str(node[2])} 189 | ast_node = parse_expr(node) 190 | case models.Integer(node): 191 | ast_node = build_node(vy_nodes.Int, value=int(node)) 192 | case models.String(node): 193 | ast_node = build_node(vy_nodes.Str, value=str(node)) 194 | case models.Symbol(node): 195 | str_node = str(node) 196 | str_node = ALIASES.get(str_node, str_node) 197 | if str_node in CONSTS: 198 | ast_node = parse_node(CONSTS[str(node)]) 199 | elif str_node in BUILTIN_FUNCS: 200 | ast_node = parse_builtin(node) 201 | elif str_node in NAME_CONSTS: 202 | ast_node = build_node( 203 | vy_nodes.NameConstant, value=py_ast.literal_eval(str(node)) 204 | ) 205 | elif str_node.startswith("0x"): 206 | ast_node = build_node(vy_nodes.Hex, value=str_node) 207 | elif "/" in str_node: 208 | target, attr = str(node).split("/") 209 | replacement_node = models.Expression( 210 | (models.Symbol("."), models.Symbol(target), models.Symbol(attr)) 211 | ) 212 | ast_node = parse_node(replacement_node) 213 | elif "." in str_node and len(str_node) > 1: 214 | target, attr = str(node).split(".") 215 | replacement_node = models.Expression( 216 | (models.Symbol("."), models.Symbol(target), models.Symbol(attr)) 217 | ) 218 | ast_node = parse_node(replacement_node) 219 | else: 220 | ast_node = build_node(vy_nodes.Name, id=str(node)) 221 | case models.Keyword(node): 222 | ast_node = build_node(vy_nodes.Name, id=str(node)) 223 | case models.Bytes(byt): 224 | ast_node = build_node(vy_nodes.Bytes, value=byt) 225 | case models.List(lst): 226 | ast_node = build_node( 227 | vy_nodes.List, elements=[parse_node(elmt) for elmt in lst] 228 | ) 229 | case models.Float(node): 230 | raise NotImplementedError("Floating point not supported (yet)") 231 | case models.Dict(node): 232 | keys = [parse_node(k) for k in node.keys()] 233 | values = [parse_node(v) for v in node.values()] 234 | ast_node = build_node(vy_nodes.Dict, keys=keys, values=values) 235 | case _: 236 | raise ValueError(f"No match for node {node}. Unsupported node type.") 237 | return add_src_map(SRC, node, ast_node) 238 | 239 | 240 | def parse_src(src: str): 241 | global SRC 242 | SRC = src 243 | mod_node: vy_nodes.Module = build_node( 244 | vy_nodes.Module, body=[], name="", doc_string="" 245 | ) 246 | 247 | vars = [] 248 | fs = [] 249 | settings = {} 250 | for element in hy.read_many(src): 251 | # parse each top-level form 252 | ast = parse_node(element) 253 | 254 | if isinstance(ast, list): 255 | for v in ast: 256 | add_src_map(src, element, v) 257 | elif isinstance(ast, dict): 258 | settings.update(ast) 259 | continue 260 | elif ast: 261 | add_src_map(src, element, ast) 262 | else: 263 | continue 264 | 265 | if isinstance(ast, vy_nodes.Module): 266 | mod_node = ast 267 | elif isinstance( 268 | ast, 269 | ( 270 | vy_nodes.VariableDecl, 271 | vy_nodes.StructDef, 272 | vy_nodes.EventDef, 273 | vy_nodes.InterfaceDef, 274 | vy_nodes.EnumDef, 275 | ), 276 | ): 277 | vars.append(ast) 278 | elif isinstance(ast, vy_nodes.FunctionDef): 279 | fs.append(ast) 280 | elif isinstance(ast, list): 281 | for var in ast: 282 | var_node = var 283 | if isinstance(var, vy_nodes.AnnAssign): 284 | var_node = convert_annassign(var) 285 | elif isinstance(var, vy_nodes.FunctionDef): 286 | fs.append(var) 287 | continue 288 | vars.append(var_node) 289 | elif isinstance(ast, vy_nodes.AnnAssign): 290 | new_node = convert_annassign(ast) 291 | vars.append(new_node) 292 | else: 293 | raise Exception(f"Unrecognized top-level form {element} {ast}") 294 | 295 | for e in vars + fs: 296 | mod_node.add_to_body(e) 297 | 298 | return mod_node, settings 299 | -------------------------------------------------------------------------------- /dasy/parser/stmt.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z80dev/dasy/6c96690a79f415f69e9bc4667d2e70eb1440a748/dasy/parser/stmt.py -------------------------------------------------------------------------------- /dasy/parser/utils.hy: -------------------------------------------------------------------------------- 1 | (import vyper.ast.nodes * 2 | hy.models [Symbol Sequence] 3 | hyrule.iterables [flatten] 4 | vyper.semantics.types.primitives [SINT UINT BytesM_T]) 5 | 6 | (require 7 | hyrule [assoc] 8 | hyrule.control [case branch] 9 | hyrule.argmove [->]) 10 | 11 | (defn get-ir-type [name] 12 | ;; check if starts with type prefix 13 | ;; if so, return the corresponding type class 14 | ;; otherwise, return None 15 | (let [name-str (str name) 16 | [type-constructor size-index] (branch (name-str.startswith it) 17 | "uint" [UINT 4] 18 | "int" [SINT 3] 19 | "bytes" [BytesM_T 5])] 20 | (-> name-str 21 | (cut size-index None) 22 | int 23 | type-constructor))) 24 | 25 | (defn counter-gen [] 26 | (setv counter 0) 27 | (while True 28 | (yield counter) 29 | (setv counter (+ counter 1)))) 30 | 31 | (defn next-node-id-maker [] 32 | (setv counter (counter-gen)) 33 | (fn [] 34 | (next counter))) 35 | 36 | (setv next_nodeid (next-node-id-maker)) 37 | 38 | (defn pairwise [iterable] 39 | (setv a (iter iterable)) 40 | (zip a a)) 41 | 42 | (defn has-return [tree] 43 | (cond 44 | (isinstance tree Symbol) (= (str tree) "return") 45 | (isinstance tree Sequence) (for [el tree] (when (has-return el) (return True))) 46 | True (return False))) 47 | 48 | (defn is-venom [tree] 49 | (cond 50 | (isinstance tree Symbol) (= (str tree) "venom") 51 | (isinstance tree Sequence) (for [el tree] (when (is-venom el) (return True))) 52 | True (return False))) 53 | 54 | (defn filename-to-contract-name [fname] 55 | ;; converts a filename to a contract name 56 | ;; e.g. "contracts/my_contract.vy" -> "MyContract" 57 | (let [words (-> fname 58 | (.split "/") 59 | (get -1) 60 | (.split ".") 61 | (get 0) 62 | (.split "_")) 63 | capitalized_words (map (fn [word] (.capitalize word)) words)] 64 | (.join "" capitalized_words))) 65 | 66 | 67 | (defn build-node [node-class #* args #** kwargs] 68 | (setv args-dict kwargs) 69 | ;; set positional args according to node-class.__slots__ 70 | (when args 71 | (setv args-zip-dict (dict (zip node-class.__slots__ args))) 72 | (.update args-dict args-zip-dict) 73 | (for [slot (list (cut node-class.__slots__ (len args) None))] 74 | (assoc args-dict slot None))) 75 | (let [node-id (.get args-dict "node_id" (next_nodeid))] 76 | (when (in "node_id" args-dict) (del (get args-dict "node_id"))) 77 | (-> (node-class :node-id node-id :ast-type (. node-class __name__) #** args-dict) 78 | (set-parent-children (.values args-dict))))) 79 | 80 | 81 | (defn set-parent-children [parent children] 82 | (for [n children] 83 | (branch (isinstance n it) 84 | list (set-parent-children parent n) 85 | VyperNode (do 86 | (.add (. parent _children) n) 87 | (setv (. n _parent) parent)))) 88 | parent) 89 | 90 | (defn add-src-map [src-code element ast-node] 91 | (when ast-node 92 | (if (isinstance ast-node list) 93 | (for [n ast-node] 94 | (add-src-map src-code element n)) 95 | (do 96 | (setv (. ast-node full_source_code) src-code) 97 | (when (hasattr element "start_line") 98 | (do 99 | (setv ast-node.lineno element.start_line) 100 | (setv ast-node.end_lineno element.end_line) 101 | (setv ast-node.col_offset element.start_column) 102 | (setv ast-node.end_col_offset element.end_column)))))) 103 | ast-node) 104 | 105 | (defn process-body [body] 106 | (flatten 107 | (lfor f body 108 | (branch (isinstance f it) 109 | list f 110 | List (lfor f2 (. f elements) 111 | (if (isinstance f2 Call) 112 | (build-node Expr :value f2) 113 | f2)) 114 | Call [(build-node Expr :value f)] 115 | else [f])))) 116 | -------------------------------------------------------------------------------- /dasybyexample.md: -------------------------------------------------------------------------------- 1 | - [Hello World](#orgab104e1) 2 | - [Data Types - Values](#orgdfdb8d3) 3 | - [Data Types - References](#orgf9cd3a1) 4 | - [Dynamic Arrays](#org35e30a0) 5 | - [Functions](#org2ab743a) 6 | - [Internal and External Functions](#org8c1b980) 7 | - [View and Pure Functions](#orgba4049c) 8 | - [Constructor](#orge3eecd9) 9 | - [Private and Public State Variables](#org8273a91) 10 | - [Constants](#orgbd0900e) 11 | - [Immutable](#org5e764a4) 12 | - [If/Else](#orgec4103d) 13 | - [For Loop](#org2665dcc) 14 | - [Errors](#org2223378) 15 | - [Events](#org988b4c5) 16 | - [Payable](#org20aaaff) 17 | - [Default Function](#orge23c5a9) 18 | - [Send Ether](#orga2f7ce2) 19 | - [Raw Call](#org8c71915) 20 | - [Delegate Call](#org5a607a3) 21 | - [Interface](#org46151b1) 22 | - [Hash Function](#orgdaa5d01) 23 | - [Re-Entrancy Lock](#org1b506f7) 24 | - [Self Destruct](#org0108c1e) 25 | 26 | 27 | 28 | 29 | 30 | # Hello World 31 | 32 | ```clojure 33 | 1 ;; Create a string variable that can store maximum 100 characters 34 | 2 (defvar greet (public (string 100))) 35 | 3 36 | 4 (defn __init__ [] :external 37 | 5 (set self/greet "Hello World")) 38 | ``` 39 | 40 | 41 | 42 | 43 | # Data Types - Values 44 | 45 | ```clojure 46 | 1 (defvars 47 | 2 b (public :bool) 48 | 3 i (public :int128) 49 | 4 u (public :uint256) 50 | 5 addr (public :address) 51 | 6 b32 :bytes32 52 | 7 bs (public (bytes 100)) 53 | 8 s (public (string 100))) 54 | 9 55 | 10 (defn __init__ [] :external 56 | 11 (set self/b False) 57 | 12 (set self/i -1) 58 | 13 (set self/u 123) 59 | 14 (set self/b32 0xada1b75f8ae9a65dcc16f95678ac203030505c6b465c8206e26ae84b525cdacb) 60 | 15 (set self/bs b"\x01") 61 | 16 (set self/s "Hello Dasy")) 62 | ``` 63 | 64 | 65 | 66 | 67 | # Data Types - References 68 | 69 | ```clojure 70 | 1 (defstruct Person 71 | 2 name (string 100) 72 | 3 age :uint256) 73 | 4 74 | 5 (defvars 75 | 6 nums (public (array :uint256 10)) ;; fixed size list, must be bounded 76 | 7 myMap (public (hash-map :address :uint256)) 77 | 8 person (public Person)) 78 | 9 79 | 10 (defn __init__ [] :external 80 | 11 (doto self/nums 81 | 12 (set-at 0 123) ;; this updates self.nums[0] 82 | 13 (set-at 9 456)) ;; this updates self.nums[9] 83 | 14 84 | 15 ;; copies self.nums to array in memory 85 | 16 (defvar arr (array :uint256 10) self/nums) 86 | 17 (set-at arr 0 123) ;; does not modify self/nums 87 | 18 88 | 19 ;; this updates self/myMap 89 | 20 (doto self/myMap 90 | 21 (set-at msg/sender 1) ;; self.myMap[msg.sender] = 1 91 | 22 (set-at msg/sender 11)) ;; self.myMap[msg.sender] = 11 92 | 23 93 | 24 ;; this updates self/person 94 | 25 (doto self/person 95 | 26 (set-in age 11) 96 | 27 (set-in name "Dasy")) 97 | 28 98 | 29 ;; you could put defvar inside a doto like the arr example 99 | 30 ;; above, but I don't think that is very readable 100 | 31 ;; doing it this way is clearer, leaving the defvar out of doto 101 | 32 ;; Person struct is copied into memory 102 | 33 (defvar p Person self/person) 103 | 34 (set-in p name "Solidity")) 104 | ``` 105 | 106 | 107 | 108 | 109 | # Dynamic Arrays 110 | 111 | ```clojure 112 | 1 ;; dynamic array of type uint256, max 3 elements 113 | 2 (defvar nums (public (dyn-array :uint256 3))) 114 | 3 115 | 4 (defn __init__ [] :external 116 | 5 (doto self/nums 117 | 6 (.append 11) 118 | 7 (.append 22) 119 | 8 (.append 33) 120 | 9 ;; this will revert, appending to array with max 3 elements 121 | 10 ;; (.append self/nums 44) 122 | 11 ) 123 | 12 ;; delete all elements 124 | 13 (set self/nums []) 125 | 14 ;; set values 126 | 15 (set self/nums [1 2 3])) 127 | 16 128 | 17 (defn examples [(dyn-array :uint256 5) xs] (dyn-array :uint256 8) [:external :pure] 129 | 18 (defvar ys (dyn-array :uint256 8) [1 2 3]) 130 | 19 (for [x xs] 131 | 20 (.append ys x)) 132 | 21 (return ys)) 133 | 22 134 | 23 (defn filter [(dyn-array :address 5) addrs] (dyn-array :address 5) [:external :pure] 135 | 24 (defvar nonzeros (dyn-array :address 5) []) 136 | 25 (for [addr addrs] 137 | 26 (if (!= addr (empty :address)) 138 | 27 (do (.append nonzeros addr)))) 139 | 28 (return nonzeros)) 140 | ``` 141 | 142 | 143 | 144 | 145 | # Functions 146 | 147 | ```clojure 148 | 1 (defn multiply [:uint256 x y] :uint256 [:external :pure] 149 | 2 (* x y)) 150 | 3 151 | 4 (defn divide [:uint256 x y] :uint256 [:external :pure] 152 | 5 (/ x y)) 153 | 6 154 | 7 (defn multiOut [] '(:uint256 :bool) [:external :pure] 155 | 8 '(1 True)) 156 | 9 157 | 10 (defn addAndSub [:uint256 x y] '(:uint256 :uint256) [:external :pure] 158 | 11 '((+ x y) (- x y))) 159 | ``` 160 | 161 | 162 | 163 | 164 | # Internal and External Functions 165 | 166 | ```clojure 167 | 1 ;; internal functions can only be called inside this contract 168 | 2 (defn _add [:uint256 x y] :uint256 [:internal :pure] 169 | 3 (+ x y)) 170 | 4 171 | 5 ;; external functions can only be called from outside this contract 172 | 6 (defn extFunc [] :bool [:external :view] 173 | 7 True) 174 | 8 175 | 9 ;; external functions can only be called from outside this contract 176 | 10 (defn avg [:uint256 x y] :uint256 [:external :view] 177 | 11 ;; cannot call other external function 178 | 12 ;; (.extFunc self) 179 | 13 180 | 14 ;; can call internal functions 181 | 15 (defvar z :uint256 (self/_add x y)) 182 | 16 (/ (+ x y) 183 | 17 2)) 184 | 18 185 | 19 (defn _sqr [:uint256 x] :uint256 [:internal :pure] 186 | 20 (* x x)) 187 | 21 188 | 22 (defn sumOfSquares [:uint256 x y] :uint256 [:external :view] 189 | 23 (+ (self/_sqr x) 190 | 24 (self/_sqr y))) 191 | ``` 192 | 193 | 194 | 195 | 196 | # View and Pure Functions 197 | 198 | ```clojure 199 | 1 (defvar num (public :uint256)) 200 | 2 201 | 3 ;; Pure functions do not read any state or global variables 202 | 4 (defn pureFunc [:uint256 x] :uint256 [:external :pure] 203 | 5 x) 204 | 6 205 | 7 ;; View functions might read state or global state, or call an internal function 206 | 8 (defn viewFunc [:uint256 x] :bool [:external :view] 207 | 9 (> x self/num)) 208 | 10 209 | 11 (defn sum [:uint256 x y z] :uint256 [:external :pure] 210 | 12 (+ x y z)) 211 | 13 212 | 14 (defn addNum [:uint256 x] :uint256 [:external :view] 213 | 15 (+ x self/num)) 214 | ``` 215 | 216 | 217 | 218 | 219 | # Constructor 220 | 221 | ```clojure 222 | 1 (defvars owner (public :address) 223 | 2 createdAt (public :uint256) 224 | 3 expiresAt (public :uint256) 225 | 4 name (public (string 10))) 226 | 5 227 | 6 (defn __init__ [(string 10) name :uint256 duration] :external 228 | 7 ;; set owner to caller 229 | 8 (set self/owner msg/sender) 230 | 9 ;; set name from input 231 | 10 (set self/name name) 232 | 11 (set self/createdAt block/timestamp) 233 | 12 (set self/expiresAt (+ block/timestamp 234 | 13 duration))) 235 | ``` 236 | 237 | 238 | 239 | 240 | # Private and Public State Variables 241 | 242 | ```clojure 243 | 1 (defvars 244 | 2 owner (public :address) 245 | 3 foo :uint256 246 | 4 bar (public :bool)) 247 | 5 248 | 6 (defn __init__ [] :external 249 | 7 (set self/owner msg/sender) 250 | 8 (set self/foo 123) 251 | 9 (set self/bar True)) 252 | ``` 253 | 254 | 255 | 256 | 257 | # Constants 258 | 259 | ```clojure 260 | 1 (defconst MY_CONSTANT 123) 261 | 2 (defconst MIN 1) 262 | 3 (defconst MAX 10) 263 | 4 (defconst ADDR 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B) 264 | 5 265 | 6 (defn getMyConstants [] '(:uint256 :uint256 :address) [:external :pure] 266 | 7 '(MIN MAX ADDR)) 267 | 8 268 | 9 (defn test [:uint256 x] :uint256 [:external :pure] 269 | 10 (+ x MIN)) 270 | ``` 271 | 272 | 273 | 274 | 275 | # Immutable 276 | 277 | ```clojure 278 | 1 (defvar OWNER (immutable :address)) 279 | 2 (defvar MY_IMMUTABLE (immutable :uint256)) 280 | 3 281 | 4 (defn __init__ [:uint256 _val] :external 282 | 5 (set OWNER msg/sender) 283 | 6 (set MY_IMMUTABLE _val)) 284 | 7 285 | 8 (defn getMyImmutable [] :uint256 [:external :pure] 286 | 9 MY_IMMUTABLE) 287 | ``` 288 | 289 | 290 | 291 | 292 | # If/Else 293 | 294 | ```clojure 295 | 1 (defn ifElse [:uint256 x] :uint256 :external 296 | 2 (if (<= x 10) 297 | 3 (return 1) 298 | 4 (if (<= x 20) 299 | 5 (return 2) 300 | 6 (return 3)))) 301 | 7 302 | 8 (defn absoluteValue [:uint256 x y] :uint256 [:external :pure] 303 | 9 (if (>= x y) 304 | 10 (return (- x y))) 305 | 11 (return (- y x))) 306 | ``` 307 | 308 | 309 | 310 | 311 | # For Loop 312 | 313 | ```clojure 314 | 1 (defn forLoop [] :uint256 [:external :pure] 315 | 2 (defvar s :uint256 0) 316 | 3 (for [i (range 10)] 317 | 4 (+= s i)) 318 | 5 ;; for loop through array elements 319 | 6 ;; find minimum of nums 320 | 7 (defvar nums (array :uint256 5) [4 5 1 9 3]) 321 | 8 (defvar x :uint256 (max_value :uint256)) 322 | 9 (for [num nums] 323 | 10 (if (< num x) 324 | 11 (set x num))) 325 | 12 (defvar c :uint256 0) 326 | 13 (for [i [1 2 3 4 5]] 327 | 14 (if (== i 2) 328 | 15 (continue)) 329 | 16 (if (== i 4) 330 | 17 (break)) 331 | 18 (+= c 1)) 332 | 19 c) 333 | 20 334 | 21 (defn sum [(array :uint256 10) nums] :uint256 [:external :pure] 335 | 22 (defvar s :uint256 0) 336 | 23 (for [n nums] 337 | 24 (+= s n)) 338 | 25 s) 339 | ``` 340 | 341 | 342 | 343 | 344 | # Errors 345 | 346 | ```clojure 347 | 1 (defvars 348 | 2 x (public :uint256) 349 | 3 owner (public :address)) 350 | 4 351 | 5 (defn __init__ [] :external 352 | 6 (set self/owner msg/sender)) 353 | 7 354 | 8 (defn testAssert [:uint256 x] :external 355 | 9 (assert (>= x 1) "x < 1") 356 | 10 (set self/x x)) 357 | 11 358 | 12 (defn testRaise [:uint256 x] :external 359 | 13 (if (<= x 1) 360 | 14 (raise "x < 1")) 361 | 15 (set self/x x)) 362 | 16 363 | 17 (defn _testErrorBubblesUp [:uint256 x] :internal 364 | 18 (assert (>= x 1) "x < 1") 365 | 19 (set self/x x)) 366 | 20 367 | 21 (defn testErrorBubblesUp [:uint256 x] :external 368 | 22 (self/_testErrorBubblesUp x) 369 | 23 (set self/x 123)) 370 | 24 371 | 25 (defn setOwner [:address owner] :external 372 | 26 (assert (== msg/sender self/owner) "!owner") 373 | 27 (assert (!= owner (empty :address)) "owner = zero") 374 | 28 (set self/owner owner)) 375 | ``` 376 | 377 | 378 | 379 | 380 | # Events 381 | 382 | ```clojure 383 | 1 (defevent Transfer 384 | 2 sender (indexed :address) 385 | 3 receiver (indexed :address) 386 | 4 amount :uint256) 387 | 5 388 | 6 (defn transfer [:address receiver :uint256 amount] :external 389 | 7 (log (Transfer msg/sender receiver amount))) 390 | 8 391 | 9 (defn mint [:uint256 amount] :external 392 | 10 (log (Transfer (empty :address) msg/sender amount))) 393 | 11 394 | 12 (defn burn [:uint256 amount] :external 395 | 13 (log (Transfer msg/sender (empty :address) amount))) 396 | ``` 397 | 398 | 399 | 400 | 401 | # Payable 402 | 403 | ```clojure 404 | 1 (defevent Deposit 405 | 2 sender (indexed :address) 406 | 3 amount :uint256) 407 | 4 408 | 5 (defn deposit [] [:external :payable] 409 | 6 (log (Deposit msg/sender msg/value))) 410 | 7 411 | 8 (defn getBalance [] :uint256 [:external :view] 412 | 9 ;; get balance of Ether stored in this contract 413 | 10 self/balance) 414 | 11 415 | 12 (defvar owner (public :address)) 416 | 13 417 | 14 (defn pay [] [:external :payable] 418 | 15 (assert (> msg/value 0) "msg.value = 0") 419 | 16 (set self/owner msg/sender)) 420 | ``` 421 | 422 | 423 | 424 | 425 | # Default Function 426 | 427 | ```clojure 428 | 1 (defevent Payment 429 | 2 sender (indexed :address) 430 | 3 amount :uint256) 431 | 4 432 | 5 (defn __default__ [] [:external :payable] 433 | 6 (log (Payment msg/sender msg/value))) 434 | ``` 435 | 436 | 437 | 438 | 439 | # Send Ether 440 | 441 | ```clojure 442 | 1 ;; receive ether into the contract 443 | 2 (defn __default__ [] [:external :payable] 444 | 3 (pass)) 445 | 4 446 | 5 (defn sendEther [:address to :uint256 amount] :external 447 | 6 ;; calls the default fn in the receiving contract 448 | 7 (send to amount)) 449 | 8 450 | 9 (defn sendAll [:address to] :external 451 | 10 (send to self/balance)) 452 | ``` 453 | 454 | 455 | 456 | 457 | # Raw Call 458 | 459 | ```clojure 460 | 1 (defn testRawCall [:address to :uint256 x y] :uint256 :external 461 | 2 (defvar res (bytes 32) 462 | 3 (raw_call to 463 | 4 (concat (method_id "multiply(uint256,uint256)") 464 | 5 (convert x :bytes32) 465 | 6 (convert y :bytes32)) 466 | 7 :max_outsize 32 467 | 8 :gas 100000 468 | 9 :value 0 469 | 10 )) 470 | 11 (defvar z :uint256 (convert res :uint256)) 471 | 12 z) 472 | 13 473 | 14 (defn sendEth [:address to] [:external :payable] 474 | 15 (raw_call to b"" :value msg/value)) 475 | ``` 476 | 477 | 478 | 479 | 480 | # Delegate Call 481 | 482 | ```clojure 483 | 1 (defvars x (public :uint256) 484 | 2 y (public :uint256)) 485 | 3 486 | 4 (defn updateX [:uint256 x] :external 487 | 5 (set self/x (+ x 1))) 488 | 6 489 | 7 (defn updateY [:uint256 y] :external 490 | 8 (set self/y (* y y))) 491 | ``` 492 | 493 | ```clojure 494 | 1 (defvars x (public :uint256) 495 | 2 y (public :uint256)) 496 | 3 497 | 4 (defn updateX [:address to :uint256 x] :external 498 | 5 (raw_call to 499 | 6 (concat 500 | 7 (method_id "updateX(uint256)") 501 | 8 (convert x :bytes32)) 502 | 9 :is_delegate_call True)) 503 | 10 504 | 11 (defn updateY [:address to :uint256 y] :external 505 | 12 (raw_call to 506 | 13 (concat 507 | 14 (method_id "updateY(uint256)") 508 | 15 (convert y :bytes32)) 509 | 16 :is_delegate_call True)) 510 | ``` 511 | 512 | 513 | 514 | 515 | # Interface 516 | 517 | ```clojure 518 | 1 (definterface TestInterface 519 | 2 (defn owner [] :address :view) 520 | 3 (defn setOwner [:address owner] :nonpayable) 521 | 4 (defn sendEth [] :payable) 522 | 5 (defn setOwnerAndSendEth [:address owner] :payable)) 523 | 6 524 | 7 (defvar test (public TestInterface)) 525 | 8 526 | 9 (defn __init__ [:address test] :external 527 | 10 (set self/test (TestInterface test))) 528 | 11 529 | 12 (defn getOwner [] :address [:external :view] 530 | 13 (.owner self/test)) 531 | 14 532 | 15 (defn getOwnerFromAddress [:address test] :address [:external :view] 533 | 16 (.owner (TestInterface test))) 534 | 17 535 | 18 (defn setOwner [:address owner] :external 536 | 19 (.setOwner self/test owner)) 537 | ``` 538 | 539 | ```clojure 540 | 1 (defvars 541 | 2 owner (public :address) 542 | 3 eth (public :uint256)) 543 | 4 544 | 5 (defn setOwner [:address owner] :external 545 | 6 (set self/owner owner)) 546 | 7 547 | 8 (defn sendEth [] [:external :payable] 548 | 9 (set self/eth msg/value)) 549 | 10 550 | 11 (defn setOwnerAndSendEth [:address owner] [:external :payable] 551 | 12 (set self/owner owner) 552 | 13 (set self/eth msg/value)) 553 | ``` 554 | 555 | 556 | 557 | 558 | # Hash Function 559 | 560 | ```clojure 561 | 1 (defn getHash [:address addr :uint256 num] :bytes32 [:external :pure] 562 | 2 (keccak256 563 | 3 (concat 564 | 4 (convert addr :bytes32) 565 | 5 (convert num :bytes32) 566 | 6 (convert "THIS IS A STRING" (bytes 16))))) 567 | 7 568 | 8 (defn getMessageHash [(string 100) _str] :bytes32 [:external :pure] 569 | 9 (keccak256 _str)) 570 | ``` 571 | 572 | 573 | 574 | 575 | # Re-Entrancy Lock 576 | 577 | ```clojure 578 | 1 (defn func0 [] [:external (nonreentrant "lock")] 579 | 2 (raw_call msg/sender b"" :value 0)) 580 | 3 581 | 4 (defn func1 [] [:external (nonreentrant "lock-2")] 582 | 5 (raw_call msg/sender b"" :value 0)) 583 | 6 584 | 7 (defn func2 [] [:external (nonreentrant "lock-2")] 585 | 8 (raw_call msg/sender b"" :value 0)) 586 | ``` 587 | 588 | 589 | 590 | 591 | # Self Destruct 592 | 593 | ```clojure 594 | 1 (defn __default__ [] [:external :payable] 595 | 2 (pass)) 596 | 3 597 | 4 (defn kill [] :external 598 | 5 (selfdestruct msg/sender)) 599 | 6 600 | 7 (defn burn [] :external 601 | 8 (selfdestruct (empty :address))) 602 | ``` 603 | -------------------------------------------------------------------------------- /dasybyexample.org: -------------------------------------------------------------------------------- 1 | #+title: Dasy By Example 2 | * Hello World 3 | #+include: "./examples/hello_world.dasy" src clojure -n 4 | * Data Types - Values 5 | #+include: "./examples/value_types.dasy" src clojure -n 6 | * Data Types - References 7 | #+include: "./examples/reference_types.dasy" src clojure -n 8 | * Dynamic Arrays 9 | #+include: "./examples/dynamic_arrays.dasy" src clojure -n 10 | * Functions 11 | #+include: "./examples/functions.dasy" src clojure -n 12 | * Internal and External Functions 13 | #+include: "./examples/function_visibility.dasy" src clojure -n 14 | * View and Pure Functions 15 | #+include: "./examples/view_pure.dasy" src clojure -n 16 | * Constructor 17 | #+include: "./examples/constructor.dasy" src clojure -n 18 | * Private and Public State Variables 19 | #+include: "./examples/private_public_state.dasy" src clojure -n 20 | * Constants 21 | #+include: "./examples/constants.dasy" src clojure -n 22 | * Immutable 23 | #+include: "./examples/immutable.dasy" src clojure -n 24 | * If/Else 25 | #+include: "./examples/if_else.dasy" src clojure -n 26 | * For Loop 27 | #+include: "./examples/for_loop.dasy" src clojure -n 28 | * Errors 29 | #+include: "./examples/error.dasy" src clojure -n 30 | * Events 31 | #+include: "./examples/event.dasy" src clojure -n 32 | * Payable 33 | #+include: "./examples/payable.dasy" src clojure -n 34 | * Default Function 35 | #+include: "./examples/default_function.dasy" src clojure -n 36 | * Send Ether 37 | #+include: "./examples/send_ether.dasy" src clojure -n 38 | * Raw Call 39 | #+include: "./examples/raw_call.dasy" src clojure -n 40 | * Delegate Call 41 | #+include: "./examples/test_delegate_call.dasy" src clojure -n 42 | 43 | #+include: "./examples/delegate_call.dasy" src clojure -n 44 | * Interface 45 | #+include: "./examples/interface.dasy" src clojure -n 46 | 47 | #+include: "./examples/test_interface.dasy" src clojure -n 48 | * Hash Function 49 | #+include: "./examples/hashing.dasy" src clojure -n 50 | * Re-Entrancy Lock 51 | #+include: "./examples/nonreentrant.dasy" src clojure -n 52 | * Self Destruct 53 | #+include: "./examples/selfdestruct.dasy" src clojure -n 54 | -------------------------------------------------------------------------------- /docs.org: -------------------------------------------------------------------------------- 1 | #+title: Dasy Docs 2 | #+SETUPFILE: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup 3 | * Current Status 4 | Dasy is currently in pre-alpha. The language's core is still being designed and implemented. 5 | * Syntax 6 | Dasy has a clojure-inspired lisp syntax with some influences from python. Some constructs are dasy-specific. 7 | Most vyper code can be translated by wrapping in parentheses properly. For example, you can assume that for =arr.append(10)= in Vyper, the equivalent Dasy is =(.append arr 10)= 8 | 9 | ** Tuples 10 | Tuples are represented by a quoted list such as ~'(1 2 3)~ 11 | 12 | The vyper equivalent is ~(1, 2, 3)~ 13 | ** Arrays 14 | Arrays are represented by a bracketed list, such as ~[1 2 3]~ 15 | 16 | The vyper equivalent is ~[1, 2, 3]~ 17 | ** Types 18 | Dasy has all of Vyper's types. Base types such as ~uint256~ are represented with a dasy 'keyword', which uses a colon and an identifier. Complex types are represented with a function-call syntax. Arrays are created with ~array~, or ~dyn-array~ for dynamic arrays. 19 | | Vyper | Dasy | 20 | |--------------------------+-----------------------------| 21 | | ~uint256~ | ~:uint256~ | 22 | | ~bool~ | ~:bool~ | 23 | | ~bytes32~ | ~:bytes32~ | 24 | | ~String[10]~ | ~(string 10)~ | 25 | | ~uint256[10]~ | ~(array :uint256 10)~ | 26 | | ~HashMap[uint256, bool]~ | ~(hash-map :uint256 :bool)~ | 27 | | ~DynArray[uint256, 5]~ | ~(dyn-array :uint256 5)~ | 28 | 29 | ** Operators 30 | Dasy has mostly identical operators and builtins as Vyper. There are a few small differences. 31 | *** Built-in chaining 32 | Binary operations are chained by default in Dasy. This allows you to specify more than two arguments at at time. 33 | 34 | Because of this, in Dasy, ~+~ functions like a ~sum~ operator. 35 | 36 | | Vyper | Dasy | 37 | |-----------------------------+---------------| 38 | | =2 + 3 + 4 + 5= | =(+ 2 3 4 5)= | 39 | | =x < y and y < z and z < a= | =(< x y z a)= | 40 | 41 | 42 | * Core Forms 43 | ** ~defn~ 44 | 45 | ~(defn fn-name args [return-type] visibility & body)~ 46 | 47 | This special form declares and defines a function within a smart contract. 48 | 49 | The ~args~ list may be an empty list, but must be present. Returning multiple values requires declaring the return type as a tuple. 50 | 51 | The ~return-type~ object is optional. If present, it may be a single keyword representing the return type, or it may be a tuple of keywords for returning multiple values. 52 | 53 | The ~visibility~ object may also be a keyword or list of keywords. Valid values are: 54 | 55 | - ~:external~ 56 | - ~:internal~ 57 | - ~:payable~ 58 | - ~:view~ 59 | - ~:pure~ 60 | - ~(nonreentrant "lock-name")~ 61 | 62 | #+begin_src clojure 63 | (defn noArgs [] :external (pass)) 64 | 65 | (defn setNum [:uint256 x] :external (pass)) 66 | 67 | (defn addNums [:uint256 x y] :uint256 [:external :pure] 68 | (+ x y)) 69 | 70 | (defn addAndSub [:uint256 x y] '(:uint256 :uint256) [:external :pure] 71 | '((+ x y) (- x y))) 72 | #+end_src 73 | ** ~defvar~ 74 | ~(defvar variable-name type [value])~ 75 | 76 | This special form declares and optionally assigns a value to a variable. 77 | 78 | Outside of a ~defn~ form, variables are stored in ~storage~ and accessible via ~self.variable-name~. 79 | 80 | Inside a ~defn~ form, variables are stored in ~memory~ and accessible directly. 81 | 82 | The ~value~ form is optional. 83 | 84 | #+begin_src clojure 85 | (defvar owner (public :address)) 86 | (defvar enabled :bool) 87 | 88 | (defn foo [] :external 89 | (defvar owner_memory :address self/owner)) ;; declare copy in memory 90 | #+end_src 91 | ** ~set~ 92 | ~(set name value)~ 93 | 94 | This special form assigns a value to a name. It is roughly equivalent to the equal sign ~=~ in Vyper. 95 | #+begin_src clojure 96 | ;; Create a string variable that can store maximum 100 characters 97 | (defvar greet (public (string 100))) 98 | 99 | (defn __init__ [] :external 100 | (set self/greet "Hello World")) ;; in vyper: self.greet = "Hello World" 101 | #+end_src 102 | ** ~definterface~ 103 | ~(definterface name & fns)~ 104 | 105 | This special form declares an interface. 106 | 107 | #+begin_src clojure 108 | (definterface TestInterface 109 | (defn owner [] :address :view) 110 | (defn setOwner [:address owner] :nonpayable) 111 | (defn sendEth [] :payable) 112 | (defn setOwnerAndSendEth [:address owner] :payable)) 113 | #+end_src 114 | ** ~defstruct~ 115 | ~(defstruct name & variables)~ 116 | 117 | This special form declares a struct. Variables should be declared in pairs of ~name~ and ~type~ 118 | 119 | #+begin_src clojure 120 | (defstruct Person 121 | name (string 100) 122 | age :uint256) 123 | #+end_src 124 | ** ~defevent~ 125 | ~(defevent name & fields)~ 126 | 127 | This special form declares an event. Fields should be declared in pairs of ~name~ and ~type~ 128 | 129 | #+begin_src clojure 130 | (defevent Transfer 131 | sender (indexed :address) 132 | receiver (indexed :address) 133 | amount :uint256) 134 | #+end_src 135 | ** ~defconst~ 136 | ~(defconst name value)~ 137 | 138 | This special form defines a constant. The value must be provided when defined. This value can never change. 139 | 140 | #+begin_src clojure 141 | (defconst MIN_AMT 100) 142 | (defconst GREETING "Hello") 143 | #+end_src 144 | ** ~defmacro~ 145 | ~(defmacro name args & body)~ 146 | 147 | This special form defines a macro. Macros are functions that run at compile time. Their inputs are code, and their outputs are code. They transform your code as it is built. 148 | 149 | Macros can be used to implement convenient shorthand syntaxes. They can also be used to pull in information from the outside world into your contract at build time. 150 | 151 | In the most simple terms, macros allow you to extend the Dasy compiler yourself in whichever way you see fit. 152 | 153 | #+begin_src clojure 154 | ;; (set-at myArr 0 100) -> (set (subscript myArr 0) 100) 155 | (defmacro set-at [array index new-val] `(set (subscript ~array ~index) ~new-val)) 156 | 157 | ;; (doto obj (.append 10) (.append 20)) -> (do (.append obj 10) (.append obj 20)) 158 | (defmacro doto [ obj #*cmds] 159 | (lfor c cmds 160 | `(~(get c 0) ~obj ~@(cut c 1 None)))) 161 | #+end_src 162 | * Control Structures 163 | ** If 164 | ~(if test body else-body)~ 165 | If executes a test, and depending on the result, either executes the ~body~ or the ~else-body~. 166 | 167 | #+begin_src clojure 168 | (if (x < 100) 169 | *** (return 1) (return 0)) 170 | #+end_src 171 | ** Loops 172 | *** For Loops 173 | ~for~ loops can operate on arrays directly, or on a ~range~ 174 | #+begin_src clojure 175 | (for [i nums] 176 | (+= sum i)) 177 | 178 | (for [i [1 2 3 4 5]] 179 | (+= sum i)) 180 | 181 | (for [i (range 10)] 182 | (+= sum i)) 183 | #+end_src 184 | 185 | In a ~for~ loop's body, ~continue~ and ~break~ behave the same as they do in Vyper. 186 | #+begin_src clojure 187 | (for [i (range 10)] 188 | (if (== i 5) 189 | (continue)) 190 | (+= sum i)) 191 | 192 | #+end_src 193 | * Errors 194 | ** ~assert~ 195 | ~assert~ behaves as it does in Vyper. It expects a test and an optional error message. 196 | #+begin_src clojure 197 | (assert (< x 100) "x must be less than 100") 198 | #+end_src 199 | ** ~raise~ 200 | ~raise~ behaves as it does in Vyper. It expects a message. 201 | #+begin_src clojure 202 | (if (>= x 100) 203 | (raise "x must be less than 100")) 204 | #+end_src 205 | 206 | * Built-in Macros 207 | 208 | ** ~/~ 209 | 210 | ~(set self/foo bar)~ 211 | 212 | Access object attributes. ~obj/name~ is shorthand for ~(. obj name)~ 213 | ** ~cond~ 214 | ~(cond & body)~ 215 | 216 | ~cond~ saves you from having too many nested if/elses 217 | 218 | #+begin_src clojure 219 | (if (< x 100) 220 | 100 221 | (if (< x 1000) 222 | 1000 223 | (if (< x 10000) 224 | 10000))) 225 | 226 | ;; this is equivalent 227 | (cond 228 | (< x 100) 100 229 | (< x 1000) 1000 230 | (< x 10000) 10000) 231 | 232 | #+end_src 233 | ** ~set-at~ 234 | 235 | ~(set-at obj index val)~ 236 | 237 | Sets a value at an index within an object. This object can be an array, dynamic array, or hashmap. 238 | 239 | This expands to ~(set (subscript array index) new-val)~ 240 | 241 | The vyper equivalent looks like ~obj[index] = val~ 242 | 243 | #+begin_src clojure 244 | (defvar arr (array :uint256 10) 245 | myMap (hash-map :addr :bool)) 246 | (set-at arr 0 100) ;; arr[0] = 100 247 | (set-at myMap 0x1234.... True) ;; myMap[0x1234....] = True 248 | #+end_src 249 | ** ~set-in~ 250 | 251 | ~(set-in obj attr val)~ 252 | 253 | Sets the value of an attribute of an object. This object is usually a struct. 254 | 255 | This expands to ~(set (. obj attr) val)~ 256 | 257 | #+begin_src clojure 258 | (defstruct Person 259 | name (string 10) 260 | age :uint256) 261 | 262 | (defvar p Person) 263 | (set-in p age 40) 264 | (set-in p name "Vitalik") 265 | #+end_src 266 | ** ~doto~ 267 | ~(doto obj & body)~ 268 | 269 | Call multiple functions on the same object. Allows for shorter code. 270 | 271 | ~(doto obj (.foo 10) (.bar 100))~ expands to ~(do (.foo obj 10) (.bar obj 100))~ 272 | 273 | #+begin_src clojure 274 | ;; above example rewritten with doto 275 | (defstruct Person 276 | name (string 10) 277 | age :uint256) 278 | 279 | (doto p 280 | (defvar Person) 281 | (set-in age 40) 282 | (set-in name "Vitalik")) 283 | #+end_src 284 | -------------------------------------------------------------------------------- /examples/ERC20.dasy: -------------------------------------------------------------------------------- 1 | (defevent Transfer 2 | sender (indexed :address) 3 | receiver (indexed :address) 4 | value :uint256) 5 | 6 | (defevent Approval 7 | owner (indexed :address) 8 | spender (indexed :address) 9 | value :uint256) 10 | 11 | (defvars 12 | :public 13 | name (string 32) 14 | symbol (string 32) 15 | decimals :uint8 16 | balanceOf (hash-map :address :uint256) 17 | allowance (hash-map :address (hash-map :address :uint256)) 18 | totalSupply :uint256 19 | minter :address) 20 | 21 | (defn __init__ [(string 32) name symbol :uint8 decimals :uint256 supply] :external 22 | (defvar totalSupply :uint256 (* supply 23 | (** 10 24 | (convert decimals :uint256)))) 25 | (set-self name symbol decimals totalSupply) 26 | (set-at self.balanceOf msg.sender totalSupply) 27 | (set self.minter msg.sender) 28 | (log (Transfer (empty address) msg/sender totalSupply))) 29 | 30 | (defn transfer [:address to :uint256 val] :bool :external 31 | (doto (get-at self/balanceOf msg/sender) 32 | (-= val)) 33 | (doto (get-at self/balanceOf to) 34 | (+= val)) 35 | (log (Transfer msg/sender to val)) 36 | True) 37 | 38 | (defn transferFrom [:address _from _to :uint256 val] :bool :external 39 | (doto (get-at self/balanceOf _from) 40 | (-= val)) 41 | (doto (get-at self/balanceOf _to) 42 | (+= val)) 43 | (doto (get-at self/allowance _from msg/sender) 44 | (-= val)) 45 | (log (Transfer _from _to val)) 46 | True) 47 | 48 | (defn approve [:address spender :uint256 val] :bool :external 49 | (set-at self/allowance msg/sender spender val) 50 | (log (Approval msg/sender spender val)) 51 | True) 52 | 53 | (defn mint [:address to :uint256 val] :external 54 | (assert (== msg/sender self/minter)) 55 | (assert (!= to (empty :address))) 56 | (+= self/totalSupply val) 57 | (doto (get-at self/balanceOf to) 58 | (+= val)) 59 | (log (Transfer (empty :address) to val))) 60 | 61 | (defn _burn [:address to :uint256 val] :internal 62 | (assert (!= to (empty :address))) 63 | (-= self/totalSupply val) 64 | (vyper "self.balanceOf[to] -= val") 65 | (log (Transfer to (empty :address) val))) 66 | 67 | (defn burn [:uint256 val] :external 68 | (self/_burn msg/sender val)) 69 | 70 | (defn burnFrom [:address _from :uint256 val] :external 71 | (doto (get-at self/allowance _from msg/sender) 72 | (-= val)) 73 | (self/_burn _from val)) 74 | -------------------------------------------------------------------------------- /examples/ERC20.vy: -------------------------------------------------------------------------------- 1 | # @dev Implementation of ERC-20 token standard. 2 | # @author Takayuki Jimba (@yudetamago) 3 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 4 | 5 | from vyper.interfaces import ERC20 6 | from vyper.interfaces import ERC20Detailed 7 | 8 | implements: ERC20 9 | implements: ERC20Detailed 10 | 11 | event Transfer: 12 | sender: indexed(address) 13 | receiver: indexed(address) 14 | value: uint256 15 | 16 | event Approval: 17 | owner: indexed(address) 18 | spender: indexed(address) 19 | value: uint256 20 | 21 | name: public(String[32]) 22 | symbol: public(String[32]) 23 | decimals: public(uint8) 24 | 25 | # NOTE: By declaring `balanceOf` as public, vyper automatically generates a 'balanceOf()' getter 26 | # method to allow access to account balances. 27 | # The _KeyType will become a required parameter for the getter and it will return _ValueType. 28 | # See: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings 29 | balanceOf: public(HashMap[address, uint256]) 30 | # By declaring `allowance` as public, vyper automatically generates the `allowance()` getter 31 | allowance: public(HashMap[address, HashMap[address, uint256]]) 32 | # By declaring `totalSupply` as public, we automatically create the `totalSupply()` getter 33 | totalSupply: public(uint256) 34 | minter: address 35 | 36 | 37 | @external 38 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 39 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 40 | self.name = _name 41 | self.symbol = _symbol 42 | self.decimals = _decimals 43 | self.balanceOf[msg.sender] = init_supply 44 | self.totalSupply = init_supply 45 | self.minter = msg.sender 46 | log Transfer(empty(address), msg.sender, init_supply) 47 | 48 | 49 | 50 | @external 51 | def transfer(_to : address, _value : uint256) -> bool: 52 | """ 53 | @dev Transfer token for a specified address 54 | @param _to The address to transfer to. 55 | @param _value The amount to be transferred. 56 | """ 57 | # NOTE: vyper does not allow underflows 58 | # so the following subtraction would revert on insufficient balance 59 | self.balanceOf[msg.sender] -= _value 60 | self.balanceOf[_to] += _value 61 | log Transfer(msg.sender, _to, _value) 62 | return True 63 | 64 | 65 | @external 66 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 67 | """ 68 | @dev Transfer tokens from one address to another. 69 | @param _from address The address which you want to send tokens from 70 | @param _to address The address which you want to transfer to 71 | @param _value uint256 the amount of tokens to be transferred 72 | """ 73 | # NOTE: vyper does not allow underflows 74 | # so the following subtraction would revert on insufficient balance 75 | self.balanceOf[_from] -= _value 76 | self.balanceOf[_to] += _value 77 | # NOTE: vyper does not allow underflows 78 | # so the following subtraction would revert on insufficient allowance 79 | self.allowance[_from][msg.sender] -= _value 80 | log Transfer(_from, _to, _value) 81 | return True 82 | 83 | 84 | @external 85 | def approve(_spender : address, _value : uint256) -> bool: 86 | """ 87 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 88 | Beware that changing an allowance with this method brings the risk that someone may use both the old 89 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 90 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 91 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 92 | @param _spender The address which will spend the funds. 93 | @param _value The amount of tokens to be spent. 94 | """ 95 | self.allowance[msg.sender][_spender] = _value 96 | log Approval(msg.sender, _spender, _value) 97 | return True 98 | 99 | 100 | @external 101 | def mint(_to: address, _value: uint256): 102 | """ 103 | @dev Mint an amount of the token and assigns it to an account. 104 | This encapsulates the modification of balances such that the 105 | proper events are emitted. 106 | @param _to The account that will receive the created tokens. 107 | @param _value The amount that will be created. 108 | """ 109 | assert msg.sender == self.minter 110 | assert _to != empty(address) 111 | self.totalSupply += _value 112 | self.balanceOf[_to] += _value 113 | log Transfer(empty(address), _to, _value) 114 | 115 | 116 | @internal 117 | def _burn(_to: address, _value: uint256): 118 | """ 119 | @dev Internal function that burns an amount of the token of a given 120 | account. 121 | @param _to The account whose tokens will be burned. 122 | @param _value The amount that will be burned. 123 | """ 124 | assert _to != empty(address) 125 | self.totalSupply -= _value 126 | self.balanceOf[_to] -= _value 127 | log Transfer(_to, empty(address), _value) 128 | 129 | 130 | @external 131 | def burn(_value: uint256): 132 | """ 133 | @dev Burn an amount of the token of msg.sender. 134 | @param _value The amount that will be burned. 135 | """ 136 | self._burn(msg.sender, _value) 137 | 138 | 139 | @external 140 | def burnFrom(_to: address, _value: uint256): 141 | """ 142 | @dev Burn an amount of the token from a given account. 143 | @param _to The account whose tokens will be burned. 144 | @param _value The amount that will be burned. 145 | """ 146 | self.allowance[_to][msg.sender] -= _value 147 | self._burn(_to, _value) 148 | -------------------------------------------------------------------------------- /examples/Transient.vy: -------------------------------------------------------------------------------- 1 | #pragma evm-version cancun 2 | 3 | @nonreentrant("lock") 4 | @external 5 | def foo() -> uint256: 6 | return 4 7 | -------------------------------------------------------------------------------- /examples/constants.dasy: -------------------------------------------------------------------------------- 1 | (defconst MY_CONSTANT 123) 2 | (defconst MIN 1) 3 | (defconst MAX 10) 4 | (defconst ADDR 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B) 5 | 6 | (defn getMyConstants [] '(:uint256 :uint256 :address) [:external :pure] 7 | '(MIN MAX ADDR)) 8 | 9 | (defn test [:uint256 x] :uint256 [:external :pure] 10 | (+ x MIN)) 11 | -------------------------------------------------------------------------------- /examples/constructor.dasy: -------------------------------------------------------------------------------- 1 | (defvars owner (public :address) 2 | createdAt (public :uint256) 3 | expiresAt (public :uint256) 4 | name (public (string 10))) 5 | 6 | (defn __init__ [(string 10) name :uint256 duration] :external 7 | ;; set owner to caller 8 | (set self/owner msg/sender) 9 | ;; set name from input 10 | (set self/name name) 11 | (set self/createdAt block/timestamp) 12 | (set self/expiresAt (+ block/timestamp 13 | duration))) 14 | -------------------------------------------------------------------------------- /examples/default_function.dasy: -------------------------------------------------------------------------------- 1 | (defevent Payment 2 | sender (indexed :address) 3 | amount :uint256) 4 | 5 | (defn __default__ [] [:external :payable] 6 | (log (Payment msg/sender msg/value))) 7 | -------------------------------------------------------------------------------- /examples/delegate_call.dasy: -------------------------------------------------------------------------------- 1 | (defvars x (public :uint256) 2 | y (public :uint256)) 3 | 4 | (defn updateX [:address to :uint256 x] :external 5 | (raw_call to 6 | (concat 7 | (method_id "updateX(uint256)") 8 | (convert x :bytes32)) 9 | :is_delegate_call True)) 10 | 11 | (defn updateY [:address to :uint256 y] :external 12 | (raw_call to 13 | (concat 14 | (method_id "updateY(uint256)") 15 | (convert y :bytes32)) 16 | :is_delegate_call True)) 17 | -------------------------------------------------------------------------------- /examples/dynamic_arrays.dasy: -------------------------------------------------------------------------------- 1 | ;; dynamic array of type uint256, max 3 elements 2 | (defvar nums (public (dyn-array :uint256 3))) 3 | 4 | (defn __init__ [] :external 5 | (doto self/nums 6 | (.append 11) 7 | (.append 22) 8 | (.append 33) 9 | ;; this will revert, appending to array with max 3 elements 10 | ;; (.append self/nums 44) 11 | ) 12 | ;; delete all elements 13 | (set self/nums []) 14 | ;; set values 15 | (set self/nums [1 2 3])) 16 | 17 | (defn examples [(dyn-array :uint256 5) xs] (dyn-array :uint256 8) [:external :pure] 18 | (defvar ys (dyn-array :uint256 8) [1 2 3]) 19 | (for [x xs] 20 | (.append ys x)) 21 | (return ys)) 22 | 23 | (defn filter [(dyn-array :address 5) addrs] (dyn-array :address 5) [:external :pure] 24 | (defvar nonzeros (dyn-array :address 5) []) 25 | (for [addr addrs] 26 | (if (!= addr (empty :address)) 27 | (do (.append nonzeros addr)))) 28 | (return nonzeros)) 29 | -------------------------------------------------------------------------------- /examples/enum.dasy: -------------------------------------------------------------------------------- 1 | (defenum Roles 2 | ADMIN 3 | USER) 4 | 5 | (defvar roles (public (hash-map :address Roles))) 6 | 7 | (defn __init__ [] :external 8 | (set-at self/roles msg/sender Roles/ADMIN)) 9 | 10 | (defn getPrice [] :uint256 [:external :view] 11 | (if (== (get-at self/roles msg/sender) Roles/ADMIN) 12 | 10 13 | (if (== (get-at self/roles msg/sender) Roles/USER) 14 | 20 15 | 30))) 16 | 17 | (defn getPriceUsingCondp [] :uint256 [:external :view] 18 | (defvar role Roles (get-at self/roles msg/sender)) 19 | (condp == role 20 | Roles/ADMIN 10 21 | Roles/USER 20 22 | :else 30)) 23 | -------------------------------------------------------------------------------- /examples/error.dasy: -------------------------------------------------------------------------------- 1 | (defvars 2 | x (public :uint256) 3 | owner (public :address)) 4 | 5 | (defn __init__ [] :external 6 | (set self/owner msg/sender)) 7 | 8 | (defn testAssert [:uint256 x] :external 9 | (assert (>= x 1) "x < 1") 10 | (set self/x x)) 11 | 12 | (defn testRaise [:uint256 x] :external 13 | (if (<= x 1) 14 | (raise "x < 1")) 15 | (set self/x x)) 16 | 17 | (defn _testErrorBubblesUp [:uint256 x] :internal 18 | (assert (>= x 1) "x < 1") 19 | (set self/x x)) 20 | 21 | (defn testErrorBubblesUp [:uint256 x] :external 22 | (self/_testErrorBubblesUp x) 23 | (set self/x 123)) 24 | 25 | (defn setOwner [:address owner] :external 26 | (assert (== msg/sender self/owner) "!owner") 27 | (assert (!= owner (empty :address)) "owner = zero") 28 | (set self/owner owner)) 29 | -------------------------------------------------------------------------------- /examples/event.dasy: -------------------------------------------------------------------------------- 1 | (defevent Transfer 2 | sender (indexed :address) 3 | receiver (indexed :address) 4 | amount :uint256) 5 | 6 | (defn transfer [:address receiver :uint256 amount] :external 7 | (log (Transfer msg/sender receiver amount))) 8 | 9 | (defn mint [:uint256 amount] :external 10 | (log (Transfer (empty :address) msg/sender amount))) 11 | 12 | (defn burn [:uint256 amount] :external 13 | (log (Transfer msg/sender (empty :address) amount))) 14 | -------------------------------------------------------------------------------- /examples/for_loop.dasy: -------------------------------------------------------------------------------- 1 | (defn forLoop [] :uint256 [:external :pure] 2 | (defvar s :uint256 0) 3 | (for [i (range 10)] 4 | (+= s i)) 5 | ;; for loop through array elements 6 | ;; find minimum of nums 7 | (defvar nums (array :uint256 5) [4 5 1 9 3]) 8 | (defvar x :uint256 (max_value :uint256)) 9 | (for [num nums] 10 | (if (< num x) 11 | (set x num))) 12 | (defvar c :uint256 0) 13 | (for [i [1 2 3 4 5]] 14 | (if (== i 2) 15 | (continue)) 16 | (if (== i 4) 17 | (break)) 18 | (+= c 1)) 19 | c) 20 | 21 | (defn sum [(array :uint256 10) nums] :uint256 [:external :pure] 22 | (defvar s :uint256 0) 23 | (for [n nums] 24 | (+= s n)) 25 | s) 26 | -------------------------------------------------------------------------------- /examples/function_visibility.dasy: -------------------------------------------------------------------------------- 1 | ;; internal functions can only be called inside this contract 2 | (defn _add [:uint256 x y] :uint256 [:internal :pure] 3 | (+ x y)) 4 | 5 | ;; external functions can only be called from outside this contract 6 | (defn extFunc [] :bool [:external :view] 7 | True) 8 | 9 | ;; external functions can only be called from outside this contract 10 | (defn avg [:uint256 x y] :uint256 [:external :view] 11 | ;; cannot call other external function 12 | ;; (.extFunc self) 13 | 14 | ;; can call internal functions 15 | (defvar z :uint256 (self._add x y)) 16 | (/ (+ x y) 17 | 2)) 18 | 19 | (defn _sqr [:uint256 x] :uint256 [:internal :pure] 20 | (* x x)) 21 | 22 | (defn sumOfSquares [:uint256 x y] :uint256 [:external :view] 23 | (+ (self._sqr x) 24 | (self._sqr y))) 25 | -------------------------------------------------------------------------------- /examples/functions.dasy: -------------------------------------------------------------------------------- 1 | (defn multiply [:uint256 x y] :uint256 [:external :pure] 2 | (* x y)) 3 | 4 | (defn divide [:uint256 x y] :uint256 [:external :pure] 5 | (/ x y)) 6 | 7 | (defn multiOut [] '(:uint256 :bool) [:external :pure] 8 | '(1 True)) 9 | 10 | (defn addAndSub [:uint256 x y] '(:uint256 :uint256) [:external :pure] 11 | '((+ x y) (- x y))) 12 | -------------------------------------------------------------------------------- /examples/hashing.dasy: -------------------------------------------------------------------------------- 1 | (defn getHash [:address addr :uint256 num] :bytes32 [:external :pure] 2 | (keccak256 3 | (concat 4 | (convert addr :bytes32) 5 | (convert num :bytes32) 6 | (convert "THIS IS A STRING" (bytes 16))))) 7 | 8 | (defn getMessageHash [(string 100) _str] :bytes32 [:external :pure] 9 | (keccak256 _str)) 10 | -------------------------------------------------------------------------------- /examples/hello_world.dasy: -------------------------------------------------------------------------------- 1 | 2 | ;; Contract containing a single greeting variable 3 | 4 | ;; Create a string variable that can store maximum 100 characters 5 | (defvar greet (public (string 100))) 6 | 7 | (defn __init__ [] :external 8 | (set self/greet "Hello World")) 9 | -------------------------------------------------------------------------------- /examples/if_else.dasy: -------------------------------------------------------------------------------- 1 | (defn useIf [:uint256 x] :uint256 :external 2 | (if (<= x 10) 3 | 1 4 | (if (<= x 20) 5 | 2 6 | 3))) 7 | 8 | (defn useCond [:uint256 x] :uint256 :external 9 | (cond 10 | (<= x 10) 1 11 | (<= x 20) 2 12 | :else 3)) 13 | 14 | (defn useCondp [:uint256 x] :uint256 :external 15 | (condp <= x 16 | 10 1 17 | 20 2 18 | :else 3)) 19 | 20 | (defn absoluteValue [:uint256 x y] :uint256 [:external :pure] 21 | (if (>= x y) 22 | (- x y) 23 | (- y x))) 24 | 25 | (defn setIf [:uint256 x] :uint256 [:external :pure] 26 | (defvar y :uint256 27 | (if (<= x 10) 28 | 1 29 | 2)) 30 | y) 31 | -------------------------------------------------------------------------------- /examples/immutable.dasy: -------------------------------------------------------------------------------- 1 | (defvar OWNER (immutable :address)) 2 | (defvar MY_IMMUTABLE (immutable :uint256)) 3 | 4 | (defn __init__ [:uint256 _val] :external 5 | (set OWNER msg/sender) 6 | (set MY_IMMUTABLE _val)) 7 | 8 | (defn getMyImmutable [] :uint256 [:external :pure] 9 | MY_IMMUTABLE) 10 | -------------------------------------------------------------------------------- /examples/infix_macro.dasy: -------------------------------------------------------------------------------- 1 | ;; Infix Notation Macro Demo 2 | ;; 3 | ;; This file demonstrates how to use macros to create infix notation 4 | 5 | ;; This is a simple function that adds two numbers. It is written in 6 | ;; prefix notation, which is the default in lisp. 7 | 8 | (defn prefix_add [:uint256 x y] :uint256 [:external :pure] 9 | (+ x y)) 10 | 11 | ;; in lisp, we can use macros to create new syntax. Here we define a 12 | ;; macro called infix. The macro takes an expression as an argument, 13 | ;; and returns a new expression. The new expression swaps the first 14 | ;; two arguments of the original expression. 15 | 16 | (defmacro infix [expr] 17 | "Swap the first two arguments of an expression" 18 | (let [[arg1 op arg2] expr] ;; destructure the expression 19 | `(~op ~arg1 ~arg2))) 20 | 21 | 22 | ;; Now we can write infix_add, which is more readable than 23 | ;; prefix_add to some people. The compiler will expand infix_add to 24 | ;; prefix_add, so the two functions are equivalent. 25 | (defn infix_add [:uint256 x y] :uint256 [:external :pure] 26 | (infix 27 | (x + y))) 28 | -------------------------------------------------------------------------------- /examples/interface.dasy: -------------------------------------------------------------------------------- 1 | (interface! "examples/test_interface.vy") 2 | 3 | (defvar test (public TestInterface)) 4 | 5 | (defn __init__ [:address test] :external 6 | (set self/test (TestInterface test))) 7 | 8 | (defn getOwner [] :address [:external :view] 9 | (.owner self/test)) 10 | 11 | (defn getOwnerFromAddress [:address test] :address [:external :view] 12 | (.owner (TestInterface test))) 13 | 14 | (defn setOwner [:address owner] :external 15 | (.setOwner self/test owner)) 16 | -------------------------------------------------------------------------------- /examples/mutable_hello.dasy: -------------------------------------------------------------------------------- 1 | 2 | ;; Contract which extends the hello_world contract via `include` 3 | 4 | ;; code from hello_world.dasy is injected here 5 | (include! "examples/hello_world.dasy") 6 | 7 | ;; define a new function, which references code from the hello_world contract 8 | (defn setGreeting [(string 100) newGreeting] :external 9 | (set self/greet newGreeting)) 10 | -------------------------------------------------------------------------------- /examples/nonreentrant.dasy: -------------------------------------------------------------------------------- 1 | (pragma :evm-version "paris") 2 | 3 | (defn func0 [] [:external (nonreentrant "lock")] 4 | (raw_call msg/sender b"" :value 0)) 5 | 6 | (defn func1 [] [:external (nonreentrant "lock_2")] 7 | (raw_call msg/sender b"" :value 0)) 8 | 9 | (defn func2 [] [:external (nonreentrant "lock_2")] 10 | (raw_call msg/sender b"" :value 0)) 11 | -------------------------------------------------------------------------------- /examples/nonreentrant2.dasy: -------------------------------------------------------------------------------- 1 | ;; (interface! "examples/nonreentrantenforcer.dasy") 2 | (definterface Nonreentrantenforcer 3 | (defn func0 [] :nonpayable)) 4 | 5 | (defvar target (public Nonreentrantenforcer)) 6 | 7 | (defn __init__ [:address target] :external 8 | (set self/target (Nonreentrantenforcer target))) 9 | 10 | (defn callback [] :external 11 | (.func0 self/target)) 12 | 13 | (defn __fallback__ [] :external 14 | (.func0 self/target)) 15 | -------------------------------------------------------------------------------- /examples/nonreentrantenforcer.dasy: -------------------------------------------------------------------------------- 1 | (pragma :evm-version "cancun") 2 | 3 | (defn func0 [] [:external (nonreentrant "lock")] 4 | (raw_call msg/sender b"" :value 0)) 5 | -------------------------------------------------------------------------------- /examples/nonreentrantenforcer.vy: -------------------------------------------------------------------------------- 1 | #pragma evm-version cancun 2 | 3 | @nonreentrant("lock") 4 | @external 5 | def func0(): 6 | raw_call(msg.sender, b"", value=0) 7 | -------------------------------------------------------------------------------- /examples/payable.dasy: -------------------------------------------------------------------------------- 1 | (defevent Deposit 2 | sender (indexed :address) 3 | amount :uint256) 4 | 5 | (defn deposit [] [:external :payable] 6 | (log (Deposit msg/sender msg/value))) 7 | 8 | (defn getBalance [] :uint256 [:external :view] 9 | ;; get balance of Ether stored in this contract 10 | self/balance) 11 | 12 | (defvar owner (public :address)) 13 | 14 | (defn pay [] [:external :payable] 15 | (assert (> msg/value 0) "msg.value = 0") 16 | (set self/owner msg/sender)) 17 | -------------------------------------------------------------------------------- /examples/private_public_state.dasy: -------------------------------------------------------------------------------- 1 | (defvars 2 | owner (public :address) 3 | foo :uint256 4 | bar (public :bool)) 5 | 6 | (defn __init__ [] :external 7 | (set self/owner msg/sender) 8 | (set self/foo 123) 9 | (set self/bar True)) 10 | -------------------------------------------------------------------------------- /examples/raw_call.dasy: -------------------------------------------------------------------------------- 1 | (defn testRawCall [:address to :uint256 x y] :uint256 :external 2 | (defvar res (bytes 32) 3 | (raw_call to 4 | (concat (method_id "multiply(uint256,uint256)") 5 | (convert x :bytes32) 6 | (convert y :bytes32)) 7 | :max_outsize 32 8 | :gas 100000 9 | :value 0 10 | )) 11 | (defvar z :uint256 (convert res :uint256)) 12 | z) 13 | 14 | (defn sendEth [:address to] [:external :payable] 15 | (raw_call to b"" :value msg/value)) 16 | -------------------------------------------------------------------------------- /examples/reference_types.dasy: -------------------------------------------------------------------------------- 1 | (defstruct Person 2 | name (string 100) 3 | age :uint256) 4 | 5 | (defvars 6 | :public 7 | nums (array :uint256 10) ;; fixed size list, must be bounded 8 | myMap (hash-map :address :uint256) 9 | person Person) 10 | 11 | (defn __init__ [] :external 12 | (doto self/nums 13 | (set-at 0 123) ;; this updates self.nums[0] 14 | (set-at 9 456)) ;; this updates self.nums[9] 15 | 16 | ;; copies self.nums to array in memory 17 | (defvar arr (array :uint256 10) self/nums) 18 | (set-at arr 0 123) ;; does not modify self/nums 19 | 20 | ;; this updates self/myMap 21 | (doto self/myMap 22 | (set-at msg/sender 1) ;; self.myMap[msg.sender] = 1 23 | (set-at msg/sender 11)) ;; self.myMap[msg.sender] = 11 24 | 25 | ;; this updates self/person 26 | (doto self/person 27 | (set-in age 11) 28 | (set-in name "Dasy")) 29 | 30 | ;; you could put defvar inside a doto like the arr example 31 | ;; above, but I don't think that is very readable 32 | ;; doing it this way is clearer, leaving the defvar out of doto 33 | ;; Person struct is copied into memory 34 | (defvar p Person self/person) 35 | (set-in p name "Solidity")) 36 | 37 | (defn literalPerson [] Person :external 38 | (Person {:name "Foo" :age 100})) 39 | -------------------------------------------------------------------------------- /examples/selfdestruct.dasy: -------------------------------------------------------------------------------- 1 | (defn __default__ [] [:external :payable] 2 | (pass)) 3 | 4 | (defn kill [] :external 5 | (selfdestruct msg/sender)) 6 | 7 | (defn burn [] :external 8 | (selfdestruct (empty :address))) 9 | -------------------------------------------------------------------------------- /examples/send_ether.dasy: -------------------------------------------------------------------------------- 1 | ;; receive ether into the contract 2 | (defn __default__ [] [:external :payable] 3 | (pass)) 4 | 5 | (defn sendEther [:address to :uint256 amount] :external 6 | ;; calls the default fn in the receiving contract 7 | (send to amount)) 8 | 9 | (defn sendAll [:address to] :external 10 | (send to self/balance)) 11 | -------------------------------------------------------------------------------- /examples/simple_auction.dasy: -------------------------------------------------------------------------------- 1 | ;; Open Auction 2 | 3 | (defvars 4 | ;; Auction params 5 | ;; beneficiary receives money from highest bidder 6 | beneficiary (public :address) 7 | auctionStart (public :uint256) 8 | auctionEnd (public :uint256) 9 | 10 | ;; current state of auction 11 | highestBidder (public :address) 12 | highestBid (public :uint256) 13 | 14 | ;; set to true at the end, disallows any change 15 | ended (public :bool) 16 | 17 | ;; keep track of refunded bids so we can follow the entire withdraw pattern 18 | pendingReturns (public (hash-map :address :uint256))) 19 | 20 | ;; create a simple auction with auction_start and 21 | ;; bidding_time seconds bidding time on behalf of 22 | ;; the beneficiary address beneficiary 23 | (defn __init__ [:address beneficiary :uint256 auction_start bidding_time] :external 24 | (set self/beneficiary beneficiary) 25 | ;; auction start time can be in the past, present, or future 26 | (set self/auctionStart auction_start) 27 | ;; auction end time should be in the future 28 | (->> bidding_time 29 | (+ self.auctionStart) 30 | (set self/auctionEnd))) 31 | 32 | ;; Bid on the auction with the value sent with the transaction 33 | ;; the value will only be refunded if the auction is not won 34 | (defn bid [] [:external :payable] 35 | ;; check if bidding period has started 36 | (assert (>= block/timestamp self/auctionStart)) 37 | ;; Check if bidding period is over 38 | (assert (< block/timestamp self/auctionEnd)) 39 | ;; Check if bid is high enough 40 | (assert (> msg/value self/highestBid)) 41 | ;; Track the refund for the previous highest bidder 42 | (-> self/pendingReturns 43 | (subscript self/highestBidder) 44 | (+= self/highestBid)) 45 | ;; Track new high bid 46 | (set self/highestBidder msg/sender) 47 | (set self/highestBid msg/value)) 48 | 49 | ;; withdraw a previously refunded bid 50 | (defn withdraw [] :external 51 | (defvar pending_amount :uint256 (get-at self/pendingReturns msg/sender)) 52 | (set-at self/pendingReturns msg/sender 0) 53 | (send msg/sender pending_amount)) 54 | 55 | ;; end the auction and send the highest bid 56 | (defn endAuction [] :external 57 | ;; check if auction end time has been reached) 58 | (assert (>= block/timestamp self/auctionEnd)) 59 | ;; check if this function has already been called 60 | (assert (not self/ended)) 61 | 62 | ;; effects 63 | (set self/ended True) 64 | 65 | ;; interactions 66 | (send self/beneficiary self/highestBid)) 67 | -------------------------------------------------------------------------------- /examples/test_delegate_call.dasy: -------------------------------------------------------------------------------- 1 | (defvars x (public :uint256) 2 | y (public :uint256)) 3 | 4 | (defn updateX [:uint256 x] :external 5 | (set self/x (+ x 1))) 6 | 7 | (defn updateY [:uint256 y] :external 8 | (set self/y (* y y))) 9 | -------------------------------------------------------------------------------- /examples/test_interface.dasy: -------------------------------------------------------------------------------- 1 | (defvars 2 | owner (public :address) 3 | eth (public :uint256)) 4 | 5 | (defn setOwner [:address owner] :external 6 | (set self/owner owner)) 7 | 8 | (defn sendEth [] [:external :payable] 9 | (set self/eth msg/value)) 10 | 11 | (defn setOwnerAndSendEth [:address owner] [:external :payable] 12 | (set self/owner owner) 13 | (set self/eth msg/value)) 14 | -------------------------------------------------------------------------------- /examples/test_interface.vy: -------------------------------------------------------------------------------- 1 | owner: public(address) 2 | eth: public(uint256) 3 | 4 | 5 | @external 6 | def setOwner(owner: address): 7 | self.owner = owner 8 | 9 | 10 | @external 11 | @payable 12 | def sendEth(): 13 | self.eth = msg.value 14 | 15 | 16 | @external 17 | @payable 18 | def setOwnerAndSendEth(owner: address): 19 | self.owner = owner 20 | self.eth = msg.value 21 | -------------------------------------------------------------------------------- /examples/transient_nonreentrant.dasy: -------------------------------------------------------------------------------- 1 | 2 | ;; use `pragma` to specify the EVM version 3 | (pragma :evm-version "cancun") 4 | 5 | ;; this reentrancy lock will use tstore/tload 6 | (defn func0 [] [:external (nonreentrant "lock")] 7 | (raw_call msg/sender b"" :value 0)) 8 | 9 | -------------------------------------------------------------------------------- /examples/unsafe_ops.dasy: -------------------------------------------------------------------------------- 1 | (defn unsafe_sub [:uint256 x y] :uint256 :external 2 | (-! x y)) 3 | 4 | (defn unsafe_add [:uint256 x y] :uint256 :external 5 | (+! x y)) 6 | 7 | (defn unsafe_mul [:uint256 x y] :uint256 :external 8 | (*! x y)) 9 | 10 | (defn unsafe_div [:uint256 x y] :uint256 :external 11 | (/! x y)) 12 | -------------------------------------------------------------------------------- /examples/value_types.dasy: -------------------------------------------------------------------------------- 1 | (defvars 2 | b (public :bool) 3 | i (public :int128) 4 | u (public :uint256) 5 | addr (public :address) 6 | b32 :bytes32 7 | bs (public (bytes 100)) 8 | s (public (string 100))) 9 | 10 | (defn __init__ [] :external 11 | (set self/b False) 12 | (set self/i -1) 13 | (set self/u 123) 14 | (set self/b32 0xada1b75f8ae9a65dcc16f95678ac203030505c6b465c8206e26ae84b525cdacb) 15 | (set self/bs b"\x01") 16 | (set self/s "Hello Dasy")) 17 | -------------------------------------------------------------------------------- /examples/venom.dasy: -------------------------------------------------------------------------------- 1 | (defn retOne [] :uint256 :external 2 | (ir :uint256 (seq 1))) 3 | 4 | (defn addTwoNums [:uint256 x y] :uint256 :external 5 | (ir :uint256 6 | (add (calldataload 4) 7 | (calldataload 36)))) 8 | -------------------------------------------------------------------------------- /examples/venom_comp.vy: -------------------------------------------------------------------------------- 1 | @external 2 | def retOne() -> uint256: 3 | return 1 4 | 5 | 6 | @external 7 | def addTwoNums(a: uint256, b: uint256) -> uint256: 8 | return unsafe_add(a, b) 9 | -------------------------------------------------------------------------------- /examples/view_pure.dasy: -------------------------------------------------------------------------------- 1 | (defvar num (public :uint256)) 2 | 3 | ;; Pure functions do not read any state or global variables 4 | (defn pureFunc [:uint256 x] :uint256 [:external :pure] 5 | x) 6 | 7 | ;; View functions might read state or global state, or call an internal function 8 | (defn viewFunc [:uint256 x] :bool [:external :view] 9 | (> x self/num)) 10 | 11 | (defn sum [:uint256 x y z] :uint256 [:external :pure] 12 | (+ x y z)) 13 | 14 | (defn addNum [:uint256 x] :uint256 [:external :view] 15 | (+ x self/num)) 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "dasy" 3 | version = "0.1.29" 4 | description = "an evm lisp" 5 | authors = ["z80 "] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.10, <3.12" 9 | argparse = "^1.4.0" 10 | dasy-hy = "0.24.2" 11 | hyrule = "^0.2" 12 | pytest = "^7.1.3" 13 | vyper = "^0.3.10" 14 | eth-abi = "^4.0.0" 15 | eth-typing = "^3.2.0" 16 | py-evm = ">=0.6.1a2" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = ">=7.1" 20 | black = {version = "^22.8.0", allow-prereleases = true} 21 | titanoboa = "0.1.8" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | 27 | [tool.poetry.scripts] 28 | "dasy" = "dasy:main" 29 | 30 | [tool.pytest.ini_options] 31 | filterwarnings = [ 32 | "error", 33 | "ignore::UserWarning", 34 | # note the use of single quote below to denote "raw" strings in TOML 35 | 'ignore::DeprecationWarning', 36 | ] 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | vyper 2 | titanoboa 3 | dasy-hy 4 | hyrule 5 | black 6 | pytest 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z80dev/dasy/6c96690a79f415f69e9bc4667d2e70eb1440a748/tests/__init__.py -------------------------------------------------------------------------------- /tests/parser/test_utils.py: -------------------------------------------------------------------------------- 1 | from dasy.parser.utils import ( 2 | process_body, 3 | add_src_map, 4 | set_parent_children, 5 | build_node, 6 | next_node_id_maker, 7 | pairwise, 8 | filename_to_contract_name, 9 | has_return, 10 | ) 11 | from vyper.ast.nodes import Expr 12 | import dasy 13 | 14 | 15 | def test_filename_to_contract_name(): 16 | filename = "test.vy" 17 | assert filename_to_contract_name(filename) == "Test" 18 | 19 | filename = "test_test.vy" 20 | assert filename_to_contract_name(filename) == "TestTest" 21 | 22 | 23 | def test_next_nodeid(): 24 | next_nodeid = next_node_id_maker() 25 | assert next_nodeid() == 0 26 | assert next_nodeid() == 1 27 | assert next_nodeid() == 2 28 | 29 | 30 | def test_pairwise(): 31 | assert list(pairwise([1, 2, 3, 4])) == [(1, 2), (3, 4)] 32 | 33 | 34 | def test_has_return(): 35 | assert has_return(dasy.read("(return 1)")) 36 | assert has_return(dasy.read("(return 1 2)")) 37 | assert has_return(dasy.read("(return)")) 38 | assert has_return(dasy.read("(return (return 1))")) 39 | assert not has_return(dasy.read("(1 2 3)")) 40 | 41 | 42 | def test_build_node(): 43 | node = build_node(Expr, value=1) 44 | node_id = node.node_id 45 | assert node == Expr(node_id=node_id, ast_type="Expr", value=1) 46 | 47 | 48 | def test_set_parent_children(): 49 | parent = build_node(Expr, value=1) 50 | child = build_node(Expr, value=2) 51 | set_parent_children(parent, [child]) 52 | assert child._parent == parent 53 | assert parent._children == set([child]) 54 | 55 | 56 | def test_add_src_map(): 57 | src = "(1 2 3)" 58 | node = dasy.read(src) 59 | ast_node = build_node(Expr, value=1) 60 | ast_node = add_src_map(src, node, ast_node) 61 | assert ast_node.full_source_code == src 62 | assert ast_node.lineno == 1 63 | assert ast_node.end_lineno == 1 64 | assert ast_node.col_offset == 1 65 | assert ast_node.end_col_offset == 7 66 | 67 | 68 | def test_process_body(): 69 | body = [dasy.read("(1 2 3)"), dasy.read("(4 5 6)")] 70 | assert process_body(body) == [ 71 | dasy.read("1"), 72 | dasy.read("2"), 73 | dasy.read("3"), 74 | dasy.read("4"), 75 | dasy.read("5"), 76 | dasy.read("6"), 77 | ] 78 | -------------------------------------------------------------------------------- /tests/test_dasy.py: -------------------------------------------------------------------------------- 1 | from vyper.evm.opcodes import anchor_evm_version 2 | import dasy 3 | from boa.vyper.contract import VyperContract 4 | import boa 5 | 6 | 7 | def test_compare_venom_vyper(): 8 | c = compile("examples/venom.dasy") 9 | v = boa.load("examples/venom_comp.vy") 10 | 11 | for contract in [c, v]: 12 | assert contract.retOne() == 1 13 | assert contract.addTwoNums(1, 2) == 3 14 | 15 | 16 | # def test_merkle(): 17 | # leaf3 = 0xdca3326ad7e8121bf9cf9c12333e6b2271abe823ec9edfe42f813b1e768fa57b 18 | # leaf_bytes = leaf3.to_bytes(32, 'big') 19 | # merkle_root = 0xcc086fcc038189b4641db2cc4f1de3bb132aefbd65d510d817591550937818c7 20 | # root_bytes = merkle_root.to_bytes(32, 'big') 21 | # proof = [0x8da9e1c820f9dbd1589fd6585872bc1063588625729e7ab0797cfc63a00bd950, 0x995788ffc103b987ad50f5e5707fd094419eb12d9552cc423bd0cd86a3861433] 22 | # proof_bytes = [x.to_bytes(32, 'big') for x in proof] 23 | # vyper_merkle = boa.load("examples/merkle.vy") 24 | 25 | # in1 = 0x835ba2995566015bd49e561c1210937952c6843e10010f333a65b51f69247b44 26 | # in1 = in1.to_bytes(32, 'big') 27 | 28 | # in2 = 0x97bcb6ec8d1a742a9e39be8bf20cd581d3af6b4faa63e4e72d67ff57a81b72e9 29 | # in2 = in2.to_bytes(32, 'big') 30 | 31 | # in3 = 0xdd1b8d11e7734e8c06816161afb24a5dfa82761dd92afaec2f037f0cd0e369f4 32 | # in3 = in3.to_bytes(32, 'big') 33 | 34 | # leaf = 0x1aD91ee08f21bE3dE0BA2ba6918E714dA6B45836000000000000000000000000 35 | # leaf = leaf.to_bytes(32, 'big') 36 | 37 | # assert vyper_merkle.verify([in1, in2], in3, leaf) 38 | 39 | 40 | def compile_src(src: str, *args) -> VyperContract: 41 | ast = dasy.compile(src, include_abi=True) 42 | return VyperContract(ast, *args) 43 | 44 | 45 | def compile(filename: str, *args) -> VyperContract: 46 | with open(filename) as f: 47 | src = f.read() 48 | return compile_src(src, *args) 49 | 50 | 51 | def test_binops(): 52 | src = """ 53 | (defn plus [] :uint256 :external 54 | (+ 1 2)) 55 | """ 56 | c = compile_src(src) 57 | assert c.plus() == 3 58 | 59 | 60 | def test_chain_binops(): 61 | src = """ 62 | (defn plus [] :uint256 :external 63 | (+ 1 2 3 4 5 6)) 64 | """ 65 | c = compile_src(src) 66 | assert c.plus() == 21 67 | 68 | 69 | def test_defvars(): 70 | src = """ 71 | (defvars x :uint256) 72 | (defn setX [:uint256 x] :external 73 | (set self/x x)) 74 | (defn getX [] :uint256 [:external :view] self/x) 75 | """ 76 | c = compile_src(src) 77 | c.setX(10) 78 | assert c.getX() == 10 79 | 80 | 81 | def test_hello_world(): 82 | c = compile("examples/hello_world.dasy") 83 | assert c.greet() == "Hello World" 84 | 85 | 86 | def test_include(): 87 | c = compile("examples/mutable_hello.dasy") 88 | assert c.greet() == "Hello World" 89 | c.setGreeting("yo yo") 90 | assert c.greet() == "yo yo" 91 | 92 | 93 | def test_call_internal(): 94 | c = compile_src( 95 | """ 96 | (defn _getX [] :uint256 :internal 4) 97 | (defn useX [] :uint256 :external 98 | (+ 2 (self/_getX))) 99 | """ 100 | ) 101 | assert c.useX() == 6 102 | 103 | 104 | def test_pure_fn(): 105 | c = compile_src( 106 | """ 107 | (defn pureX [:uint256 x] :uint256 [:external :pure] x) 108 | """ 109 | ) 110 | assert c.pureX(6) == 6 111 | 112 | 113 | def test_constructor(): 114 | c = compile("examples/constructor.dasy", "z80", 100) 115 | 116 | createdAt = c.createdAt() 117 | expiresAt = c.expiresAt() 118 | assert expiresAt == createdAt + 100 119 | assert c.name() == "z80" 120 | 121 | 122 | def test_if(): 123 | c = compile_src( 124 | """ 125 | (defn absValue [:uint256 x y] :uint256 [:external :pure] 126 | (if (>= x y) 127 | (return (- x y)) 128 | (return (- y x))))""" 129 | ) 130 | assert c.absValue(4, 7) == 3 131 | 132 | 133 | def test_if_expr(): 134 | c = compile_src( 135 | """ 136 | (defn absValue [:uint256 x y] :uint256 [:external :pure] 137 | (if (>= x y) 138 | (- x y) 139 | (- y x)))""" 140 | ) 141 | assert c.absValue(4, 7) == 3 142 | 143 | 144 | def test_struct(): 145 | c = compile_src( 146 | """ 147 | (defstruct Person 148 | age :uint256 149 | name (string 100)) 150 | (defvars person (public Person)) 151 | (defn __init__ [] :external 152 | (set (. self/person age) 12)) 153 | (defn memoryPerson [] Person :external 154 | (def mPers Person self/person) 155 | (set-in mPers age 10) 156 | mPers) 157 | (defn literalPerson [] Person :external 158 | (Person {:age 100 :name "Foo"})) 159 | """ 160 | ) 161 | assert c.person()[0] == 12 162 | assert c.memoryPerson() == (10, "") 163 | assert c.literalPerson() == (100, "Foo") 164 | 165 | 166 | def test_arrays(): 167 | c = compile_src( 168 | """ 169 | (defvars nums (public (array :uint256 10))) 170 | (defn __init__ [] :external 171 | (doto self/nums 172 | (set-at 0 5) 173 | (set-at 1 10)) 174 | ) 175 | """ 176 | ) 177 | assert c.nums(0) == 5 178 | assert c.nums(1) == 10 179 | 180 | 181 | def test_map(): 182 | c = compile_src( 183 | """ 184 | (defvars myMap (public (hash-map :address :uint256)) 185 | owner (public :address)) 186 | (defn __init__ [] :external 187 | (set self/owner msg/sender) 188 | (set-at! self/myMap [msg/sender] 10)) 189 | (defn getOwnerNum [] :uint256 :external 190 | (get-at! self/myMap [msg/sender])) 191 | """ 192 | ) 193 | assert c.myMap("0x8B4de256180CFEC54c436A470AF50F9EE2813dbB") == 0 194 | assert c.myMap(c.owner()) == 10 195 | assert c.getOwnerNum() == 10 196 | 197 | 198 | def test_reference_types(): 199 | with anchor_evm_version("cancun"): 200 | c = compile_src( 201 | """ 202 | (defvar nums (array :uint256 10)) 203 | (defn memoryArrayVal [] '(:uint256 :uint256) :external 204 | (defvar arr (array :uint256 10) self/nums) 205 | (set-at arr 1 12) 206 | '((get-at arr 0) (get-at arr 1))) 207 | """ 208 | ) 209 | assert c.memoryArrayVal() == (0, 12) 210 | 211 | d = compile("examples/reference_types.dasy") 212 | assert d.person() == ("Dasy", 11) 213 | assert d.nums(0) == 123 214 | assert d.nums(1) == 0 215 | assert d.nums(9) == 456 216 | 217 | 218 | def test_dynarrays(): 219 | c = compile("examples/dynamic_arrays.dasy") 220 | assert c.nums(0) == 1 221 | assert c.nums(1) == 2 222 | assert c.nums(2) == 3 223 | assert c.examples([9, 8, 7, 6, 5]) == [1, 2, 3, 9, 8, 7, 6, 5] 224 | 225 | 226 | def test_expr_wrap(): 227 | c = compile_src( 228 | """ 229 | (defvar owner (public :address)) 230 | (defvar nums (public (dyn-array :uint256 3))) 231 | (defn test [] :external 232 | (set self/owner msg/sender) 233 | (.append self/nums 1)) 234 | """ 235 | ) 236 | c.test() 237 | 238 | 239 | def test_funtions(): 240 | c = compile("examples/functions.dasy") 241 | assert c.multiply(5, 10) == 50 242 | assert c.divide(100, 10) == 10 243 | assert c.multiOut() == (1, True) 244 | assert c.addAndSub(50, 25) == (75, 25) 245 | 246 | 247 | def test_visibility(): 248 | c = compile("examples/function_visibility.dasy") 249 | assert c.extFunc() 250 | assert c.sumOfSquares(2, 5) == 29 251 | assert c.avg(20, 80) == 50 252 | 253 | 254 | def test_view_pure(): 255 | c = compile("examples/view_pure.dasy") 256 | assert c.pureFunc(5) == 5 257 | assert c.viewFunc(1) 258 | assert c.sum(4, 5, 6) == 15 259 | assert c.addNum(10) == 10 260 | 261 | 262 | def test_constants(): 263 | c = compile("examples/constants.dasy") 264 | assert c.getMyConstants() == (1, 10, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") 265 | assert c.test(5) == 6 266 | 267 | 268 | def test_immutables(): 269 | c = compile("examples/immutable.dasy", 10) 270 | assert c.getMyImmutable() == 10 271 | 272 | 273 | def test_ifelse(): 274 | c = compile("examples/if_else.dasy") 275 | assert c.useCond(5) == 1 276 | assert c.useCond(15) == 2 277 | assert c.useCond(25) == 3 278 | assert c.absoluteValue(10, 5) == 5 279 | assert c.setIf(100) == 2 280 | assert c.setIf(1) == 1 281 | 282 | 283 | def test_for_loop(): 284 | c = compile("examples/for_loop.dasy") 285 | assert c.forLoop() == 2 286 | assert c.sum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) == 55 287 | 288 | 289 | def testError(): 290 | c = compile("examples/error.dasy") 291 | with boa.reverts(): 292 | # TODO: implement checking error msg 293 | c.testAssert(0) 294 | c.testAssert(10) 295 | with boa.reverts(): 296 | c.testRaise(0) 297 | c.testRaise(10) 298 | with boa.reverts(): 299 | c.testErrorBubblesUp(0) 300 | c.testErrorBubblesUp(10) 301 | with boa.reverts(): 302 | c.setOwner("0x0000000000000000000000000000000000000000") 303 | c.setOwner("0xab5801a7d398351b8be11c439e05c5b3259aec9b") 304 | with boa.reverts(): 305 | c.setOwner("0xab5801a7d398351b8be11c439e05c5b3259aec9b") 306 | 307 | 308 | def testEvent(): 309 | c = compile("examples/event.dasy") 310 | c.mint(100) 311 | 312 | 313 | def testPayable(): 314 | c = compile("examples/payable.dasy") 315 | assert c.getBalance() == 0 316 | 317 | 318 | def testHashing(): 319 | c = compile("examples/hashing.dasy") 320 | assert ( 321 | c.getMessageHash("hi") 322 | == b"v$w\x8d\xed\xc7_\x8b2+\x9f\xa1c*a\r@\xb8^\x10l}\x9b\xf0\xe7C\xa9\xce)\x1b\x9co" 323 | ) 324 | 325 | 326 | def testRawCall(): 327 | b = compile("examples/functions.dasy") 328 | c = compile("examples/raw_call.dasy") 329 | assert c.testRawCall(b.address, 4, 3) == 12 330 | 331 | 332 | def testDelegateCall(): 333 | b = compile("examples/test_delegate_call.dasy") 334 | c = compile("examples/delegate_call.dasy") 335 | c.updateX(b.address, 10) 336 | c.updateY(b.address, 20) 337 | assert c.x() == 11 338 | assert c.y() == 400 339 | 340 | 341 | def testInterface(): 342 | b = compile("examples/test_interface.dasy") 343 | c = compile("examples/interface.dasy", b.address) 344 | addr1 = boa.env.generate_address() 345 | assert b.owner() == c.getOwner() 346 | c.setOwner(addr1) 347 | # convert addr1 to 0x formatted hex string 348 | assert b.owner() == addr1 349 | 350 | 351 | def test_reentrancy(): 352 | # TODO: This test should fail! 353 | with anchor_evm_version("cancun"): 354 | c = compile("examples/nonreentrantenforcer.dasy") # noqa: F841 355 | # v = boa.load("examples/nonreentrantenforcer.vy") 356 | # print("vyper settings") 357 | # print(v.compiler_data.settings) 358 | helper = compile("examples/nonreentrant2.dasy", c.address) # noqa: F841 359 | with boa.reverts(): 360 | helper.callback() 361 | 362 | 363 | def test_auction(): 364 | a = boa.env.generate_address() 365 | c = compile("examples/simple_auction.dasy", a, 100, 10000000) # noqa: F841 366 | 367 | 368 | def test_token(): 369 | a = boa.env.generate_address() 370 | b = boa.env.generate_address() 371 | with boa.env.prank(a): 372 | t = compile("examples/ERC20.dasy", "Dasy Token", "DASY", 18, 100) 373 | assert t.minter() == a 374 | assert t.name() == "Dasy Token" 375 | assert t.symbol() == "DASY" 376 | assert t.balanceOf(a) == 100 * 10**18 377 | with boa.env.prank(a): 378 | t.transfer(b, 1 * 10**18) 379 | t.burn(1 * 10**18) 380 | assert t.balanceOf(b) == 1 * 10**18 381 | assert t.totalSupply() == 99 * 10**18 382 | 383 | 384 | def test_enums(): 385 | c = compile("examples/enum.dasy") 386 | assert c.getPrice() == 10 387 | assert c.getPriceUsingCondp() == 10 388 | 389 | 390 | def test_in(): 391 | c = compile_src( 392 | """ 393 | (defn foo [] :bool :external 394 | (return (in 3 [1 2 3]))) 395 | (defn bar [] :bool :external 396 | (return (notin 3 [1 2 3])))""" 397 | ) 398 | assert c.foo() 399 | assert not c.bar() 400 | 401 | 402 | def test_return_variable(): 403 | c = compile_src( 404 | """ 405 | (defvar x (public :uint256)) 406 | (defn foo [] :uint256 :external 407 | (def x :uint256 5) 408 | (return x)) 409 | """ 410 | ) 411 | 412 | 413 | def test_usub(): 414 | c = compile_src( 415 | """ 416 | (defn foo [:int256 x] :int256 :external 417 | (return (usub x))) 418 | """ 419 | ) 420 | 421 | assert c.foo(10) == -10 422 | 423 | 424 | def test_unsafe_ops(): 425 | c = compile("examples/unsafe_ops.dasy") 426 | assert ( 427 | c.unsafe_sub(0, 1) 428 | == 115792089237316195423570985008687907853269984665640564039457584007913129639935 429 | ) 430 | 431 | 432 | def test_infix(): 433 | d = compile("examples/infix_macro.dasy") 434 | assert d.infix_add(10, 1) == 11 435 | --------------------------------------------------------------------------------