Dasy By Example
Table of Contents
- 1. Hello World
- 2. Data Types - Values
- 3. Data Types - References
- 4. Dynamic Arrays
- 5. Functions
- 6. Internal and External Functions
- 7. View and Pure Functions
- 8. Constructor
- 9. Private and Public State Variables
- 10. Constants
- 11. Immutable
- 12. If/Else
- 13. For Loop
- 14. Errors
- 15. Events
- 16. Payable
- 17. Default Function
- 18. Send Ether
- 19. Raw Call
- 20. Delegate Call
- 21. Interface
- 22. Hash Function
- 23. Re-Entrancy Lock
- 24. Self Destruct
1. Hello World
1: ;; Create a string variable that can store maximum 100 characters 2: (defvar greet (public (string 100))) 3: 4: (defn __init__ [] :external 5: (setv self/greet "Hello World"))
2. Data Types - Values
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: (setv self/b False) 12: (setv self/i -1) 13: (setv self/u 123) 14: (setv self/b32 0xada1b75f8ae9a65dcc16f95678ac203030505c6b465c8206e26ae84b525cdacb) 15: (setv self/bs b"\x01") 16: (setv self/s "Hello Dasy"))
3. Data Types - References
References types are data types that are passed by their reference, pointer to where the actual data is stored.
1: (defstruct Person 2: name (string 100) 3: age :uint256) 4: 5: (defvars 6: nums (public (array :uint256 10)) ;; fixed size list, must be bounded 7: myMap (public (hash-map :address :uint256)) 8: person (public Person)) 9: 10: (defn __init__ [] :external 11: (doto self/nums 12: (set-at 0 123) ;; this updates self.nums[0] 13: (set-at 9 456)) ;; this updates self.nums[9] 14: 15: ;; copies self.nums to array in memory 16: (defvar arr (array :uint256 10) self/nums) 17: (set-at arr 0 123) ;; does not modify self/nums 18: 19: ;; this updates self/myMap 20: (doto self/myMap 21: (set-at msg/sender 1) ;; self.myMap[msg.sender] = 1 22: (set-at msg/sender 11)) ;; self.myMap[msg.sender] = 11 23: 24: ;; this updates self/person 25: (doto self/person 26: (set-in age 11) 27: (set-in name "Dasy")) 28: 29: ;; you could put defvar inside a doto like the arr example 30: ;; above, but I don't think that is very readable 31: ;; doing it this way is clearer, leaving the defvar out of doto 32: ;; Person struct is copied into memory 33: (defvar p Person self/person) 34: (set-in p name "Solidity"))
4. Dynamic Arrays
Dynamic arrays are bounded arrays whose length can change. The length of the array cannot exceed the maximum length set where the array is declared.
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: (setv self/nums []) 14: ;; set values 15: (setv 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))
5. Functions
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)))
6. Internal and External Functions
:internal
functions can only be called inside the contract.
:external
functions can only be called from outside the contract.
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)))
7. View and Pure Functions
Both pure
and view
functions are read-only. They cannot write anything to the blockchain.
pure
functions do not read any state or global variables.
view
functions can read state variables, global variables, and call internal (view) functions.
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))
8. Constructor
__init__
is a special function that is executed only once, when the contract is deployed.
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: (setv self/owner msg/sender) 9: ;; set name from input 10: (setv self/name name) 11: (setv self/createdAt block/timestamp) 12: (setv self/expiresAt (+ block/timestamp 13: duration)))
9. Private and Public State Variables
Private state variables cannot be accessed from outside of the contract.
Public state variables can be read by anyone, including other contracts.
1: (defvars 2: owner (public :address) 3: foo :uint256 4: bar (public :bool)) 5: 6: (defn __init__ [] :external 7: (setv self/owner msg/sender) 8: (setv self/foo 123) 9: (setv self/bar True))
10. Constants
Constants are variables that cannot change. Any usage of a constant is replaced with its value at compile time.
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. Immutable
Immutables are variables that must be set at compile time and cannot change afterwards. Any usage of a constant in runtime code is replaced with its value at deployment time.
1: (defvar OWNER (immutable :address)) 2: (defvar MY_IMMUTABLE (immutable :uint256)) 3: 4: (defn __init__ [:uint256 _val] :external 5: (setv OWNER msg/sender) 6: (setv MY_IMMUTABLE _val)) 7: 8: (defn getMyImmutable [] :uint256 [:external :pure] 9: MY_IMMUTABLE)
11.1. When to use immutable variables?
- You have a variable that needs to be set when the contract is deployed, for example like setting contract owner to
msg.sender
- and this variable will never change after deployment
11.2. Why declare variables as immutable
?
- Like constants,
immutable
variables save run time gas.
12. If/Else
1: (defn useIf [:uint256 x] :uint256 :external 2: (if (<= x 10) 3: (return 1) 4: (if (<= x 20) 5: (return 2) 6: (return 3)))) 7: 8: (defn useCond [:uint256 x] :uint256 :external 9: (cond 10: (<= x 10) (return 1) 11: (<= x 20) (return 2) 12: :else (return 3))) 13: 14: (defn useCondp [:uint256 x] :uint256 :external 15: (condp <= x 16: 10 (return 1) 17: 20 (return 2) 18: :else (return 3))) 19: 20: (defn absoluteValue [:uint256 x y] :uint256 [:external :pure] 21: (if (>= x y) 22: (return (- x y))) 23: (return (- y x)))
13. For Loop
There are 2 ways to loop through an array, using range
or directly looping through array elements.
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: (setv 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)
14. Errors
Use assert
and raise
to check inputs and validate state.
When an error occurs, it will halt the entire function call, undoing any changes.
You will still need to pay gas for the failed transaction.
1: (defvars 2: x (public :uint256) 3: owner (public :address)) 4: 5: (defn __init__ [] :external 6: (setv self/owner msg/sender)) 7: 8: (defn testAssert [:uint256 x] :external 9: (assert (>= x 1) "x < 1") 10: (setv self/x x)) 11: 12: (defn testRaise [:uint256 x] :external 13: (if (<= x 1) 14: (raise "x < 1")) 15: (setv self/x x)) 16: 17: (defn _testErrorBubblesUp [:uint256 x] :internal 18: (assert (>= x 1) "x < 1") 19: (setv self/x x)) 20: 21: (defn testErrorBubblesUp [:uint256 x] :external 22: (self/_testErrorBubblesUp x) 23: (setv 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: (setv self/owner owner))
15. Events
Events write logs to the blockchain, commonly used by application to monitor blockchain state and as a cheaper alternative to store data on the blockchain without using state variables.
Events can be efficiently searched by indexing their arguments. Up to 3 parameters can be indexed.
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)))
16. Payable
Functions declared with payable
can receive Ether.
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: (setv self/owner msg/sender))
17. Default Function
A contract can have a default function, executed when a function that does not exist is called. This is the same function as the fallback function in Solidity.
This function is named __default__
and it is commonly used to receive Ether.
1: (defevent Payment 2: sender (indexed :address) 3: amount :uint256) 4: 5: (defn __default__ [] [:external :payable] 6: (log (Payment msg/sender msg/value)))
18. Send Ether
There are two ways to send Ether from a contract, send
and raw_call
. Here we introduce the simpler function to use, send
.
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))
19. Raw Call
raw_call
is a low level function. It is used to call and send Ether to other functions.
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))
20. Delegate Call
When contract A
delegates call to contract B
, B
’s code will be executed inside contract A
. This will update state variables and Ether balance inside contract A
and not B
.
Delegate call is commonly used to create an upgradable contract.
Here is the contract that we will delegate call to.
1: (defvars x (public :uint256) 2: y (public :uint256)) 3: 4: (defn updateX [:uint256 x] :external 5: (setv self/x (+ x 1))) 6: 7: (defn updateY [:uint256 y] :external 8: (setv self/y (* y y)))
Here is the contract that will execute the delegated calls.
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))
21. Interface
Use definterface
to define interfaces to use for calling other smart contracts.
Here is the contract that will be called via interface:
1: (defvars 2: owner (public :address) 3: eth (public :uint256)) 4: 5: (defn setOwner [:address owner] :external 6: (setv self/owner owner)) 7: 8: (defn sendEth [] [:external :payable] 9: (setv self/eth msg/value)) 10: 11: (defn setOwnerAndSendEth [:address owner] [:external :payable] 12: (setv self/owner owner) 13: (setv self/eth msg/value))
Here is the contract executing the call via interface:
1: (definterface TestInterface 2: (defn owner [] :address :view) 3: (defn setOwner [:address owner] :nonpayable) 4: (defn sendEth [] :payable) 5: (defn setOwnerAndSendEth [:address owner] :payable)) 6: 7: (defvar test (public TestInterface)) 8: 9: (defn __init__ [:address test] :external 10: (setv self/test (TestInterface test))) 11: 12: (defn getOwner [] :address [:external :view] 13: (.owner self/test)) 14: 15: (defn getOwnerFromAddress [:address test] :address [:external :view] 16: (.owner (TestInterface test))) 17: 18: (defn setOwner [:address owner] :external 19: (.setOwner self/test owner))
22. Hash Function
Dasy supports the same hash function available in Vyper and Solidity, keccak256
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))
23. Re-Entrancy Lock
Dasy has a handy way to secure your contract from re-entrancy.
A re-entrancy lock can be created on a function with (nonreentrant “lock”) in the decorator list.
Functions can be locked together by using the same name for the locks.
1: (defn func0 [] [:external (nonreentrant "lock")] 2: (raw_call msg/sender b"" :value 0)) 3: 4: (defn func1 [] [:external (nonreentrant "lock-2")] 5: (raw_call msg/sender b"" :value 0)) 6: 7: (defn func2 [] [:external (nonreentrant "lock-2")] 8: (raw_call msg/sender b"" :value 0))
24. Self Destruct
selfdestruct
deletes the contract from the blockchain. It takes a single input, an address to send all of Ether stored in the contract.
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)))