Skip to content

Commit

Permalink
✨ added static hex word class and constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
jcchevalier-ledger committed Sep 15, 2023
1 parent f3448aa commit d8fecbc
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 32 deletions.
6 changes: 3 additions & 3 deletions model/src/main/scala/cleareth/model/Address.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package cleareth.model

import cleareth.model.constraint.HexWord
import cleareth.model.constraint.IsHexWordStrict
import io.github.iltotore.iron.*
import scodec.bits.ByteVector

/** This object represents an Ethereum Public address, which must be an hexadecimal string of 20 bytes
*/
opaque type Address = ByteVector :| HexWord[20]
opaque type Address = ByteVector :| IsHexWordStrict[20]

object Address extends HexWord.Utils[20, Address]:
object Address extends IsHexWordStrict.Utils[20, Address]:
val mint: Address = Address("0x0000000000000000000000000000000000000000")
6 changes: 3 additions & 3 deletions model/src/main/scala/cleareth/model/BlockHash.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cleareth.model

import cleareth.model.constraint.HexWord
import cleareth.model.constraint.IsHexWordStrict
import io.github.iltotore.iron.*
import scodec.bits.ByteVector

/** This object represents an Ethereum Block hash, which must be an hexadecimal string of 32 bytes
*/
opaque type BlockHash = ByteVector :| HexWord[32]
opaque type BlockHash = ByteVector :| IsHexWordStrict[32]

object BlockHash extends HexWord.Utils[32, BlockHash]
object BlockHash extends IsHexWordStrict.Utils[32, BlockHash]
28 changes: 28 additions & 0 deletions model/src/main/scala/cleareth/model/HexWord.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cleareth.model

import cleareth.model.constraint.IsHexWord
import io.github.iltotore.iron.*
import scodec.bits.ByteVector

/** This object represents a static hex word, which must be an hexadecimal string of at most 32 bytes
*/
opaque type HexWord = ByteVector :| IsHexWord

object HexWord extends RefinedTypeOpsImpl[ByteVector, IsHexWord, HexWord]:

inline def apply(inline bv: ByteVector): HexWord = applyUnsafe(bv)
inline def apply(inline str: String): HexWord = applyUnsafe(ByteVector.fromValidHex(str))

inline def from(inline bv: ByteVector): Option[HexWord] = option(bv)
inline def from(inline str: String): Option[HexWord] =
for
bv <- ByteVector.fromHex(str)
word <- option(bv)
yield word

inline def fromDescriptive(inline bv: ByteVector): Either[String, HexWord] = either(bv)
inline def fromDescriptive(inline str: String): Either[String, HexWord] =
for
bv <- ByteVector.fromHexDescriptive(str)
word <- either(bv.padTo(32))
yield word
6 changes: 3 additions & 3 deletions model/src/main/scala/cleareth/model/TxHash.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cleareth.model

import cleareth.model.constraint.HexWord
import cleareth.model.constraint.IsHexWordStrict
import io.github.iltotore.iron.*
import scodec.bits.ByteVector

/** This object represents an Ethereum Transaction hash, which must be an hexadecimal string of 32 bytes
*/
opaque type TxHash = ByteVector :| HexWord[32]
opaque type TxHash = ByteVector :| IsHexWordStrict[32]

object TxHash extends HexWord.Utils[32, TxHash]
object TxHash extends IsHexWordStrict.Utils[32, TxHash]
6 changes: 3 additions & 3 deletions model/src/main/scala/cleareth/model/Wei.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cleareth.model

import cleareth.model.constraint.UInt
import cleareth.model.constraint.IsUInt
import io.github.iltotore.iron.*
import scodec.bits.ByteVector

opaque type Wei = ByteVector :| UInt[256]
opaque type Wei = ByteVector :| IsUInt[256]

object Wei extends UInt.Utils[256, Wei]
object Wei extends IsUInt.Utils[256, Wei]
14 changes: 14 additions & 0 deletions model/src/main/scala/cleareth/model/constraint/IsHexWord.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cleareth.model.constraint

import io.github.iltotore.iron.*
import scala.compiletime.constValue
import scodec.bits.ByteVector

final class IsHexWord

object IsHexWord:
trait Utils[L <: ByteLength, IT <: ByteVector :| IsHexWord] extends ByteVectorUtils[IsHexWord, IT]

given [L <: ByteLength]: Constraint[ByteVector, IsHexWord] with
inline def test(value: ByteVector): Boolean = value.length <= EVM_WORD_LENGTH
inline def message: String = s"Expecting at most $EVM_WORD_LENGTH bytes"
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import io.github.iltotore.iron.*
import scala.compiletime.constValue
import scodec.bits.ByteVector

final class HexWord[L <: ByteLength]
final class IsHexWordStrict[L <: ByteLength]

object HexWord:
trait Utils[L <: ByteLength, IT <: ByteVector :| HexWord[L]] extends ByteVectorUtils[HexWord[L], IT]
object IsHexWordStrict:
trait Utils[L <: ByteLength, IT <: ByteVector :| IsHexWordStrict[L]] extends ByteVectorUtils[IsHexWordStrict[L], IT]

given [L <: ByteLength]: Constraint[ByteVector, HexWord[L]] with
given [L <: ByteLength]: Constraint[ByteVector, IsHexWordStrict[L]] with
inline def test(value: ByteVector): Boolean = value.length == constValue[L]
inline def message: String = s"Expecting ${constValue[L]} bytes"
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,59 @@ import io.github.iltotore.iron.*
import scala.compiletime.constValue
import scodec.bits.ByteVector

final class UInt[L <: BitLength]
final class IsUInt[L <: BitLength]

object UInt:
object IsUInt:

trait Utils[L <: BitLength, IT <: ByteVector :| UInt[L]] extends ByteVectorUtils[UInt[L], IT]:
inline def apply(inline short: Short)(using Constraint[ByteVector, UInt[L]]): IT =
trait Utils[L <: BitLength, IT <: ByteVector :| IsUInt[L]] extends ByteVectorUtils[IsUInt[L], IT]:
inline def apply(inline short: Short)(using Constraint[ByteVector, IsUInt[L]]): IT =
require(short >= 0, s"supplied short should be positive, got $short")
applyUnsafe(ByteVector.fromShort(short))

inline def apply(inline int: Int)(using Constraint[ByteVector, UInt[L]]): IT =
inline def apply(inline int: Int)(using Constraint[ByteVector, IsUInt[L]]): IT =
require(int >= 0, s"supplied int should be positive, got $int")
applyUnsafe(ByteVector.fromInt(int))

inline def apply(inline long: Long)(using Constraint[ByteVector, UInt[L]]): IT =
inline def apply(inline long: Long)(using Constraint[ByteVector, IsUInt[L]]): IT =
require(long >= 0, s"supplied long should be positive, got $long")
applyUnsafe(ByteVector.fromLong(long))

inline def apply(inline bigInt: BigInt)(using Constraint[ByteVector, UInt[L]]): IT =
inline def apply(inline bigInt: BigInt)(using Constraint[ByteVector, IsUInt[L]]): IT =
require(bigInt >= 0, s"supplied bigInt should be positive, got $bigInt")
applyUnsafe(ByteVector(bigInt.toByteArray).dropWhile(_ == 0))

inline def from(inline short: Short)(using Constraint[ByteVector, UInt[L]]): Option[IT] =
inline def from(inline short: Short)(using Constraint[ByteVector, IsUInt[L]]): Option[IT] =
require(short >= 0, s"supplied short should be positive, got $short")
option(ByteVector.fromShort(short))

inline def from(inline int: Int)(using Constraint[ByteVector, UInt[L]]): Option[IT] =
inline def from(inline int: Int)(using Constraint[ByteVector, IsUInt[L]]): Option[IT] =
require(int >= 0, s"supplied int should be positive, got $int")
option(ByteVector.fromInt(int))

inline def from(inline long: Long)(using Constraint[ByteVector, UInt[L]]): Option[IT] =
inline def from(inline long: Long)(using Constraint[ByteVector, IsUInt[L]]): Option[IT] =
require(long >= 0, s"supplied long should be positive, got $long")
option(ByteVector.fromLong(long))

inline def from(inline bigInt: BigInt)(using Constraint[ByteVector, UInt[L]]): Option[IT] =
inline def from(inline bigInt: BigInt)(using Constraint[ByteVector, IsUInt[L]]): Option[IT] =
require(bigInt >= 0, s"supplied bigInt should be positive, got $bigInt")
option(ByteVector(bigInt.toByteArray).dropWhile(_ == 0))

inline def fromDescriptive(inline short: Short)(using Constraint[ByteVector, UInt[L]]): Either[String, IT] =
inline def fromDescriptive(inline short: Short)(using Constraint[ByteVector, IsUInt[L]]): Either[String, IT] =
require(short >= 0, s"supplied short should be positive, got $short")
either(ByteVector.fromShort(short))

inline def fromDescriptive(inline int: Int)(using Constraint[ByteVector, UInt[L]]): Either[String, IT] =
inline def fromDescriptive(inline int: Int)(using Constraint[ByteVector, IsUInt[L]]): Either[String, IT] =
require(int >= 0, s"supplied int should be positive, got $int")
either(ByteVector.fromInt(int))

inline def fromDescriptive(inline long: Long)(using Constraint[ByteVector, UInt[L]]): Either[String, IT] =
inline def fromDescriptive(inline long: Long)(using Constraint[ByteVector, IsUInt[L]]): Either[String, IT] =
require(long >= 0, s"supplied long should be positive, got $long")
either(ByteVector.fromLong(long))

inline def fromDescriptive(inline bigInt: BigInt)(using Constraint[ByteVector, UInt[L]]): Either[String, IT] =
inline def fromDescriptive(inline bigInt: BigInt)(using Constraint[ByteVector, IsUInt[L]]): Either[String, IT] =
require(bigInt >= 0, s"supplied bigInt should be positive, got $bigInt")
either(ByteVector(bigInt.toByteArray).dropWhile(_ == 0))

given [L <: BitLength]: Constraint[ByteVector, UInt[L]] with
given [L <: BitLength]: Constraint[ByteVector, IsUInt[L]] with
inline def test(value: ByteVector): Boolean = value.length * 8 <= constValue[L]
inline def message: String = s"Expecting ${constValue[L]} bits"
2 changes: 2 additions & 0 deletions model/src/main/scala/cleareth/model/constraint/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ package object constraint:
type BitLength = 8 | 16 | 32 | 64 | 128 | 256
type ByteLength = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32

val EVM_WORD_LENGTH = 32
24 changes: 24 additions & 0 deletions model/src/test/scala/cleareth/model/HexWordTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cleareth.model

import munit.FunSuite
import scodec.bits.ByteVector

class HexWordTest extends FunSuite:

test(s"creation from a valid hexadecimal string should succeed (length < 64)") {
HexWord("0xdeadbeefdeadbeef")
}

test(s"creation from a valid hexadecimal string should succeed (`0x` string)") {
HexWord("0x")
}

test(s"creation from a valid hexadecimal string should succeed (length = 64)") {
HexWord("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
}

test(s"creation from a valid hexadecimal string should fail (length > 64)") {
interceptMessage[IllegalArgumentException](
"Expecting at most 32 bytes"
)(HexWord("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
}

0 comments on commit d8fecbc

Please sign in to comment.