From bf9fccf37bdee7aa05b655b86ab31f7397d82df9 Mon Sep 17 00:00:00 2001 From: Martin Allen <31280145+martyall@users.noreply.github.com> Date: Thu, 5 Oct 2023 23:07:22 -0700 Subject: [PATCH] Experiemental (#172) * generator works and tests pass * seems to work * dep * t clean up * add dep * fix instance chain * tidy * remove extra type param * add one more layer of nesting for nested tuples test * wip * wip * now with more nesting * compiling sigs * seems to work with web3-tests * oops * wip * added a shitload of traces * eureka * remove unused internal module, simplify to/from record fields names and class def * cleanup unused things and rename for sanity * add vector instance * update arrayparser logic * fix generic impl * update generics --- spago.dhall | 2 + src/Network/Ethereum/Web3.purs | 2 +- src/Network/Ethereum/Web3/Contract.purs | 47 +- .../Ethereum/Web3/Contract/Events.purs | 23 +- .../Ethereum/Web3/Contract/Internal.purs | 33 -- src/Network/Ethereum/Web3/Solidity.purs | 67 +-- .../Ethereum/Web3/Solidity/AbiEncoding.purs | 487 +++++++++++------- .../Ethereum/Web3/Solidity/EncodingType.purs | 80 --- src/Network/Ethereum/Web3/Solidity/Event.purs | 143 +++-- .../Ethereum/Web3/Solidity/Generic.purs | 229 -------- src/Network/Ethereum/Web3/Solidity/Int.purs | 11 +- .../Ethereum/Web3/Solidity/Internal.purs | 141 +++++ src/Network/Ethereum/Web3/Solidity/Tuple.purs | 56 +- src/Network/Ethereum/Web3/Solidity/UInt.purs | 11 +- .../Ethereum/Web3/Solidity/Vector.purs | 14 +- test/web3/Main.purs | 2 + .../Web3Spec/Encoding/ContainersSpec.purs | 148 ++++-- test/web3/Web3Spec/Encoding/DataSpec.purs | 13 +- test/web3/Web3Spec/Encoding/GenericSpec.purs | 83 ++- 19 files changed, 780 insertions(+), 812 deletions(-) delete mode 100644 src/Network/Ethereum/Web3/Contract/Internal.purs delete mode 100644 src/Network/Ethereum/Web3/Solidity/EncodingType.purs delete mode 100644 src/Network/Ethereum/Web3/Solidity/Generic.purs create mode 100644 src/Network/Ethereum/Web3/Solidity/Internal.purs diff --git a/spago.dhall b/spago.dhall index 01ef064..f32abf9 100644 --- a/spago.dhall +++ b/spago.dhall @@ -36,7 +36,9 @@ , "tuples" , "typelevel-prelude" , "unfoldable" + , "unsafe-coerce" , "variant" + , "identity" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] diff --git a/src/Network/Ethereum/Web3.purs b/src/Network/Ethereum/Web3.purs index f92f8e0..274228c 100644 --- a/src/Network/Ethereum/Web3.purs +++ b/src/Network/Ethereum/Web3.purs @@ -16,7 +16,7 @@ import Network.Ethereum.Web3.Solidity , UIntN , Vector , fromByteString - , fromData + , abiDecode , intNFromBigNumber , nilVector , toVector diff --git a/src/Network/Ethereum/Web3/Contract.purs b/src/Network/Ethereum/Web3/Contract.purs index 828618b..297b455 100644 --- a/src/Network/Ethereum/Web3/Contract.purs +++ b/src/Network/Ethereum/Web3/Contract.purs @@ -16,7 +16,6 @@ import Control.Monad.Error.Class (throwError) import Data.Bifunctor (lmap) import Data.Either (Either(..)) import Data.Functor.Tagged (Tagged, untagged) -import Data.Generic.Rep (class Generic, Constructor) import Data.Lens ((.~), (%~), (?~)) import Data.Maybe (Maybe(..)) import Data.Symbol (class IsSymbol, reflectSymbol) @@ -25,7 +24,8 @@ import Network.Ethereum.Core.Keccak256 (toSelector) import Network.Ethereum.Types (Address, HexString) import Network.Ethereum.Web3.Api (eth_call, eth_sendTransaction) import Network.Ethereum.Web3.Contract.Events (MultiFilterStreamState(..), event', FilterStreamState, ChangeReceipt, EventHandler) -import Network.Ethereum.Web3.Solidity (class DecodeEvent, class GenericABIDecode, class GenericABIEncode, class RecordFieldsIso, genericABIEncode, genericFromData, genericFromRecordFields) +import Network.Ethereum.Web3.Solidity (class ABIDecode, class ABIEncode, class DecodeEvent, class RecordFieldsIso, fromRecord) +import Network.Ethereum.Web3.Solidity.AbiEncoding (abiDecode, abiEncode) import Network.Ethereum.Web3.Types (class TokenUnit, CallError(..), ChainCursor, ETHER, Filter, NoPay, TransactionOptions, Value, Web3, _data, _value, convert) import Type.Proxy (Proxy(..)) @@ -82,17 +82,16 @@ class CallMethod (selector :: Symbol) a b where -> Web3 (Either CallError b) -- ^ `Web3` wrapped result -instance (Generic a rep, GenericABIEncode rep) => TxMethod s a where +instance ABIEncode a => TxMethod s a where sendTx = _sendTransaction -instance (Generic a arep, GenericABIEncode arep, Generic b brep, GenericABIDecode brep) => CallMethod s a b where +instance (ABIEncode a, ABIDecode b) => CallMethod s a b where call = _call _sendTransaction - :: forall a u rep selector + :: forall a u selector . IsSymbol selector - => Generic a rep - => GenericABIEncode rep + => ABIEncode a => TokenUnit (Value (u ETHER)) => TransactionOptions u -> Tagged selector a @@ -100,7 +99,7 @@ _sendTransaction _sendTransaction txOptions dat = do let sel = toSelector <<< reflectSymbol $ (Proxy :: Proxy selector) - eth_sendTransaction $ txdata $ sel <> (genericABIEncode <<< untagged $ dat) + eth_sendTransaction $ txdata $ sel <> (abiEncode <<< untagged $ dat) where txdata d = txOptions # _data .~ Just d @@ -108,12 +107,10 @@ _sendTransaction txOptions dat = do %~ map convert _call - :: forall a arep b brep selector + :: forall a b selector . IsSymbol selector - => Generic a arep - => GenericABIEncode arep - => Generic b brep - => GenericABIDecode brep + => ABIEncode a + => ABIDecode b => TransactionOptions NoPay -> ChainCursor -> Tagged selector a @@ -124,9 +121,9 @@ _call txOptions cursor dat = do sel = toSelector sig - fullData = sel <> (genericABIEncode <<< untagged $ dat) - res <- eth_call (txdata $ sel <> (genericABIEncode <<< untagged $ dat)) cursor - case genericFromData res of + fullData = sel <> (abiEncode <<< untagged $ dat) + res <- eth_call (txdata $ sel <> (abiEncode <<< untagged $ dat)) cursor + case abiDecode res of Left err -> if res == mempty then pure <<< Left @@ -141,9 +138,8 @@ _call txOptions cursor dat = do txdata d = txOptions # _data .~ Just d deployContract - :: forall a rep t - . Generic a rep - => GenericABIEncode rep + :: forall a t + . ABIEncode a => TransactionOptions NoPay -> HexString -> Tagged t a @@ -151,18 +147,17 @@ deployContract deployContract txOptions deployByteCode args = let txdata = - txOptions # _data ?~ deployByteCode <> genericABIEncode (untagged args) + txOptions # _data ?~ deployByteCode <> abiEncode (untagged args) # _value %~ map convert in eth_sendTransaction txdata mkDataField - :: forall selector a name args fields l + :: forall selector a fields . IsSymbol selector - => Generic a (Constructor name args) - => RecordFieldsIso args fields l - => GenericABIEncode (Constructor name args) + => RecordFieldsIso a () fields + => ABIEncode a => Proxy (Tagged selector a) -> Record fields -> HexString @@ -172,6 +167,6 @@ mkDataField _ r = sel = toSelector sig - args = genericFromRecordFields r :: a + args = fromRecord r :: a in - sel <> (genericABIEncode args) + sel <> abiEncode args diff --git a/src/Network/Ethereum/Web3/Contract/Events.purs b/src/Network/Ethereum/Web3/Contract/Events.purs index e32ed34..b22be46 100644 --- a/src/Network/Ethereum/Web3/Contract/Events.purs +++ b/src/Network/Ethereum/Web3/Contract/Events.purs @@ -20,6 +20,7 @@ module Network.Ethereum.Web3.Contract.Events ) where import Prelude + import Control.Coroutine (Process, Consumer, producer, consumer, pullFrom, runProcess) import Control.Coroutine.Transducer (Transducer, awaitForever, fromProducer, toProducer, yieldT, (=>=)) import Control.Monad.Fork.Class (bracket) @@ -27,14 +28,14 @@ import Control.Monad.Reader.Trans (ReaderT, runReaderT) import Control.Monad.Rec.Class (class MonadRec) import Control.Monad.Trans.Class (lift) import Control.Parallel (class Parallel) -import Data.Array (catMaybes, sort) +import Data.Array (sort) import Data.Either (Either(..)) import Data.Functor.Tagged (Tagged, tagged, untagged) import Data.Lens ((.~), (^.)) import Data.Maybe (Maybe(..)) import Data.Newtype (over) import Data.Symbol (class IsSymbol) -import Data.Traversable (for_) +import Data.Traversable (for_, traverse) import Data.Tuple (Tuple(..), fst) import Data.Variant (Variant, class VariantMatchCases, expand, inj, match) import Effect.Aff (delay, Milliseconds(..)) @@ -45,7 +46,7 @@ import Network.Ethereum.Core.BigNumber (BigNumber, fromInt) import Network.Ethereum.Core.HexString (HexString) import Network.Ethereum.Web3.Api (eth_blockNumber, eth_getFilterChanges, eth_getLogs, eth_newFilter, eth_uninstallFilter) import Network.Ethereum.Web3.Solidity.Event (class DecodeEvent, decodeEvent) -import Network.Ethereum.Web3.Types (BlockNumber(..), ChainCursor(..), Change(..), EventAction(..), Filter, FilterId, Web3, _fromBlock, _toBlock) +import Network.Ethereum.Web3.Types (BlockNumber(..), ChainCursor(..), Change(..), EventAction(..), Filter, FilterId, Web3, Web3Error, _fromBlock, _toBlock, throwWeb3) import Prim.RowList as RowList import Record as Record import Type.Proxy (Proxy(..)) @@ -365,8 +366,8 @@ mkFilterChanges => Proxy sym -> Proxy e -> Array Change - -> Array (FilterChange (Variant r)) -mkFilterChanges sp _ cs = catMaybes $ map pairChange cs + -> Either Web3Error (Array (FilterChange (Variant r))) +mkFilterChanges sp _ cs = traverse pairChange cs where pairChange rawChange = do a :: e <- decodeEvent rawChange @@ -387,8 +388,10 @@ instance queryAllLogs :: ) => FoldingWithIndex QueryAllLogs (Proxy sym) (Web3 (Array (FilterChange (Variant r')))) (Filter e) (Web3 (Array (FilterChange (Variant r)))) where foldingWithIndex QueryAllLogs (prop :: Proxy sym) acc filter = do - changes :: Array (FilterChange (Variant r)) <- mkFilterChanges prop (Proxy :: Proxy e) <$> eth_getLogs (filter :: Filter e) - (<>) changes <$> (map (map expand) <$> acc) + eRes <- mkFilterChanges prop (Proxy :: Proxy e) <$> eth_getLogs (filter :: Filter e) + case eRes of + Left err -> throwWeb3 err + Right changes -> (<>) changes <$> (map (map expand) <$> acc) data MultiFilterStreamState fs = MultiFilterStreamState { currentBlock :: BlockNumber @@ -428,8 +431,10 @@ instance checkMultiFilterLogs :: ) => FoldingWithIndex CheckMultiFilter (Proxy sym) (Web3 (Array (FilterChange (Variant r')))) (Tagged e FilterId) (Web3 (Array (FilterChange (Variant r)))) where foldingWithIndex CheckMultiFilter (prop :: Proxy sym) acc filterId = do - changes :: Array (FilterChange (Variant r)) <- mkFilterChanges prop (Proxy :: Proxy e) <$> eth_getFilterChanges (untagged filterId) - (<>) changes <$> (map (map expand) <$> acc) + eRes <- mkFilterChanges prop (Proxy :: Proxy e) <$> eth_getFilterChanges (untagged filterId) + case eRes of + Left err -> throwWeb3 err + Right changes -> (<>) changes <$> (map (map expand) <$> acc) data CloseMultiFilter = CloseMultiFilter diff --git a/src/Network/Ethereum/Web3/Contract/Internal.purs b/src/Network/Ethereum/Web3/Contract/Internal.purs deleted file mode 100644 index 0da9ee6..0000000 --- a/src/Network/Ethereum/Web3/Contract/Internal.purs +++ /dev/null @@ -1,33 +0,0 @@ -module Network.Ethereum.Web3.Contract.Internal - ( class UncurryFields - , uncurryFields - ) where - -import Prelude -import Data.Functor.Tagged (Tagged, tagged) -import Record as Record -import Data.Symbol (class IsSymbol) -import Type.Row as Row -import Network.Ethereum.Web3.Types (Web3) -import Type.Proxy (Proxy(..)) - --------------------------------------------------------------------------------- --- * Uncurry Helper --------------------------------------------------------------------------------- --- | Useful class for using records as arguments to solidity functions -class UncurryFields fields curried result | curried -> result fields where - uncurryFields :: Record fields -> curried -> result - -instance uncurryFieldsEmpty :: UncurryFields () (Web3 b) (Web3 b) where - uncurryFields _ = identity - -instance uncurryFieldsInductive :: (IsSymbol s, Row.Cons s a before after, Row.Lacks s before, UncurryFields before f b) => UncurryFields after (Tagged s a -> f) b where - uncurryFields r f = - let - arg = (Record.get (Proxy :: Proxy s) r) - - before = Record.delete (Proxy :: Proxy s) r :: Record before - - partiallyApplied = f (tagged arg :: Tagged s a) - in - uncurryFields before partiallyApplied diff --git a/src/Network/Ethereum/Web3/Solidity.purs b/src/Network/Ethereum/Web3/Solidity.purs index c4dc1fd..7cfd895 100644 --- a/src/Network/Ethereum/Web3/Solidity.purs +++ b/src/Network/Ethereum/Web3/Solidity.purs @@ -2,7 +2,7 @@ module Network.Ethereum.Web3.Solidity ( module Network.Ethereum.Web3.Solidity.Vector , module Network.Ethereum.Web3.Solidity.Bytes , module Network.Ethereum.Web3.Solidity.Tuple - , module Network.Ethereum.Web3.Solidity.Generic + , module Network.Ethereum.Web3.Solidity.Internal , module Network.Ethereum.Web3.Solidity.Int , module Network.Ethereum.Web3.Solidity.UInt , module Network.Ethereum.Web3.Solidity.AbiEncoding @@ -11,64 +11,13 @@ module Network.Ethereum.Web3.Solidity , module Data.ByteString ) where -import Network.Ethereum.Web3.Solidity.Vector (Vector, unVector, nilVector, vCons, (:<), vectorLength, toVector) +import Data.ByteString (ByteString) +import Network.Ethereum.Types (BigNumber, Address) +import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIDecode, class ABIEncode, class EncodingType, abiDecode, isDynamic, abiEncode) import Network.Ethereum.Web3.Solidity.Bytes (BytesN, unBytesN, proxyBytesN, update, fromByteString) -import Network.Ethereum.Web3.Solidity.Tuple - ( Tuple0(..) - , Tuple1(..) - , unTuple1 - , uncurry1 - , curry1 - , Tuple2(..) - , uncurry2 - , curry2 - , Tuple3(..) - , uncurry3 - , curry3 - , Tuple4(..) - , uncurry4 - , curry4 - , Tuple5(..) - , uncurry5 - , curry5 - , Tuple6(..) - , uncurry6 - , curry6 - , Tuple7(..) - , uncurry7 - , curry7 - , Tuple8(..) - , uncurry8 - , curry8 - , Tuple9(..) - , uncurry9 - , curry9 - , Tuple10(..) - , uncurry10 - , curry10 - , Tuple11(..) - , uncurry11 - , curry11 - , Tuple12(..) - , uncurry12 - , curry12 - , Tuple13(..) - , uncurry13 - , curry13 - , Tuple14(..) - , uncurry14 - , curry14 - , Tuple15(..) - , uncurry15 - , curry15 - , Tuple16(..) - , uncurry16 - , curry16 - ) -import Network.Ethereum.Web3.Solidity.Int (IntN, unIntN, intNFromBigNumber) import Network.Ethereum.Web3.Solidity.Event (class DecodeEvent, decodeEvent, class IndexedEvent, isAnonymous) +import Network.Ethereum.Web3.Solidity.Int (IntN, unIntN, intNFromBigNumber) +import Network.Ethereum.Web3.Solidity.Internal (class RecordFieldsIso, fromRecord, toRecord) +import Network.Ethereum.Web3.Solidity.Tuple (Tuple0(..), Tuple1(..), unTuple1, uncurry1, curry1, Tuple2(..), uncurry2, curry2, Tuple3(..), uncurry3, curry3, Tuple4(..), uncurry4, curry4, Tuple5(..), uncurry5, curry5, Tuple6(..), uncurry6, curry6, Tuple7(..), uncurry7, curry7, Tuple8(..), uncurry8, curry8, Tuple9(..), uncurry9, curry9, Tuple10(..), uncurry10, curry10, Tuple11(..), uncurry11, curry11, Tuple12(..), uncurry12, curry12, Tuple13(..), uncurry13, curry13, Tuple14(..), uncurry14, curry14, Tuple15(..), uncurry15, curry15, Tuple16(..), uncurry16, curry16) import Network.Ethereum.Web3.Solidity.UInt (UIntN, unUIntN, uIntNFromBigNumber) -import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIEncode, class ABIDecode, toDataBuilder, fromDataParser, fromData) -import Network.Ethereum.Web3.Solidity.Generic (class GenericABIEncode, class GenericABIDecode, class ArgsToRowListProxy, genericABIEncode, genericABIDecode, genericFromData, class RecordFieldsIso, toRecordFields, fromRecordFields, genericToRecordFields, genericFromRecordFields) -import Network.Ethereum.Types (BigNumber, Address) -import Data.ByteString (ByteString) +import Network.Ethereum.Web3.Solidity.Vector (Vector, unVector, nilVector, vCons, (:<), vectorLength, toVector) diff --git a/src/Network/Ethereum/Web3/Solidity/AbiEncoding.purs b/src/Network/Ethereum/Web3/Solidity/AbiEncoding.purs index 5f5437b..203dba1 100644 --- a/src/Network/Ethereum/Web3/Solidity/AbiEncoding.purs +++ b/src/Network/Ethereum/Web3/Solidity/AbiEncoding.purs @@ -1,196 +1,315 @@ module Network.Ethereum.Web3.Solidity.AbiEncoding - ( bytesBuilder - , class ABIDecode - , fromDataParser + ( class ABIDecode + , abiDecode + , _abiDecode , class ABIEncode - , toDataBuilder - , fromBool - , fromData - , int256HexBuilder - , parseBytes - , toBool - , uInt256HexBuilder - , uInt256HexParser + , abiEncode + , class EncodingType + , isDynamic + , class GEncodingType + , gIsDynamic + , class GenericABIDecode + , gABIDecode + , class GenericABIEncode + , gAbiEncode ) where import Prelude -import Data.Array (cons, fold, foldMap, length) -import Data.Array.Partial (init) +import Data.Array (foldMap, foldl, length, sortBy, (:)) import Data.ByteString (ByteString) import Data.ByteString (toUTF8, fromUTF8, length) as BS import Data.Either (Either) import Data.Functor.Tagged (Tagged, tagged, untagged) -import Data.Maybe (fromJust, maybe) +import Data.Generic.Rep (class Generic, Argument(..), Constructor(..), NoArguments(..), Product(..), from, repOf, to) +import Data.Identity (Identity(..)) +import Data.Maybe (Maybe(..), maybe) +import Data.Monoid.Endo (Endo(..)) +import Data.Newtype (un) import Data.Reflectable (class Reflectable, reflectType) -import Data.Traversable (for, scanl) +import Data.Symbol (class IsSymbol) +import Data.Traversable (foldMapDefaultR) import Data.Tuple (Tuple(..)) import Data.Unfoldable (replicateA) import Network.Ethereum.Core.BigNumber (fromString, fromTwosComplement, toString, toTwosComplement, unsafeToInt) import Network.Ethereum.Core.HexString (HexString, PadByte(..), fromByteString, mkHexString, numberOfBytes, padLeft, padRight, splitAtByteOffset, toByteString, unHex) import Network.Ethereum.Types (Address, BigNumber, fromInt, mkAddress, unAddress) import Network.Ethereum.Web3.Solidity.Bytes (BytesN, unBytesN, update, proxyBytesN) -import Network.Ethereum.Web3.Solidity.EncodingType (class EncodingType, isDynamic) import Network.Ethereum.Web3.Solidity.Int (IntN, unIntN, intNFromBigNumber) import Network.Ethereum.Web3.Solidity.UInt (UIntN, unUIntN, uIntNFromBigNumber) import Network.Ethereum.Web3.Solidity.Vector (Vector, unVector) -import Parsing (ParseError, Parser, ParseState(..), Position(..), ParserT, fail, getParserT, stateParserT, runParser) +import Parsing (ParseError, ParseState(..), Parser, ParserT, Position(..), fail, getParserT, runParser, stateParserT) import Parsing.Combinators (lookAhead) -import Partial.Unsafe (unsafeCrashWith, unsafePartial) +import Partial.Unsafe (unsafeCrashWith) import Type.Proxy (Proxy(..)) +class EncodingType :: forall k. k -> Constraint +class EncodingType a where + isDynamic :: Proxy a -> Boolean + +instance EncodingType Boolean where + isDynamic = const false +else instance EncodingType Int where + isDynamic = const false +else instance EncodingType BigNumber where + isDynamic = const false +else instance EncodingType (UIntN n) where + isDynamic = const false +else instance EncodingType (IntN n) where + isDynamic = const false +else instance EncodingType String where + isDynamic = const true +else instance EncodingType Address where + isDynamic = const false +else instance EncodingType a => EncodingType (Array a) where + isDynamic = const true +else instance EncodingType (BytesN n) where + isDynamic = const false +else instance EncodingType a => EncodingType (Vector n a) where + isDynamic _ = isDynamic (Proxy :: Proxy a) +else instance EncodingType ByteString where + isDynamic = const true +else instance EncodingType a => EncodingType (Tagged s a) where + isDynamic _ = isDynamic (Proxy :: Proxy a) +else instance EncodingType a => EncodingType (Identity a) where + isDynamic _ = isDynamic (Proxy :: Proxy a) +else instance (Generic a rep, GEncodingType rep) => EncodingType a where + isDynamic p = gIsDynamic (repOf p) + +class GEncodingType :: forall k. k -> Constraint +class GEncodingType rep where + gIsDynamic :: Proxy rep -> Boolean + +instance GEncodingType NoArguments where + gIsDynamic _ = false +else instance EncodingType a => GEncodingType (Argument a) where + gIsDynamic _ = isDynamic (Proxy @a) +else instance (GEncodingType a, GEncodingType b) => GEncodingType (Product a b) where + gIsDynamic _ = gIsDynamic (Proxy @a) || gIsDynamic (Proxy @b) +else instance GEncodingType a => GEncodingType (Constructor s a) where + gIsDynamic _ = gIsDynamic (Proxy @a) + -- | Class representing values that have an encoding and decoding instance to/from a solidity type. -class ABIEncode a where - toDataBuilder :: a -> HexString +class EncodingType a <= ABIEncode a where + abiEncode :: a -> HexString -class ABIDecode a where - fromDataParser :: Parser HexString a +instance ABIEncode BigNumber where + abiEncode = int256HexBuilder -instance abiEncodeAlgebra :: ABIEncode BigNumber where - toDataBuilder = int256HexBuilder +else instance ABIEncode Boolean where + abiEncode b = uInt256HexBuilder $ if b then one else zero -instance abiDecodeAlgebra :: ABIDecode BigNumber where - fromDataParser = int256HexParser +else instance ABIEncode Int where + abiEncode = int256HexBuilder <<< fromInt --- | Parse encoded value, droping the leading `0x` -fromData :: forall a. ABIDecode a => HexString -> Either ParseError a -fromData = flip runParser fromDataParser +else instance Reflectable n Int => ABIEncode (UIntN n) where + abiEncode a = uInt256HexBuilder <<< unUIntN $ a -instance abiEncodeBool :: ABIEncode Boolean where - toDataBuilder = uInt256HexBuilder <<< fromBool +else instance ABIEncode Address where + abiEncode addr = padLeft Zero <<< unAddress $ addr -instance abiDecodeBool :: ABIDecode Boolean where - fromDataParser = toBool <$> uInt256HexParser +else instance Reflectable n Int => ABIEncode (BytesN n) where + abiEncode bs = bytesBuilder <<< unBytesN $ bs -instance abiEncodeInt :: ABIEncode Int where - toDataBuilder = int256HexBuilder <<< fromInt +else instance Reflectable n Int => ABIEncode (IntN n) where + abiEncode a = int256HexBuilder <<< unIntN $ a -instance abiDecodeInt :: ABIDecode Int where - fromDataParser = unsafeToInt <$> int256HexParser +else instance ABIEncode ByteString where + abiEncode bytes = uInt256HexBuilder (fromInt $ BS.length bytes) <> bytesBuilder bytes -instance abiEncodeAddress :: ABIEncode Address where - toDataBuilder addr = padLeft Zero <<< unAddress $ addr +else instance ABIEncode String where + abiEncode = abiEncode <<< BS.toUTF8 -instance abiDecodeAddress :: ABIDecode Address where - fromDataParser = do - _ <- parseBytes 12 - maddr <- mkAddress <$> parseBytes 20 - maybe (fail "Address is 20 bytes, receieved more") pure maddr +else instance ABIEncode a => ABIEncode (Array a) where + abiEncode l = + uInt256HexBuilder (fromInt $ length l) <> + (combineEncodedValues $ un Endo (foldMapDefaultR factorBuilder l) []) + +else instance (ABIEncode a, Reflectable n Int) => ABIEncode (Vector n a) where + abiEncode l = + combineEncodedValues $ un Endo (foldMapDefaultR factorBuilder $ unVector l) [] + +else instance ABIEncode a => ABIEncode (Identity a) where + abiEncode = abiEncode <<< un Identity + +else instance ABIEncode a => ABIEncode (Tagged s a) where + abiEncode = abiEncode <<< untagged + +else instance (Generic a rep, EncodingType a, GenericABIEncode rep) => ABIEncode a where + abiEncode a = combineEncodedValues $ un Endo (gAbiEncode $ from a) [] + +type EncodedValue = + { order :: Int + , isDynamic :: Boolean + , encoding :: HexString + , encodingLengthInBytes :: Int -- cache + } + +type ABIDataBuilder = Endo (->) (Array EncodedValue) + +-- | An internally used class for encoding +class GenericABIEncode rep where + gAbiEncode :: rep -> ABIDataBuilder + +combineEncodedValues :: Array EncodedValue -> HexString +combineEncodedValues = + sortBy (\_a _b -> _a.order `compare` _b.order) + >>> \encodings -> + let + wordLengthInBytes = 32 + + headsOffsetInBytes :: Int + headsOffsetInBytes = + let + f = \encodedValueSimple -> + if encodedValueSimple.isDynamic then wordLengthInBytes + else encodedValueSimple.encodingLengthInBytes + in + foldl (+) 0 $ map f encodings + + (heads :: HexString) = + foldl + ( \{ accumulator, lengthOfPreviousDynamicValues } encodedValue -> + if encodedValue.isDynamic then + { accumulator: accumulator <> uInt256HexBuilder (fromInt $ headsOffsetInBytes + lengthOfPreviousDynamicValues) + , lengthOfPreviousDynamicValues: lengthOfPreviousDynamicValues + encodedValue.encodingLengthInBytes + } + else + { accumulator: accumulator <> encodedValue.encoding + , lengthOfPreviousDynamicValues: lengthOfPreviousDynamicValues + } + ) + { accumulator: mempty + , lengthOfPreviousDynamicValues: 0 + } + encodings + # _.accumulator + + (tails :: HexString) = + foldMap + ( \encodedValue -> + if encodedValue.isDynamic then + encodedValue.encoding + else + mempty + ) + encodings + in + heads <> tails + +instance GenericABIEncode NoArguments where + gAbiEncode _ = mempty + +else instance GenericABIEncode b => GenericABIEncode (Constructor s b) where + gAbiEncode (Constructor b) = gAbiEncode b + +else instance ABIEncode b => GenericABIEncode (Argument b) where + gAbiEncode (Argument b) = factorBuilder b + +else instance (GenericABIEncode a, GenericABIEncode b) => GenericABIEncode (Product a b) where + gAbiEncode (Product a b) = gAbiEncode a <> gAbiEncode b + +factorBuilder :: forall a. ABIEncode a => a -> ABIDataBuilder +factorBuilder a = Endo \encoded -> + let + encoding = abiEncode a + in + { encoding + , order: 1 + , isDynamic: isDynamic (Proxy :: Proxy a) + , encodingLengthInBytes: numberOfBytes encoding + } : map (\x -> x { order = x.order + 1 }) encoded + +-- | base16 encode, then utf8 encode, then pad +bytesBuilder :: ByteString -> HexString +bytesBuilder = padRight Zero <<< fromByteString + +-- | Encode something that is essentaially a signed integer. +int256HexBuilder :: BigNumber -> HexString +int256HexBuilder x = + let + x' = case mkHexString $ toString $ toTwosComplement 256 x of + Nothing -> unsafeCrashWith $ "Failed to encode as hex string: " <> show x + Just a -> a + in + if x < zero then padLeft FF x' + else padLeft Zero x' + +-- | Encode something that is essentially an unsigned integer. +uInt256HexBuilder :: BigNumber -> HexString +uInt256HexBuilder x = + case padLeft Zero <$> mkHexString (toString x) of + Nothing -> unsafeCrashWith $ "Failed to encode as hex string: " <> show x + Just a -> a + +-------------------------------------------------------------------------------- + +abiDecode :: forall a. ABIDecode a => HexString -> Either ParseError a +abiDecode = flip runParser _abiDecode -instance abiEncodeBytesD :: ABIEncode ByteString where - toDataBuilder bytes = uInt256HexBuilder (fromInt $ BS.length bytes) <> bytesBuilder bytes +class EncodingType a <= ABIDecode a where + _abiDecode :: Parser HexString a -instance abiDecodeBytesD :: ABIDecode ByteString where - fromDataParser = do - len <- fromDataParser - toByteString <$> parseBytes (unsafeToInt len) +instance ABIDecode BigNumber where + _abiDecode = int256HexParser -instance abiEncodeString :: ABIEncode String where - toDataBuilder = toDataBuilder <<< BS.toUTF8 +else instance ABIDecode Boolean where + _abiDecode = toBool <$> uInt256HexParser + where + toBool bn = not $ bn == zero + +else instance ABIDecode Int where + _abiDecode = unsafeToInt <$> int256HexParser + +else instance ABIDecode Address where + _abiDecode = do + _ <- parseBytes 12 + maddr <- mkAddress <$> parseBytes 20 + maybe (fail "Address is 20 bytes, receieved more") pure maddr -instance abiDecodeString :: ABIDecode String where - fromDataParser = BS.fromUTF8 <$> fromDataParser +else instance ABIDecode ByteString where + _abiDecode = do + len <- _abiDecode + toByteString <$> parseBytes len -instance abiEncodeBytesN :: Reflectable n Int => ABIEncode (BytesN n) where - toDataBuilder bs = bytesBuilder <<< unBytesN $ bs +else instance ABIDecode String where + _abiDecode = BS.fromUTF8 <$> _abiDecode -instance abiDecodeBytesN :: Reflectable n Int => ABIDecode (BytesN n) where - fromDataParser = do +else instance Reflectable n Int => ABIDecode (BytesN n) where + _abiDecode = do let len = reflectType (Proxy :: Proxy n) - zeroBytes = 32 - len raw <- parseBytes len _ <- parseBytes zeroBytes pure <<< update proxyBytesN <<< toByteString $ raw -instance abiEncodeVec :: (EncodingType a, ABIEncode a, Reflectable n Int) => ABIEncode (Vector n a) where - toDataBuilder l = - if isDynamic (Proxy :: Proxy a) then do - let - encs = map toDataBuilder (unVector l) - lengths = map numberOfBytes encs - len = reflectType (Proxy :: Proxy n) - offsets = - let - seed = 32 * len - in - seed `cons` (unsafePartial $ init $ scanl (+) seed lengths) - foldMap toDataBuilder offsets <> fold encs - else - foldMap toDataBuilder $ (unVector l :: Array a) - -instance abiDecodeVec :: (EncodingType a, Reflectable n Int, ABIDecode a) => ABIDecode (Vector n a) where - fromDataParser = do +else instance (Reflectable n Int, ABIDecode a) => ABIDecode (Vector n a) where + _abiDecode = let len = reflectType (Proxy :: Proxy n) - if isDynamic (Proxy :: Proxy a) then do - offsets <- replicateA len uInt256HexParser - let - currentOffset = 32 * len - for offsets - $ \dataOffset -> - lookAhead - $ do - _ <- parseBytes (unsafeToInt dataOffset - currentOffset) - fromDataParser - else - replicateA len fromDataParser - -instance abiEncodeAray :: (EncodingType a, ABIEncode a) => ABIEncode (Array a) where - toDataBuilder l = do - uInt256HexBuilder (fromInt $ length l) - <> - if isDynamic (Proxy :: Proxy a) then do - let - encs = map toDataBuilder l - - lengths = map numberOfBytes encs - - offsets = - let - seed = 32 * length l - in - seed `cons` (unsafePartial $ init $ scanl (+) seed lengths) - foldMap (uInt256HexBuilder <<< fromInt) offsets <> fold encs - else - foldMap toDataBuilder l - -instance abiDecodeArray :: (EncodingType a, ABIDecode a) => ABIDecode (Array a) where - fromDataParser = do - len <- unsafeToInt <$> uInt256HexParser - if isDynamic (Proxy :: Proxy a) then do - offsets <- replicateA len uInt256HexParser - let - currentOffset = 32 * len - for offsets - $ \dataOffset -> - lookAhead - $ do - _ <- parseBytes (unsafeToInt dataOffset - currentOffset) - fromDataParser - else - replicateA len fromDataParser - -instance abiEncodeUint :: Reflectable n Int => ABIEncode (UIntN n) where - toDataBuilder a = uInt256HexBuilder <<< unUIntN $ a - -instance abiDecodeUint :: Reflectable n Int => ABIDecode (UIntN n) where - fromDataParser = do + in + replicateA len factorParser + +else instance ABIDecode a => ABIDecode (Array a) where + _abiDecode = do + len <- _abiDecode + resetOffset + replicateA len factorParser + +else instance Reflectable n Int => ABIDecode (UIntN n) where + _abiDecode = do a <- uInt256HexParser - maybe (fail $ msg a) pure <<< uIntNFromBigNumber (Proxy :: Proxy n) $ a + maybe (fail $ msg a) pure <<< uIntNFromBigNumber (Proxy @n) $ a where msg n = let - size = reflectType (Proxy :: Proxy n) + size = reflectType (Proxy @n) in "Couldn't parse as uint" <> show size <> " : " <> show n -instance abiEncodeIntN :: Reflectable n Int => ABIEncode (IntN n) where - toDataBuilder a = int256HexBuilder <<< unIntN $ a - -instance abiDecodeIntN :: Reflectable n Int => ABIDecode (IntN n) where - fromDataParser = do +else instance Reflectable n Int => ABIDecode (IntN n) where + _abiDecode = do a <- int256HexParser maybe (fail $ msg a) pure <<< intNFromBigNumber (Proxy :: Proxy n) $ a where @@ -200,36 +319,42 @@ instance abiDecodeIntN :: Reflectable n Int => ABIDecode (IntN n) where in "Couldn't parse as int" <> show size <> " : " <> show n -instance abiEncodeTagged :: ABIEncode a => ABIEncode (Tagged s a) where - toDataBuilder = toDataBuilder <<< untagged +else instance ABIDecode a => ABIDecode (Tagged s a) where + _abiDecode = tagged <$> _abiDecode -instance abiDecodeTagged :: ABIDecode a => ABIDecode (Tagged s a) where - fromDataParser = tagged <$> fromDataParser +else instance ABIDecode a => ABIDecode (Identity a) where + _abiDecode = Identity <$> _abiDecode --------------------------------------------------------------------------------- --- | Special Builders and Parsers --------------------------------------------------------------------------------- +else instance (Generic a rep, EncodingType a, GenericABIDecode rep) => ABIDecode a where + _abiDecode = to <$> gABIDecode --- | base16 encode, then utf8 encode, then pad -bytesBuilder :: ByteString -> HexString -bytesBuilder = padRight Zero <<< fromByteString +class GenericABIDecode a where + gABIDecode :: Parser HexString a --- | Encode something that is essentaially a signed integer. -int256HexBuilder :: BigNumber -> HexString -int256HexBuilder x = - let - a = toTwosComplement 256 x - x' = - if a < zero then unsafeCrashWith $ "FUCK " <> show a - else unsafePartial $ fromJust $ mkHexString (toString a) - in - if x < zero then padLeft FF x' - else padLeft Zero x' +instance GenericABIDecode NoArguments where + gABIDecode = pure NoArguments --- | Encode something that is essentially an unsigned integer. -uInt256HexBuilder :: BigNumber -> HexString -uInt256HexBuilder x = unsafePartial $ fromJust $ - padLeft Zero <$> mkHexString (toString x) +else instance ABIDecode a => GenericABIDecode (Argument a) where + gABIDecode = Argument <$> factorParser + +else instance (IsSymbol name, GenericABIDecode a) => GenericABIDecode (Constructor name a) where + gABIDecode = Constructor <$> gABIDecode + +else instance (GenericABIDecode b, GenericABIDecode a) => GenericABIDecode (Product a b) where + gABIDecode = Product <$> gABIDecode <*> gABIDecode + +factorParser :: forall a. ABIDecode a => Parser HexString a +factorParser + | isDynamic (Proxy :: Proxy a) = do + dataOffset <- _abiDecode + found <- lookAhead + $ do + (ParseState _ (Position { index }) _) <- getParserT + void $ parseBytes (dataOffset - index) + resetOffset + _abiDecode + pure found + | otherwise = _abiDecode -- | Parse as a signed `BigNumber` int256HexParser :: forall m. Monad m => ParserT HexString m BigNumber @@ -242,28 +367,22 @@ int256HexParser = do uInt256HexParser :: forall m. Monad m => ParserT HexString m BigNumber uInt256HexParser = do bs <- unHex <$> parseBytes 32 - a <- maybe (fail $ "Failed to parse bytes as BigNumber " <> bs) pure (fromString bs) - pure a - --- | Decode a `Boolean` as a BigNumber -fromBool :: Boolean -> BigNumber -fromBool b = if b then one else zero - --- | Encode a `Boolean` as a `BigNumber` -toBool :: BigNumber -> Boolean -toBool bn = not $ bn == zero + maybe (fail $ "Failed to parse bytes as BigNumber " <> bs) pure (fromString bs) -- | Read any number of HexDigits parseBytes :: forall m. Monad m => Int -> ParserT HexString m HexString -parseBytes n = do - ParseState input (Position position) _ <- getParserT - if numberOfBytes input < n then - fail "Unexpected EOF" - else do - let - { after, before } = splitAtByteOffset n input - - position' = Position $ position { column = position.column + n } - - let newState = ParseState after position' true - stateParserT $ const (Tuple before newState) +parseBytes n + | n < 0 = fail "Cannot parse negative bytes" + | n == 0 = pure mempty + | otherwise = do + ParseState input (Position position) _ <- getParserT + when (numberOfBytes input < n) $ fail "Unexpected EOF" + let + { after, before } = splitAtByteOffset n input + position' = Position $ position { index = position.index + n } + newState = ParseState after position' true + stateParserT $ const (Tuple before newState) + +resetOffset :: forall m. Monad m => ParserT HexString m Unit +resetOffset = stateParserT \(ParseState s (Position p) c) -> + Tuple unit (ParseState s (Position p { index = 0 }) c) diff --git a/src/Network/Ethereum/Web3/Solidity/EncodingType.purs b/src/Network/Ethereum/Web3/Solidity/EncodingType.purs deleted file mode 100644 index 331f0da..0000000 --- a/src/Network/Ethereum/Web3/Solidity/EncodingType.purs +++ /dev/null @@ -1,80 +0,0 @@ -module Network.Ethereum.Web3.Solidity.EncodingType - ( class EncodingType - , typeName - , isDynamic - ) where - -import Prelude - -import Data.ByteString (ByteString) -import Data.Functor.Tagged (Tagged) -import Data.Reflectable (class Reflectable, reflectType) -import Network.Ethereum.Types (Address, BigNumber) -import Network.Ethereum.Web3.Solidity.Bytes (BytesN) -import Network.Ethereum.Web3.Solidity.Int (IntN) -import Network.Ethereum.Web3.Solidity.UInt (UIntN) -import Network.Ethereum.Web3.Solidity.Vector (Vector) -import Type.Proxy (Proxy(..)) - -class EncodingType :: forall k. k -> Constraint -class EncodingType a where - typeName :: Proxy a -> String - isDynamic :: Proxy a -> Boolean - -instance encodingTypeBoolean :: EncodingType Boolean where - typeName = const "bool" - isDynamic = const false - -instance encodingTypeInt :: EncodingType Int where - typeName = const "int" - isDynamic = const false - -instance encodingTypeBigNumber :: EncodingType BigNumber where - typeName = const "int" - isDynamic = const false - -instance encodingTypeUIntN :: Reflectable n Int => EncodingType (UIntN n) where - typeName = const $ "uint" <> (show $ reflectType (Proxy :: Proxy n)) - isDynamic = const false - -instance encodingTypeIntN :: Reflectable n Int => EncodingType (IntN n) where - typeName = const $ "int" <> (show $ reflectType (Proxy :: Proxy n)) - isDynamic = const false - -instance encodingTypeString :: EncodingType String where - typeName = const "string" - isDynamic = const true - -instance encodingTypeAddress :: EncodingType Address where - typeName = const "address" - isDynamic = const false - -instance encodingTypeArray :: EncodingType a => EncodingType (Array a) where - typeName = const "[]" - isDynamic = const true - -instance encodingTypeBytes :: Reflectable n Int => EncodingType (BytesN n) where - typeName = - let - n = show (reflectType (Proxy :: Proxy n)) - in - const $ "bytes[" <> n <> "]" - isDynamic = const false - -instance encodingTypeVector :: (Reflectable n Int, EncodingType a) => EncodingType (Vector n a) where - typeName = - let - n = show (reflectType (Proxy :: Proxy n)) - - baseTypeName = typeName (Proxy :: Proxy a) - in - const $ baseTypeName <> "[" <> n <> "]" - isDynamic _ = isDynamic (Proxy :: Proxy a) - -instance encodingTypeBytesD :: EncodingType ByteString where - typeName = const "bytes[]" - isDynamic = const true - -instance encodingTypeTagged :: EncodingType a => EncodingType (Tagged s a) where - typeName _ = typeName (Proxy :: Proxy a) - isDynamic _ = isDynamic (Proxy :: Proxy a) diff --git a/src/Network/Ethereum/Web3/Solidity/Event.purs b/src/Network/Ethereum/Web3/Solidity/Event.purs index 5af1eb6..8e4239b 100644 --- a/src/Network/Ethereum/Web3/Solidity/Event.purs +++ b/src/Network/Ethereum/Web3/Solidity/Event.purs @@ -1,57 +1,69 @@ module Network.Ethereum.Web3.Solidity.Event ( class DecodeEvent , decodeEvent - , decodeEventDef , class ArrayParser , arrayParser - , genericArrayParser + , class GArrayParser + , gArrayParser , class IndexedEvent , isAnonymous ) where import Prelude -import Control.Error.Util (hush) + +import Control.Error.Util (note) +import Control.Monad.Error.Class (throwError) import Data.Array (uncons) +import Data.Bifunctor (lmap) +import Data.Either (Either(..)) import Data.Generic.Rep (class Generic, Argument(..), Constructor(..), NoArguments(..), Product(..), to) import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype, wrap) +import Data.Tuple (Tuple(..)) import Network.Ethereum.Types (HexString) -import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIDecode, fromData) -import Network.Ethereum.Web3.Solidity.Generic (class GenericABIDecode, class RecordFieldsIso, genericFromData, genericToRecordFields) -import Network.Ethereum.Web3.Types (Change(..)) +import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIDecode, class ABIEncode, abiDecode) +import Network.Ethereum.Web3.Solidity.Internal (class RecordFieldsIso, toRecord) +import Network.Ethereum.Web3.Types (Change(..), Web3Error(..)) import Prim.Row as Row -import Record.Builder (build, merge) +import Record (disjointUnion) import Type.Proxy (Proxy(..)) -------------------------------------------------------------------------------- -- Array Parsers -------------------------------------------------------------------------------- class ArrayParser a where - arrayParser :: Array HexString -> Maybe a + arrayParser :: Array HexString -> Either Web3Error (Tuple a (Array HexString)) + +instance (Generic a rep, GArrayParser rep) => ArrayParser a where + arrayParser hx = do + Tuple a rest <- gArrayParser hx + case rest of + [] -> pure $ Tuple (to a) rest + _ -> throwError $ ParserError "too many arguments to arrayParser" -instance arrayParserNoArgs :: ArrayParser NoArguments where - arrayParser _ = Just NoArguments +class GArrayParser rep where + gArrayParser :: Array HexString -> Either Web3Error (Tuple rep (Array HexString)) -instance arrayParserBase :: ABIDecode a => ArrayParser (Argument a) where - arrayParser hxs = case uncons hxs of - Nothing -> Nothing - Just { head } -> map Argument <<< hush <<< fromData $ head +instance GArrayParser NoArguments where + gArrayParser as = pure (Tuple NoArguments as) -instance arrayParserInductive :: (ArrayParser as, ABIDecode a) => ArrayParser (Product (Argument a) as) where - arrayParser hxs = case uncons hxs of - Nothing -> Nothing - Just { head, tail } -> Product <$> (map Argument <<< hush <<< fromData $ head) <*> arrayParser tail +else instance ABIDecode a => GArrayParser (Argument a) where + gArrayParser hxs = case uncons hxs of + Nothing -> Left $ ParserError "no arguments found for arrayParser" + Just { head, tail } -> do + res <- lmap (ParserError <<< show) <<< abiDecode $ head + pure $ Tuple (Argument res) tail -instance arrayParserConstructor :: ArrayParser as => ArrayParser (Constructor name as) where - arrayParser = map Constructor <<< arrayParser +else instance (GArrayParser as, GArrayParser bs) => GArrayParser (Product as bs) where + gArrayParser hxs = do + Tuple a rest <- gArrayParser hxs + Tuple b rest' <- gArrayParser rest + pure $ Tuple (Product a b) rest' -genericArrayParser - :: forall a rep - . Generic a rep - => ArrayParser rep - => Array HexString - -> Maybe a -genericArrayParser = map to <<< arrayParser +else instance GArrayParser as => GArrayParser (Constructor name as) where + gArrayParser hxs = do + Tuple a rest <- gArrayParser hxs + pure $ Tuple (Constructor a) rest -------------------------------------------------------------------------------- -- | Event Parsers @@ -59,75 +71,56 @@ genericArrayParser = map to <<< arrayParser data Event i ni = Event i ni parseChange - :: forall a b arep brep - . Generic a arep - => ArrayParser arep - => Generic b brep - => GenericABIDecode brep + :: forall a b + . ArrayParser a + => ABIDecode b => Change -> Boolean - -> Maybe (Event a b) + -> Either Web3Error (Event a b) parseChange (Change change) anonymous = do - topics <- if anonymous then pure change.topics else _.tail <$> uncons change.topics - a <- genericArrayParser topics - b <- hush <<< genericFromData $ change.data + topics <- + if anonymous then pure change.topics + else note (ParserError "no topics found") (_.tail <$> uncons change.topics) + Tuple a _ <- arrayParser topics + b <- lmap (ParserError <<< show) $ abiDecode change.data pure $ Event a b combineChange - :: forall aargs afields al (a :: Type) aname bargs bfields bl (b :: Type) bname c cfields cfieldsRes - . RecordFieldsIso aargs afields al - => Generic a (Constructor aname aargs) - => RecordFieldsIso bargs bfields bl - => Generic b (Constructor bname bargs) + :: forall afields a bfields b c cfields + . RecordFieldsIso a () afields + => RecordFieldsIso b () bfields => Row.Union afields bfields cfields - => Row.Nub cfields cfieldsRes - => Newtype c (Record cfieldsRes) + => Row.Nub cfields cfields + => Newtype c (Record cfields) => Event a b -> c -combineChange (Event a b) = wrap $ build (merge (genericToRecordFields a)) (genericToRecordFields b) +combineChange (Event a b) = + wrap $ disjointUnion (toRecord a :: Record afields) (toRecord b :: Record bfields) class IndexedEvent :: forall k1 k2 k3. k1 -> k2 -> k3 -> Constraint class IndexedEvent a b c | c -> a b where isAnonymous :: Proxy c -> Boolean -decodeEventDef - :: forall aargs afields al a aname bargs bfields bl b bname c cfields cfieldsRes - . ArrayParser aargs - => RecordFieldsIso aargs afields al - => Generic a (Constructor aname aargs) - => RecordFieldsIso bargs bfields bl - => Generic b (Constructor bname bargs) - => GenericABIDecode bargs - => Row.Union afields bfields cfields - => Row.Nub cfields cfieldsRes - => Newtype c (Record cfieldsRes) - => IndexedEvent a b c - => Change - -> Maybe c -decodeEventDef change = do - let - anonymous = isAnonymous (Proxy :: Proxy c) - (e :: Event a b) <- parseChange change anonymous - pure $ combineChange e - class DecodeEvent :: forall k1 k2. k1 -> k2 -> Type -> Constraint class IndexedEvent a b c <= DecodeEvent a b c | c -> a b where - decodeEvent :: Change -> Maybe c + decodeEvent :: Change -> Either Web3Error c -instance defaultInstance :: - ( ArrayParser aargs - , RecordFieldsIso aargs afields al - , Generic a (Constructor aname aargs) - , RecordFieldsIso bargs bfields bl - , Generic b (Constructor bname bargs) - , GenericABIDecode bargs +instance + ( ArrayParser a + , RecordFieldsIso a () afields + , ABIEncode a + , RecordFieldsIso b () bfields + , ABIDecode b , Row.Union afields bfields cfields - , Row.Nub cfields cfieldsRes - , Newtype c (Record cfieldsRes) + , Row.Nub cfields cfields + , Newtype c (Record cfields) , IndexedEvent a b c ) => DecodeEvent a b c where - decodeEvent = decodeEventDef + decodeEvent change = do + let anonymous = isAnonymous (Proxy :: Proxy c) + (e :: Event a b) <- parseChange change anonymous + pure $ combineChange e diff --git a/src/Network/Ethereum/Web3/Solidity/Generic.purs b/src/Network/Ethereum/Web3/Solidity/Generic.purs deleted file mode 100644 index e128faf..0000000 --- a/src/Network/Ethereum/Web3/Solidity/Generic.purs +++ /dev/null @@ -1,229 +0,0 @@ -module Network.Ethereum.Web3.Solidity.Generic where - -import Prelude -import Data.Array (foldMap, foldl, length, sortBy, (:)) -import Data.Either (Either) -import Data.Functor.Tagged (Tagged, untagged, tagged) -import Data.Generic.Rep (class Generic, Argument(..), Constructor(..), NoArguments(..), Product(..), from, to) -import Data.Symbol (class IsSymbol) -import Network.Ethereum.Core.BigNumber (fromInt, unsafeToInt) -import Network.Ethereum.Core.HexString (HexString, numberOfBytes) -import Parsing (ParseError, ParseState(..), Parser, Position(..), getParserT, runParser) -import Parsing.Combinators (lookAhead) -import Type.Proxy (Proxy(..)) -import Prim.Row as Row -import Type.RowList (class ListToRow, Cons, Nil, RowList) -import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIDecode, class ABIEncode, fromDataParser, parseBytes, toDataBuilder, uInt256HexBuilder) -import Network.Ethereum.Web3.Solidity.EncodingType (class EncodingType, isDynamic) -import Record as Record - --- | A class for encoding generically composed datatypes to their abi encoding -class GenericABIEncode a where - genericToDataBuilder :: a -> HexString - --- | A class for decoding generically composed datatypes from their abi encoding -class GenericABIDecode a where - genericFromDataParser :: Parser HexString a - --- | An internally used type for encoding -type EncodedValue = - { order :: Int - , isDynamic :: Boolean - , encoding :: HexString - , encodingLengthInBytes :: Int -- cache - } - -combineEncodedValues :: Array EncodedValue -> HexString -combineEncodedValues = - sortBy (\a b -> a.order `compare` b.order) - >>> \encodings -> - let - wordLengthInBytes = 32 - - headsOffsetInBytes :: Int - headsOffsetInBytes = foldl (+) 0 $ map (\encodedValueSimple -> if encodedValueSimple.isDynamic then wordLengthInBytes else encodedValueSimple.encodingLengthInBytes) encodings - - (heads :: HexString) = - foldl - ( \{ accumulator, lengthOfPreviousDynamicValues } encodedValue -> - if encodedValue.isDynamic then - { accumulator: accumulator <> uInt256HexBuilder (fromInt $ headsOffsetInBytes + lengthOfPreviousDynamicValues) - , lengthOfPreviousDynamicValues: lengthOfPreviousDynamicValues + encodedValue.encodingLengthInBytes - } - else - { accumulator: accumulator <> encodedValue.encoding - , lengthOfPreviousDynamicValues: lengthOfPreviousDynamicValues - } - ) - { accumulator: mempty - , lengthOfPreviousDynamicValues: 0 - } - encodings - # _.accumulator - - (tails :: HexString) = - foldMap - ( \encodedValue -> - if encodedValue.isDynamic then - encodedValue.encoding - else - mempty - ) - encodings - in - heads <> tails - -mkEncodedValue :: forall a. EncodingType a => ABIEncode a => Array EncodedValue -> a -> EncodedValue -mkEncodedValue otherEncodedArray a = - let - encoding = toDataBuilder a - in - { encoding - , order: 1 + length otherEncodedArray - , isDynamic: isDynamic (Proxy :: Proxy a) - , encodingLengthInBytes: numberOfBytes encoding - } - --- | An internally used class for encoding -class ABIData a where - _serialize :: Array EncodedValue -> a -> Array EncodedValue - -instance abiDataBaseNull :: ABIData NoArguments where - _serialize encoded _ = encoded - -instance abiDataBase :: (EncodingType b, ABIEncode b) => ABIData (Argument b) where - _serialize encoded (Argument b) = mkEncodedValue encoded b : encoded - -instance abiDataInductive :: (EncodingType b, ABIEncode b, ABIData a) => ABIData (Product (Argument b) a) where - _serialize encoded (Product (Argument b) a) = _serialize (mkEncodedValue encoded b : encoded) a - -instance abiEncodeConstructor :: ABIData a => GenericABIEncode (Constructor name a) where - genericToDataBuilder (Constructor a) = combineEncodedValues $ _serialize [] a - --- | Encode a generic type into its abi encoding, works only for types of the form --- | `Constructor name (Product (Argument a1) (Product ... (Argument an)))` -genericABIEncode - :: forall a rep - . Generic a rep - => GenericABIEncode rep - => a - -> HexString -genericABIEncode = genericToDataBuilder <<< from - -instance baseAbiDecode :: (EncodingType a, ABIDecode a) => GenericABIDecode (Argument a) where - genericFromDataParser = Argument <$> factorParser - -instance baseNullAbiDecode :: GenericABIDecode NoArguments where - genericFromDataParser = pure NoArguments - -instance inductiveAbiDecode :: (EncodingType b, ABIDecode b, GenericABIDecode a) => GenericABIDecode (Product (Argument b) a) where - genericFromDataParser = Product <$> (Argument <$> factorParser) <*> genericFromDataParser - -instance abiDecodeConstructor :: GenericABIDecode a => GenericABIDecode (Constructor name a) where - genericFromDataParser = Constructor <$> genericFromDataParser - --- | Encode a generic type into its abi encoding, works only for types of the form --- | `Constructor name (Product (Argument a1) (Product ... (Argument an)))` -genericABIDecode - :: forall a rep - . Generic a rep - => GenericABIDecode rep - => Parser HexString a -genericABIDecode = to <$> genericFromDataParser - -genericFromData - :: forall a rep - . Generic a rep - => GenericABIDecode rep - => HexString - -> Either ParseError a -genericFromData = flip runParser genericABIDecode - --- helpers -factorParser - :: forall a - . ABIDecode a - => EncodingType a - => Parser HexString a -factorParser - | isDynamic (Proxy :: Proxy a) = dynamicFactorParser - | otherwise = fromDataParser - -dynamicFactorParser :: forall a. ABIDecode a => Parser HexString a -dynamicFactorParser = do - dataOffset <- unsafeToInt <$> fromDataParser - lookAhead - $ do - (ParseState _ (Position p) _) <- getParserT - _ <- parseBytes (dataOffset - (p.column - 1)) - fromDataParser - -class ArgsToRowListProxy :: forall k. k -> RowList Type -> Constraint -class ArgsToRowListProxy args l | args -> l, l -> args where - argsToRowListProxy :: Proxy args -> Proxy l - -instance argsToRowListProxyBaseNull :: ArgsToRowListProxy NoArguments Nil where - argsToRowListProxy _ = Proxy - -instance argsToRowListProxyBase :: ArgsToRowListProxy (Argument (Tagged (Proxy s) a)) (Cons s a Nil) where - argsToRowListProxy _ = Proxy -else instance argsToRowListProxyInductive :: ArgsToRowListProxy as l => ArgsToRowListProxy (Product (Argument (Tagged (Proxy s) a)) as) (Cons s a l) where - argsToRowListProxy _ = Proxy - -class RecordFieldsIso args fields (rowList :: RowList Type) | args -> rowList, rowList -> args fields where - toRecordFields :: forall proxy. proxy rowList -> args -> Record fields - fromRecordFields :: forall proxy. proxy rowList -> Record fields -> args - -instance isoRecordBase :: - ( IsSymbol s - , Row.Cons s a () r - , Row.Lacks s () - ) => - RecordFieldsIso (Argument (Tagged s a)) r (Cons s a Nil) where - toRecordFields _ (Argument a) = Record.insert (Proxy :: Proxy s) (untagged a) {} - fromRecordFields _ r = Argument (tagged $ Record.get (Proxy :: Proxy s) r) - -instance isoRecordBaseNull :: RecordFieldsIso NoArguments () Nil where - toRecordFields _ _ = {} - fromRecordFields _ _ = NoArguments - -instance isoRecordInductive :: - ( RecordFieldsIso as r1 (Cons ls la ll) - , Row.Cons s a r1 r2 - , Row.Lacks s r1 - , IsSymbol s - , ListToRow (Cons ls la ll) r1 - ) => - RecordFieldsIso (Product (Argument (Tagged s a)) as) r2 (Cons s a (Cons ls la ll)) where - toRecordFields _ (Product (Argument a) as) = Record.insert (Proxy :: Proxy s) (untagged a) rest - where - rest = (toRecordFields (Proxy :: Proxy (Cons ls la ll)) as :: Record r1) - fromRecordFields _ r = - let - a = Argument (tagged $ Record.get (Proxy :: Proxy s) r) - - before = Record.delete (Proxy :: Proxy s) r :: Record r1 - - rest = fromRecordFields (Proxy :: Proxy (Cons ls la ll)) before - in - Product a rest - -genericToRecordFields - :: forall args fields l a name - . RecordFieldsIso args fields l - => Generic a (Constructor name args) - => a - -> Record fields -genericToRecordFields a = - let - Constructor row = from a - in - toRecordFields (Proxy :: Proxy l) row - -genericFromRecordFields - :: forall args fields l a name - . RecordFieldsIso args fields l - => Generic a (Constructor name args) - => Record fields - -> a -genericFromRecordFields r = to $ Constructor $ fromRecordFields (Proxy :: Proxy l) r diff --git a/src/Network/Ethereum/Web3/Solidity/Int.purs b/src/Network/Ethereum/Web3/Solidity/Int.purs index ae565cc..076b5da 100644 --- a/src/Network/Ethereum/Web3/Solidity/Int.purs +++ b/src/Network/Ethereum/Web3/Solidity/Int.purs @@ -8,11 +8,11 @@ module Network.Ethereum.Web3.Solidity.Int import Prelude import Control.Monad.Gen (class MonadGen) -import Data.Maybe (Maybe(..), fromJust) +import Data.Maybe (Maybe(..)) import Data.Reflectable (class Reflectable, reflectType) import Network.Ethereum.Core.BigNumber (BigNumber, fromInt, fromString, fromTwosComplement, pow) import Network.Ethereum.Core.HexString as Hex -import Partial.Unsafe (unsafePartial) +import Partial.Unsafe (unsafeCrashWith) import Type.Proxy (Proxy(..)) -------------------------------------------------------------------------------- @@ -30,10 +30,13 @@ generator :: forall n m. Reflectable n Int => MonadGen m => Proxy n -> m (IntN n generator p = do bs <- Hex.generator (reflectType p `div` 8) let + n = reflectType (Proxy @n) a = if bs == mempty then zero - else unsafePartial $ fromJust $ fromString $ Hex.unHex $ bs - pure $ IntN $ fromTwosComplement (reflectType (Proxy @n)) a + else case fromString $ Hex.unHex $ bs of + Nothing -> unsafeCrashWith $ "int" <> show n <> " generator: invalid hex string: " <> show bs + Just x -> x + pure $ IntN $ fromTwosComplement n a -- | Access the raw underlying integer unIntN :: forall n. IntN n -> BigNumber diff --git a/src/Network/Ethereum/Web3/Solidity/Internal.purs b/src/Network/Ethereum/Web3/Solidity/Internal.purs new file mode 100644 index 0000000..01a8a1c --- /dev/null +++ b/src/Network/Ethereum/Web3/Solidity/Internal.purs @@ -0,0 +1,141 @@ +module Network.Ethereum.Web3.Solidity.Internal + ( class RecordFieldsIso + , _toRecord + , fromRecord + , toRecord + , class GRecordFieldsIso + , gToRecord + , gFromRecord + ) where + +import Prelude + +import Data.Functor.Tagged (Tagged, untagged, tagged) +import Data.Generic.Rep (class Generic, Argument(..), Constructor(..), NoArguments(..), Product(..), from, to) +import Network.Ethereum.Web3.Solidity.Vector (Vector) +import Data.Identity (Identity(..)) +import Data.Newtype (un) +import Data.Symbol (class IsSymbol) +import Prim.Row as Row +import Record (disjointUnion) +import Record as Record +import Record.Builder (Builder) +import Record.Builder as Builder +import Type.Proxy (Proxy(..)) +import Unsafe.Coerce (unsafeCoerce) + +class GRecordFieldsIso rep from to | rep -> to, to rep -> from where + gToRecord :: rep -> Builder { | from } { | to } + gFromRecord :: Record to -> rep + +instance GRecordFieldsIso NoArguments from from where + gToRecord _ = identity + gFromRecord _ = NoArguments + +else instance + ( IsSymbol name + , GRecordFieldsIso a from to + ) => + GRecordFieldsIso (Constructor name a) from to where + gToRecord (Constructor a) = gToRecord a + gFromRecord r = Constructor (gFromRecord r) + +else instance + ( GRecordFieldsIso a () ato + , GRecordFieldsIso b () bto + , Row.Union ato bto to + , Row.Union to from to + , Row.Nub to to + ) => + GRecordFieldsIso (Product a b) from to where + gToRecord (Product as bs) = + let + r = Builder.buildFromScratch (gToRecord as) `disjointUnion` Builder.buildFromScratch (gToRecord bs) + in + Builder.merge r + + gFromRecord r = + let + as = gFromRecord (unsafeCoerce r) + bs = gFromRecord (unsafeCoerce r) + in + Product as bs + +else instance + ( RecordFieldsIso a from to + ) => + GRecordFieldsIso (Argument a) from to where + gToRecord (Argument a) = _toRecord a + gFromRecord r = Argument $ fromRecord r + +class RecordFieldsIso a from to | from a -> to, a to -> from where + _toRecord :: a -> Builder { | from } { | to } + fromRecord :: Record to -> a + +instance + ( IsSymbol s + , Row.Cons s (Array (Record to)) from to' + , Row.Lacks s from + , Generic a rep + , GRecordFieldsIso rep () to + ) => + RecordFieldsIso (Tagged s (Array a)) from to' where + _toRecord a = + Builder.insert (Proxy @s) $ map + (Builder.buildFromScratch <<< (gToRecord <<< from)) + (untagged a) + fromRecord r = + tagged $ map (to <<< gFromRecord) $ Record.get (Proxy @s) r + +else instance + ( IsSymbol s + , Row.Cons s (Vector n (Record to)) from to' + , Row.Lacks s from + , Generic a rep + , GRecordFieldsIso rep () to + ) => + RecordFieldsIso (Tagged s (Vector n a)) from to' where + _toRecord a = + Builder.insert (Proxy @s) $ map + (Builder.buildFromScratch <<< (gToRecord <<< from)) + (untagged a) + fromRecord r = + tagged $ map (to <<< gFromRecord) $ Record.get (Proxy @s) r + +else instance + ( IsSymbol s + , Row.Cons s a from to + , Row.Lacks s from + ) => + RecordFieldsIso (Tagged s (Identity a)) from to where + _toRecord a = Builder.insert (Proxy @s) (un Identity $ untagged a) + fromRecord r = tagged $ Identity $ Record.get (Proxy @s) r + +else instance + ( IsSymbol s + , Row.Cons s (Record to) from to' + , Row.Lacks s from + , Generic a rep + , GRecordFieldsIso rep () to + ) => + RecordFieldsIso (Tagged s a) from to' where + _toRecord a = Builder.insert (Proxy @s) $ + Builder.buildFromScratch (gToRecord $ from $ untagged a) + fromRecord r = tagged $ to $ gFromRecord $ Record.get (Proxy @s) r + +else instance + ( Generic a arep + , GRecordFieldsIso arep from to + ) => + RecordFieldsIso a from to where + _toRecord a = + gToRecord $ from a + fromRecord r = + to $ gFromRecord r + +toRecord + :: forall a fields + . RecordFieldsIso a () fields + => a + -> Record fields +toRecord a = Builder.buildFromScratch $ _toRecord a diff --git a/src/Network/Ethereum/Web3/Solidity/Tuple.purs b/src/Network/Ethereum/Web3/Solidity/Tuple.purs index ad1dc78..055a146 100644 --- a/src/Network/Ethereum/Web3/Solidity/Tuple.purs +++ b/src/Network/Ethereum/Web3/Solidity/Tuple.purs @@ -53,7 +53,6 @@ module Network.Ethereum.Web3.Solidity.Tuple import Prelude -import Data.Eq.Generic (genericEq) import Data.Generic.Rep (class Generic) import Data.Show.Generic (genericShow) @@ -65,22 +64,20 @@ derive instance Generic Tuple0 _ instance Show Tuple0 where show _ = "Tuple0" -instance Eq Tuple0 where - eq _ _ = true +derive instance Eq Tuple0 -- * Tuple 1 newtype Tuple1 a = Tuple1 a derive instance Generic (Tuple1 a) _ -unTuple1 :: forall a. Tuple1 a -> a -unTuple1 (Tuple1 a) = a - instance Show a => Show (Tuple1 a) where show = genericShow -instance Eq a => Eq (Tuple1 a) where - eq = genericEq +derive instance Eq a => Eq (Tuple1 a) + +unTuple1 :: forall a. Tuple1 a -> a +unTuple1 (Tuple1 a) = a uncurry1 :: forall a b. (a -> b) -> Tuple1 a -> b uncurry1 fun (Tuple1 a) = fun a @@ -96,8 +93,7 @@ derive instance Generic (Tuple2 a b) _ instance (Show a, Show b) => Show (Tuple2 a b) where show = genericShow -instance (Eq a, Eq b) => Eq (Tuple2 a b) where - eq = genericEq +derive instance (Eq a, Eq b) => Eq (Tuple2 a b) uncurry2 :: forall a b c. (a -> b -> c) -> Tuple2 a b -> c uncurry2 fun (Tuple2 a b) = fun a b @@ -113,8 +109,7 @@ derive instance Generic (Tuple3 a b c) _ instance (Show a, Show b, Show c) => Show (Tuple3 a b c) where show = genericShow -instance (Eq a, Eq b, Eq c) => Eq (Tuple3 a b c) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c) => Eq (Tuple3 a b c) uncurry3 :: forall a b c d. (a -> b -> c -> d) -> Tuple3 a b c -> d uncurry3 fun (Tuple3 a b c) = fun a b c @@ -130,8 +125,7 @@ derive instance Generic (Tuple4 a b c d) _ instance (Show a, Show b, Show c, Show d) => Show (Tuple4 a b c d) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d) => Eq (Tuple4 a b c d) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d) => Eq (Tuple4 a b c d) uncurry4 :: forall a b c d e. (a -> b -> c -> d -> e) -> Tuple4 a b c d -> e uncurry4 fun (Tuple4 a b c d) = fun a b c d @@ -147,8 +141,7 @@ derive instance Generic (Tuple5 a b c d e) _ instance (Show a, Show b, Show c, Show d, Show e) => Show (Tuple5 a b c d e) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e) => Eq (Tuple5 a b c d e) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e) => Eq (Tuple5 a b c d e) uncurry5 :: forall a b c d e f. (a -> b -> c -> d -> e -> f) -> Tuple5 a b c d e -> f uncurry5 fun (Tuple5 a b c d e) = fun a b c d e @@ -164,8 +157,7 @@ derive instance Generic (Tuple6 a b c d e f) _ instance (Show a, Show b, Show c, Show d, Show e, Show f) => Show (Tuple6 a b c d e f) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f) => Eq (Tuple6 a b c d e f) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f) => Eq (Tuple6 a b c d e f) uncurry6 :: forall a b c d e f g. (a -> b -> c -> d -> e -> f -> g) -> Tuple6 a b c d e f -> g uncurry6 fun (Tuple6 a b c d e f) = fun a b c d e f @@ -181,8 +173,7 @@ derive instance Generic (Tuple7 a b c d e f g) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g) => Show (Tuple7 a b c d e f g) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g) => Eq (Tuple7 a b c d e f g) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g) => Eq (Tuple7 a b c d e f g) uncurry7 :: forall a b c d e f g h. (a -> b -> c -> d -> e -> f -> g -> h) -> Tuple7 a b c d e f g -> h uncurry7 fun (Tuple7 a b c d e f g) = fun a b c d e f g @@ -195,6 +186,8 @@ data Tuple8 a b c d e f g h = Tuple8 a b c d e f g h derive instance Generic (Tuple8 a b c d e f g h) _ +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h) => Eq (Tuple8 a b c d e f g h) + instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h) => Show (Tuple8 a b c d e f g h) where show = genericShow @@ -212,8 +205,7 @@ derive instance Generic (Tuple9 a b c d e f g h i) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i) => Show (Tuple9 a b c d e f g h i) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i) => Eq (Tuple9 a b c d e f g h i) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i) => Eq (Tuple9 a b c d e f g h i) uncurry9 :: forall a b c d e f g h i j. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) -> Tuple9 a b c d e f g h i -> j uncurry9 fun (Tuple9 a b c d e f g h i) = fun a b c d e f g h i @@ -229,8 +221,7 @@ derive instance Generic (Tuple10 a b c d e f g h i j) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j) => Show (Tuple10 a b c d e f g h i j) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j) => Eq (Tuple10 a b c d e f g h i j) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j) => Eq (Tuple10 a b c d e f g h i j) uncurry10 :: forall a b c d e f g h i j k. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k) -> Tuple10 a b c d e f g h i j -> k uncurry10 fun (Tuple10 a b c d e f g h i j) = fun a b c d e f g h i j @@ -246,6 +237,8 @@ derive instance Generic (Tuple11 a b c d e f g h i j k) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k) => Show (Tuple11 a b c d e f g h i j k) where show = genericShow +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k) => Eq (Tuple11 a b c d e f g h i j k) + uncurry11 :: forall a b c d e f g h i j k l. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l) -> Tuple11 a b c d e f g h i j k -> l uncurry11 fun (Tuple11 a b c d e f g h i j k) = fun a b c d e f g h i j k @@ -260,8 +253,7 @@ derive instance Generic (Tuple12 a b c d e f g h i j k l) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l) => Show (Tuple12 a b c d e f g h i j k l) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l) => Eq (Tuple12 a b c d e f g h i j k l) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l) => Eq (Tuple12 a b c d e f g h i j k l) uncurry12 :: forall a b c d e f g h i j k l m. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m) -> Tuple12 a b c d e f g h i j k l -> m uncurry12 fun (Tuple12 a b c d e f g h i j k l) = fun a b c d e f g h i j k l @@ -277,8 +269,7 @@ derive instance genericTuple13 :: Generic (Tuple13 a b c d e f g h i j k l m) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m) => Show (Tuple13 a b c d e f g h i j k l m) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m) => Eq (Tuple13 a b c d e f g h i j k l m) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m) => Eq (Tuple13 a b c d e f g h i j k l m) uncurry13 :: forall a b c d e f g h i j k l m n. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> n) -> Tuple13 a b c d e f g h i j k l m -> n uncurry13 fun (Tuple13 a b c d e f g h i j k l m) = fun a b c d e f g h i j k l m @@ -294,8 +285,7 @@ derive instance Generic (Tuple14 a b c d e f g h i j k l m n) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n) => Show (Tuple14 a b c d e f g h i j k l m n) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n) => Eq (Tuple14 a b c d e f g h i j k l m n) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n) => Eq (Tuple14 a b c d e f g h i j k l m n) uncurry14 :: forall a b c d e f g h i j k l m n o. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> n -> o) -> Tuple14 a b c d e f g h i j k l m n -> o uncurry14 fun (Tuple14 a b c d e f g h i j k l m n) = fun a b c d e f g h i j k l m n @@ -311,8 +301,7 @@ derive instance genericTuple15 :: Generic (Tuple15 a b c d e f g h i j k l m n o instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o) => Show (Tuple15 a b c d e f g h i j k l m n o) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n, Eq o) => Eq (Tuple15 a b c d e f g h i j k l m n o) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n, Eq o) => Eq (Tuple15 a b c d e f g h i j k l m n o) uncurry15 :: forall a b c d e f g h i j k l m n o p. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> n -> o -> p) -> Tuple15 a b c d e f g h i j k l m n o -> p uncurry15 fun (Tuple15 a b c d e f g h i j k l m n o) = fun a b c d e f g h i j k l m n o @@ -328,8 +317,7 @@ derive instance Generic (Tuple16 a b c d e f g h i j k l m n o p) _ instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o, Show p) => Show (Tuple16 a b c d e f g h i j k l m n o p) where show = genericShow -instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n, Eq o, Eq p) => Eq (Tuple16 a b c d e f g h i j k l m n o p) where - eq = genericEq +derive instance (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i, Eq j, Eq k, Eq l, Eq m, Eq n, Eq o, Eq p) => Eq (Tuple16 a b c d e f g h i j k l m n o p) uncurry16 :: forall a b c d e f g h i j k l m n o p q. (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> n -> o -> p -> q) -> Tuple16 a b c d e f g h i j k l m n o p -> q uncurry16 fun (Tuple16 a b c d e f g h i j k l m n o p) = fun a b c d e f g h i j k l m n o p diff --git a/src/Network/Ethereum/Web3/Solidity/UInt.purs b/src/Network/Ethereum/Web3/Solidity/UInt.purs index e38d028..df2f0bb 100644 --- a/src/Network/Ethereum/Web3/Solidity/UInt.purs +++ b/src/Network/Ethereum/Web3/Solidity/UInt.purs @@ -8,11 +8,11 @@ module Network.Ethereum.Web3.Solidity.UInt import Prelude import Control.Monad.Gen (class MonadGen, chooseInt) -import Data.Maybe (Maybe(..), fromJust) +import Data.Maybe (Maybe(..)) import Data.Reflectable (class Reflectable, reflectType) import Network.Ethereum.Core.BigNumber (BigNumber, fromInt, fromString, pow) import Network.Ethereum.Core.HexString as Hex -import Partial.Unsafe (unsafePartial) +import Partial.Unsafe (unsafeCrashWith) import Type.Proxy (Proxy(..)) -------------------------------------------------------------------------------- @@ -33,12 +33,15 @@ generator => Proxy n -> m (UIntN n) generator p = do - nBytes <- (flip div 8) <$> chooseInt 1 (reflectType p) + let nBits = reflectType p + nBytes <- flip div 8 <$> chooseInt 1 nBits bs <- Hex.generator nBytes let a = if bs == mempty then zero - else unsafePartial $ fromJust $ fromString $ Hex.unHex bs + else case fromString $ Hex.unHex bs of + Nothing -> unsafeCrashWith $ "uint" <> show nBits <> " generator: invalid hex string: " <> show bs + Just x -> x pure $ UIntN $ if a < zero then -a else a -- | Access the raw underlying unsigned integer diff --git a/src/Network/Ethereum/Web3/Solidity/Vector.purs b/src/Network/Ethereum/Web3/Solidity/Vector.purs index f29c8e1..e93ee7b 100644 --- a/src/Network/Ethereum/Web3/Solidity/Vector.purs +++ b/src/Network/Ethereum/Web3/Solidity/Vector.purs @@ -26,13 +26,13 @@ import Type.Proxy (Proxy(..)) -- | See module [Network.Ethereum.Web3.Solidity.Sizes](/Network.Ethereum.Web3.Solidity.Sizes) for some predefined sizes. newtype Vector (n :: Int) a = Vector (Array a) -derive newtype instance showVector :: Show a => Show (Vector n a) -derive newtype instance eqVector :: Eq a => Eq (Vector n a) -derive newtype instance functorVector :: Functor (Vector n) -derive newtype instance unfoldable1Vector :: Unfoldable1 (Vector n) -derive newtype instance unfoldableVector :: Unfoldable (Vector n) -derive newtype instance foldableVector :: Foldable (Vector n) -derive newtype instance traversableVector :: Traversable (Vector n) +derive newtype instance Show a => Show (Vector n a) +derive newtype instance Eq a => Eq (Vector n a) +derive newtype instance Functor (Vector n) +derive newtype instance Unfoldable1 (Vector n) +derive newtype instance Unfoldable (Vector n) +derive newtype instance Foldable (Vector n) +derive newtype instance Traversable (Vector n) generator :: forall n m proxy a diff --git a/test/web3/Main.purs b/test/web3/Main.purs index c18b3d6..2cb15aa 100644 --- a/test/web3/Main.purs +++ b/test/web3/Main.purs @@ -8,6 +8,7 @@ import Data.Newtype (un) import Effect (Effect) import Effect.Aff (Aff, Milliseconds(..), launchAff_) import Effect.Class (liftEffect) +import Effect.Class.Console as Console import Network.Ethereum.Web3.Types.Provider (httpProvider) import Test.Spec (Spec, SpecT, mapSpecTree) import Test.Spec.Reporter.Console (consoleReporter) @@ -26,6 +27,7 @@ main :: Effect Unit main = launchAff_ do + Console.log "Running tests..." let cfg = defaultConfig { timeout = Just (Milliseconds $ 120.0 * 1000.0) } p <- liftEffect $ httpProvider "http://localhost:8545" diff --git a/test/web3/Web3Spec/Encoding/ContainersSpec.purs b/test/web3/Web3Spec/Encoding/ContainersSpec.purs index 212f73d..7fd2649 100644 --- a/test/web3/Web3Spec/Encoding/ContainersSpec.purs +++ b/test/web3/Web3Spec/Encoding/ContainersSpec.purs @@ -3,32 +3,31 @@ module Web3Spec.Encoding.ContainersSpec (spec, BMPString(..)) where import Prelude import Control.Monad.Gen (chooseInt, frequency, oneOf, suchThat) -import Data.Array (filter, foldMap, (..)) +import Data.Array (filter, foldMap, take, (..)) import Data.Array.NonEmpty (NonEmptyArray, fromArray) import Data.Array.NonEmpty as NEA import Data.Either (Either(..)) import Data.Enum (toEnumWithDefaults) import Data.Foldable (for_) -import Data.Generic.Rep (class Generic) import Data.Int (toNumber) -import Data.Maybe (fromJust) +import Data.Maybe (Maybe(..)) +import Data.Newtype (class Newtype, un) import Data.NonEmpty (NonEmpty(..)) import Data.Reflectable (reifyType) import Data.String (CodePoint, fromCodePointArray) import Data.Tuple (Tuple(..)) import Effect.Class (liftEffect) +import Effect.Class.Console as Console import Network.Ethereum.Core.HexString as Hex import Network.Ethereum.Core.Signatures as Address -import Network.Ethereum.Web3.Solidity (class GenericABIDecode, class GenericABIEncode, Tuple4(..), Tuple5(..), genericABIEncode, genericFromData) -import Network.Ethereum.Web3.Solidity.AbiEncoding (class ABIEncode, class ABIDecode, toDataBuilder, fromData) +import Network.Ethereum.Web3.Solidity (class ABIDecode, class ABIEncode, class EncodingType, Tuple2(..), Tuple3(..), Tuple4(..), Tuple5(..), abiDecode, abiEncode) import Network.Ethereum.Web3.Solidity.Bytes as BytesN -import Network.Ethereum.Web3.Solidity.EncodingType (class EncodingType) import Network.Ethereum.Web3.Solidity.Int as IntN import Network.Ethereum.Web3.Solidity.UInt as UIntN import Network.Ethereum.Web3.Solidity.Vector as Vector import Parsing (ParseError) -import Partial.Unsafe (unsafePartial) -import Test.QuickCheck (class Arbitrary, arbitrary, quickCheck, quickCheckGen, (===)) +import Partial.Unsafe (unsafeCrashWith) +import Test.QuickCheck (class Arbitrary, arbitrary, quickCheck, quickCheckGen, quickCheckGen', (===)) import Test.QuickCheck.Gen (Gen, arrayOf) import Test.Spec (Spec, describe, it) @@ -45,7 +44,12 @@ typePropertyTests :: Spec Unit typePropertyTests = describe "Type property tests" do it "can encode/decode a string" $ liftEffect $ do - quickCheck \(x :: BMPString) -> (encodeDecode x) === Right x + Console.log "wtf" + quickCheck \(x :: BMPString) -> + let + y = un BMPString x + in + (encodeDecode y) === Right y it "can encode/decode bytestring" $ liftEffect $ do quickCheckGen $ do @@ -80,7 +84,11 @@ typePropertyTests = pure $ encodeDecode x === Right x it "can encode/decode string" $ liftEffect $ do - quickCheck \(x :: BMPString) -> encodeDecode x === Right x + quickCheck \(x :: BMPString) -> + let + y = un BMPString x + in + encodeDecode y === Right y arrayTypePropertyTests :: Spec Unit arrayTypePropertyTests = do @@ -112,7 +120,10 @@ arrayTypePropertyTests = do it "Can encode/decode string[]" $ liftEffect do quickCheck $ \(x :: Array BMPString) -> - encodeDecode x === Right x + let + y = map (un BMPString) x + in + encodeDecode y === Right y vecTypePropertyTests :: Spec Unit vecTypePropertyTests = do @@ -157,7 +168,8 @@ vecTypePropertyTests = do quickCheckGen $ do k <- chooseInt 1 10 reifyType k \pk -> do - x <- Vector.generator pk (arbitrary :: Gen BMPString) + _x <- Vector.generator pk (arbitrary :: Gen BMPString) + let x = un BMPString <$> _x pure $ encodeDecode x === Right x nestedTypePropertyTests :: Spec Unit @@ -181,7 +193,8 @@ nestedTypePropertyTests = do k2 <- chooseInt 1 10 reifyType k1 \pk1 -> reifyType k2 \pk2 -> do - x <- Vector.generator pk2 (Vector.generator pk1 (arbitrary :: Gen BMPString)) + _x <- Vector.generator pk2 (Vector.generator pk1 (arbitrary :: Gen BMPString)) + let x = map (un BMPString) <$> _x pure $ encodeDecode x === Right x describe "Nested type property tests for array, vector" do @@ -199,7 +212,8 @@ nestedTypePropertyTests = do quickCheckGen $ do k <- chooseInt 1 10 reifyType k \pk -> do - x <- arrayOf (Vector.generator pk (arbitrary :: Gen BMPString)) + _x <- arrayOf (Vector.generator pk (arbitrary :: Gen BMPString)) + let x = map (un BMPString) <$> _x pure $ encodeDecode x === Right x describe "Nested type property tests for vector, array" do @@ -217,7 +231,8 @@ nestedTypePropertyTests = do quickCheckGen $ do k <- chooseInt 1 10 reifyType k \pk -> do - x <- (Vector.generator pk (arrayOf (arbitrary :: Gen BMPString))) + _x <- (Vector.generator pk (arrayOf (arbitrary :: Gen BMPString))) + let x = map (un BMPString) <$> _x pure $ encodeDecode x === Right x describe "Nested type property tests for array, array" do @@ -231,10 +246,14 @@ nestedTypePropertyTests = do it "Can encode/decode string[][]" $ liftEffect do quickCheck \(x :: Array (Array BMPString)) -> - encodeDecode x === Right x + let + y = map (map (un BMPString)) x + in + encodeDecode y === Right y tupleTests :: Spec Unit tupleTests = do + describe "Basic static sized Tuple Tests" $ do it "Can encode/decode (intN, address, bool, uintN, bytesN)" $ liftEffect do @@ -251,10 +270,10 @@ tupleTests = do uint <- UIntN.generator pm bytes <- BytesN.generator pk let x = Tuple5 int addr bool uint bytes - pure $ genericEncodeDecode x === Right x + pure $ encodeDecode x === Right x it "Can encode/decode (address[k], bool, intN[k], uint)" $ liftEffect do - quickCheckGen $ do + quickCheckGen' 1 $ do k1 <- chooseInt 1 10 k2 <- chooseInt 1 10 n <- oneOf (pure <$> intSizes) @@ -268,7 +287,7 @@ tupleTests = do ints <- Vector.generator pk2 (IntN.generator pn) uint <- (UIntN.generator pm) let x = Tuple4 addrs bool ints uint - pure $ genericEncodeDecode x === Right x + pure $ encodeDecode x === Right x describe "Basic dynamic sized Tuple Tests" $ do @@ -285,11 +304,11 @@ tupleTests = do addrs <- Vector.generator pm (arrayOf Address.generator) strings <- arrayOf (Vector.generator pk (arbitrary @BMPString)) bool <- arbitrary :: Gen Boolean - let x = Tuple5 ints bytes addrs strings bool - pure $ genericEncodeDecode x === Right x + let x = Tuple5 ints bytes addrs (map (un BMPString) <$> strings) bool + pure $ encodeDecode x === Right x it "Can encode/decode (address[k], bool, intN[k], uint)" $ liftEffect do - quickCheckGen $ do + quickCheckGen' 5 $ do k1 <- chooseInt 1 10 k2 <- chooseInt 1 10 n <- oneOf (pure <$> intSizes) @@ -303,16 +322,70 @@ tupleTests = do ints <- Vector.generator pk2 (IntN.generator pn) uint <- (UIntN.generator pm) let x = Tuple4 addrs bool ints uint - pure $ genericEncodeDecode x === Right x + pure $ encodeDecode x === Right x + + it "Can encode/decode arrays of tuples" $ liftEffect do + quickCheckGen' 5 $ do + k1 <- chooseInt 1 3 + reifyType k1 \pk1 -> + do + let + tupleGen = do + addrs <- arrayOf (Vector.generator pk1 Address.generator) + bool <- arbitrary @Boolean + pure $ Tuple2 addrs bool + as <- take 2 <$> arrayOf tupleGen + pure $ encodeDecode as === Right as + + -- this test is admittedly pretty ad hoc + it "Can encode/decode nested tuples" $ liftEffect do + quickCheckGen' 5 do + k1 <- chooseInt 1 10 + k2 <- chooseInt 1 10 + n <- oneOf (pure <$> intSizes) + m <- oneOf (pure <$> intSizes) + reifyType k1 \pk1 -> + reifyType k2 \pk2 -> + reifyType n \pn -> + reifyType m \pm -> do + let + mkTuple4 = do + addrs <- arrayOf (Vector.generator pk1 Address.generator) + bool <- arbitrary @Boolean + ints <- Vector.generator pk2 (IntN.generator pn) + uint <- (UIntN.generator pm) + pure $ Tuple4 addrs bool ints uint + _n <- oneOf (pure <$> intSizes) + _m <- chooseInt 1 10 + _k <- chooseInt 1 10 + reifyType _n \_pn -> + reifyType _m \_pm -> + reifyType _k \_pk -> do + let + mkTuple5 = do + ints <- arrayOf (IntN.generator _pn) + bytes <- Hex.toByteString <$> (chooseInt 1 100 >>= Hex.generator) + addrs <- Vector.generator _pm (arrayOf Address.generator) + strings <- map (map (un BMPString)) <$> + arrayOf (Vector.generator _pk (arbitrary @BMPString)) + bool <- arbitrary :: Gen Boolean + pure $ Tuple5 ints bytes addrs strings bool + mkTuple2 = do + strings <- map (un BMPString) <$> + arrayOf (arbitrary @BMPString) + addrs <- Vector.generator pk2 (arrayOf Address.generator) + pure $ Tuple2 strings addrs + + t <- Tuple3 <$> mkTuple5 <*> mkTuple4 <*> mkTuple2 + pure $ encodeDecode t === Right t -------------------------------------------------------------------------------- newtype BMPString = BMPString String derive newtype instance Eq BMPString derive newtype instance Show BMPString -derive newtype instance ABIDecode BMPString -derive newtype instance ABIEncode BMPString -derive newtype instance EncodingType BMPString + +derive instance Newtype BMPString _ data UnicodeChar = Normal CodePoint | Surrogates CodePoint CodePoint @@ -347,32 +420,21 @@ encodeDecode :: forall a . Show a => Eq a + => EncodingType a => ABIEncode a => ABIDecode a => a -> Either ParseError a encodeDecode x = let - a = toDataBuilder x + a = abiEncode x in - (fromData a) - -genericEncodeDecode - :: forall a rep - . Show a - => Eq a - => Generic a rep - => GenericABIEncode rep - => GenericABIDecode rep - => a - -> Either ParseError a -genericEncodeDecode a = - genericFromData $ genericABIEncode a + abiDecode a intSizes :: NonEmptyArray Int -intSizes = unsafePartial fromJust - $ fromArray - $ filter (\x -> x `mod` 8 == 0) (8 .. 256) +intSizes = case fromArray $ filter (\x -> x `mod` 8 == 0) (8 .. 256) of + Nothing -> unsafeCrashWith "intSizes: impossible" + Just x -> x bytesSizes :: NonEmptyArray Int bytesSizes = 1 NEA... 32 diff --git a/test/web3/Web3Spec/Encoding/DataSpec.purs b/test/web3/Web3Spec/Encoding/DataSpec.purs index 80cd4b9..5a83a29 100644 --- a/test/web3/Web3Spec/Encoding/DataSpec.purs +++ b/test/web3/Web3Spec/Encoding/DataSpec.purs @@ -3,13 +3,14 @@ module Web3Spec.Encoding.DataSpec (spec, approve) where import Prelude import Data.Functor.Tagged (Tagged, tagged) +import Data.Identity (Identity) import Effect.Class (liftEffect) import Network.Ethereum.Core.Keccak256 (toSelector) import Network.Ethereum.Core.Signatures as Address import Network.Ethereum.Web3.Contract (sendTx, mkDataField) import Network.Ethereum.Web3.Solidity (Tuple2, UIntN) -import Network.Ethereum.Web3.Solidity.AbiEncoding (toDataBuilder) -import Network.Ethereum.Web3.Solidity.Generic (genericFromRecordFields) +import Network.Ethereum.Web3.Solidity.AbiEncoding (abiEncode) +import Network.Ethereum.Web3.Solidity.Internal (fromRecord) import Network.Ethereum.Web3.Solidity.UInt as UIntN import Network.Ethereum.Web3.Types (Address, HexString, NoPay, TransactionOptions, Web3) import Test.QuickCheck (quickCheckGen, (===)) @@ -23,14 +24,14 @@ spec = quickCheckGen do args <- { _spender: _, _value: _ } <$> Address.generator <*> UIntN.generator (Proxy @256) let - approvalD = mkDataField (Proxy :: Proxy ApproveFn) args + approvalD = mkDataField (Proxy @ApproveFn) args sel = toSelector "approve(address,uint256)" - fullDat = sel <> toDataBuilder args._spender <> toDataBuilder args._value + fullDat = sel <> abiEncode args._spender <> abiEncode args._value pure $ approvalD === fullDat -type ApproveFn = Tagged "approve(address,uint256)" (Tuple2 (Tagged "_spender" Address) (Tagged "_value" (UIntN 256))) +type ApproveFn = Tagged "approve(address,uint256)" (Tuple2 (Tagged "_spender" (Identity Address)) (Tagged "_value" (Identity (UIntN 256)))) approve :: TransactionOptions NoPay -> { _spender :: Address, _value :: (UIntN 256) } -> Web3 HexString -approve txOpts r = sendTx txOpts (tagged (genericFromRecordFields r) :: ApproveFn) +approve txOpts r = sendTx txOpts (tagged (fromRecord r) :: ApproveFn) diff --git a/test/web3/Web3Spec/Encoding/GenericSpec.purs b/test/web3/Web3Spec/Encoding/GenericSpec.purs index b064418..16ab4e5 100644 --- a/test/web3/Web3Spec/Encoding/GenericSpec.purs +++ b/test/web3/Web3Spec/Encoding/GenericSpec.purs @@ -2,14 +2,22 @@ module Web3Spec.Encoding.GenericSpec (spec) where import Prelude +import Data.Either (Either, isRight) import Data.Functor.Tagged (Tagged, tagged) import Data.Generic.Rep (class Generic) +import Data.Identity (Identity(..)) +import Data.Maybe (fromJust) import Effect.Class (liftEffect) -import Network.Ethereum.Web3.Solidity (Tuple2(..), Tuple3(..)) -import Network.Ethereum.Web3.Solidity.Generic (genericToRecordFields) +import Network.Ethereum.Core.HexString (HexString, mkHexString) +import Network.Ethereum.Web3.Solidity (BytesN, Tuple2(..), Tuple3(..), UIntN) +import Network.Ethereum.Web3.Solidity.AbiEncoding (abiDecode) +import Network.Ethereum.Web3.Solidity.Internal (toRecord) +import Parsing (ParseError) +import Partial.Unsafe (unsafePartial) import Record.Builder (build, merge) import Test.QuickCheck (quickCheck, (===)) import Test.Spec (Spec, describe, it) +import Test.Spec.Assertions (shouldSatisfy) spec :: Spec Unit spec = @@ -18,27 +26,35 @@ spec = toRecordFieldsSpec :: Spec Unit toRecordFieldsSpec = - describe "test ToRecordFields class" do - it "pass toRecordFields basic test" $ liftEffect do + describe "test RecordFieldsIso class" do + + it "Can parse nested tuples: " $ + let + eRes :: Either ParseError Nested + eRes = abiDecode nestedTupleBytes + in + (toRecord <$> eRes) `shouldSatisfy` isRight + + it "pass _toRecord basic test" $ liftEffect do quickCheck $ \(x :: { a :: Int, b :: Int, c :: String, d :: String }) -> let - as = Tuple2 (tagged x.a) (tagged x.b) :: Tuple2 (Tagged "a" Int) (Tagged "b" Int) - bs = Tuple2 (tagged x.c) (tagged x.d) :: Tuple2 (Tagged "c" String) (Tagged "d" String) + as = Tuple2 (tagged $ Identity x.a) (tagged $ Identity x.b) :: Tuple2 (Tagged "a" (Identity Int)) (Tagged "b" (Identity Int)) + bs = Tuple2 (tagged $ Identity x.c) (tagged $ Identity x.d) :: Tuple2 (Tagged "c" (Identity String)) (Tagged "d" (Identity String)) + cs = Tuple2 (tagged as :: Tagged "as" _) (tagged bs :: Tagged "bs" _) + --q = from as :: Int in - (build (merge (genericToRecordFields as)) (genericToRecordFields bs)) + toRecord cs === - { a: x.a - , b: x.b - , c: x.c - , d: x.d + { as: { a: x.a, b: x.b } + , bs: { c: x.c, d: x.d } } - it "pass toRecordFields basic test" $ liftEffect do + it "pass _toRecord basic test" $ liftEffect do quickCheck $ \(x :: { a :: Int, b :: Int, c :: String, d :: String, e :: Char }) -> let - as = Tuple3 (tagged x.a) (tagged x.d) (tagged x.e) :: Tuple3 (Tagged "a" Int) (Tagged "d" String) (Tagged "e" Char) + as = Tuple3 (tagged $ Identity x.a) (tagged $ Identity x.d) (tagged $ Identity x.e) :: Tuple3 (Tagged "a" (Identity Int)) (Tagged "d" (Identity String)) (Tagged "e" (Identity Char)) in - WeirdTuple (genericToRecordFields as) + WeirdTuple (toRecord as) === WeirdTuple { a: x.a @@ -49,11 +65,11 @@ toRecordFieldsSpec = it "passes the merging test" $ liftEffect do quickCheck $ \(x :: { a :: Int, b :: Int, c :: String, d :: String, e :: Char }) -> let - as = Tuple3 (tagged x.a) (tagged x.d) (tagged x.e) :: Tuple3 (Tagged "a" Int) (Tagged "d" String) (Tagged "e" Char) + as = Tuple3 (tagged $ Identity x.a) (tagged $ Identity x.d) (tagged $ Identity x.e) :: Tuple3 (Tagged "a" (Identity Int)) (Tagged "d" (Identity String)) (Tagged "e" (Identity Char)) - as' = Tuple2 (tagged x.b) (tagged x.c) :: Tuple2 (Tagged "b" Int) (Tagged "c" String) + as' = Tuple2 (tagged $ Identity x.b) (tagged $ Identity x.c) :: Tuple2 (Tagged "b" (Identity Int)) (Tagged "c" (Identity String)) - c = CombinedTuple $ build (merge (genericToRecordFields as)) (genericToRecordFields as') + c = CombinedTuple $ build (merge (toRecord as)) (toRecord as') in c === CombinedTuple x @@ -75,4 +91,35 @@ newtype CombinedTuple = CombinedTuple { a :: Int, b :: Int, c :: String, d :: St derive instance Generic CombinedTuple _ derive newtype instance Show CombinedTuple -derive newtype instance Eq CombinedTuple \ No newline at end of file +derive newtype instance Eq CombinedTuple + +type NestedRec = + { x :: { a1 :: UIntN 256, a2 :: String } + , y :: { b1 :: Array String, b2 :: BytesN 32 } + , z :: + Array { a :: { a1 :: UIntN 256, a2 :: String }, b :: { b1 :: Array String, b2 :: BytesN 32 } } + } + +type Nested = Tuple3 + (Tagged "x" (Tuple2 (Tagged "a1" (Identity (UIntN 256))) (Tagged "a2" (Identity String)))) + ( Tagged "y" + (Tuple2 (Tagged "b1" (Identity (Array String))) (Tagged "b2" (Identity (BytesN 32)))) + ) + ( Tagged "z" + ( Array + ( Tuple2 + ( Tagged "a" + (Tuple2 (Tagged "a1" (Identity (UIntN 256))) (Tagged "a2" (Identity String))) + ) + ( Tagged "b" + (Tuple2 (Tagged "b1" (Identity (Array String))) (Tagged "b2" (Identity (BytesN 32)))) + ) + ) + ) + ) + +nestedTupleBytes :: HexString +nestedTupleBytes = + unsafePartial + $ fromJust + $ mkHexString "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000badab5ed11c7ca0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000fe0a183e6bc96e0b098e9ba96e4a3b5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000406d0eb6eb8c2f86ddc21333dd73ea2ed919be82ebd61aee27be6beefce602f7c5000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001aeebd9601eba6ace3af98e79297e98183eaa7acf394a5b6e285bf000000000000000000000000000000000000000000000000000000000000000000000000001fe691a0e993baefaabae194b8e7a0a5eb80b6e580b2e8898af0aab68eefbc8700000000000000000000000000000000000000000000000000000000000000000ce4b896e7ae94e79c98e8908900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e5928000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e59390e89b86e991ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012e5aba1eaa0bde7b090e6a4b3ef849ce1a68a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000badab5ed11c7ca0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000fe0a183e6bc96e0b098e9ba96e4a3b5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000406d0eb6eb8c2f86ddc21333dd73ea2ed919be82ebd61aee27be6beefce602f7c5000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001aeebd9601eba6ace3af98e79297e98183eaa7acf394a5b6e285bf000000000000000000000000000000000000000000000000000000000000000000000000001fe691a0e993baefaabae194b8e7a0a5eb80b6e580b2e8898af0aab68eefbc8700000000000000000000000000000000000000000000000000000000000000000ce4b896e7ae94e79c98e8908900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e5928000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e59390e89b86e991ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012e5aba1eaa0bde7b090e6a4b3ef849ce1a68a0000000000000000000000000000"