hevm-0.16: test/test.hs
{-# Language OverloadedStrings #-}
{-# Language QuasiQuotes #-}
import Data.Text (Text)
import Data.ByteString (ByteString)
import qualified Data.Text as Text
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as Hex
import Test.Tasty
import Test.Tasty.QuickCheck (testProperty, Arbitrary (..), NonNegative (..))
import Test.Tasty.HUnit
import Control.Monad.State.Strict (execState, runState)
import Control.Lens
import Data.Monoid
import qualified Data.Vector as Vector
import Data.String.Here
import Data.Binary.Put (runPut)
import Data.Binary.Get (runGetOrFail)
import EVM
import EVM.ABI
import EVM.Concrete
import EVM.Exec
import EVM.Solidity
import EVM.Types
import EVM.Precompiled
main :: IO ()
main = defaultMain $ testGroup "hevm"
[ testGroup "ABI"
[ testProperty "Put/get inverse" $ \x ->
case runGetOrFail (getAbi (abiValueType x)) (runPut (putAbi x)) of
Right ("", _, x') -> x' == x
_ -> False
]
, testGroup "Solidity expressions"
[ testCase "Trivial" $ do
SolidityCall "x = 3;" []
===> AbiUInt 256 3
, testCase "Arithmetic" $ do
SolidityCall "x = a + 1;"
[AbiUInt 256 1] ===> AbiUInt 256 2
SolidityCall "x = a - 1;"
[AbiUInt 8 0] ===> AbiUInt 8 255
, testCase "keccak256()" $ do
SolidityCall "x = uint(keccak256(a));"
[AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
]
, testGroup "ecrecover"
[ testCase "Example 1" $
let
h = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
r = "0xc84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732"
s = "0x1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"
in SolidityCall
(Text.unlines
[ "bytes32 h = keccak256(\"\\x19Ethereum Signed Message:\\n32\", d);"
, "x = ecrecover(h, a, b, c);"
])
[ AbiUInt 8 28
, AbiBytes 32 (hexText r)
, AbiBytes 32 (hexText s)
, AbiBytes 32 (hexText h)
]
===>
AbiAddress 0x2d5e56d45c63150d937f2182538a0f18510cb11f
]
, testGroup "Precompiled contracts" $
[ testGroup "Example (reverse)"
[ testCase "success" $
assertEqual "example contract reverses"
(execute 0xdeadbeef "foobar" 6) (Just "raboof")
, testCase "failure" $
assertEqual "example contract fails on length mismatch"
(execute 0xdeadbeef "foobar" 5) Nothing
]
, testGroup "ECRECOVER"
[ testCase "success" $ do
let
r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732"
s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"
v = hex "000000000000000000000000000000000000000000000000000000000000001c"
h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"
a = hex "0000000000000000000000002d5e56d45c63150d937f2182538a0f18510cb11f"
assertEqual "successful recovery"
(Just a)
(execute 1 (h <> v <> r <> s) 32)
, testCase "fail on made up values" $ do
let
r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4731"
s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"
v = hex "000000000000000000000000000000000000000000000000000000000000001c"
h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31"
assertEqual "fail because bit flip"
Nothing
(execute 1 (h <> v <> r <> s) 32)
]
]
, testGroup "Byte/word manipulations"
[ testProperty "padLeft length" $ \n (Bytes bs) ->
BS.length (padLeft n bs) == max n (BS.length bs)
, testProperty "padLeft identity" $ \(Bytes bs) ->
padLeft (BS.length bs) bs == bs
, testProperty "padRight length" $ \n (Bytes bs) ->
BS.length (padLeft n bs) == max n (BS.length bs)
, testProperty "padRight identity" $ \(Bytes bs) ->
padLeft (BS.length bs) bs == bs
, testProperty "padLeft zeroing" $ \(NonNegative n) (Bytes bs) ->
let x = BS.take n (padLeft (BS.length bs + n) bs)
y = BS.replicate n 0
in x == y
]
]
where
(===>) = assertSolidityComputation
hex :: ByteString -> ByteString
hex s =
case Hex.decode s of
(x, "") -> x
_ -> error "internal error"
singleContract :: Text -> Text -> IO (Maybe ByteString)
singleContract x s =
solidity x [i|
contract ${x} { ${s} }
|]
runStatements
:: Text -> [AbiValue] -> AbiType
-> IO (Maybe ByteString)
runStatements stmts args t = do
let params =
Text.intercalate ", "
(map (\(x, c) -> abiTypeSolidity (abiValueType x)
<> " " <> Text.pack [c])
(zip args "abcdefg"))
sig =
"foo(" <> Text.intercalate ","
(map (abiTypeSolidity . abiValueType) args) <> ")"
Just x <- singleContract "X" [i|
function foo(${params}) public pure returns (${abiTypeSolidity t} x) {
${stmts}
}
|]
case runState exec (vmForEthrunCreation x) of
(VMSuccess (B targetCode), vm1) -> do
let target = view (state . contract) vm1
vm2 = execState (replaceCodeOfSelf targetCode) vm1
case flip runState vm2
(do resetState
assign (state . gas) 0xffffffffffffffff -- kludge
loadContract target
assign (state . calldata)
(B (abiCalldata sig (Vector.fromList args)))
exec) of
(VMSuccess (B out), _) ->
return (Just out)
(VMFailure problem, _) -> do
print problem
return Nothing
_ ->
return Nothing
newtype Bytes = Bytes ByteString
deriving Eq
instance Show Bytes where
showsPrec _ (Bytes x) _ = show (BS.unpack x)
instance Arbitrary Bytes where
arbitrary = fmap (Bytes . BS.pack) arbitrary
data Invocation
= SolidityCall Text [AbiValue]
deriving Show
assertSolidityComputation :: Invocation -> AbiValue -> IO ()
assertSolidityComputation (SolidityCall s args) x =
do y <- runStatements s args (abiValueType x)
assertEqual (Text.unpack s)
(fmap Bytes (Just (encodeAbiValue x)))
(fmap Bytes y)