{-# Language DeriveAnyClass #-}
{-# Language StrictData #-}
{-# Language TemplateHaskell #-}
module EVM.ABI
( AbiValue (..)
, AbiType (..)
, Event (..)
, Anonymity (..)
, Indexed (..)
, putAbi
, getAbi
, getAbiSeq
, abiValueType
, abiTypeSolidity
, abiCalldata
, encodeAbiValue
, parseTypeName
) where
import EVM.Keccak (abiKeccak)
import EVM.Types ()
import Control.Monad (replicateM, replicateM_, forM_, void)
import Data.Binary.Get (Get, label, getWord8, getWord32be, skip)
import Data.Binary.Put (Put, runPut, putWord8, putWord32be)
import Data.Bits (shiftL, shiftR, (.&.))
import Data.ByteString (ByteString)
import Data.DoubleWord (Word256, Int256, Word160, signedWord)
import Data.Monoid ((<>))
import Data.Text (Text, pack)
import Data.Text.Encoding (encodeUtf8)
import Data.Vector (Vector)
import Data.Word (Word32, Word8)
import GHC.Generics
import Test.QuickCheck hiding ((.&.), label)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSLazy
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
data AbiValue
= AbiUInt Int Word256
| AbiInt Int Int256
| AbiAddress Word160
| AbiBool Bool
| AbiBytes Int BS.ByteString
| AbiBytesDynamic BS.ByteString
| AbiString BS.ByteString
| AbiArrayDynamic AbiType (Vector AbiValue)
| AbiArray Int AbiType (Vector AbiValue)
deriving (Show, Read, Eq, Ord, Generic)
data AbiType
= AbiUIntType Int
| AbiIntType Int
| AbiAddressType
| AbiBoolType
| AbiBytesType Int
| AbiBytesDynamicType
| AbiStringType
| AbiArrayDynamicType AbiType
| AbiArrayType Int AbiType
deriving (Show, Read, Eq, Ord, Generic)
data AbiKind = Dynamic | Static
deriving (Show, Read, Eq, Ord, Generic)
data Anonymity = Anonymous | NotAnonymous
deriving (Show, Ord, Eq, Generic)
data Indexed = Indexed | NotIndexed
deriving (Show, Ord, Eq, Generic)
data Event = Event Text Anonymity [(AbiType, Indexed)]
deriving (Show, Ord, Eq, Generic)
abiKind :: AbiType -> AbiKind
abiKind = \case
AbiBytesDynamicType -> Dynamic
AbiStringType -> Dynamic
AbiArrayDynamicType _ -> Dynamic
AbiArrayType _ t -> abiKind t
_ -> Static
abiValueType :: AbiValue -> AbiType
abiValueType = \case
AbiUInt n _ -> AbiUIntType n
AbiInt n _ -> AbiIntType n
AbiAddress _ -> AbiAddressType
AbiBool _ -> AbiBoolType
AbiBytes n _ -> AbiBytesType n
AbiBytesDynamic _ -> AbiBytesDynamicType
AbiString _ -> AbiStringType
AbiArrayDynamic t _ -> AbiArrayDynamicType t
AbiArray n t _ -> AbiArrayType n t
abiTypeSolidity :: AbiType -> Text
abiTypeSolidity = \case
AbiUIntType n -> "uint" <> pack (show n)
AbiIntType n -> "int" <> pack (show n)
AbiAddressType -> "address"
AbiBoolType -> "bool"
AbiBytesType n -> "bytes" <> pack (show n)
AbiBytesDynamicType -> "bytes"
AbiStringType -> "string"
AbiArrayDynamicType t -> abiTypeSolidity t <> "[]"
AbiArrayType n t -> abiTypeSolidity t <> "[" <> pack (show n) <> "]"
getAbi :: AbiType -> Get AbiValue
getAbi t = label (Text.unpack (abiTypeSolidity t)) $
case t of
AbiUIntType n -> do
let word32Count = 8 * div (n + 255) 256
xs <- replicateM word32Count getWord32be
pure (AbiUInt n (pack32 word32Count xs))
AbiIntType n -> asUInt n (AbiInt n)
AbiAddressType -> asUInt 256 AbiAddress
AbiBoolType -> asUInt 256 (AbiBool . (== (1 :: Int)))
AbiBytesType n ->
AbiBytes n <$> getBytesWith256BitPadding n
AbiBytesDynamicType ->
AbiBytesDynamic <$>
(label "bytes length prefix" getWord256
>>= label "bytes data" . getBytesWith256BitPadding)
AbiStringType -> do
AbiBytesDynamic x <- getAbi AbiBytesDynamicType
pure (AbiString x)
AbiArrayType n t' ->
AbiArray n t' <$> getAbiSeq n (repeat t')
AbiArrayDynamicType t' -> do
AbiUInt _ n <- label "array length" (getAbi (AbiUIntType 256))
AbiArrayDynamic t' <$>
label "array body" (getAbiSeq (fromIntegral n) (repeat t'))
putAbi :: AbiValue -> Put
putAbi = \case
AbiUInt n x -> do
let word32Count = div (roundTo256Bits n) 4
forM_ (reverse [0 .. word32Count - 1]) $ \i ->
putWord32be (fromIntegral (shiftR x (i * 32) .&. 0xffffffff))
AbiInt n x -> putAbi (AbiUInt n (fromIntegral x))
AbiAddress x -> putAbi (AbiUInt 160 (fromIntegral x))
AbiBool x -> putAbi (AbiUInt 8 (if x then 1 else 0))
AbiBytes n xs -> do
forM_ [0 .. n-1] (putWord8 . BS.index xs)
replicateM_ (roundTo256Bits n - n) (putWord8 0)
AbiBytesDynamic xs -> do
let n = BS.length xs
putAbi (AbiUInt 256 (fromIntegral n))
putAbi (AbiBytes n xs)
AbiString s ->
putAbi (AbiBytesDynamic s)
AbiArray _ _ xs ->
putAbiSeq xs
AbiArrayDynamic _ xs -> do
putAbi (AbiUInt 256 (fromIntegral (Vector.length xs)))
putAbiSeq xs
getAbiSeq :: Int -> [AbiType] -> Get (Vector AbiValue)
getAbiSeq n ts = label "sequence" $ do
hs <- label "sequence head" (getAbiHead n ts)
Vector.fromList <$>
label "sequence tail" (mapM (either getAbi pure) hs)
getAbiHead :: Int -> [AbiType]
-> Get [Either AbiType AbiValue]
getAbiHead 0 _ = pure []
getAbiHead _ [] = fail "ran out of types"
getAbiHead n (t:ts) = do
case abiKind t of
Dynamic ->
(Left t :) <$> (skip 32 *> getAbiHead (n - 1) ts)
Static ->
do x <- getAbi t
xs <- getAbiHead (n - 1) ts
pure (Right x : xs)
putAbiTail :: AbiValue -> Put
putAbiTail x =
case abiKind (abiValueType x) of
Static -> pure ()
Dynamic -> putAbi x
abiValueSize :: AbiValue -> Int
abiValueSize x =
case x of
AbiUInt n _ -> roundTo256Bits n
AbiInt n _ -> roundTo256Bits n
AbiBytes n _ -> roundTo256Bits n
AbiAddress _ -> 32
AbiBool _ -> 32
AbiArray _ _ xs -> Vector.sum (Vector.map abiHeadSize xs) +
Vector.sum (Vector.map abiTailSize xs)
AbiBytesDynamic xs -> 32 + roundTo256Bits (BS.length xs)
AbiArrayDynamic _ xs -> 32 + Vector.sum (Vector.map abiHeadSize xs) +
Vector.sum (Vector.map abiTailSize xs)
AbiString s -> 32 + roundTo256Bits (BS.length s)
abiTailSize :: AbiValue -> Int
abiTailSize x =
case abiKind (abiValueType x) of
Static -> 0
Dynamic ->
case x of
AbiString s -> 32 + roundTo256Bits (BS.length s)
AbiBytesDynamic s -> 32 + roundTo256Bits (BS.length s)
AbiArrayDynamic _ xs -> 32 + Vector.sum (Vector.map abiValueSize xs)
AbiArray _ _ xs -> Vector.sum (Vector.map abiValueSize xs)
_ -> error "impossible"
abiHeadSize :: AbiValue -> Int
abiHeadSize x =
case abiKind (abiValueType x) of
Dynamic -> 32
Static ->
case x of
AbiUInt n _ -> roundTo256Bits n
AbiInt n _ -> roundTo256Bits n
AbiBytes n _ -> roundTo256Bits n
AbiAddress _ -> 32
AbiBool _ -> 32
AbiArray _ _ xs -> Vector.sum (Vector.map abiHeadSize xs) +
Vector.sum (Vector.map abiTailSize xs)
AbiBytesDynamic _ -> 32
AbiArrayDynamic _ _ -> 32
AbiString _ -> 32
putAbiSeq :: Vector AbiValue -> Put
putAbiSeq xs =
do snd $ Vector.foldl' f (headSize, pure ()) (Vector.zip xs tailSizes)
Vector.sequence_ (Vector.map putAbiTail xs)
where
headSize = Vector.sum $ Vector.map abiHeadSize xs
tailSizes = Vector.map abiTailSize xs
f (i, m) (x, j) =
case abiKind (abiValueType x) of
Static -> (i, m >> putAbi x)
Dynamic -> (i + j, m >> putAbi (AbiUInt 256 (fromIntegral i)))
encodeAbiValue :: AbiValue -> BS.ByteString
encodeAbiValue = BSLazy.toStrict . runPut . putAbi
abiCalldata :: Text -> Vector AbiValue -> BS.ByteString
abiCalldata s xs = BSLazy.toStrict . runPut $ do
putWord32be (abiKeccak (encodeUtf8 s))
putAbiSeq xs
parseTypeName :: Text -> Maybe AbiType
parseTypeName = P.parseMaybe typeWithArraySuffix
typeWithArraySuffix :: P.Parsec () Text AbiType
typeWithArraySuffix = do
base <- basicType
sizes <-
P.many $
P.between
(P.char '[') (P.char ']')
(P.many P.digitChar)
let
parseSize :: AbiType -> [Char] -> AbiType
parseSize t "" = AbiArrayDynamicType t
parseSize t s = AbiArrayType (read s) t
pure (foldl parseSize base sizes)
basicType :: P.Parsec () Text AbiType
basicType =
P.choice
[ P.string "address" *> pure AbiAddressType
, P.string "bool" *> pure AbiBoolType
, P.string "string" *> pure AbiStringType
, sizedType "uint" AbiUIntType
, sizedType "int" AbiIntType
, sizedType "bytes" AbiBytesType
, P.string "bytes" *> pure AbiBytesDynamicType
]
where
sizedType :: Text -> (Int -> AbiType) -> P.Parsec () Text AbiType
sizedType s f = P.try $ do
void (P.string s)
fmap (f . read) (P.some P.digitChar)
pack32 :: Int -> [Word32] -> Word256
pack32 n xs =
sum [ shiftL x ((n - i) * 32)
| (x, i) <- zip (map fromIntegral xs) [1..] ]
pack8 :: Int -> [Word8] -> Word256
pack8 n xs =
sum [ shiftL x ((n - i) * 8)
| (x, i) <- zip (map fromIntegral xs) [1..] ]
asUInt :: Integral i => Int -> (i -> a) -> Get a
asUInt n f = (\(AbiUInt _ x) -> f (fromIntegral x)) <$> getAbi (AbiUIntType n)
getWord256 :: Get Word256
getWord256 = pack32 8 <$> replicateM 8 getWord32be
roundTo256Bits :: Integral a => a -> a
roundTo256Bits n = 32 * div (n + 255) 256
getBytesWith256BitPadding :: Integral a => a -> Get ByteString
getBytesWith256BitPadding i =
(BS.pack <$> replicateM n getWord8)
<* skip ((roundTo256Bits n) - n)
where n = fromIntegral i
genAbiValue :: AbiType -> Gen AbiValue
genAbiValue = \case
AbiUIntType n -> genUInt n
AbiIntType n ->
do AbiUInt _ x <- genUInt n
b <- arbitrary
pure $ AbiInt n (signedWord x * if b then 1 else -1)
AbiAddressType ->
(\(AbiUInt _ x) -> AbiAddress (fromIntegral x)) <$> genUInt 20
AbiBoolType ->
elements [AbiBool False, AbiBool True]
AbiBytesType n ->
do xs <- replicateM n arbitrary
pure (AbiBytes n (BS.pack xs))
AbiBytesDynamicType ->
AbiBytesDynamic . BS.pack <$> listOf arbitrary
AbiStringType ->
AbiString . BS.pack <$> listOf arbitrary
AbiArrayDynamicType t ->
do xs <- listOf1 (scale (`div` 2) (genAbiValue t))
pure (AbiArrayDynamic t (Vector.fromList xs))
AbiArrayType n t ->
AbiArray n t . Vector.fromList <$>
replicateM n (scale (`div` 2) (genAbiValue t))
where
genUInt n =
do x <- pack8 (div n 8) <$> replicateM n arbitrary
pure . AbiUInt n $
if n == 256 then x else mod x (2 ^ n)
instance Arbitrary AbiType where
arbitrary = oneof
[ (AbiUIntType . (* 8)) <$> choose (1, 32)
, (AbiIntType . (* 8)) <$> choose (1, 32)
, pure AbiAddressType
, pure AbiBoolType
, AbiBytesType . getPositive <$> arbitrary
, pure AbiBytesDynamicType
, pure AbiStringType
, AbiArrayDynamicType <$> scale (`div` 2) arbitrary
, AbiArrayType
<$> (getPositive <$> arbitrary)
<*> scale (`div` 2) arbitrary
]
instance Arbitrary AbiValue where
arbitrary = arbitrary >>= genAbiValue
shrink = \case
AbiArrayDynamic t v ->
Vector.toList v ++
map (AbiArrayDynamic t . Vector.fromList)
(shrinkList shrink (Vector.toList v))
AbiArray _ t v ->
Vector.toList v ++
map (\x -> AbiArray (length x) t (Vector.fromList x))
(shrinkList shrink (Vector.toList v))
_ -> []