Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hevm abi encode #868

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/dapp-tests/integration/tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,63 @@ test_calldata_19() {
}

test_calldata_20() {
local output
output=$(seth calldata 'f(tuple(bool))' "(true)")

assert_equals "0x19cabbc50000000000000000000000000000000000000000000000000000000000000001" "$output"
}

test_calldata_21() {
local output
output=$(seth calldata 'f((bool, bool))' "(true, false)")

assert_equals "0x59c4785b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" "$output"
}

test_calldata_22() {
local output
output=$(seth calldata 'f(Data(bool, bool))' "(true, false)")

assert_equals "0x59c4785b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" "$output"
}

test_calldata_23() {
local output
output=$(seth calldata 'f(uint, Data(bool, bytes))' 1 "(true, 0xcafe)")

assert_equals "0xf1dd2cfe00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002cafe000000000000000000000000000000000000000000000000000000000000" "$output"
}

test_calldata_24() {
local output
output=$(seth calldata "_f2(address, bool, uint32, address,(bool[] x,uint256, uint32[4] y), bytes memory b) public view mod returns (bool,(bytes,string)) ; " \
0x49c92F2cE8F876b070b114a6B2F8A60b83c281Ad true 111 0x49c92F2cE8F876b070b114a6B2F8A60b83c281Ad "([true, false], 33, [1, 2, 3, 4])" 0xcafefe)

assert_equals "0x3c31a0e800000000000000000000000049c92f2ce8f876b070b114a6b2f8a60b83c281ad0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006f00000000000000000000000049c92f2ce8f876b070b114a6b2f8a60b83c281ad00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003cafefe0000000000000000000000000000000000000000000000000000000000" "$output"
}

test_calldata_25() {
local output
output=$(seth calldata 'f(uint[][], Data(bool, bytes)[][2])' "[[1, 2], [3]]" "[[(true, 0xaa), (true, 0xbb)], [(true, 0xcc)]]")

assert_equals "0x6ba52bdd000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001aa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001bb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001cc00000000000000000000000000000000000000000000000000000000000000" "$output"
}

test_calldata_26() {
local output
output=$(seth calldata 'f((uint, uint)[][3])' "[[(10, 11), (12, 13)], [(14, 15), (16, 17)], [(18, 19), (20, 21)]]")

assert_equals "0x86c6d56a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015" "$output"
}

test_calldata_27() {
local output
output=$(seth calldata 'f((uint, uint)[2][3])' "[[(10, 11), (12, 13)], [(14, 15), (16, 17)], [(18, 19), (20, 21)]]")

assert_equals "0x4c1620bd000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015" "$output"
}

test_calldata_28() {
local output
output=$(seth calldata 'foo(bytes32, bytes4, bytes16)' '0x' '0x' '0x')

Expand Down
123 changes: 110 additions & 13 deletions src/hevm/hevm-cli/hevm-cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import EVM.SymExec
import EVM.Debug
import EVM.ABI
import EVM.Solidity
import EVM.SolidityABI
import EVM.Types hiding (word)
import EVM.UnitTest (UnitTestOptions, coverageReport, coverageForUnitTestContract)
import EVM.UnitTest (runUnitTestContract)
Expand All @@ -58,7 +59,7 @@ import Data.ByteString (ByteString)
import Data.List (intercalate, isSuffixOf)
import Data.Tree
import Data.Text (unpack, pack)
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.IO (hPutStr)
import Data.Maybe (fromMaybe, fromJust)
import Data.Version (showVersion)
Expand All @@ -74,15 +75,17 @@ import qualified Data.Aeson as JSON
import qualified Data.Aeson.Types as JSON
import Data.Aeson (FromJSON (..), (.:))
import Data.Aeson.Lens hiding (values)
import qualified Data.Vector as V
import qualified Data.ByteString.Lazy as Lazy
import Data.Aeson.Encode.Pretty (encodePretty', Config(..), keyOrder, defConfig, Indent(..))

import qualified Data.Vector as V
import qualified Data.ByteString.Lazy as Lazy
import qualified Data.SBV as SBV
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Char8 as Char8
import qualified Data.ByteString.Lazy as LazyByteString
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Data.Text.IO as TextIO
import qualified System.Timeout as Timeout

import qualified Paths_hevm as Paths
Expand Down Expand Up @@ -228,6 +231,20 @@ data Command w
{ abi :: w ::: Maybe String <?> "Signature of types to decode / encode"
, arg :: w ::: [String] <?> "Values to encode"
}
| Abi
{ abi :: w ::: Maybe String <?> "Signature of types to decode / encode"
}
| Abidecode
{ abi :: w ::: Maybe String <?> "Signature of types to decode / encode"
, calldata :: w ::: Maybe ByteString <?> "Tx: calldata"
, returndata :: w ::: Maybe ByteString <?> "returndata"
}
| Normalise
{ abi :: w ::: Maybe String <?> "Signature of types to decode / encode"
}
| Selector
{ abi :: w ::: Maybe String <?> "Signature of types to decode / encode"
}
| MerkleTest -- Insert a set of key values and check against the given root
{ file :: w ::: String <?> "Path to .json test file"
}
Expand Down Expand Up @@ -320,14 +337,23 @@ main = do
cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator"
let
root = fromMaybe "." (dappRoot cmd)
required arg = fromMaybe (error ("missing required argument --" <> arg))
case cmd of
Version {} -> putStrLn (showVersion Paths.version)
Symbolic {} -> withCurrentDirectory root $ assert cmd
Equivalence {} -> equivalence cmd
Exec {} ->
launchExec cmd
Abiencode {} ->
print . ByteStringS $ abiencode (abi cmd) (arg cmd)
print . ByteStringS $ abiencode (required "abi" (abi cmd)) (arg cmd)
Abidecode {} ->
TextIO.putStrLn $ abidecode (abi cmd) (calldata cmd) (returndata cmd)
Abi {} ->
TextIO.putStrLn $ makeabi $ required "abi" (abi cmd)
Normalise {} ->
TextIO.putStrLn $ normaliseSig $ required "abi" (abi cmd)
Selector {} ->
print . ByteStringS $ abiselector $ required "abi" (abi cmd)
BcTest {} ->
launchTest cmd
DappTest {} ->
Expand Down Expand Up @@ -968,16 +994,87 @@ runVMTest diffmode mode timelimit (name, x) =

#endif

parseAbi :: (AsValue s) => s -> (Text, [AbiType])
parseAbi abijson =
(signature abijson, snd
<$> parseMethodInput
<$> V.toList
(fromMaybe (error "Malformed function abi") (abijson ^? key "inputs" . _Array)))

abiencode :: (AsValue s) => Maybe s -> [String] -> ByteString
abiencode Nothing _ = error "missing required argument: abi"
abiencode (Just abijson) args =
normaliseSig :: String -> Text
normaliseSig abi = fst $ parseAbi abi

abidecode :: Maybe String -> Maybe ByteString -> Maybe ByteString -> Text
abidecode Nothing _ _ = error "missing required argument --abi"
abidecode _ Nothing Nothing = error "must provide either --calldata or --returndata"
abidecode (Just abi) (Just calldata) Nothing =
let (sig, types) = parseAbi abi
abiSelector = strip0x (selector sig)
(dataSelector, dataBody) =
ByteString.splitAt 4 (hexByteString "--calldata" $ strip0x $ calldata)
AbiTuple (values) = decodeAbiValue (AbiTupleType (V.fromList types)) (Lazy.fromStrict $ dataBody)
in
if (abiSelector == dataSelector) then
pack $ intercalate "\n" (show <$> V.toList values)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pack $ intercalate "\n" (show <$> V.toList values)
pack $ unlines (show <$> V.toList values)

and sim

else
error $ "abi and calldata signatures do not match."
<> "\nabi: " <> show (ByteStringS abiSelector)
<> "\ncalldata: " <> show (ByteStringS dataSelector)

abidecode (Just abi) Nothing (Just returndata) =
let (_, types) = parseAbiOutputs abi
dataBody = hexByteString "--returndata" $ strip0x $ returndata
AbiTuple values = decodeAbiValue (AbiTupleType (V.fromList types)) (Lazy.fromStrict $ dataBody)
in pack $ intercalate "\n" (show <$> V.toList values)

abidecode (Just abi) (Just calldata) (Just returndata) =
let inputs = abidecode (Just abi) (Just calldata) Nothing
outputs = abidecode (Just abi) Nothing (Just returndata)
in inputs <> "\n->\n" <> outputs

abiselector :: String -> ByteString
abiselector abi = selector $ fst $ parseAbi abi

makeabi :: String -> Text
makeabi abi =
case parseSolidityAbi abi of
Left e -> error $ "could not parse abi fragment. result: " ++ e
Right (Just method, _, _) -> render (JSON.toJSON method)
Right (Nothing, Just event, _) -> render (JSON.toJSON event)
Right (Nothing, Nothing, Just err) -> render (JSON.toJSON err)
where
render = decodeUtf8 . Lazy.toStrict . encoder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
render = decodeUtf8 . Lazy.toStrict . encoder
render :: ToJSON a => a -> Text
render = decodeUtf8 . Lazy.toStrict . encoder

ish

encoder = encodePretty' $
defConfig { confIndent = Spaces 2,
confCompare = keyOrder [ "type"
, "name"
, "indexed"
, "stateMutability"
, "anonymous"
, "components"
, "inputs"
, "outputs"
]}

parseAbi :: String -> (Text, [AbiType])
parseAbi abi =
case (abi ^? key "inputs" . _Array) of
Just inputs ->
(signature abi, snd <$> parseMethodInput <$> V.toList inputs)
Nothing ->
case parseSolidityAbi abi of
Left e -> error $ "could not parse abi fragment. result: " ++ e
Right (Just (Method _ types _ sig _), _, _) -> (sig, snd <$> types)
Right (Nothing, Nothing, Just (SolError name types)) -> (sig, types)
where sig = name <> pack (show (AbiTupleType (V.fromList types)))
Right (_, Just _, _) -> error "event encoding is not currently supported"

parseAbiOutputs :: String -> (Text, [AbiType])
parseAbiOutputs abi =
case (abi ^? key "outputs" . _Array) of
Just outputs ->
(signature abi, snd <$> parseMethodInput <$> V.toList outputs)
Nothing ->
case parseSolidityAbi abi of
Left e -> error $ "could not parse abi fragment. result: " ++ e
Right (Just (Method outputs _ _ sig _), _, _) -> (sig, snd <$> outputs)

abiencode :: String -> [String] -> ByteString
abiencode abijson args =
let (sig', declarations) = parseAbi abijson
in if length declarations == length args
then abiMethod sig' $ AbiTuple . V.fromList $ zipWith makeAbiValue declarations args
Expand Down
5 changes: 4 additions & 1 deletion src/hevm/hevm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cabal-version: 2.2
name:
hevm
version:
0.49.0
0.50.0
synopsis:
Ethereum virtual machine evaluator
description:
Expand Down Expand Up @@ -53,6 +53,7 @@ library
EVM.Precompiled,
EVM.RLP,
EVM.Solidity,
EVM.SolidityABI,
EVM.Stepper,
EVM.StorageLayout,
EVM.Symbolic,
Expand Down Expand Up @@ -89,6 +90,7 @@ library
tree-view >= 0.5 && < 0.6,
abstract-par >= 0.3.3 && < 0.4,
aeson >= 1.5.6 && < 1.6,
aeson-pretty,
bytestring >= 0.10.8 && < 0.11,
scientific >= 0.3.6 && < 0.4,
binary >= 0.8.6 && < 0.9,
Expand Down Expand Up @@ -163,6 +165,7 @@ executable hevm
build-depends:
QuickCheck,
aeson,
aeson-pretty,
ansi-wl-pprint,
async,
base,
Expand Down
3 changes: 1 addition & 2 deletions src/hevm/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
let
inherit (dapphub) pkgs;


drv = pkgs.haskellPackages.shellFor {
packages = p: [
p.hevm
];
buildInputs = with pkgs.haskellPackages; [
buildInputs = with pkgs.haskellPackages; with pkgs; [
cabal-install
haskell-language-server
];
Expand Down
Loading