Dasy By Example

Table of Contents

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)))

Author: z80

Created: 2022-09-08 Thu 22:00