Skip to content

Commit

Permalink
Implement instructions for known-size bytes
Browse files Browse the repository at this point in the history
See: #49
  • Loading branch information
kwannoel committed Jul 9, 2021
1 parent a1cd085 commit bbb6449
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 1 deletion.
109 changes: 109 additions & 0 deletions evm-instructions.ss
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
(export #t)
(import
:gerbil/gambit/bits :gerbil/gambit/bytes :gerbil/gambit/exact
:std/misc/number :std/sugar :std/misc/list :std/format
:clan/base :clan/number :clan/with-id :std/srfi/1
:clan/poo/object (only-in :clan/poo/mop Type)
:clan/crypto/secp256k1
./assembly ./ethereum ./types ./evm-runtime)

;; --------------------------------
;; General purpose EVM instructions
;; --------------------------------
;; Extends instruction set.
;; Examples include:
;; - Storing bytes larger than EVM Word
;; - Storing to memory with reference
;; - Load from memory with reference
;; NOTE: Reference is start-offset, length on stack.
;;
;; -------------
;; Byte encoding
;; -------------
;; Instructions encode bytes as big-endian.
;;
;; For example, given the value: 0x00ff,
;; After PUSH: Stack will have "0x00" on top, followed by "0xff".
;; After STORE: Memory location will have "0x00" at offset, "0xff" at offset+1.
;;
;; For larger bytes (larger than a EVM word),
;; we partition them into smaller chunks,
;; less than or equal to a EVM word.
;; We use the notation: "part[n]", to denote the partition index.
;; E.g. 65-bytes -> | 32-bytes | 32-bytes | 1-byte |
;; | part0 | part1 | part2 |


;; ----------------------
;; Instruction defintions
;; ----------------------

;; stack input: -
;; stack output: part0 part1 ... partn
(def (&push/any-size bytes (start 0) (len (u8vector-length bytes)))
(assert! (<= 1 len) (format "size: ~d not in range: [1,∞]")) ; TODO: provide a concrete upper bound
(def end (min len (+ start 32)))
(when (< start end)
(let ()
(def bytes<=32 (subu8vector bytes start end))
(def n-bytes (u8vector-length bytes<=32))
(&begin (&push/any-size bytes end) [&push-bytes bytes<=32])
)))

;; Helper function - Make list of word-sizes
;; E.g. (sizes/word-size<-size 65 32) -> [32 32 1]
(def (sizes/word-size<-size size (word-size 32))
(def n-words (truncate (/ size word-size)))
(def words (make-list n-words word-size))
(def rem (modulo size word-size))
(if (eq? rem 0) words [words ... rem])
)

;; NOTE: Uses brk@ for offset via &brk-cons, dependent on EVM memory layout.
;; stack input: part0 part1 ... partn
;; stack output: -
(def (&mstore/free/any-size size)
(assert! (<= 1 size) (format "size: ~d not in range: [1,∞]")) ; TODO: provide a concrete upper bound
(def sizes/base-32 (sizes/word-size<-size size))
(&begin (map &brk-cons sizes/base-32) ...)
)

;; Helper function - Make list of relative offsets and sizes for partitions.
;; E.g. (offsets-and-sizes<-size 65) -> [[0 32] [32 32] [64 1]]
;; List [RelativeOffset Size] <- Nat
(def (offsets-and-sizes<-size size)
(def sizes (sizes/word-size<-size size))
(def relative-offsets (iota (length sizes) 0 32))
(zip relative-offsets sizes))

;; Given relative-offset and size,
;; generates EVM code to:
;; - load specified bytes
;; - maintain start-offset for loading next segment
;;
;; stack input: start-offset
;; stack output: start-offset bytes[offset:end]
;; where offset = offset+relative-offset
;; end = offset+size
(def &mload-1/any-size
(match <>
([relative-offset size]
(&begin ; start-offset
DUP1 relative-offset ADD ; offset start-offset
(&mload size) SWAP1)))) ; start-offset bytes[offset:end]

;; stack input: start-offset
;; stack output: part0 part1 ... partn
(def (&mload/any-size size)
(assert! (<= 0 size) (format "size: ~d not in range: [1,∞]")) ; TODO: provide a concrete upper bound
(match (offsets-and-sizes<-size size)
;; = 0 bytes
([] (&begin POP 0)
)
;; > 0 bytes
([[_ start-size] . rest]
(&begin
(map &mload-1/any-size (reverse rest)) ...
(&mload start-size)))
)
)
91 changes: 91 additions & 0 deletions t/100-evm-instructions-integrationtest.ss
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
(export #t)

(import
:std/test :clan/number :clan/poo/object
../types ../assembly ../evm-runtime
../testing ../evm-instructions.ss
./10-json-rpc-integrationtest)

;; Stores free memory pointer at free memory location for returning
(def &store-brk
(&begin (&mloadat brk@) DUP1 MSTORE))

(def 100-evm-instructions-integrationtest
(test-suite "integration tests for evm instructions"
(test-case "EVM-type: &mstore/free/any-size, size = 1"
(evm-test [] (&begin
#x20 (&mstoreat brk@ 32)
(&push/any-size #u8(1)) ; stack: 1
(&mstore/free/any-size 1)
&store-brk
)
[[Bytes1 . #u8(1)]
[UInt256 . 33]]
result-in-memory?: #t
result-start: #x20))

(test-case "EVM-type: &mstore/free/any-size, size = 65"
(def 65-bytes (list->u8vector (make-list 65 65)))
(u8vector-set! 65-bytes 0 66)
(u8vector-set! 65-bytes 32 67)
(u8vector-set! 65-bytes 64 68)

(def 65-bytes/0-32 (subu8vector 65-bytes 0 32))
(def 65-bytes/32-64 (subu8vector 65-bytes 32 64))
(def 65-bytes/64-65 (subu8vector 65-bytes 64 65))

(evm-test [] (&begin
#x20 (&mstoreat brk@ 32)
(&push/any-size 65-bytes)
(&mstore/free/any-size 65)
&store-brk
)
[[Bytes32 . 65-bytes/0-32]
[Bytes32 . 65-bytes/32-64]
[Bytes1 . 65-bytes/64-65]
[UInt256 . 97] ; 32 (free mem ptr) + 65 (str65) = 97
]
result-in-memory?: #t
result-start: #x20)
)

(test-case "EVM-type &mload/free/any-size, size = 65"
(def 65-bytes (list->u8vector (make-list 65 65)))
(u8vector-set! 65-bytes 0 66)
(u8vector-set! 65-bytes 32 67)
(u8vector-set! 65-bytes 64 68)

(def 65-bytes/0-32 (subu8vector 65-bytes 0 32))
(def 65-bytes/32-64 (subu8vector 65-bytes 32 64))
(def 65-bytes/64-65 (subu8vector 65-bytes 64 65))

(evm-test [] (&begin
;; init freememptr
#x20 (&mstoreat brk@ 32) ; -

;; Store str65 in mem
(&push/any-size 65-bytes) ; bytes[0-32] bytes[32-64] bytes[64-65]
(&mstore/free/any-size 65) ; -
(&mloadat/any-size #x20 65) ; bytes[0-32] bytes[32-64] bytes[64-65]
(&mstore/free/any-size 65) ; -

&store-brk
)

;; Original 65-bytes
[[Bytes32 . 65-bytes/0-32]
[Bytes32 . 65-bytes/32-64]
[Bytes1 . 65-bytes/64-65]

;; Duplicate 65-bytes
[Bytes32 . 65-bytes/0-32]
[Bytes32 . 65-bytes/32-64]
[Bytes1 . 65-bytes/64-65]

;; freememptr: 32 + 65 + 65 = 162
[UInt256 . 162 ]
]
result-in-memory?: #t
result-start: #x20)
)
))
14 changes: 13 additions & 1 deletion t/80-evm-eval-integrationtest.ss
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,16 @@

(test-case "&marshal UInt8"
(evm-test [] (&begin brk DUP1 DUP1 (&marshal UInt16 7))
[[UInt16 . 2]]))))
[[UInt16 . 2]]))

(test-case "&mstore 1 byte"
(evm-test [] (&begin 42 0 (&mstore 1))
[[UInt8 . 42]]
result-in-memory?: #t))

(test-case "&mstore 32 bytes"
(def maxUInt256 (- (expt 2 256) 1))
(evm-test [] (&begin maxUInt256 0 (&mstore 32))
[[UInt256 . maxUInt256]]
result-in-memory?: #t))
))
39 changes: 39 additions & 0 deletions t/evm-instructions-test.ss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
(export #t)

(import
:std/test :clan/poo/object
../assembly ../evm-instructions ../types)

;; Verify assembled bytecode of instruction(s)
(def (check-inst? i/actual i/expected)
(check-equal? (assemble/bytes i/actual) (assemble/bytes i/expected)))

;; NOTE: Boxed/Unboxed stack<-mem (load) methods are dependent
;; on EVM network (eip145).
;; Hence they are tested in integration tests.
(def evm-instructions-test
(test-suite "test suite for evm-instructions"
(test-case "&push/any-size <= 32"
(def 1-byte #u8(1))
(check-inst? (&push/any-size 1-byte) [1-byte])

(def 5-bytes #u8(104 101 108 108 111))
(check-inst? (&push/any-size 5-bytes) [5-bytes])

(def 32-bytes (list->u8vector (make-list 32 65)))
(check-inst? (&push/any-size 32-bytes) [32-bytes])
)

(test-case "&push/any-size > 32"
(def 65-bytes (list->u8vector (make-list 65 65)))
(u8vector-set! 65-bytes 0 66) ; bytes/0-32: 66 65 65 ... 65
(u8vector-set! 65-bytes 32 67) ; bytes/32-64: 67 65 65 ... 65
(u8vector-set! 65-bytes 64 68) ; bytes/64-65: 68

(check-inst? (&push/any-size 65-bytes)
[(subu8vector 65-bytes 64 65) ; bytes/64-65: 68
(subu8vector 65-bytes 32 64) ; bytes/32-64: 67 65 65 ... 65
(subu8vector 65-bytes 0 32) ; bytes/0-32: 66 65 65 ... 65
])
)
))

0 comments on commit bbb6449

Please sign in to comment.