hevm-0.51.0: test/rpc.hs
{-# Language DataKinds #-}
module Main where
import Test.Tasty
import Test.Tasty.HUnit
import Data.Maybe
import Data.Map qualified as Map
import Data.Text (Text)
import Data.Vector qualified as V
import EVM (makeVm)
import EVM.ABI
import EVM.Fetch
import EVM.SMT
import EVM.Solvers
import EVM.Stepper qualified as Stepper
import EVM.SymExec
import EVM.Test.Utils
import EVM.Solidity (ProjectType(..))
import EVM.Types hiding (BlockNumber)
main :: IO ()
main = defaultMain tests
tests :: TestTree
tests = testGroup "rpc"
[ testGroup "Block Parsing Tests"
[ testCase "pre-merge-block" $ do
let block = BlockNumber 15537392
(cb, numb, basefee, prevRan) <- fetchBlockFrom block testRpc >>= \case
Nothing -> error "Could not fetch block"
Just Block{..} -> return ( coinbase
, number
, baseFee
, prevRandao
)
assertEqual "coinbase" (Addr 0xea674fdde714fd979de3edf0f56aa9716b898ec8) cb
assertEqual "number" (BlockNumber numb) block
assertEqual "basefee" 38572377838 basefee
assertEqual "prevRan" 11049842297455506 prevRan
, testCase "post-merge-block" $ do
let block = BlockNumber 16184420
(cb, numb, basefee, prevRan) <- fetchBlockFrom block testRpc >>= \case
Nothing -> error "Could not fetch block"
Just Block{..} -> return ( coinbase
, number
, baseFee
, prevRandao
)
assertEqual "coinbase" (Addr 0x690b9a9e9aa1c9db991c7721a92d351db4fac990) cb
assertEqual "number" (BlockNumber numb) block
assertEqual "basefee" 22163046690 basefee
assertEqual "prevRan" 0x2267531ab030ed32fd5f2ef51f81427332d0becbd74fe7f4cd5684ddf4b287e0 prevRan
]
, testGroup "execution with remote state"
-- execute against remote state from a ds-test harness
[ testCase "dapp-test" $ do
let testFile = "test/contracts/pass/rpc.sol"
runSolidityTestCustom testFile ".*" Nothing False testRpcInfo Foundry >>= assertEqual "test result" True
-- concretely exec "transfer" on WETH9 using remote rpc
-- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
, testCase "weth-conc" $ do
let
blockNum = 16198552
wad = 0x999999999999999999
calldata' = ConcreteBuf $ abiMethod "transfer(address,uint256)" (AbiTuple (V.fromList [AbiAddress (Addr 0xdead), AbiUInt 256 wad]))
vm <- weth9VM blockNum (calldata', [])
postVm <- withSolvers Z3 1 Nothing $ \solvers ->
Stepper.interpret (oracle solvers (Just (BlockNumber blockNum, testRpc))) vm Stepper.runFully
let
postStore = case postVm.env.storage of
ConcreteStore s -> s
_ -> error "ConcreteStore expected"
wethStore = fromJust $ Map.lookup 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 postStore
receiverBal = fromJust $ Map.lookup (keccak' (word256Bytes 0xdead <> word256Bytes 0x3)) wethStore
msg = case postVm.result of
Just (VMSuccess m) -> m
_ -> error "VMSuccess expected"
assertEqual "should succeed" msg (ConcreteBuf $ word256Bytes 0x1)
assertEqual "should revert" receiverBal (W256 $ 2595433725034301 + wad)
-- symbolically exec "transfer" on WETH9 using remote rpc
-- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
, testCase "weth-sym" $ do
let
blockNum = 16198552
calldata' = symCalldata "transfer(address,uint256)" [AbiAddressType, AbiUIntType 256] ["0xdead"] (AbstractBuf "txdata")
postc _ (Failure _ (Revert _)) = PBool False
postc _ _ = PBool True
vm <- weth9VM blockNum calldata'
(_, [Cex (_, model)]) <- withSolvers Z3 1 Nothing $ \solvers ->
verify solvers (rpcVeriOpts (BlockNumber blockNum, testRpc)) vm (Just postc)
assertBool "model should exceed caller balance" (getVar model "arg2" >= 695836005599316055372648)
]
]
-- call into WETH9 from 0xf04a... (a large holder)
weth9VM :: W256 -> (Expr Buf, [Prop]) -> IO VM
weth9VM blockNum calldata' = do
let
caller' = Lit 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e
weth9 = Addr 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
callvalue' = Lit 0
vmFromRpc blockNum calldata' callvalue' caller' weth9
vmFromRpc :: W256 -> (Expr Buf, [Prop]) -> Expr EWord -> Expr EWord -> Addr -> IO VM
vmFromRpc blockNum calldata' callvalue' caller' address' = do
ctrct <- fetchContractFrom (BlockNumber blockNum) testRpc address' >>= \case
Nothing -> error $ "contract not found: " <> show address'
Just contract' -> return contract'
blk <- fetchBlockFrom (BlockNumber blockNum) testRpc >>= \case
Nothing -> error "could not fetch block"
Just b -> pure b
pure $ makeVm $ VMOpts
{ contract = ctrct
, calldata = calldata'
, value = callvalue'
, address = address'
, caller = caller'
, origin = 0xacab
, gas = 0xffffffffffffffff
, gaslimit = 0xffffffffffffffff
, baseFee = blk.baseFee
, priorityFee = 0
, coinbase = blk.coinbase
, number = blk.number
, timestamp = blk.timestamp
, blockGaslimit = blk.gaslimit
, gasprice = 0
, maxCodeSize = blk.maxCodeSize
, prevRandao = blk.prevRandao
, schedule = blk.schedule
, chainId = 1
, create = False
, initialStorage = EmptyStore
, txAccessList = mempty
, allowFFI = False
}
testRpc :: Text
testRpc = "https://eth-mainnet.alchemyapi.io/v2/vpeKFsEF6PHifHzdtcwXSDbhV3ym5Ro4"
testRpcInfo :: RpcInfo
testRpcInfo = Just (BlockNumber 16198552, testRpc)