├── .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 |
--------------------------------------------------------------------------------